From 5890d6c39c21e411cd64a90b58cb888912a2b3e5 Mon Sep 17 00:00:00 2001 From: SquidSpirit Date: Wed, 15 Oct 2025 04:05:28 +0800 Subject: [PATCH] feat: enhance post listing functionality with unpublished post visibility option --- .../auth/adapter/presenter/authLoadedStore.ts | 2 +- .../framework/components/ui/table/index.ts | 28 ++++++++++ .../components/ui/table/table-body.svelte | 20 ++++++++ .../components/ui/table/table-caption.svelte | 20 ++++++++ .../components/ui/table/table-cell.svelte | 23 +++++++++ .../components/ui/table/table-footer.svelte | 20 ++++++++ .../components/ui/table/table-head.svelte | 23 +++++++++ .../components/ui/table/table-header.svelte | 20 ++++++++ .../components/ui/table/table-row.svelte | 23 +++++++++ .../components/ui/table/table.svelte | 22 ++++++++ frontend/src/lib/container.ts | 2 +- .../framework/ui/ImageManagementPage.svelte | 2 +- .../post/adapter/gateway/postApiService.ts | 3 +- .../post/adapter/gateway/postListQueryDto.ts | 13 +++++ .../adapter/gateway/postRepositoryImpl.ts | 8 +-- .../post/adapter/presenter/postLoadedStore.ts | 2 +- .../adapter/presenter/postsListedStore.ts | 10 ++-- .../application/gateway/postRepository.ts | 2 +- .../application/useCase/getAllPostsUseCase.ts | 4 +- .../post/framework/api/postApiServiceImpl.ts | 4 +- .../post/framework/ui/PostContentPage.svelte | 2 +- .../framework/ui/PostManagementPage.svelte | 51 ++++++++++++++++--- frontend/src/routes/dashboard/+layout.svelte | 2 +- .../src/routes/dashboard/image/+page.svelte | 2 +- .../src/routes/dashboard/post/+page.server.ts | 13 +++++ .../src/routes/dashboard/post/+page.svelte | 13 ++++- 26 files changed, 306 insertions(+), 28 deletions(-) create mode 100644 frontend/src/lib/common/framework/components/ui/table/index.ts create mode 100644 frontend/src/lib/common/framework/components/ui/table/table-body.svelte create mode 100644 frontend/src/lib/common/framework/components/ui/table/table-caption.svelte create mode 100644 frontend/src/lib/common/framework/components/ui/table/table-cell.svelte create mode 100644 frontend/src/lib/common/framework/components/ui/table/table-footer.svelte create mode 100644 frontend/src/lib/common/framework/components/ui/table/table-head.svelte create mode 100644 frontend/src/lib/common/framework/components/ui/table/table-header.svelte create mode 100644 frontend/src/lib/common/framework/components/ui/table/table-row.svelte create mode 100644 frontend/src/lib/common/framework/components/ui/table/table.svelte create mode 100644 frontend/src/lib/post/adapter/gateway/postListQueryDto.ts create mode 100644 frontend/src/routes/dashboard/post/+page.server.ts diff --git a/frontend/src/lib/auth/adapter/presenter/authLoadedStore.ts b/frontend/src/lib/auth/adapter/presenter/authLoadedStore.ts index f44582f..c24bc87 100644 --- a/frontend/src/lib/auth/adapter/presenter/authLoadedStore.ts +++ b/frontend/src/lib/auth/adapter/presenter/authLoadedStore.ts @@ -6,7 +6,7 @@ import type { BaseStore } from '$lib/common/adapter/presenter/baseStore'; import { captureException } from '@sentry/sveltekit'; import { get, writable } from 'svelte/store'; -export type AuthState = AsyncState; +type AuthState = AsyncState; export class AuthLoadedStore implements BaseStore { private readonly state = writable(AsyncState.idle(null)); diff --git a/frontend/src/lib/common/framework/components/ui/table/index.ts b/frontend/src/lib/common/framework/components/ui/table/index.ts new file mode 100644 index 0000000..5173776 --- /dev/null +++ b/frontend/src/lib/common/framework/components/ui/table/index.ts @@ -0,0 +1,28 @@ +import Root from './table.svelte'; +import Body from './table-body.svelte'; +import Caption from './table-caption.svelte'; +import Cell from './table-cell.svelte'; +import Footer from './table-footer.svelte'; +import Head from './table-head.svelte'; +import Header from './table-header.svelte'; +import Row from './table-row.svelte'; + +export { + Root, + Body, + Caption, + Cell, + Footer, + Head, + Header, + Row, + // + Root as Table, + Body as TableBody, + Caption as TableCaption, + Cell as TableCell, + Footer as TableFooter, + Head as TableHead, + Header as TableHeader, + Row as TableRow, +}; diff --git a/frontend/src/lib/common/framework/components/ui/table/table-body.svelte b/frontend/src/lib/common/framework/components/ui/table/table-body.svelte new file mode 100644 index 0000000..af85435 --- /dev/null +++ b/frontend/src/lib/common/framework/components/ui/table/table-body.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/frontend/src/lib/common/framework/components/ui/table/table-caption.svelte b/frontend/src/lib/common/framework/components/ui/table/table-caption.svelte new file mode 100644 index 0000000..dcee16a --- /dev/null +++ b/frontend/src/lib/common/framework/components/ui/table/table-caption.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/frontend/src/lib/common/framework/components/ui/table/table-cell.svelte b/frontend/src/lib/common/framework/components/ui/table/table-cell.svelte new file mode 100644 index 0000000..9fb1123 --- /dev/null +++ b/frontend/src/lib/common/framework/components/ui/table/table-cell.svelte @@ -0,0 +1,23 @@ + + + + {@render children?.()} + diff --git a/frontend/src/lib/common/framework/components/ui/table/table-footer.svelte b/frontend/src/lib/common/framework/components/ui/table/table-footer.svelte new file mode 100644 index 0000000..bdbe995 --- /dev/null +++ b/frontend/src/lib/common/framework/components/ui/table/table-footer.svelte @@ -0,0 +1,20 @@ + + +tr]:last:border-b-0', className)} + {...restProps} +> + {@render children?.()} + diff --git a/frontend/src/lib/common/framework/components/ui/table/table-head.svelte b/frontend/src/lib/common/framework/components/ui/table/table-head.svelte new file mode 100644 index 0000000..81d5956 --- /dev/null +++ b/frontend/src/lib/common/framework/components/ui/table/table-head.svelte @@ -0,0 +1,23 @@ + + + + {@render children?.()} + diff --git a/frontend/src/lib/common/framework/components/ui/table/table-header.svelte b/frontend/src/lib/common/framework/components/ui/table/table-header.svelte new file mode 100644 index 0000000..ceb8b31 --- /dev/null +++ b/frontend/src/lib/common/framework/components/ui/table/table-header.svelte @@ -0,0 +1,20 @@ + + + + {@render children?.()} + diff --git a/frontend/src/lib/common/framework/components/ui/table/table-row.svelte b/frontend/src/lib/common/framework/components/ui/table/table-row.svelte new file mode 100644 index 0000000..dda35a1 --- /dev/null +++ b/frontend/src/lib/common/framework/components/ui/table/table-row.svelte @@ -0,0 +1,23 @@ + + +svelte-css-wrapper]:[&>th,td]:bg-muted/50', + className + )} + {...restProps} +> + {@render children?.()} + diff --git a/frontend/src/lib/common/framework/components/ui/table/table.svelte b/frontend/src/lib/common/framework/components/ui/table/table.svelte new file mode 100644 index 0000000..7d070e2 --- /dev/null +++ b/frontend/src/lib/common/framework/components/ui/table/table.svelte @@ -0,0 +1,22 @@ + + +
+ + {@render children?.()} +
+
diff --git a/frontend/src/lib/container.ts b/frontend/src/lib/container.ts index 67f8357..cb9cc20 100644 --- a/frontend/src/lib/container.ts +++ b/frontend/src/lib/container.ts @@ -6,7 +6,7 @@ import { GetCurrentUserUseCase } from '$lib/auth/application/useCase/getCurrentU import { AuthApiServiceImpl } from '$lib/auth/framework/api/authApiServiceImpl'; import type { ImageApiService } from '$lib/image/adapter/gateway/imageApiService'; import { ImageRepositoryImpl } from '$lib/image/adapter/gateway/imageRepositoryImpl'; -import { ImageUploadedStore } from '$lib/image/adapter/presenter/ImageUploadedStore'; +import { ImageUploadedStore } from '$lib/image/adapter/presenter/imageUploadedStore'; import type { ImageRepository } from '$lib/image/application/gateway/imageRepository'; import { UploadImageUseCase } from '$lib/image/application/useCase/uploadImageUseCase'; import { ImageApiServiceImpl } from '$lib/image/framework/api/imageApiServiceImpl'; diff --git a/frontend/src/lib/image/framework/ui/ImageManagementPage.svelte b/frontend/src/lib/image/framework/ui/ImageManagementPage.svelte index 4000eeb..ddb7f08 100644 --- a/frontend/src/lib/image/framework/ui/ImageManagementPage.svelte +++ b/frontend/src/lib/image/framework/ui/ImageManagementPage.svelte @@ -2,7 +2,7 @@ import { getContext } from 'svelte'; import UploadImageDialoag from '$lib/image/framework/ui/UploadImageDialoag.svelte'; import { toast } from 'svelte-sonner'; - import { ImageUploadedStore } from '$lib/image/adapter/presenter/ImageUploadedStore'; + import { ImageUploadedStore } from '$lib/image/adapter/presenter/imageUploadedStore'; const store = getContext(ImageUploadedStore.name); const state = $derived($store); diff --git a/frontend/src/lib/post/adapter/gateway/postApiService.ts b/frontend/src/lib/post/adapter/gateway/postApiService.ts index d9f4a31..3d5d9d2 100644 --- a/frontend/src/lib/post/adapter/gateway/postApiService.ts +++ b/frontend/src/lib/post/adapter/gateway/postApiService.ts @@ -1,9 +1,10 @@ import type { CreatePostRequestDto } from '$lib/post/adapter/gateway/creatPostRequestDto'; import type { PostInfoResponseDto } from '$lib/post/adapter/gateway/postInfoResponseDto'; +import type { PostListQueryDto } from '$lib/post/adapter/gateway/postListQueryDto'; import type { PostResponseDto } from '$lib/post/adapter/gateway/postResponseDto'; export interface PostApiService { - getAllPosts(): Promise; + getAllPosts(searchParams: PostListQueryDto): Promise; getPost(id: string): Promise; createPost(payload: CreatePostRequestDto): Promise; } diff --git a/frontend/src/lib/post/adapter/gateway/postListQueryDto.ts b/frontend/src/lib/post/adapter/gateway/postListQueryDto.ts new file mode 100644 index 0000000..7c9eb74 --- /dev/null +++ b/frontend/src/lib/post/adapter/gateway/postListQueryDto.ts @@ -0,0 +1,13 @@ +export class PostListQueryDto { + readonly showUnpublished: boolean; + + constructor(props: { showUnpublished: boolean }) { + this.showUnpublished = props.showUnpublished; + } + + toSearchParams(): URLSearchParams { + const params = new URLSearchParams(); + params.append('is_published_only', (!this.showUnpublished).toString()); + return params; + } +} diff --git a/frontend/src/lib/post/adapter/gateway/postRepositoryImpl.ts b/frontend/src/lib/post/adapter/gateway/postRepositoryImpl.ts index 6a69c72..31279f2 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 { PostListQueryDto } from '$lib/post/adapter/gateway/postListQueryDto'; import type { CreatePostParams, PostRepository, @@ -10,9 +11,10 @@ import type { PostInfo } from '$lib/post/domain/entity/postInfo'; export class PostRepositoryImpl implements PostRepository { constructor(private readonly postApiService: PostApiService) {} - async getAllPosts(): Promise { - const dtos = await this.postApiService.getAllPosts(); - return dtos.map((dto) => dto.toEntity()); + async getAllPosts(showUnpublished: boolean): Promise { + const queryDto = new PostListQueryDto({ showUnpublished }); + const responseDtos = await this.postApiService.getAllPosts(queryDto); + return responseDtos.map((dto) => dto.toEntity()); } async getPost(id: string): Promise { diff --git a/frontend/src/lib/post/adapter/presenter/postLoadedStore.ts b/frontend/src/lib/post/adapter/presenter/postLoadedStore.ts index 9c5a3b9..1f423f1 100644 --- a/frontend/src/lib/post/adapter/presenter/postLoadedStore.ts +++ b/frontend/src/lib/post/adapter/presenter/postLoadedStore.ts @@ -5,7 +5,7 @@ import type { GetPostUseCase } from '$lib/post/application/useCase/getPostUseCas import { captureException } from '@sentry/sveltekit'; import { get, writable } from 'svelte/store'; -export type PostState = AsyncState; +type PostState = AsyncState; export class PostLoadedStore implements BaseStore { private readonly state = writable(AsyncState.idle(null)); diff --git a/frontend/src/lib/post/adapter/presenter/postsListedStore.ts b/frontend/src/lib/post/adapter/presenter/postsListedStore.ts index 3a98774..1c1ad7e 100644 --- a/frontend/src/lib/post/adapter/presenter/postsListedStore.ts +++ b/frontend/src/lib/post/adapter/presenter/postsListedStore.ts @@ -5,9 +5,9 @@ import type { GetAllPostsUseCase } from '$lib/post/application/useCase/getAllPos import { captureException } from '@sentry/sveltekit'; import { get, writable } from 'svelte/store'; -export type PostListState = AsyncState; +type PostListState = AsyncState; -export class PostsListedStore implements BaseStore { +export class PostsListedStore implements BaseStore { private readonly state = writable(AsyncState.idle([])); constructor( @@ -24,15 +24,15 @@ export class PostsListedStore implements BaseStore { } get trigger() { - return () => this.loadPosts(); + return (options?: { showUnpublished: boolean }) => this.loadPosts(options?.showUnpublished); } - private async loadPosts(): Promise { + private async loadPosts(showUnpublished?: boolean): Promise { this.state.set(AsyncState.loading(get(this.state).data)); let result: PostListState; try { - const posts = await this.getAllPostsUseCase.execute(); + const posts = await this.getAllPostsUseCase.execute(showUnpublished); const postViewModels = posts.map((post) => PostInfoViewModel.fromEntity(post)); result = AsyncState.success(postViewModels); } catch (e) { diff --git a/frontend/src/lib/post/application/gateway/postRepository.ts b/frontend/src/lib/post/application/gateway/postRepository.ts index bff44f9..a866273 100644 --- a/frontend/src/lib/post/application/gateway/postRepository.ts +++ b/frontend/src/lib/post/application/gateway/postRepository.ts @@ -2,7 +2,7 @@ import type { Post } from '$lib/post/domain/entity/post'; import type { PostInfo } from '$lib/post/domain/entity/postInfo'; export interface PostRepository { - getAllPosts(): Promise; + getAllPosts(showUnpublished: boolean): Promise; getPost(id: string): Promise; createPost(params: CreatePostParams): Promise; } diff --git a/frontend/src/lib/post/application/useCase/getAllPostsUseCase.ts b/frontend/src/lib/post/application/useCase/getAllPostsUseCase.ts index b585fa5..bdb6883 100644 --- a/frontend/src/lib/post/application/useCase/getAllPostsUseCase.ts +++ b/frontend/src/lib/post/application/useCase/getAllPostsUseCase.ts @@ -4,7 +4,7 @@ import type { PostInfo } from '$lib/post/domain/entity/postInfo'; export class GetAllPostsUseCase { constructor(private readonly postRepository: PostRepository) {} - execute(): Promise { - return this.postRepository.getAllPosts(); + execute(showUnpublished: boolean = false): Promise { + return this.postRepository.getAllPosts(showUnpublished); } } diff --git a/frontend/src/lib/post/framework/api/postApiServiceImpl.ts b/frontend/src/lib/post/framework/api/postApiServiceImpl.ts index 13359b6..deada9a 100644 --- a/frontend/src/lib/post/framework/api/postApiServiceImpl.ts +++ b/frontend/src/lib/post/framework/api/postApiServiceImpl.ts @@ -4,13 +4,15 @@ 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 type { PostListQueryDto } from '$lib/post/adapter/gateway/postListQueryDto'; import { PostResponseDto } from '$lib/post/adapter/gateway/postResponseDto'; export class PostApiServiceImpl implements PostApiService { constructor(private readonly fetchFn: typeof fetch) {} - async getAllPosts(): Promise { + async getAllPosts(searchParams: PostListQueryDto): Promise { const url = new URL('post', Environment.API_BASE_URL); + url.search = searchParams.toSearchParams().toString(); const response = await this.fetchFn(url); diff --git a/frontend/src/lib/post/framework/ui/PostContentPage.svelte b/frontend/src/lib/post/framework/ui/PostContentPage.svelte index 604c05c..a9cf1b8 100644 --- a/frontend/src/lib/post/framework/ui/PostContentPage.svelte +++ b/frontend/src/lib/post/framework/ui/PostContentPage.svelte @@ -34,7 +34,7 @@ {/if} {/if} -
+
{#if postInfo}
diff --git a/frontend/src/lib/post/framework/ui/PostManagementPage.svelte b/frontend/src/lib/post/framework/ui/PostManagementPage.svelte index 1f40708..dfc98a2 100644 --- a/frontend/src/lib/post/framework/ui/PostManagementPage.svelte +++ b/frontend/src/lib/post/framework/ui/PostManagementPage.svelte @@ -1,14 +1,26 @@

Post

- +
-

+ + + + ID + Title + Labels + Published Time + + + + {#if postsListedState.isSuccess()} + {#each postsListedState.data as postInfo (postInfo.id)} + + {postInfo.id} + {postInfo.title} + + {#each postInfo.labels as label (label.id)} + + {/each} + + {postInfo.formattedPublishedTime || '---'} + + {/each} + {/if} + +
diff --git a/frontend/src/routes/dashboard/+layout.svelte b/frontend/src/routes/dashboard/+layout.svelte index 314b34c..ad99747 100644 --- a/frontend/src/routes/dashboard/+layout.svelte +++ b/frontend/src/routes/dashboard/+layout.svelte @@ -31,7 +31,7 @@ {:else if !isAuthenticated} {:else} -
+
{@render children()}
diff --git a/frontend/src/routes/dashboard/image/+page.svelte b/frontend/src/routes/dashboard/image/+page.svelte index cb65962..4139c2e 100644 --- a/frontend/src/routes/dashboard/image/+page.svelte +++ b/frontend/src/routes/dashboard/image/+page.svelte @@ -1,6 +1,6 @@