From 7b19bea790617b8242aa56c531c4f97c46068d81 Mon Sep 17 00:00:00 2001 From: chatenium Date: Fri, 3 Apr 2026 09:18:34 +0200 Subject: [PATCH] Finished implementing PictureService --- src/domain/pictureService.schema.ts | 11 +++++ src/mocks/handlers/picture.http.ts | 35 ++++++++++++++ src/mocks/index.ts | 4 +- src/services/pictureService.test.ts | 30 ++++++++++++ src/services/pictureService.ts | 75 ++++++++++++++++++++++++++--- tests/networkService.test.ts | 2 +- tests/pictureService.test.ts | 66 +++++++++++++++++++++++++ 7 files changed, 213 insertions(+), 10 deletions(-) create mode 100644 src/mocks/handlers/picture.http.ts create mode 100644 src/services/pictureService.test.ts create mode 100644 tests/pictureService.test.ts diff --git a/src/domain/pictureService.schema.ts b/src/domain/pictureService.schema.ts index 612acfa..c8fd651 100644 --- a/src/domain/pictureService.schema.ts +++ b/src/domain/pictureService.schema.ts @@ -95,4 +95,15 @@ export interface Image { liked: boolean likes: number userData: PublicUserData | null +} + +export interface Comment { + commentId: string + author: string + imageId: string + comment: string + commented_at: TimeStamp + username: string + pfp: string + displayName: string } \ No newline at end of file diff --git a/src/mocks/handlers/picture.http.ts b/src/mocks/handlers/picture.http.ts new file mode 100644 index 0000000..af93f68 --- /dev/null +++ b/src/mocks/handlers/picture.http.ts @@ -0,0 +1,35 @@ +import {http, HttpResponse} from "msw"; +import {CreateNetworkReq, Network, NetworkInvite} from "../../domain/networkService.schema"; +import {Album, Comment, CreateAlbumReq, GetResp} from "../../domain/pictureService.schema"; + +export const pictureHandlers = [ + http.get('*/picture/pictures', () => { + return HttpResponse.json({ + pictures: [{ + name: "Album name" + }], + userData: { + username: "bob" + } + }) + }), + + http.post('*/picture/createAlbum', async ({request}) => { + const body = await request.json() as CreateAlbumReq; + return HttpResponse.json({ + name: body.name, + }) + }), + + http.get('*/picture/getAlbums', () => { + return HttpResponse.json([{ + name: "Album name" + }]) + }), + + http.get('*/picture/comment', () => { + return HttpResponse.json([{ + comment: "This is a comment", + }]) + }), +] \ No newline at end of file diff --git a/src/mocks/index.ts b/src/mocks/index.ts index 7ac4328..92b575f 100644 --- a/src/mocks/index.ts +++ b/src/mocks/index.ts @@ -1,7 +1,9 @@ import {networkHandlers} from "./handlers/auth.http"; import {authHandlers} from "./handlers/network.http"; +import {pictureHandlers} from "./handlers/picture.http"; export const allHandlers = [ ...authHandlers, - ...networkHandlers + ...networkHandlers, + ...pictureHandlers ] \ No newline at end of file diff --git a/src/services/pictureService.test.ts b/src/services/pictureService.test.ts new file mode 100644 index 0000000..f03354a --- /dev/null +++ b/src/services/pictureService.test.ts @@ -0,0 +1,30 @@ +import {describe, expect, it} from "vitest"; +import {PictureService} from "./pictureService"; +import {DatabaseMock} from "../mocks/storage/database"; +import {faker} from "@faker-js/faker/locale/en"; + +describe("PictureService", () => { + const service = new PictureService("", "", "", new DatabaseMock(), null, null) + + it('should get pictures', async () => { + const uploads = await service.get() + expect(uploads.pictures[0].name).toBe("Album name") + expect(uploads.userData.username).toBe("bob") + }) + + it('should create an album', async () => { + const albumName = faker.internet.displayName() + const album = await service.createAlbum(albumName) + expect(album.name).toBe(albumName) + }); + + it('should fetch the albums', async () => { + const albums = await service.getAlbums() + expect(albums[0].name).toBe("Album name") + }); + + it('should fetch comments', async () => { + const comments = await service.getComments("") + expect(comments[0]).toBe("This is a comment") + }) +}) \ No newline at end of file diff --git a/src/services/pictureService.ts b/src/services/pictureService.ts index 93d2c3c..50a8bb9 100644 --- a/src/services/pictureService.ts +++ b/src/services/pictureService.ts @@ -4,7 +4,7 @@ import {getClient} from "../core/http"; import {NetworkInvite} from "../domain/networkService.schema"; import {GenericErrorBody} from "../domain/http.schema"; import { - Album, ChangePictureVisibilityReq, + Album, ChangePictureVisibilityReq, Comment, CreateAlbumReq, DeleteImageReq, DiscoveryResp, EditPictureTitleReq, @@ -36,6 +36,9 @@ export class PictureService { }) } + /** + * Fetches all images uploaded by the specified uploader categorized in albums + */ async get(): Promise { try { const resp = await this.client.get(`picture/pictures?userid=${this.userid}&target=${this.uploaderId}`); @@ -49,6 +52,9 @@ export class PictureService { } } + /** + * Fetches the top 10 most liked and newest pictures + */ async discovery(): Promise { try { const resp = await this.client.get("picture/discovery"); @@ -62,6 +68,10 @@ export class PictureService { } } + /** + * Creates a new album + * @param name Provided by the user + */ async createAlbum(name: string): Promise { try { const resp = await this.client.post("picture/createAlbum", { @@ -78,6 +88,10 @@ export class PictureService { } } + /** + * Registers all uploaded pictures in the database + * @param uploadId Must the be the one that was used when uploading the images + */ async finalizeUpload(uploadId: string): Promise { try { await this.client.post("picture/finalizeUpload", { @@ -94,9 +108,13 @@ export class PictureService { } } + /** + * Deletes the specified picture + * @param imageId + */ async deletePicture(imageId: string): Promise { try { - await this.client.post("picture/delete", { + await this.client.patch("picture/delete", { userid: this.userid, imageId: imageId }); @@ -110,11 +128,18 @@ export class PictureService { } } + + /** + * Changes the visibility of the specified picture + * @param imageId + * @param newVisibility + */ async changeVisibility(imageId: string, newVisibility: string): Promise { try { - await this.client.post("picture/changeVisibility", { + await this.client.patch("picture/changeVisibility", { userid: this.userid, - visibility: newVisibility + visibility: newVisibility, + imageId: imageId }); return } catch (e) { @@ -126,11 +151,17 @@ export class PictureService { } } + /** + * Edits the title of the specified picture + * @param imageId + * @param newTitle + */ async editTitle(imageId: string, newTitle: string): Promise { try { - await this.client.post("picture/editTitle", { + await this.client.patch("picture/editTitle", { userid: this.userid, - title: newTitle + title: newTitle, + imageId: imageId }); return } catch (e) { @@ -142,6 +173,10 @@ export class PictureService { } } + /** + * (un)likes the specified picture + * @param imageId + */ async toggleLike(imageId: string): Promise { try { await this.client.post("picture/toggleLike", { @@ -158,6 +193,11 @@ export class PictureService { } } + /** + * Posts a comment under the specified picture + * @param imageId + * @param comment + */ async postComment(imageId: string, comment: string): Promise { try { await this.client.post("picture/comment", { @@ -175,6 +215,9 @@ export class PictureService { } } + /** + * (un)follows the uploader + */ async toggleFollow(): Promise { try { await this.client.post("picture/toggleFollow", { @@ -191,6 +234,9 @@ export class PictureService { } } + /** + * Fetches all albums + */ async getAlbums(): Promise { try { const resp = await this.client.get(`picture/getAlbums?userid=${this.userid}`); @@ -204,9 +250,13 @@ export class PictureService { } } - async getComments(imageId: string): Promise { + /** + * Gets all comments under the specified picture + * @param imageId + */ + async getComments(imageId: string): Promise { try { - const resp = await this.client.get(`picture/comment?userid=${this.userid}&imageId=${imageId}`); + const resp = await this.client.get(`picture/comment?userid=${this.userid}&imageId=${imageId}`); return resp.data } catch (e) { console.log(e) @@ -217,6 +267,15 @@ export class PictureService { } } + /** + * Uploads a new picture to the CDN + * @param picture + * @param title + * @param visibility + * @param album + * @param uploadId + * @param name + */ async uploadPicture(picture: string, title: string, visibility: string, album: string, uploadId: string, name: string): Promise { try { await this.cdnClient.post("picture/upload", { diff --git a/tests/networkService.test.ts b/tests/networkService.test.ts index afc02c4..69ea1f8 100644 --- a/tests/networkService.test.ts +++ b/tests/networkService.test.ts @@ -24,7 +24,7 @@ describe("NetworkService Integration Testing", () => { NETWORK_SERVICE_TESTING_TOKEN, NETWORK_SERVICE_TESTING_NETWORK_ID, new DatabaseMock(), - getClient() + getClient(false), ) it("should get invites", async () => { diff --git a/tests/pictureService.test.ts b/tests/pictureService.test.ts new file mode 100644 index 0000000..5f1ced1 --- /dev/null +++ b/tests/pictureService.test.ts @@ -0,0 +1,66 @@ +import {describe, expect, it} from "vitest"; +import {environment, SDKConfig} from "../src/core/environment"; +import {NetworkService} from "../src/services/networkService"; +import {DatabaseMock} from "../src/mocks/storage/database"; +import {getClient} from "../src/core/http"; +import {PictureService} from "../src/services/pictureService"; +import {faker} from "@faker-js/faker/locale/en"; + +const PICTURE_SERVICE_TESTING_TOKEN = "testingToken" +const PICTURE_SERVICE_TESTING_USER_ID = "000000000000000000000000" +const PICTURE_SERVICE_TESTING_IMAGE_ID = "111111111111111111111111" + +describe("PictureService Integration Test", () => { + environment.overwrite({apiUrl: "http://localhost:3000", cdnUrl: "http://localhost:4000"}) + const service = new PictureService( + PICTURE_SERVICE_TESTING_TOKEN, + PICTURE_SERVICE_TESTING_USER_ID, + PICTURE_SERVICE_TESTING_USER_ID, + new DatabaseMock(), + getClient(false), + getClient(true), + ) + + it('should get uploads', async () => { + const uploads = await service.get() + expect(uploads.pictures[0].name).toBe("Album name") + }); + + it('should create an album', async () => { + const albumName = faker.internet.displayName() + await service.createAlbum(albumName) + const uploads = await service.get() + expect(uploads.pictures[1].name).toBe(albumName) + }); + + it('should delete image', async () => { + await service.deletePicture(PICTURE_SERVICE_TESTING_IMAGE_ID) + const uploads = await service.get() + expect(uploads.pictures[0].images.length).toBe(0) + }); + + it('should change image visibility', async () => { + await service.changeVisibility(PICTURE_SERVICE_TESTING_IMAGE_ID, "private") + const uploads = await service.get() + expect(uploads.pictures[0].images[0].visibility).toBe("private") + }); + + it('should edit image title', async () => { + const newTitle = faker.internet.displayName() + await service.editTitle(PICTURE_SERVICE_TESTING_IMAGE_ID, newTitle) + const uploads = await service.get() + expect(uploads.pictures[0].images[0].title).toBe(newTitle) + }); + + it('should post and get comment', async () => { + const comment = faker.lorem.sentence() + await service.postComment(PICTURE_SERVICE_TESTING_IMAGE_ID, comment) + const comments = await service.getComments(PICTURE_SERVICE_TESTING_IMAGE_ID) + expect(comments.length).not.toBe(0) + }); + + it('should fetch albums', async () => { + const albums = await service.getAlbums() + expect(albums[0].name).toBe("Album name") + }); +}) \ No newline at end of file