Added caching and implemented UserService

This commit is contained in:
2026-04-07 11:19:38 +02:00
parent aa451e4786
commit 4f96b1d687
11 changed files with 488 additions and 3 deletions

View File

@@ -0,0 +1,114 @@
import {TimeStamp} from "./common.schema";
export interface ChangeUsernameReq {
newUsername: string;
userid: string;
}
export interface ChangeDisplayNameReq {
newDisplayName: string;
userid: string;
}
export interface ChangePasswordReq {
newPassword: string;
currentPassword: string;
userid: string;
}
export interface ChangeEmailReq {
currentPassword: string;
newMail: string;
userid: string;
}
export interface VerifyMailChangeReq {
userid: string;
vCodeCurrent: number;
vCodeNew: number;
newAddress: string;
}
export interface ChangePhoneReq {
currentPassword: string;
newPhone: string;
userid: string;
}
export interface VerifyPhoneChange {
userid: string;
vCodeCurrent: number;
vCodeNew: number;
newPhone: string;
}
export interface UploadNewPfpReq {
userid: string;
pfpId: string;
}
export interface UploadNewPfpCdnReq {
userid: string;
data: string | null;
isImage: boolean;
monogramLetter: string | null;
monogramColors: string | null;
}
export interface DeleteReq {
userid: string;
password: string;
}
export interface RegisterFCMTokenReq {
userid: string;
token: string;
language: string;
}
export interface GetSessionsReq {
userid: string;
}
export interface UpdateUserDataReq {
userid: string;
}
export interface ToggleGifSaveReq {
userid: string;
url: string;
}
export interface UploadNewPfpCdnResp {
pfpId: string;
}
export interface Session {
token: string;
os: string;
language: string;
login_at: TimeStamp | string;
}
export interface GIF {
gifId: string;
url: string;
path: string;
}
export interface CurrNewCodeTestingResp {
codeCurr: number|null;
codeNew: number|null;
}
export interface PersonalUserData {
userid: string;
username: string;
displayName: string;
pfp: string;
pictureDiscovery: boolean;
gifs: GIF[];
passwordSet: boolean;
emailSet: boolean;
phoneSet: boolean;
}

View File

@@ -0,0 +1,11 @@
import {http, HttpResponse} from "msw";
import {GetResp} from "../../domain/pictureService.schema";
import {Session} from "../../domain/userService.schema";
export const userHandler = [
http.post('*/user/getSessions', () => {
return HttpResponse.json(<Session[]>[{
token: "sessionToken"
}])
}),
]

View File

@@ -5,6 +5,7 @@ import {callHandlers} from "./handlers/call.http";
import {fileUploadHandlers} from "./handlers/fUpl.http";
import {chatHandlers} from "./handlers/chat.http";
import {dmHandlers} from "./handlers/dm.http";
import {userHandler} from "./handlers/user.http";
export const allHandlers = [
...authHandlers,
@@ -13,5 +14,6 @@ export const allHandlers = [
...callHandlers,
...fileUploadHandlers,
...chatHandlers,
...dmHandlers
...dmHandlers,
...userHandler
]

View File

@@ -164,7 +164,6 @@ export class AuthService {
});
return resp.data.authCode
} catch (e) {
console.log(e)
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}

View File

