BLOG-45 Post content page #67
1
frontend/src/app.d.ts
vendored
1
frontend/src/app.d.ts
vendored
@ -6,6 +6,7 @@ declare global {
|
|||||||
|
|
||||||
interface Locals {
|
interface Locals {
|
||||||
postListBloc: import('$lib/post/adapter/presenter/postListBloc').PostListBloc;
|
postListBloc: import('$lib/post/adapter/presenter/postListBloc').PostListBloc;
|
||||||
|
postBloc: import('$lib/post/adapter/presenter/postBloc').PostBloc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
import { PostRepositoryImpl } from '$lib/post/adapter/gateway/postRepositoryImpl';
|
import { PostRepositoryImpl } from '$lib/post/adapter/gateway/postRepositoryImpl';
|
||||||
|
import { PostBloc } from '$lib/post/adapter/presenter/postBloc';
|
||||||
import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc';
|
import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc';
|
||||||
import { GetAllPostUseCase } from '$lib/post/application/useCase/getAllPostsUseCase';
|
import { GetAllPostsUseCase } from '$lib/post/application/useCase/getAllPostsUseCase';
|
||||||
|
import { GetPostUseCase } from '$lib/post/application/useCase/getPostUseCase';
|
||||||
import { PostApiServiceImpl } from '$lib/post/framework/api/postApiServiceImpl';
|
import { PostApiServiceImpl } from '$lib/post/framework/api/postApiServiceImpl';
|
||||||
import type { Handle } from '@sveltejs/kit';
|
import type { Handle } from '@sveltejs/kit';
|
||||||
|
|
||||||
export const handle: Handle = ({ event, resolve }) => {
|
export const handle: Handle = ({ event, resolve }) => {
|
||||||
const postApiService = new PostApiServiceImpl(event.fetch);
|
const postApiService = new PostApiServiceImpl(event.fetch);
|
||||||
const postRepository = new PostRepositoryImpl(postApiService);
|
const postRepository = new PostRepositoryImpl(postApiService);
|
||||||
const getAllPostsUseCase = new GetAllPostUseCase(postRepository);
|
const getAllPostsUseCase = new GetAllPostsUseCase(postRepository);
|
||||||
|
const getPostUseCase = new GetPostUseCase(postRepository);
|
||||||
|
|
||||||
event.locals.postListBloc = new PostListBloc(getAllPostsUseCase);
|
event.locals.postListBloc = new PostListBloc(getAllPostsUseCase);
|
||||||
|
event.locals.postBloc = new PostBloc(getPostUseCase);
|
||||||
|
|
||||||
return resolve(event);
|
return resolve(event);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import type { PostInfoResponseDto } from '$lib/post/adapter/gateway/postInfoResponseDto';
|
import type { PostInfoResponseDto } from '$lib/post/adapter/gateway/postInfoResponseDto';
|
||||||
|
import type { PostResponseDto } from '$lib/post/adapter/gateway/postResponseDto';
|
||||||
|
|
||||||
export interface PostApiService {
|
export interface PostApiService {
|
||||||
getAllPosts(): Promise<PostInfoResponseDto[]>;
|
getAllPosts(): Promise<PostInfoResponseDto[]>;
|
||||||
|
getPost(id: number): Promise<PostResponseDto | null>;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
|
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
|
||||||
import type { PostRepository } from '$lib/post/application/repository/postRepository';
|
import type { PostRepository } from '$lib/post/application/repository/postRepository';
|
||||||
|
import type { Post } from '$lib/post/domain/entity/post';
|
||||||
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
||||||
|
|
||||||
export class PostRepositoryImpl implements PostRepository {
|
export class PostRepositoryImpl implements PostRepository {
|
||||||
@ -9,4 +10,9 @@ export class PostRepositoryImpl implements PostRepository {
|
|||||||
const dtos = await this.postApiService.getAllPosts();
|
const dtos = await this.postApiService.getAllPosts();
|
||||||
return dtos.map((dto) => dto.toEntity());
|
return dtos.map((dto) => dto.toEntity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getPost(id: number): Promise<Post | null> {
|
||||||
|
const dto = await this.postApiService.getPost(id);
|
||||||
|
return dto?.toEntity() ?? null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
41
frontend/src/lib/post/adapter/gateway/postResponseDto.ts
Normal file
41
frontend/src/lib/post/adapter/gateway/postResponseDto.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
PostInfoResponseDto,
|
||||||
|
PostInfoResponseSchema
|
||||||
|
} from '$lib/post/adapter/gateway/postInfoResponseDto';
|
||||||
|
import { Post } from '$lib/post/domain/entity/post';
|
||||||
|
import z from 'zod';
|
||||||
|
|
||||||
|
export const PostResponseSchema = z.object({
|
||||||
|
id: z.int32(),
|
||||||
|
info: PostInfoResponseSchema,
|
||||||
|
content: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export class PostResponseDto {
|
||||||
|
readonly id: number;
|
||||||
|
readonly info: PostInfoResponseDto;
|
||||||
|
readonly content: string;
|
||||||
|
|
||||||
|
private constructor(props: { id: number; info: PostInfoResponseDto; content: string }) {
|
||||||
|
this.id = props.id;
|
||||||
|
this.info = props.info;
|
||||||
|
this.content = props.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson(json: unknown): PostResponseDto {
|
||||||
|
const parsedJson = PostResponseSchema.parse(json);
|
||||||
|
return new PostResponseDto({
|
||||||
|
id: parsedJson.id,
|
||||||
|
info: PostInfoResponseDto.fromJson(parsedJson.info),
|
||||||
|
content: parsedJson.content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toEntity(): Post {
|
||||||
|
return new Post({
|
||||||
|
id: this.id,
|
||||||
|
info: this.info.toEntity(),
|
||||||
|
content: this.content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
62
frontend/src/lib/post/adapter/presenter/postBloc.ts
Normal file
62
frontend/src/lib/post/adapter/presenter/postBloc.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { StatusType, type AsyncState } from '$lib/common/adapter/presenter/asyncState';
|
||||||
|
import { PostViewModel } from '$lib/post/adapter/presenter/postViewModel';
|
||||||
|
import type { GetPostUseCase } from '$lib/post/application/useCase/getPostUseCase';
|
||||||
|
import { get, writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export type PostState = AsyncState<PostViewModel>;
|
||||||
|
export type PostEvent = PostLoadedEvent;
|
||||||
|
|
||||||
|
export class PostBloc {
|
||||||
|
private readonly state = writable<PostState>({
|
||||||
|
status: StatusType.Idle
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly getPostUseCase: GetPostUseCase,
|
||||||
|
initialData?: PostViewModel
|
||||||
|
) {
|
||||||
|
this.state.set({
|
||||||
|
status: StatusType.Idle,
|
||||||
|
data: initialData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get subscribe() {
|
||||||
|
return this.state.subscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
async dispatch(event: PostEvent): Promise<PostState> {
|
||||||
|
switch (event.event) {
|
||||||
|
case PostEventType.PostLoadedEvent:
|
||||||
|
return this.loadPost(event.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadPost(id: number): Promise<PostState> {
|
||||||
|
this.state.set({ status: StatusType.Loading, data: get(this.state).data });
|
||||||
|
|
||||||
|
const post = await this.getPostUseCase.execute(id);
|
||||||
|
if (!post) {
|
||||||
|
this.state.set({ status: StatusType.Error, error: new Error('Post not found') });
|
||||||
|
return get(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
const postViewModel = PostViewModel.fromEntity(post);
|
||||||
|
const result: PostState = {
|
||||||
|
status: StatusType.Success,
|
||||||
|
data: postViewModel
|
||||||
|
};
|
||||||
|
|
||||||
|
this.state.set(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PostEventType {
|
||||||
|
PostLoadedEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PostLoadedEvent {
|
||||||
|
event: PostEventType.PostLoadedEvent;
|
||||||
|
id: number;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { StatusType, type AsyncState } from '$lib/common/adapter/presenter/asyncState';
|
import { StatusType, type AsyncState } from '$lib/common/adapter/presenter/asyncState';
|
||||||
import { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel';
|
import { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel';
|
||||||
import type { GetAllPostUseCase } from '$lib/post/application/useCase/getAllPostsUseCase';
|
import type { GetAllPostsUseCase } from '$lib/post/application/useCase/getAllPostsUseCase';
|
||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
|
|
||||||
export type PostListState = AsyncState<readonly PostInfoViewModel[]>;
|
export type PostListState = AsyncState<readonly PostInfoViewModel[]>;
|
||||||
@ -12,7 +12,7 @@ export class PostListBloc {
|
|||||||
});
|
});
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly getAllPostsUseCase: GetAllPostUseCase,
|
private readonly getAllPostsUseCase: GetAllPostsUseCase,
|
||||||
initialData?: readonly PostInfoViewModel[]
|
initialData?: readonly PostInfoViewModel[]
|
||||||
) {
|
) {
|
||||||
this.state.set({
|
this.state.set({
|
||||||
|
47
frontend/src/lib/post/adapter/presenter/postViewModel.ts
Normal file
47
frontend/src/lib/post/adapter/presenter/postViewModel.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import {
|
||||||
|
PostInfoViewModel,
|
||||||
|
type DehydratedPostInfoProps
|
||||||
|
} from '$lib/post/adapter/presenter/postInfoViewModel';
|
||||||
|
import type { Post } from '$lib/post/domain/entity/post';
|
||||||
|
|
||||||
|
export class PostViewModel {
|
||||||
|
id: number;
|
||||||
|
info: PostInfoViewModel;
|
||||||
|
content: string;
|
||||||
|
|
||||||
|
private constructor(props: { id: number; info: PostInfoViewModel; content: string }) {
|
||||||
|
this.id = props.id;
|
||||||
|
this.info = props.info;
|
||||||
|
this.content = props.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromEntity(post: Post): PostViewModel {
|
||||||
|
return new PostViewModel({
|
||||||
|
id: post.id,
|
||||||
|
info: PostInfoViewModel.fromEntity(post.info),
|
||||||
|
content: post.content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static rehydrate(props: DehydratedPostProps): PostViewModel {
|
||||||
|
return new PostViewModel({
|
||||||
|
id: props.id,
|
||||||
|
info: PostInfoViewModel.rehydrate(props.info),
|
||||||
|
content: props.content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dehydrate(): DehydratedPostProps {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
info: this.info.dehydrate(),
|
||||||
|
content: this.content
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DehydratedPostProps {
|
||||||
|
id: number;
|
||||||
|
info: DehydratedPostInfoProps;
|
||||||
|
content: string;
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
|
import type { Post } from '$lib/post/domain/entity/post';
|
||||||
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
||||||
|
|
||||||
export interface PostRepository {
|
export interface PostRepository {
|
||||||
getAllPosts(): Promise<PostInfo[]>;
|
getAllPosts(): Promise<PostInfo[]>;
|
||||||
|
getPost(id: number): Promise<Post | null>;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { PostRepository } from '$lib/post/application/repository/postRepository';
|
import type { PostRepository } from '$lib/post/application/repository/postRepository';
|
||||||
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
||||||
|
|
||||||
export class GetAllPostUseCase {
|
export class GetAllPostsUseCase {
|
||||||
constructor(private readonly postRepository: PostRepository) {}
|
constructor(private readonly postRepository: PostRepository) {}
|
||||||
|
|
||||||
execute(): Promise<PostInfo[]> {
|
execute(): Promise<PostInfo[]> {
|
||||||
|
10
frontend/src/lib/post/application/useCase/getPostUseCase.ts
Normal file
10
frontend/src/lib/post/application/useCase/getPostUseCase.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import type { PostRepository } from '$lib/post/application/repository/postRepository';
|
||||||
|
import type { Post } from '$lib/post/domain/entity/post';
|
||||||
|
|
||||||
|
export class GetPostUseCase {
|
||||||
|
constructor(private readonly postRepository: PostRepository) {}
|
||||||
|
|
||||||
|
execute(id: number): Promise<Post | null> {
|
||||||
|
return this.postRepository.getPost(id);
|
||||||
|
}
|
||||||
|
}
|
13
frontend/src/lib/post/domain/entity/post.ts
Normal file
13
frontend/src/lib/post/domain/entity/post.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { PostInfo } from '$lib/post/domain/entity/postInfo';
|
||||||
|
|
||||||
|
export class Post {
|
||||||
|
id: number;
|
||||||
|
info: PostInfo;
|
||||||
|
content: string;
|
||||||
|
|
||||||
|
constructor(props: { id: number; info: PostInfo; content: string }) {
|
||||||
|
this.id = props.id;
|
||||||
|
this.info = props.info;
|
||||||
|
this.content = props.content;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { Environment } from '$lib/environment';
|
import { Environment } from '$lib/environment';
|
||||||
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
|
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
|
||||||
import { PostInfoResponseDto } from '$lib/post/adapter/gateway/postInfoResponseDto';
|
import { PostInfoResponseDto } from '$lib/post/adapter/gateway/postInfoResponseDto';
|
||||||
|
import { PostResponseDto } from '$lib/post/adapter/gateway/postResponseDto';
|
||||||
|
|
||||||
export class PostApiServiceImpl implements PostApiService {
|
export class PostApiServiceImpl implements PostApiService {
|
||||||
constructor(private fetchFn: typeof fetch) {}
|
constructor(private fetchFn: typeof fetch) {}
|
||||||
@ -17,4 +18,17 @@ export class PostApiServiceImpl implements PostApiService {
|
|||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
return json.map(PostInfoResponseDto.fromJson);
|
return json.map(PostInfoResponseDto.fromJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getPost(id: number): Promise<PostResponseDto | null> {
|
||||||
|
const url = new URL(`post/${id}`, Environment.API_BASE_URL);
|
||||||
|
|
||||||
|
const response = await this.fetchFn(url.href);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await response.json();
|
||||||
|
return PostResponseDto.fromJson(json);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,5 +9,5 @@
|
|||||||
style="background-color: {label.color.hex};"
|
style="background-color: {label.color.hex};"
|
||||||
>
|
>
|
||||||
<div class="size-2 rounded-full" style="background-color: {label.color.darken(0.2).hex};"></div>
|
<div class="size-2 rounded-full" style="background-color: {label.color.darken(0.2).hex};"></div>
|
||||||
<span>{label.name}</span>
|
<span class="text-xs">{label.name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
17
frontend/src/lib/post/framework/ui/PostContentHeader.svelte
Normal file
17
frontend/src/lib/post/framework/ui/PostContentHeader.svelte
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel';
|
||||||
|
import Label from '$lib/post/framework/ui/Label.svelte';
|
||||||
|
|
||||||
|
const { postInfo }: { postInfo: PostInfoViewModel } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-y-4 py-9">
|
||||||
|
<div class="flex flex-row gap-2">
|
||||||
|
{#each postInfo.labels as label (label.id)}
|
||||||
|
<Label {label} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<h1 class="text-3xl font-bold">{postInfo.title}</h1>
|
||||||
|
<span>{postInfo.description}</span>
|
||||||
|
<span class="text-gray-500">{postInfo.formattedPublishedTime}</span>
|
||||||
|
</div>
|
@ -1,6 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PostInfoViewModel } from "$lib/post/adapter/presenter/postInfoViewModel";
|
import { PostBloc, PostEventType } from '$lib/post/adapter/presenter/postBloc';
|
||||||
|
import PostContentHeader from '$lib/post/framework/ui/PostContentHeader.svelte';
|
||||||
|
import { getContext, onMount } from 'svelte';
|
||||||
|
|
||||||
const { post }: { post: PostInfoViewModel } = $props();
|
const { id }: { id: number } = $props();
|
||||||
|
|
||||||
|
const postBloc = getContext<PostBloc>(PostBloc.name);
|
||||||
|
const state = $derived($postBloc);
|
||||||
|
|
||||||
|
onMount(() => postBloc.dispatch({ event: PostEventType.PostLoadedEvent, id: id }));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="container pb-10">
|
||||||
|
{#if state.data}
|
||||||
|
<PostContentHeader postInfo={state.data.info} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container pb-10">
|
<div class="container pb-10">
|
||||||
<div class="py-9 text-center text-3xl font-bold text-gray-800 md:py-20 md:text-5xl">文章</div>
|
<h1 class="py-9 text-center text-3xl font-bold text-gray-800 md:py-20 md:text-5xl">文章</h1>
|
||||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 md:gap-y-8 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 md:gap-y-8 lg:grid-cols-3">
|
||||||
{#each state.data ?? [] as postInfo (postInfo.id)}
|
{#each state.data ?? [] as postInfo (postInfo.id)}
|
||||||
<PostPreview {postInfo} />
|
<PostPreview {postInfo} />
|
||||||
|
@ -5,12 +5,12 @@
|
|||||||
const { labels }: { labels: readonly LabelViewModel[] } = $props();
|
const { labels }: { labels: readonly LabelViewModel[] } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-row gap-x-2 text-xs">
|
<div class="flex flex-row gap-x-2">
|
||||||
{#each labels.slice(0, 2) as label (label.id)}
|
{#each labels.slice(0, 2) as label (label.id)}
|
||||||
<Label {label} />
|
<Label {label} />
|
||||||
{/each}
|
{/each}
|
||||||
{#if labels.length > 2}
|
{#if labels.length > 2}
|
||||||
<div class="rounded-full bg-gray-200 px-2 py-0.5">
|
<div class="rounded-full bg-gray-200 px-2 py-0.5 text-xs">
|
||||||
<span>+{labels.length - 2}</span>
|
<span>+{labels.length - 2}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/state';
|
||||||
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="mx-auto flex min-h-content-height max-w-screen-xl flex-col items-center justify-center px-4 md:px-6"
|
class="min-h-content-height mx-auto flex max-w-screen-xl flex-col items-center justify-center px-4 md:px-6"
|
||||||
>
|
>
|
||||||
<div class="flex flex-row items-end gap-x-4 md:gap-x-6">
|
<div class="flex flex-row items-end gap-x-4 md:gap-x-6">
|
||||||
<h1 class="text-5xl font-extrabold text-gray-800 underline md:text-7xl">404</h1>
|
<h1 class="text-5xl font-extrabold text-gray-800 underline md:text-7xl">404</h1>
|
||||||
@ -12,3 +16,4 @@
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>{page.error?.message}</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PostRepositoryImpl } from '$lib/post/adapter/gateway/postRepositoryImpl';
|
import { PostRepositoryImpl } from '$lib/post/adapter/gateway/postRepositoryImpl';
|
||||||
import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc';
|
import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc';
|
||||||
import { GetAllPostUseCase } from '$lib/post/application/useCase/getAllPostsUseCase';
|
import { GetAllPostsUseCase } from '$lib/post/application/useCase/getAllPostsUseCase';
|
||||||
import { PostApiServiceImpl } from '$lib/post/framework/api/postApiServiceImpl';
|
import { PostApiServiceImpl } from '$lib/post/framework/api/postApiServiceImpl';
|
||||||
import { setContext } from 'svelte';
|
import { setContext } from 'svelte';
|
||||||
import type { PageProps } from './$types';
|
import type { PageProps } from './$types';
|
||||||
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
const postApiService = new PostApiServiceImpl(fetch);
|
const postApiService = new PostApiServiceImpl(fetch);
|
||||||
const postRepository = new PostRepositoryImpl(postApiService);
|
const postRepository = new PostRepositoryImpl(postApiService);
|
||||||
const getAllPostsUseCase = new GetAllPostUseCase(postRepository);
|
const getAllPostsUseCase = new GetAllPostsUseCase(postRepository);
|
||||||
const postListBloc = new PostListBloc(getAllPostsUseCase, initialData);
|
const postListBloc = new PostListBloc(getAllPostsUseCase, initialData);
|
||||||
|
|
||||||
setContext(PostListBloc.name, postListBloc);
|
setContext(PostListBloc.name, postListBloc);
|
||||||
|
24
frontend/src/routes/post/[id]/+page.server.ts
Normal file
24
frontend/src/routes/post/[id]/+page.server.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { PostEventType } from '$lib/post/adapter/presenter/postBloc';
|
||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ locals, params }) => {
|
||||||
|
const { postBloc } = locals;
|
||||||
|
|
||||||
|
const id = parseInt(params.id, 10);
|
||||||
|
if (isNaN(id) || id <= 0) {
|
||||||
|
error(400, { message: 'Invalid post ID' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = await postBloc.dispatch({
|
||||||
|
event: PostEventType.PostLoadedEvent,
|
||||||
|
id: id
|
||||||
|
});
|
||||||
|
if (!state.data) {
|
||||||
|
error(404, { message: 'Post not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dehydratedData: state.data.dehydrate()
|
||||||
|
};
|
||||||
|
};
|
@ -1,7 +1,25 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { PostRepositoryImpl } from '$lib/post/adapter/gateway/postRepositoryImpl';
|
||||||
|
import { PostBloc } from '$lib/post/adapter/presenter/postBloc';
|
||||||
|
import { PostViewModel } from '$lib/post/adapter/presenter/postViewModel';
|
||||||
|
import { GetPostUseCase } from '$lib/post/application/useCase/getPostUseCase';
|
||||||
|
import { PostApiServiceImpl } from '$lib/post/framework/api/postApiServiceImpl';
|
||||||
|
import { setContext } from 'svelte';
|
||||||
import type { PageProps } from './$types';
|
import type { PageProps } from './$types';
|
||||||
|
import PostContentPage from '$lib/post/framework/ui/PostContentPage.svelte';
|
||||||
|
|
||||||
const { data }: PageProps = $props();
|
const { data, params }: PageProps = $props();
|
||||||
|
|
||||||
|
const id = parseInt(params.id, 10);
|
||||||
|
|
||||||
|
const initialData = PostViewModel.rehydrate(data.dehydratedData!);
|
||||||
|
|
||||||
|
const postApiService = new PostApiServiceImpl(fetch);
|
||||||
|
const postRepository = new PostRepositoryImpl(postApiService);
|
||||||
|
const getPostUseCase = new GetPostUseCase(postRepository);
|
||||||
|
const postBloc = new PostBloc(getPostUseCase, initialData);
|
||||||
|
|
||||||
|
setContext(PostBloc.name, postBloc);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>{data.id}</div>
|
<PostContentPage {id} />
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import type { PageLoad } from './$types';
|
|
||||||
|
|
||||||
export const load: PageLoad = async ({ params }) => {
|
|
||||||
return {
|
|
||||||
id: params.id,
|
|
||||||
}
|
|
||||||
};
|
|
Loading…
x
Reference in New Issue
Block a user