diff --git a/frontend/src/lib/container.ts b/frontend/src/lib/container.ts index 10a7785..42bcd94 100644 --- a/frontend/src/lib/container.ts +++ b/frontend/src/lib/container.ts @@ -17,6 +17,7 @@ import type { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoView import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc'; import type { PostViewModel } from '$lib/post/adapter/presenter/postViewModel'; import type { PostRepository } from '$lib/post/application/gateway/postRepository'; +import { CreatePostUseCase } from '$lib/post/application/useCase/createPostUseCase'; import { GetAllPostsUseCase } from '$lib/post/application/useCase/getAllPostsUseCase'; import { GetPostUseCase } from '$lib/post/application/useCase/getPostUseCase'; import { PostApiServiceImpl } from '$lib/post/framework/api/postApiServiceImpl'; @@ -43,7 +44,7 @@ export class Container { } createPostBloc(initialData?: PostViewModel): PostBloc { - return new PostBloc(this.useCases.getPostUseCase, initialData); + return new PostBloc(this.useCases.getPostUseCase, this.useCases.createPostUseCase, initialData); } } @@ -108,6 +109,7 @@ class UseCases { private _uploadImageUseCase?: UploadImageUseCase; private _getAllPostsUseCase?: GetAllPostsUseCase; private _getPostUseCase?: GetPostUseCase; + private _createPostUseCase?: CreatePostUseCase; constructor(repositories: Repositories) { this.repositories = repositories; @@ -127,8 +129,14 @@ class UseCases { this._getAllPostsUseCase ??= new GetAllPostsUseCase(this.repositories.postRepository); return this._getAllPostsUseCase; } + get getPostUseCase(): GetPostUseCase { this._getPostUseCase ??= new GetPostUseCase(this.repositories.postRepository); return this._getPostUseCase; } + + get createPostUseCase(): CreatePostUseCase { + this._createPostUseCase ??= new CreatePostUseCase(this.repositories.postRepository); + return this._createPostUseCase; + } } diff --git a/frontend/src/lib/image/framework/ui/UploadImageDialoag.svelte b/frontend/src/lib/image/framework/ui/UploadImageDialoag.svelte index 792e5cd..4f89d61 100644 --- a/frontend/src/lib/image/framework/ui/UploadImageDialoag.svelte +++ b/frontend/src/lib/image/framework/ui/UploadImageDialoag.svelte @@ -40,13 +40,9 @@ files = undefined; fileInputErrorMessage = null; } - - function close() { - open = false; - } - (open = val)}> + Upload - + diff --git a/frontend/src/lib/post/adapter/gateway/creatPostRequestDto.ts b/frontend/src/lib/post/adapter/gateway/creatPostRequestDto.ts new file mode 100644 index 0000000..0f67bc6 --- /dev/null +++ b/frontend/src/lib/post/adapter/gateway/creatPostRequestDto.ts @@ -0,0 +1,52 @@ +import type { CreatePostParams } from '$lib/post/application/gateway/postRepository'; + +export class CreatePostRequestDto { + readonly semanticId: string; + readonly title: string; + readonly description: string; + readonly content: string; + readonly labelIds: number[]; + readonly previewImageUrl: URL; + readonly publishedTime: Date | null; + + private constructor(props: { + semanticId: string; + title: string; + description: string; + content: string; + labelIds: number[]; + previewImageUrl: URL; + publishedTime: Date | null; + }) { + this.semanticId = props.semanticId; + this.title = props.title; + this.description = props.description; + this.content = props.content; + this.labelIds = props.labelIds; + this.previewImageUrl = props.previewImageUrl; + this.publishedTime = props.publishedTime; + } + + static fromParams(params: CreatePostParams): CreatePostRequestDto { + return new CreatePostRequestDto({ + ...params, + description: '', + content: '', + labelIds: [], + previewImageUrl: new URL('https://example.com'), + publishedTime: null, + }); + } + + toJson() { + return { + semantic_id: this.semanticId, + title: this.title, + description: this.description, + content: this.content, + label_ids: this.labelIds, + preview_image_url: this.previewImageUrl, + published_time: this.publishedTime, + }; + } +} diff --git a/frontend/src/lib/post/adapter/gateway/postApiService.ts b/frontend/src/lib/post/adapter/gateway/postApiService.ts index a8503d7..d9f4a31 100644 --- a/frontend/src/lib/post/adapter/gateway/postApiService.ts +++ b/frontend/src/lib/post/adapter/gateway/postApiService.ts @@ -1,7 +1,9 @@ +import type { CreatePostRequestDto } from '$lib/post/adapter/gateway/creatPostRequestDto'; import type { PostInfoResponseDto } from '$lib/post/adapter/gateway/postInfoResponseDto'; import type { PostResponseDto } from '$lib/post/adapter/gateway/postResponseDto'; export interface PostApiService { getAllPosts(): Promise; getPost(id: string): Promise; + createPost(payload: CreatePostRequestDto): Promise; } diff --git a/frontend/src/lib/post/adapter/gateway/postRepositoryImpl.ts b/frontend/src/lib/post/adapter/gateway/postRepositoryImpl.ts index e75dfed..764b3cc 100644 --- a/frontend/src/lib/post/adapter/gateway/postRepositoryImpl.ts +++ b/frontend/src/lib/post/adapter/gateway/postRepositoryImpl.ts @@ -1,5 +1,6 @@ +import { CreatePostRequestDto } from '$lib/post/adapter/gateway/creatPostRequestDto'; import type { PostApiService } from '$lib/post/adapter/gateway/postApiService'; -import type { PostRepository } from '$lib/post/application/gateway/postRepository'; +import type { CreatePostParams, PostRepository } from '$lib/post/application/gateway/postRepository'; import type { Post } from '$lib/post/domain/entity/post'; import type { PostInfo } from '$lib/post/domain/entity/postInfo'; @@ -15,4 +16,10 @@ export class PostRepositoryImpl implements PostRepository { const dto = await this.postApiService.getPost(id); return dto?.toEntity() ?? null; } + + async createPost(params: CreatePostParams): Promise { + const requestDto = CreatePostRequestDto.fromParams(params); + const responseDto = await this.postApiService.createPost(requestDto); + return responseDto.toEntity(); + } } diff --git a/frontend/src/lib/post/adapter/presenter/postBloc.ts b/frontend/src/lib/post/adapter/presenter/postBloc.ts index 0b6190e..35a6f92 100644 --- a/frontend/src/lib/post/adapter/presenter/postBloc.ts +++ b/frontend/src/lib/post/adapter/presenter/postBloc.ts @@ -4,12 +4,14 @@ import { type AsyncState, } from '$lib/common/adapter/presenter/asyncState'; import { PostViewModel } from '$lib/post/adapter/presenter/postViewModel'; +import type { CreatePostParams } from '$lib/post/application/gateway/postRepository'; +import type { CreatePostUseCase } from '$lib/post/application/useCase/createPostUseCase'; import type { GetPostUseCase } from '$lib/post/application/useCase/getPostUseCase'; import { captureException } from '@sentry/sveltekit'; import { get, writable } from 'svelte/store'; export type PostState = AsyncState; -export type PostEvent = PostLoadedEvent; +export type PostEvent = PostLoadedEvent | PostCreatedEvent; export class PostBloc { private readonly state = writable({ @@ -18,6 +20,7 @@ export class PostBloc { constructor( private readonly getPostUseCase: GetPostUseCase, + private readonly createPostUseCase: CreatePostUseCase, initialData?: PostViewModel ) { this.state.set({ @@ -34,6 +37,9 @@ export class PostBloc { switch (event.event) { case PostEventType.PostLoadedEvent: return this.loadPost(event.id); + case PostEventType.PostCreatedEvent: + const { semanticId, title } = event; + return this.createPost({ semanticId, title }); } } @@ -58,13 +64,37 @@ export class PostBloc { this.state.set(result); return result; } + + private async createPost(params: CreatePostParams): Promise { + this.state.set(StateFactory.loading(get(this.state).data)); + + let result: PostState; + try { + const post = await this.createPostUseCase.execute(params); + const postViewModel = PostViewModel.fromEntity(post); + result = StateFactory.success(postViewModel); + } catch (e) { + result = StateFactory.error(e); + captureException(e); + } + + this.state.set(result); + return result; + } } export enum PostEventType { PostLoadedEvent, + PostCreatedEvent, } interface PostLoadedEvent { event: PostEventType.PostLoadedEvent; id: string; } + +interface PostCreatedEvent { + event: PostEventType.PostCreatedEvent; + semanticId: string; + title: string; +} diff --git a/frontend/src/lib/post/application/gateway/postRepository.ts b/frontend/src/lib/post/application/gateway/postRepository.ts index 664a263..bff44f9 100644 --- a/frontend/src/lib/post/application/gateway/postRepository.ts +++ b/frontend/src/lib/post/application/gateway/postRepository.ts @@ -4,4 +4,10 @@ import type { PostInfo } from '$lib/post/domain/entity/postInfo'; export interface PostRepository { getAllPosts(): Promise; getPost(id: string): Promise; + createPost(params: CreatePostParams): Promise; +} + +export interface CreatePostParams { + semanticId: string; + title: string; } diff --git a/frontend/src/lib/post/application/useCase/createPostUseCase.ts b/frontend/src/lib/post/application/useCase/createPostUseCase.ts new file mode 100644 index 0000000..cec2d2e --- /dev/null +++ b/frontend/src/lib/post/application/useCase/createPostUseCase.ts @@ -0,0 +1,13 @@ +import type { + CreatePostParams, + PostRepository, +} from '$lib/post/application/gateway/postRepository'; +import type { Post } from '$lib/post/domain/entity/post'; + +export class CreatePostUseCase { + constructor(private readonly postRepository: PostRepository) {} + + async execute(params: CreatePostParams): Promise { + return this.postRepository.createPost(params); + } +} diff --git a/frontend/src/lib/post/framework/api/postApiServiceImpl.ts b/frontend/src/lib/post/framework/api/postApiServiceImpl.ts index 18d5485..13359b6 100644 --- a/frontend/src/lib/post/framework/api/postApiServiceImpl.ts +++ b/frontend/src/lib/post/framework/api/postApiServiceImpl.ts @@ -1,6 +1,7 @@ import { HttpError } from '$lib/common/framework/web/httpError'; import { HttpStatusCode } from '$lib/common/framework/web/httpStatusCode'; import { Environment } from '$lib/environment'; +import type { CreatePostRequestDto } from '$lib/post/adapter/gateway/creatPostRequestDto'; import type { PostApiService } from '$lib/post/adapter/gateway/postApiService'; import { PostInfoResponseDto } from '$lib/post/adapter/gateway/postInfoResponseDto'; import { PostResponseDto } from '$lib/post/adapter/gateway/postResponseDto'; @@ -17,8 +18,8 @@ export class PostApiServiceImpl implements PostApiService { throw new HttpError(response.status, url); } - const json = await response.json(); - return json.map(PostInfoResponseDto.fromJson); + const data = await response.json(); + return data.map(PostInfoResponseDto.fromJson); } async getPost(id: string): Promise { @@ -34,7 +35,24 @@ export class PostApiServiceImpl implements PostApiService { throw new HttpError(response.status, url); } - const json = await response.json(); - return PostResponseDto.fromJson(json); + const data = await response.json(); + return PostResponseDto.fromJson(data); + } + + async createPost(payload: CreatePostRequestDto): Promise { + const url = new URL('post', Environment.API_BASE_URL); + + const response = await this.fetchFn(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload.toJson()), + }); + + if (!response.ok) { + throw new HttpError(response.status, url); + } + + const data = await response.json(); + return PostResponseDto.fromJson(data); } } diff --git a/frontend/src/lib/post/framework/ui/CreatePostDialog.svelte b/frontend/src/lib/post/framework/ui/CreatePostDialog.svelte new file mode 100644 index 0000000..57e4cb1 --- /dev/null +++ b/frontend/src/lib/post/framework/ui/CreatePostDialog.svelte @@ -0,0 +1,107 @@ + + + + + + Create + e.preventDefault()} + onEscapeKeydown={(e) => e.preventDefault()} + > + + Create Post + + +
+
+ + + {#if formErrors.semanticId} +

{formErrors.semanticId}

+ {/if} +
+ +
+ + + {#if formErrors.title} +

{formErrors.title}

+ {/if} +
+
+ + + + + +
+
diff --git a/frontend/src/lib/post/framework/ui/PostManagementPage.svelte b/frontend/src/lib/post/framework/ui/PostManagementPage.svelte new file mode 100644 index 0000000..e0970ce --- /dev/null +++ b/frontend/src/lib/post/framework/ui/PostManagementPage.svelte @@ -0,0 +1,35 @@ + + +
+
+

Post

+ +
+

+
diff --git a/frontend/src/routes/+error.svelte b/frontend/src/routes/+error.svelte index 4d78c58..35d7061 100644 --- a/frontend/src/routes/+error.svelte +++ b/frontend/src/routes/+error.svelte @@ -1,4 +1,4 @@ - diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 3dfb295..cf92cb0 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,4 +1,4 @@ - diff --git a/frontend/src/routes/dashboard/image/+page.svelte b/frontend/src/routes/dashboard/image/+page.svelte index 5fc7351..c2ffd8b 100644 --- a/frontend/src/routes/dashboard/image/+page.svelte +++ b/frontend/src/routes/dashboard/image/+page.svelte @@ -1,18 +1,11 @@ diff --git a/frontend/src/routes/dashboard/post/+page.svelte b/frontend/src/routes/dashboard/post/+page.svelte index fc9a374..e3c8cf3 100644 --- a/frontend/src/routes/dashboard/post/+page.svelte +++ b/frontend/src/routes/dashboard/post/+page.svelte @@ -1 +1,12 @@ -
Post
+ + +