@@ -12,6 +12,7 @@ import {
StartNewReq,
ToggleChatMuteReq
} from "../domain/chatService.schema";
import {Message} from "../domain/dmService.schema";
/**
* ChatService is an exception because it's one instance for all chats because it's unnecessary to create a new instance for each chat
@@ -47,9 +48,9 @@ export class ChatService {
async get(): Promise<Chat[]> {
try {
const resp = await this.client.get<Chat[]>(`chat/get?userid=${this.userid}`);
this.database.set("chats", this.userid, JSON.stringify(resp.data))
return resp.data
} catch (e) {
console.log(e)
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
@@ -57,6 +58,15 @@ export class ChatService {
}
}
getQuick(): Message[] {
const chats = this.database.get("chats", this.userid)
if (chats) {
return JSON.parse(chats)
} else {
throw new Error("No chats in database")
}
}
/**
* Gets the availability of the specified user
* @param userid

View File

@@ -53,6 +53,9 @@ export class DMService {
async get(from: number = 0): Promise<Message[]> {
try {
const resp = await this.client.get<Message[]>(`chat/dm/messages?chatid=${this.chatid}&userid=${this.userid}&from=${from}`);
if (from == 0) {
this.database.set("messages", this.chatid, JSON.stringify(resp.data))
}
return resp.data
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
@@ -62,6 +65,15 @@ export class DMService {
}
}
getQuick(): Message[] {
const messages = this.database.get("messages", this.chatid)
if (messages) {
return JSON.parse(messages)
} else {
throw new Error("No messages in database")
}
}
/**
* Fetches the position of the specified message which can be used in get() to fetch an old message with it's surrounding messages
* @param messageId

View File

@@ -19,6 +19,7 @@ import {
import {PublicUserData, RGB} from "../domain/common.schema";
import {WebSocketHandler} from "../core/webSocketHandler";
import {MessageListener} from "../domain/websocket.schema";
import {Message} from "../domain/dmService.schema";
export class NetworkService {
userid: string;
@@ -97,6 +98,7 @@ export class NetworkService {
const resp = await this.client.post<Network[]>("network/get", <GetNetworksReq>{
userid: this.userid,
});
this.database.set("networks", this.userid, JSON.stringify(resp.data))
return resp.data
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
@@ -106,6 +108,15 @@ export class NetworkService {
}
}
getQuick(): Message[] {
const networks = this.database.get("networks", this.userid)
if (networks) {
return JSON.parse(networks)
} else {
throw new Error("No networks in database")
}
}
/**
* Accepts the invite and joins the network
* @param inviteId

View File

@@ -12,6 +12,7 @@ import {
GetResp, PostCommentReq, ToggleFollowReq, TogglePictureLikeReq, UploadImageReq
} from "../domain/pictureService.schema";
import {environment} from "../core/environment";
import {Message} from "../domain/dmService.schema";
export class PictureService {
userid: string;
@@ -42,6 +43,7 @@ export class PictureService {
async get(): Promise<GetResp> {
try {
const resp = await this.client.get<GetResp>(`picture/pictures?userid=${this.userid}&target=${this.uploaderId}`);
this.database.set("pictures", this.uploaderId, JSON.stringify(resp.data))
return resp.data
} catch (e) {
console.log(e)
@@ -52,6 +54,15 @@ export class PictureService {
}
}
getQuick(): Message[] {
const pictures = this.database.get("pictures", this.uploaderId)
if (pictures) {
return JSON.parse(pictures)
} else {
throw new Error("No pictures in database")
}
}
/**
* Fetches the top 10 most liked and newest pictures
*/

View File

@@ -0,0 +1,12 @@
import {describe, expect, it} from "vitest";
import {UserService} from "./userService";
import {DatabaseMock} from "../mocks/storage/database";
describe("UserService", () => {
const service = new UserService("", "", new DatabaseMock())
it('should get all sessions', async () => {
const sessions = await service.getSessions()
expect(sessions[0].token).toBe("sessionToken")
});
})

253
src/services/userService.ts Normal file
View File

@@ -0,0 +1,253 @@
import {DatabaseAPI} from "../storage/database";
import {AxiosInstance, isAxiosError} from "axios";
import {MessageListener} from "../domain/websocket.schema";
import {getClient} from "../core/http";
import {WebSocketHandler} from "../core/webSocketHandler";
import {DeleteCategoryReq} from "../domain/networkService.schema";
import {GenericErrorBody} from "../domain/http.schema";
import {
ChangeDisplayNameReq,
ChangeEmailReq,
ChangePasswordReq, ChangePhoneReq,
ChangeUsernameReq, CurrNewCodeTestingResp, DeleteReq, GetSessionsReq, GIF, RegisterFCMTokenReq, Session,
ToggleGifSaveReq, UploadNewPfpCdnReq, UploadNewPfpCdnResp,
UploadNewPfpReq, VerifyMailChangeReq, VerifyPhoneChange
} from "../domain/userService.schema";
import {RGB} from "../domain/common.schema";
import {OtpPleCodeSendTestingResp} from "../domain/authService.schema";
export class UserService {
userid: string;
database: DatabaseAPI;
client: AxiosInstance
cdnClient: AxiosInstance
constructor(userid: string, token: string, database: DatabaseAPI) {
this.userid = userid;
this.database = database;
this.client = getClient(false).create({
headers: {
"Authorization": token,
}
})
this.cdnClient = getClient(true).create({
headers: {
"Authorization": token,
}
})
}
async changeUsername(username: string): Promise<void> {
try {
await this.client.patch("user/changeUsername", <ChangeUsernameReq>{
userid: this.userid,
newUsername: username,
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async changeDisplayName(displayName: string): Promise<void> {
try {
await this.client.patch("user/changeDisplayName", <ChangeDisplayNameReq>{
userid: this.userid,
newDisplayName: displayName
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async changePassword(newPassword: string, currentPassword: string): Promise<void> {
try {
await this.client.patch("user/changePassword", <ChangePasswordReq>{
userid: this.userid,
currentPassword: currentPassword,
newPassword: newPassword,
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async changeEmail(newMail: string, currentPassword: string): Promise<void|CurrNewCodeTestingResp> {
try {
const resp = await this.client.patch<CurrNewCodeTestingResp>("user/changeEmail", <ChangeEmailReq>{
userid: this.userid,
currentPassword: currentPassword,
newMail: newMail,
});
if (resp.data.codeCurr != null) {
return resp.data
} else {
return
}
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async uploadNewPfp(pfpId: string): Promise<void> {
try {
await this.client.patch("user/uploadNewPfp", <UploadNewPfpReq>{
userid: this.userid,
pfpId: pfpId,
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async uploadNewPfpCdn(image: string | null, monogramLetter: string | null, monogramColors: RGB): Promise<string> {
try {
const resp = await this.cdnClient.post<UploadNewPfpCdnResp>("pfp", <UploadNewPfpCdnReq>{
userid: this.userid,
data: image,
monogramColors: JSON.stringify(monogramColors),
isImage: image !== null,
monogramLetter: monogramLetter,
});
console.log(resp.data.pfpId, "PFPID")
return resp.data.pfpId
} catch (e) {
console.log(e)
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async verifyEmailChange(vCodeCurrent: number, vCodeNew: number, newAddress: string): Promise<void> {
try {
await this.client.patch("user/verifyMailChange", <VerifyMailChangeReq>{
userid: this.userid,
newAddress: newAddress,
vCodeCurrent: vCodeCurrent,
vCodeNew: vCodeNew,
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async delete(password: string): Promise<void> {
try {
await this.client.post("user/deleteAccount", <DeleteReq>{
userid: this.userid,
password: password
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async registerFirebaseToken(fcmToken: string): Promise<void> {
try {
await this.client.post("user/registerFcmToken", <RegisterFCMTokenReq>{
userid: this.userid,
token: fcmToken,
});
return
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async getSessions(): Promise<Session[]> {
try {
const resp = await this.client.post<Session[]>("user/getSessions", <GetSessionsReq>{
userid: this.userid,
});
return resp.data
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async toggleGIFSave(url: string): Promise<GIF> {
try {
const resp = await this.client.patch<GIF>("user/toggleGIFSave", <ToggleGifSaveReq>{
userid: this.userid,
url: url
});
return resp.data
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async changePhoneNumber(currentPassword: string, newPhone: string): Promise<CurrNewCodeTestingResp|void> {
try {
const resp = await this.client.patch<CurrNewCodeTestingResp>("user/changePhone", <ChangePhoneReq>{
userid: this.userid,
newPhone: newPhone,
currentPassword: currentPassword,
});
if (resp.data.codeCurr != null) {
return resp.data
} else {
return
}
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
async verifyPhoneNumberChange(newPhone: string, vCodeCurrent: number, vCodeNew: number): Promise<void> {
try {
const resp = await this.client.patch("user/verifyPhoneChange", <VerifyPhoneChange>{
userid: this.userid,
newPhone: newPhone,
vCodeCurrent: vCodeCurrent,
vCodeNew: vCodeNew,
});
return resp.data
} catch (e) {
if (isAxiosError<GenericErrorBody>(e)) {
throw e;
}
throw new Error("Unexpected error")
}
}
}