BLOG-126 Post management (list and create) #139
@ -1,15 +1,15 @@
|
|||||||
import { AuthViewModel } from '$lib/auth/adapter/presenter/authViewModel';
|
import { AuthViewModel } from '$lib/auth/adapter/presenter/authViewModel';
|
||||||
import { UserViewModel } from '$lib/auth/adapter/presenter/userViewModel';
|
import { UserViewModel } from '$lib/auth/adapter/presenter/userViewModel';
|
||||||
import type { GetCurrentUserUseCase } from '$lib/auth/application/useCase/getCurrentUserUseCase';
|
import type { GetCurrentUserUseCase } from '$lib/auth/application/useCase/getCurrentUserUseCase';
|
||||||
import { StateFactory, type AsyncState } from '$lib/common/adapter/presenter/asyncState';
|
import { AsyncState } from '$lib/common/adapter/presenter/asyncState';
|
||||||
|
import type { BaseStore } from '$lib/common/adapter/presenter/baseStore';
|
||||||
import { captureException } from '@sentry/sveltekit';
|
import { captureException } from '@sentry/sveltekit';
|
||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
|
|
||||||
export type AuthState = AsyncState<AuthViewModel>;
|
export type AuthState = AsyncState<AuthViewModel>;
|
||||||
export type AuthEvent = CurrentUserLoadedEvent;
|
|
||||||
|
|
||||||
export class AuthBloc {
|
export class AuthLoadedStore implements BaseStore<AuthState> {
|
||||||
private readonly state = writable<AuthState>(StateFactory.idle());
|
private readonly state = writable<AuthState>(AsyncState.idle<AuthViewModel>(null));
|
||||||
|
|
||||||
constructor(private readonly getCurrentUserUseCase: GetCurrentUserUseCase) {}
|
constructor(private readonly getCurrentUserUseCase: GetCurrentUserUseCase) {}
|
||||||
|
|
||||||
@ -17,24 +17,21 @@ export class AuthBloc {
|
|||||||
return this.state.subscribe;
|
return this.state.subscribe;
|
||||||
}
|
}
|
||||||
|
|
||||||
async dispatch(event: AuthEvent): Promise<AuthState> {
|
get trigger() {
|
||||||
switch (event.event) {
|
return () => this.loadCurrentUser();
|
||||||
case AuthEventType.CurrentUserLoadedEvent:
|
|
||||||
return this.loadCurrentUser();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadCurrentUser(): Promise<AuthState> {
|
private async loadCurrentUser(): Promise<AuthState> {
|
||||||
this.state.set(StateFactory.loading(get(this.state).data));
|
this.state.set(AsyncState.loading(get(this.state).data));
|
||||||
|
|
||||||
let result: AuthState;
|
let result: AuthState;
|
||||||
try {
|
try {
|
||||||
const user = await this.getCurrentUserUseCase.execute();
|
const user = await this.getCurrentUserUseCase.execute();
|
||||||
const userViewModel = user ? UserViewModel.fromEntity(user) : null;
|
const userViewModel = user ? UserViewModel.fromEntity(user) : null;
|
||||||
const authViewModel = AuthViewModel.fromEntity(userViewModel);
|
const authViewModel = AuthViewModel.fromEntity(userViewModel);
|
||||||
result = StateFactory.success(authViewModel);
|
result = AsyncState.success(authViewModel);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
result = StateFactory.error(e);
|
result = AsyncState.error(e, get(this.state).data);
|
||||||
captureException(e);
|
captureException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,11 +39,3 @@ export class AuthBloc {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AuthEventType {
|
|
||||||
CurrentUserLoadedEvent,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CurrentUserLoadedEvent {
|
|
||||||
event: AuthEventType.CurrentUserLoadedEvent;
|
|
||||||
}
|
|
@ -1,60 +1,110 @@
|
|||||||
export enum StatusType {
|
enum AsyncStateStatus {
|
||||||
Idle,
|
Idle = 'idle',
|
||||||
Loading,
|
Loading = 'loading',
|
||||||
Success,
|
Success = 'success',
|
||||||
Error,
|
Error = 'error',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AsyncState<T> = IdleState<T> | LoadingState<T> | SuccessState<T> | ErrorState<T>;
|
export abstract class AsyncState<T> {
|
||||||
|
abstract readonly status: AsyncStateStatus;
|
||||||
|
abstract readonly data: T | null;
|
||||||
|
abstract readonly error: Error | null;
|
||||||
|
|
||||||
interface IdleState<T> {
|
static idle<T>(data: T | null): IdleState<T> {
|
||||||
status: StatusType.Idle;
|
return new IdleState(data);
|
||||||
data?: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LoadingState<T> {
|
|
||||||
status: StatusType.Loading;
|
|
||||||
data?: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SuccessState<T> {
|
|
||||||
status: StatusType.Success;
|
|
||||||
data: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ErrorState<T> {
|
|
||||||
status: StatusType.Error;
|
|
||||||
data?: T;
|
|
||||||
error: Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class StateFactory {
|
|
||||||
static idle<T>(data?: T): AsyncState<T> {
|
|
||||||
return {
|
|
||||||
status: StatusType.Idle,
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static loading<T>(data?: T): AsyncState<T> {
|
static loading<T>(data: T | null): LoadingState<T> {
|
||||||
return {
|
return new LoadingState(data);
|
||||||
status: StatusType.Loading,
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static success<T>(data: T): AsyncState<T> {
|
static success<T>(data: T): SuccessState<T> {
|
||||||
return {
|
return new SuccessState(data);
|
||||||
status: StatusType.Success,
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static error<T>(error: unknown, data?: T): AsyncState<T> {
|
static error<T>(error: unknown, data: T | null): ErrorState<T> {
|
||||||
return {
|
const errorInstance = error instanceof Error ? error : new Error(String(error));
|
||||||
status: StatusType.Error,
|
return new ErrorState(errorInstance, data);
|
||||||
error: error instanceof Error ? error : new Error('Unknown error'),
|
}
|
||||||
data,
|
|
||||||
};
|
isIdle(): this is IdleState<T> {
|
||||||
|
return this.status === AsyncStateStatus.Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading(): this is LoadingState<T> {
|
||||||
|
return this.status === AsyncStateStatus.Loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSuccess(): this is SuccessState<T> {
|
||||||
|
return this.status === AsyncStateStatus.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
isError(): this is ErrorState<T> {
|
||||||
|
return this.status === AsyncStateStatus.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IdleState<T> extends AsyncState<T> {
|
||||||
|
readonly status = AsyncStateStatus.Idle;
|
||||||
|
readonly data: T | null;
|
||||||
|
readonly error = null;
|
||||||
|
|
||||||
|
constructor(data: T | null) {
|
||||||
|
super();
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
toLoading(): LoadingState<T> {
|
||||||
|
return new LoadingState(this.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadingState<T> extends AsyncState<T> {
|
||||||
|
readonly status = AsyncStateStatus.Loading;
|
||||||
|
readonly data: T | null;
|
||||||
|
readonly error = null;
|
||||||
|
|
||||||
|
constructor(data: T | null) {
|
||||||
|
super();
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
toSuccess(data: T): SuccessState<T> {
|
||||||
|
return new SuccessState(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
toError(error: Error): ErrorState<T> {
|
||||||
|
return new ErrorState(error, this.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SuccessState<T> extends AsyncState<T> {
|
||||||
|
readonly status = AsyncStateStatus.Success;
|
||||||
|
readonly data: T;
|
||||||
|
readonly error = null;
|
||||||
|
|
||||||
|
constructor(data: T) {
|
||||||
|
super();
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
toLoading(): LoadingState<T> {
|
||||||
|
return new LoadingState(this.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorState<T> extends AsyncState<T> {
|
||||||
|
readonly status = AsyncStateStatus.Error;
|
||||||
|
readonly data: T | null;
|
||||||
|
readonly error: Error;
|
||||||
|
|
||||||
|
constructor(error: Error, data: T | null) {
|
||||||
|
super();
|
||||||
|
this.error = error;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
toLoading(): LoadingState<T> {
|
||||||
|
return new LoadingState(this.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
frontend/src/lib/common/adapter/presenter/baseStore.ts
Normal file
7
frontend/src/lib/common/adapter/presenter/baseStore.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { AsyncState } from '$lib/common/adapter/presenter/asyncState';
|
||||||
|
import type { Readable } from 'svelte/store';
|
||||||
|
|
||||||
|
export interface BaseStore<T extends AsyncState<unknown>, U = void> {
|
||||||
|
get subscribe(): Readable<T>['subscribe'];
|
||||||
|
get trigger(): (arg: U) => Promise<T>;
|
||||||
|
}
|
@ -1,20 +1,21 @@
|
|||||||
import type { AuthApiService } from '$lib/auth/adapter/gateway/authApiService';
|
import type { AuthApiService } from '$lib/auth/adapter/gateway/authApiService';
|
||||||
import { AuthRepositoryImpl } from '$lib/auth/adapter/gateway/authRepositoryImpl';
|
import { AuthRepositoryImpl } from '$lib/auth/adapter/gateway/authRepositoryImpl';
|
||||||
import { AuthBloc } from '$lib/auth/adapter/presenter/authBloc';
|
import { AuthLoadedStore } from '$lib/auth/adapter/presenter/authLoadedStore';
|
||||||
import type { AuthRepository } from '$lib/auth/application/gateway/authRepository';
|
import type { AuthRepository } from '$lib/auth/application/gateway/authRepository';
|
||||||
import { GetCurrentUserUseCase } from '$lib/auth/application/useCase/getCurrentUserUseCase';
|
import { GetCurrentUserUseCase } from '$lib/auth/application/useCase/getCurrentUserUseCase';
|
||||||
import { AuthApiServiceImpl } from '$lib/auth/framework/api/authApiServiceImpl';
|
import { AuthApiServiceImpl } from '$lib/auth/framework/api/authApiServiceImpl';
|
||||||
import type { ImageApiService } from '$lib/image/adapter/gateway/imageApiService';
|
import type { ImageApiService } from '$lib/image/adapter/gateway/imageApiService';
|
||||||
import { ImageRepositoryImpl } from '$lib/image/adapter/gateway/imageRepositoryImpl';
|
import { ImageRepositoryImpl } from '$lib/image/adapter/gateway/imageRepositoryImpl';
|
||||||
import { ImageBloc } from '$lib/image/adapter/presenter/imageBloc';
|
import { ImageUploadedStore } from '$lib/image/adapter/presenter/ImageUploadedStore';
|
||||||
import type { ImageRepository } from '$lib/image/application/gateway/imageRepository';
|
import type { ImageRepository } from '$lib/image/application/gateway/imageRepository';
|
||||||
import { UploadImageUseCase } from '$lib/image/application/useCase/uploadImageUseCase';
|
import { UploadImageUseCase } from '$lib/image/application/useCase/uploadImageUseCase';
|
||||||
import { ImageApiServiceImpl } from '$lib/image/framework/api/imageApiServiceImpl';
|
import { ImageApiServiceImpl } from '$lib/image/framework/api/imageApiServiceImpl';
|
||||||
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
|
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
|
||||||
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 { PostCreatedStore } from '$lib/post/adapter/presenter/postCreatedStore';
|
||||||
import type { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel';
|
import type { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel';
|
||||||
import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc';
|
import { PostsListedStore } from '$lib/post/adapter/presenter/postsListedStore';
|
||||||
|
import { PostLoadedStore } from '$lib/post/adapter/presenter/PostLoadedStore';
|
||||||
import type { PostViewModel } from '$lib/post/adapter/presenter/postViewModel';
|
import type { PostViewModel } from '$lib/post/adapter/presenter/postViewModel';
|
||||||
import type { PostRepository } from '$lib/post/application/gateway/postRepository';
|
import type { PostRepository } from '$lib/post/application/gateway/postRepository';
|
||||||
import { CreatePostUseCase } from '$lib/post/application/useCase/createPostUseCase';
|
import { CreatePostUseCase } from '$lib/post/application/useCase/createPostUseCase';
|
||||||
@ -31,20 +32,24 @@ export class Container {
|
|||||||
this.useCases = new UseCases(repositories);
|
this.useCases = new UseCases(repositories);
|
||||||
}
|
}
|
||||||
|
|
||||||
createAuthBloc(): AuthBloc {
|
createAuthLoadedStore(): AuthLoadedStore {
|
||||||
return new AuthBloc(this.useCases.getCurrentUserUseCase);
|
return new AuthLoadedStore(this.useCases.getCurrentUserUseCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
createImageBloc(): ImageBloc {
|
createImageUploadedStore(): ImageUploadedStore {
|
||||||
return new ImageBloc(this.useCases.uploadImageUseCase);
|
return new ImageUploadedStore(this.useCases.uploadImageUseCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
createPostListBloc(initialData?: readonly PostInfoViewModel[]): PostListBloc {
|
createPostsListedStore(initialData?: readonly PostInfoViewModel[]): PostsListedStore {
|
||||||
return new PostListBloc(this.useCases.getAllPostsUseCase, initialData);
|
return new PostsListedStore(this.useCases.getAllPostsUseCase, initialData);
|
||||||
}
|
}
|
||||||
|
|
||||||
createPostBloc(initialData?: PostViewModel): PostBloc {
|
createPostLoadedStore(initialData?: PostViewModel): PostLoadedStore {
|
||||||
return new PostBloc(this.useCases.getPostUseCase, this.useCases.createPostUseCase, initialData);
|
return new PostLoadedStore(this.useCases.getPostUseCase, initialData);
|
||||||
|
}
|
||||||
|
|
||||||
|
createPostCreatedStore(initialData?: PostViewModel): PostCreatedStore {
|
||||||
|
return new PostCreatedStore(this.useCases.createPostUseCase, initialData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { StateFactory, type AsyncState } from '$lib/common/adapter/presenter/asyncState';
|
import { AsyncState } from '$lib/common/adapter/presenter/asyncState';
|
||||||
import { ImageInfoViewModel } from '$lib/image/adapter/presenter/imageInfoViewModel';
|
import { ImageInfoViewModel } from '$lib/image/adapter/presenter/imageInfoViewModel';
|
||||||
import type { UploadImageUseCase } from '$lib/image/application/useCase/uploadImageUseCase';
|
import type { UploadImageUseCase } from '$lib/image/application/useCase/uploadImageUseCase';
|
||||||
import { captureException } from '@sentry/sveltekit';
|
import { captureException } from '@sentry/sveltekit';
|
||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
|
|
||||||
export type ImageInfoState = AsyncState<ImageInfoViewModel>;
|
export type ImageInfoState = AsyncState<ImageInfoViewModel>;
|
||||||
export type ImageEvent = ImageUploadedEvent;
|
|
||||||
|
|
||||||
export class ImageBloc {
|
export class ImageUploadedStore {
|
||||||
private readonly state = writable<ImageInfoState>(StateFactory.idle());
|
private readonly state = writable<ImageInfoState>(AsyncState.idle<ImageInfoViewModel>(null));
|
||||||
|
|
||||||
constructor(private readonly uploadImageUseCase: UploadImageUseCase) {}
|
constructor(private readonly uploadImageUseCase: UploadImageUseCase) {}
|
||||||
|
|
||||||
@ -16,35 +15,23 @@ export class ImageBloc {
|
|||||||
return this.state.subscribe;
|
return this.state.subscribe;
|
||||||
}
|
}
|
||||||
|
|
||||||
async dispatch(event: ImageEvent): Promise<ImageInfoState> {
|
get trigger() {
|
||||||
switch (event.event) {
|
return (file: File) => this.uploadImage(file);
|
||||||
case ImageEventType.ImageUploadedEvent:
|
|
||||||
return this.uploadImage(event.file);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadImage(file: File): Promise<ImageInfoState> {
|
private async uploadImage(file: File): Promise<ImageInfoState> {
|
||||||
this.state.set(StateFactory.loading(get(this.state).data));
|
this.state.set(AsyncState.loading(get(this.state).data));
|
||||||
|
|
||||||
let result: ImageInfoState;
|
let result: ImageInfoState;
|
||||||
try {
|
try {
|
||||||
const imageInfo = await this.uploadImageUseCase.execute(file);
|
const imageInfo = await this.uploadImageUseCase.execute(file);
|
||||||
const imageInfoViewModel = ImageInfoViewModel.fromEntity(imageInfo);
|
const imageInfoViewModel = ImageInfoViewModel.fromEntity(imageInfo);
|
||||||
result = StateFactory.success(imageInfoViewModel);
|
result = AsyncState.success(imageInfoViewModel);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
result = StateFactory.error(e);
|
result = AsyncState.error(e, get(this.state).data);
|
||||||
captureException(e);
|
captureException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ImageEventType {
|
|
||||||
ImageUploadedEvent,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ImageUploadedEvent {
|
|
||||||
event: ImageEventType.ImageUploadedEvent;
|
|
||||||
file: File;
|
|
||||||
}
|
|
@ -1,19 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
import UploadImageDialoag from '$lib/image/framework/ui/UploadImageDialoag.svelte';
|
import UploadImageDialoag from '$lib/image/framework/ui/UploadImageDialoag.svelte';
|
||||||
import { ImageBloc, ImageEventType } from '$lib/image/adapter/presenter/imageBloc';
|
|
||||||
import { StatusType } from '$lib/common/adapter/presenter/asyncState';
|
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
|
import { ImageUploadedStore } from '$lib/image/adapter/presenter/ImageUploadedStore';
|
||||||
|
|
||||||
const imageBloc = getContext<ImageBloc>(ImageBloc.name);
|
const store = getContext<ImageUploadedStore>(ImageUploadedStore.name);
|
||||||
const state = $derived($imageBloc);
|
const state = $derived($store);
|
||||||
|
const { trigger: uploadImage } = store;
|
||||||
const isLoading = $derived(state.status === StatusType.Loading);
|
|
||||||
|
|
||||||
async function onUploadImageDialogSubmit(file: File) {
|
async function onUploadImageDialogSubmit(file: File) {
|
||||||
const state = await imageBloc.dispatch({ event: ImageEventType.ImageUploadedEvent, file });
|
const state = await uploadImage(file);
|
||||||
|
|
||||||
if (state.status === StatusType.Success) {
|
if (state.isSuccess()) {
|
||||||
const imageInfo = state.data;
|
const imageInfo = state.data;
|
||||||
console.log('Image URL', imageInfo.url.href);
|
console.log('Image URL', imageInfo.url.href);
|
||||||
|
|
||||||
@ -30,7 +28,7 @@
|
|||||||
? 'The URL is copied to clipboard'
|
? 'The URL is copied to clipboard'
|
||||||
: 'The URL is printed in console',
|
: 'The URL is printed in console',
|
||||||
});
|
});
|
||||||
} else if (state.status === StatusType.Error) {
|
} else if (state.isError()) {
|
||||||
toast.error('Failed to upload image', {
|
toast.error('Failed to upload image', {
|
||||||
description: state.error?.message ?? 'Unknown error',
|
description: state.error?.message ?? 'Unknown error',
|
||||||
});
|
});
|
||||||
@ -41,7 +39,7 @@
|
|||||||
<div class="dashboard-container mb-10">
|
<div class="dashboard-container mb-10">
|
||||||
<div class="flex flex-row items-center justify-between">
|
<div class="flex flex-row items-center justify-between">
|
||||||
<h1 class="py-16 text-5xl font-bold text-gray-800">Image</h1>
|
<h1 class="py-16 text-5xl font-bold text-gray-800">Image</h1>
|
||||||
<UploadImageDialoag disabled={isLoading} onSubmit={onUploadImageDialogSubmit} />
|
<UploadImageDialoag disabled={state.isLoading()} onSubmit={onUploadImageDialogSubmit} />
|
||||||
</div>
|
</div>
|
||||||
<p>Gallery is currently unavailable.</p>
|
<p>Gallery is currently unavailable.</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,9 +36,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
await uploadImage(file);
|
await uploadImage(file);
|
||||||
close();
|
|
||||||
files = undefined;
|
files = undefined;
|
||||||
fileInputErrorMessage = null;
|
fileInputErrorMessage = null;
|
||||||
|
open = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
import { StateFactory, 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<PostViewModel>;
|
|
||||||
export type PostEvent = PostLoadedEvent | PostCreatedEvent;
|
|
||||||
|
|
||||||
export class PostBloc {
|
|
||||||
private readonly state = writable<PostState>(StateFactory.idle());
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly getPostUseCase: GetPostUseCase,
|
|
||||||
private readonly createPostUseCase: CreatePostUseCase,
|
|
||||||
initialData?: PostViewModel
|
|
||||||
) {
|
|
||||||
this.state.set(StateFactory.idle(initialData));
|
|
||||||
}
|
|
||||||
|
|
||||||
get subscribe() {
|
|
||||||
return this.state.subscribe;
|
|
||||||
}
|
|
||||||
|
|
||||||
async dispatch(event: PostEvent): Promise<PostState> {
|
|
||||||
switch (event.event) {
|
|
||||||
case PostEventType.PostLoadedEvent:
|
|
||||||
return this.loadPost(event.id);
|
|
||||||
case PostEventType.PostCreatedEvent:
|
|
||||||
return this.createPost(event.params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async loadPost(id: string): Promise<PostState> {
|
|
||||||
this.state.set(StateFactory.loading(get(this.state).data));
|
|
||||||
|
|
||||||
let result: PostState;
|
|
||||||
try {
|
|
||||||
const post = await this.getPostUseCase.execute(id);
|
|
||||||
if (!post) {
|
|
||||||
result = StateFactory.error(new Error('Post not found'));
|
|
||||||
this.state.set(result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
const postViewModel = PostViewModel.fromEntity(post);
|
|
||||||
result = StateFactory.success(postViewModel);
|
|
||||||
} catch (e) {
|
|
||||||
result = StateFactory.error(e);
|
|
||||||
captureException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state.set(result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async createPost(params: CreatePostParams): Promise<PostState> {
|
|
||||||
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;
|
|
||||||
params: CreatePostParams;
|
|
||||||
}
|
|
47
frontend/src/lib/post/adapter/presenter/postCreatedStore.ts
Normal file
47
frontend/src/lib/post/adapter/presenter/postCreatedStore.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { AsyncState } from '$lib/common/adapter/presenter/asyncState';
|
||||||
|
import type { BaseStore } from '$lib/common/adapter/presenter/baseStore';
|
||||||
|
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 { captureException } from '@sentry/sveltekit';
|
||||||
|
import { get, writable } from 'svelte/store';
|
||||||
|
|
||||||
|
type PostState = AsyncState<PostViewModel>;
|
||||||
|
|
||||||
|
export class PostCreatedStore implements BaseStore<PostState, CreatePostParams> {
|
||||||
|
private readonly state = writable<PostState>(AsyncState.idle<PostViewModel>(null));
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly createPostUseCase: CreatePostUseCase,
|
||||||
|
initialData?: PostViewModel
|
||||||
|
) {
|
||||||
|
if (initialData) {
|
||||||
|
this.state.set(AsyncState.idle(initialData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get subscribe() {
|
||||||
|
return this.state.subscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
get trigger() {
|
||||||
|
return (params: CreatePostParams) => this.createPost(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createPost(params: CreatePostParams): Promise<PostState> {
|
||||||
|
this.state.set(AsyncState.loading(get(this.state).data));
|
||||||
|
|
||||||
|
let result: PostState;
|
||||||
|
try {
|
||||||
|
const post = await this.createPostUseCase.execute(params);
|
||||||
|
const postViewModel = PostViewModel.fromEntity(post);
|
||||||
|
result = AsyncState.success(postViewModel);
|
||||||
|
} catch (e) {
|
||||||
|
result = AsyncState.error(e, get(this.state).data);
|
||||||
|
captureException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.set(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
51
frontend/src/lib/post/adapter/presenter/postLoadedStore.ts
Normal file
51
frontend/src/lib/post/adapter/presenter/postLoadedStore.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { AsyncState } from '$lib/common/adapter/presenter/asyncState';
|
||||||
|
import type { BaseStore } from '$lib/common/adapter/presenter/baseStore';
|
||||||
|
import { PostViewModel } from '$lib/post/adapter/presenter/postViewModel';
|
||||||
|
import type { GetPostUseCase } from '$lib/post/application/useCase/getPostUseCase';
|
||||||
|
import { captureException } from '@sentry/sveltekit';
|
||||||
|
import { get, writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export type PostState = AsyncState<PostViewModel>;
|
||||||
|
|
||||||
|
export class PostLoadedStore implements BaseStore<PostState, string> {
|
||||||
|
private readonly state = writable<PostState>(AsyncState.idle<PostViewModel>(null));
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly getPostUseCase: GetPostUseCase,
|
||||||
|
initialData?: PostViewModel
|
||||||
|
) {
|
||||||
|
if (initialData) {
|
||||||
|
this.state.set(AsyncState.idle(initialData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get subscribe() {
|
||||||
|
return this.state.subscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
get trigger() {
|
||||||
|
return (id: string) => this.loadPost(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadPost(id: string): Promise<PostState> {
|
||||||
|
this.state.set(AsyncState.loading(get(this.state).data));
|
||||||
|
|
||||||
|
let result: PostState;
|
||||||
|
try {
|
||||||
|
const post = await this.getPostUseCase.execute(id);
|
||||||
|
if (!post) {
|
||||||
|
result = AsyncState.error(new Error('Post not found'), get(this.state).data);
|
||||||
|
this.state.set(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const postViewModel = PostViewModel.fromEntity(post);
|
||||||
|
result = AsyncState.success(postViewModel);
|
||||||
|
} catch (e) {
|
||||||
|
result = AsyncState.error(e, get(this.state).data);
|
||||||
|
captureException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.set(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -1,52 +1,42 @@
|
|||||||
import {
|
import { AsyncState } from '$lib/common/adapter/presenter/asyncState';
|
||||||
StateFactory,
|
import type { BaseStore } from '$lib/common/adapter/presenter/baseStore';
|
||||||
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 { GetAllPostsUseCase } from '$lib/post/application/useCase/getAllPostsUseCase';
|
import type { GetAllPostsUseCase } from '$lib/post/application/useCase/getAllPostsUseCase';
|
||||||
import { captureException } from '@sentry/sveltekit';
|
import { captureException } from '@sentry/sveltekit';
|
||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
|
|
||||||
export type PostListState = AsyncState<readonly PostInfoViewModel[]>;
|
export type PostListState = AsyncState<readonly PostInfoViewModel[]>;
|
||||||
export type PostListEvent = PostListLoadedEvent;
|
|
||||||
|
|
||||||
export class PostListBloc {
|
export class PostsListedStore implements BaseStore<PostListState> {
|
||||||
private readonly state = writable<PostListState>({
|
private readonly state = writable<PostListState>(AsyncState.idle([]));
|
||||||
status: StatusType.Idle,
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly getAllPostsUseCase: GetAllPostsUseCase,
|
private readonly getAllPostsUseCase: GetAllPostsUseCase,
|
||||||
initialData?: readonly PostInfoViewModel[]
|
initialData?: readonly PostInfoViewModel[]
|
||||||
) {
|
) {
|
||||||
this.state.set({
|
if (initialData) {
|
||||||
status: StatusType.Idle,
|
this.state.set(AsyncState.idle(initialData));
|
||||||
data: initialData,
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get subscribe() {
|
get subscribe() {
|
||||||
return this.state.subscribe;
|
return this.state.subscribe;
|
||||||
}
|
}
|
||||||
|
|
||||||
async dispatch(event: PostListEvent): Promise<PostListState> {
|
get trigger() {
|
||||||
switch (event.event) {
|
return () => this.loadPosts();
|
||||||
case PostListEventType.PostListLoadedEvent:
|
|
||||||
return this.loadPosts();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadPosts(): Promise<PostListState> {
|
private async loadPosts(): Promise<PostListState> {
|
||||||
this.state.set(StateFactory.loading(get(this.state).data));
|
this.state.set(AsyncState.loading(get(this.state).data));
|
||||||
|
|
||||||
let result: PostListState;
|
let result: PostListState;
|
||||||
try {
|
try {
|
||||||
const posts = await this.getAllPostsUseCase.execute();
|
const posts = await this.getAllPostsUseCase.execute();
|
||||||
const postViewModels = posts.map((post) => PostInfoViewModel.fromEntity(post));
|
const postViewModels = posts.map((post) => PostInfoViewModel.fromEntity(post));
|
||||||
result = StateFactory.success(postViewModels);
|
result = AsyncState.success(postViewModels);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
result = StateFactory.error(e);
|
result = AsyncState.error(e, get(this.state).data);
|
||||||
captureException(e);
|
captureException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,11 +44,3 @@ export class PostListBloc {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PostListEventType {
|
|
||||||
PostListLoadedEvent,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PostListLoadedEvent {
|
|
||||||
event: PostListEventType.PostListLoadedEvent;
|
|
||||||
}
|
|
@ -1,22 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PostBloc, PostEventType } from '$lib/post/adapter/presenter/postBloc';
|
|
||||||
import PostContentHeader from '$lib/post/framework/ui/PostContentHeader.svelte';
|
import PostContentHeader from '$lib/post/framework/ui/PostContentHeader.svelte';
|
||||||
import { getContext, onMount } from 'svelte';
|
import { getContext, onMount } from 'svelte';
|
||||||
import markdownit from 'markdown-it';
|
import markdownit from 'markdown-it';
|
||||||
import SafeHtml from '$lib/common/framework/ui/SafeHtml.svelte';
|
import SafeHtml from '$lib/common/framework/ui/SafeHtml.svelte';
|
||||||
import generateTitle from '$lib/common/framework/ui/generateTitle';
|
import generateTitle from '$lib/common/framework/ui/generateTitle';
|
||||||
import StructuredData from '$lib/post/framework/ui/StructuredData.svelte';
|
import StructuredData from '$lib/post/framework/ui/StructuredData.svelte';
|
||||||
|
import { PostLoadedStore } from '$lib/post/adapter/presenter/PostLoadedStore';
|
||||||
|
|
||||||
const { id }: { id: string } = $props();
|
const { id }: { id: string } = $props();
|
||||||
|
|
||||||
const postBloc = getContext<PostBloc>(PostBloc.name);
|
const store = getContext<PostLoadedStore>(PostLoadedStore.name);
|
||||||
const state = $derived($postBloc);
|
const state = $derived($store);
|
||||||
|
const { trigger: loadPost } = store;
|
||||||
|
|
||||||
const md = markdownit();
|
const md = markdownit();
|
||||||
const parsedContent = $derived(state.data?.content ? md.render(state.data.content) : '');
|
const parsedContent = $derived(state.data?.content ? md.render(state.data.content) : '');
|
||||||
const postInfo = $derived(state.data?.info);
|
const postInfo = $derived(state.data?.info);
|
||||||
|
|
||||||
onMount(() => postBloc.dispatch({ event: PostEventType.PostLoadedEvent, id: id }));
|
onMount(() => loadPost(id));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -1,25 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { StatusType } from '$lib/common/adapter/presenter/asyncState';
|
import { PostCreatedStore } from '$lib/post/adapter/presenter/postCreatedStore';
|
||||||
import { PostBloc, PostEventType } from '$lib/post/adapter/presenter/postBloc';
|
|
||||||
import CreatePostDialog, {
|
import CreatePostDialog, {
|
||||||
type CreatePostDialogFormParams,
|
type CreatePostDialogFormParams,
|
||||||
} from '$lib/post/framework/ui/CreatePostDialog.svelte';
|
} from '$lib/post/framework/ui/CreatePostDialog.svelte';
|
||||||
import { getContext } from 'svelte';
|
import { getContext } from 'svelte';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
|
|
||||||
const postBloc = getContext<PostBloc>(PostBloc.name);
|
const store = getContext<PostCreatedStore>(PostCreatedStore.name);
|
||||||
const state = $derived($postBloc);
|
const state = $derived($store);
|
||||||
|
const { trigger: createPost } = store;
|
||||||
const isLoading = $derived(state.status === StatusType.Loading);
|
|
||||||
|
|
||||||
async function onCreatePostDialogSubmit(params: CreatePostDialogFormParams) {
|
async function onCreatePostDialogSubmit(params: CreatePostDialogFormParams) {
|
||||||
const state = await postBloc.dispatch({ event: PostEventType.PostCreatedEvent, params });
|
const state = await createPost(params);
|
||||||
|
|
||||||
if (state.status === StatusType.Success) {
|
if (state.isSuccess()) {
|
||||||
toast.success(`Post created successfully with ID: ${state.data.id}`);
|
toast.success(`Post created successfully with ID: ${state.data.id}`);
|
||||||
} else if (state.status === StatusType.Error) {
|
} else if (state.isError()) {
|
||||||
toast.error('Failed to create post', {
|
toast.error('Failed to create post', {
|
||||||
description: state.error?.message ?? 'Unknown error',
|
description: state.error.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,7 +26,7 @@
|
|||||||
<div class="dashboard-container mb-10">
|
<div class="dashboard-container mb-10">
|
||||||
<div class="flex flex-row items-center justify-between">
|
<div class="flex flex-row items-center justify-between">
|
||||||
<h1 class="py-16 text-5xl font-bold text-gray-800">Post</h1>
|
<h1 class="py-16 text-5xl font-bold text-gray-800">Post</h1>
|
||||||
<CreatePostDialog disabled={isLoading} onSubmit={onCreatePostDialogSubmit} />
|
<CreatePostDialog disabled={state.isLoading()} onSubmit={onCreatePostDialogSubmit} />
|
||||||
</div>
|
</div>
|
||||||
<p></p>
|
<p></p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import generateTitle from '$lib/common/framework/ui/generateTitle';
|
import generateTitle from '$lib/common/framework/ui/generateTitle';
|
||||||
import { PostListBloc, PostListEventType } from '$lib/post/adapter/presenter/postListBloc';
|
import { PostsListedStore } from '$lib/post/adapter/presenter/postsListedStore';
|
||||||
import PostPreview from '$lib/post/framework/ui/PostPreview.svelte';
|
import PostPreview from '$lib/post/framework/ui/PostPreview.svelte';
|
||||||
import { getContext, onMount } from 'svelte';
|
import { getContext, onMount } from 'svelte';
|
||||||
|
|
||||||
const postListBloc = getContext<PostListBloc>(PostListBloc.name);
|
const store = getContext<PostsListedStore>(PostsListedStore.name);
|
||||||
const state = $derived($postListBloc);
|
const state = $derived($store);
|
||||||
|
const { trigger: loadPosts } = store;
|
||||||
|
|
||||||
onMount(() => postListBloc.dispatch({ event: PostListEventType.PostListLoadedEvent }));
|
onMount(() => loadPosts());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -1,42 +1,37 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AuthBloc, AuthEventType } from '$lib/auth/adapter/presenter/authBloc';
|
|
||||||
import { getContext, onMount, setContext } from 'svelte';
|
import { getContext, onMount, setContext } from 'svelte';
|
||||||
import type { LayoutProps } from './$types';
|
import type { LayoutProps } from './$types';
|
||||||
import { StatusType } from '$lib/common/adapter/presenter/asyncState';
|
|
||||||
import ErrorPage from '$lib/common/framework/ui/ErrorPage.svelte';
|
import ErrorPage from '$lib/common/framework/ui/ErrorPage.svelte';
|
||||||
import DashboardNavbar from '$lib/dashboard/framework/ui/DashboardNavbar.svelte';
|
import DashboardNavbar from '$lib/dashboard/framework/ui/DashboardNavbar.svelte';
|
||||||
import type { DashboardLink } from '$lib/dashboard/framework/ui/dashboardLink';
|
import type { DashboardLink } from '$lib/dashboard/framework/ui/dashboardLink';
|
||||||
import { Container } from '$lib/container';
|
import { Container } from '$lib/container';
|
||||||
|
import { AuthLoadedStore } from '$lib/auth/adapter/presenter/authLoadedStore';
|
||||||
|
|
||||||
const { children }: LayoutProps = $props();
|
const { children }: LayoutProps = $props();
|
||||||
const container = getContext<Container>(Container.name);
|
const container = getContext<Container>(Container.name);
|
||||||
|
const store = container.createAuthLoadedStore();
|
||||||
|
setContext(AuthLoadedStore.name, store);
|
||||||
|
|
||||||
const authBloc = container.createAuthBloc();
|
const state = $derived($store);
|
||||||
setContext(AuthBloc.name, authBloc);
|
const { trigger: loadAuthentication } = store;
|
||||||
|
|
||||||
onMount(() => authBloc.dispatch({ event: AuthEventType.CurrentUserLoadedEvent }));
|
const isAuthenticated = $derived(state.isSuccess() && state.data.isAuthenticated);
|
||||||
|
|
||||||
const authState = $derived($authBloc);
|
|
||||||
const isLoading = $derived(
|
|
||||||
authState.status === StatusType.Loading || authState.status === StatusType.Idle
|
|
||||||
);
|
|
||||||
const isAuthenticated = $derived(
|
|
||||||
authState.status === StatusType.Success && authState.data.isAuthenticated
|
|
||||||
);
|
|
||||||
|
|
||||||
const links: DashboardLink[] = [
|
const links: DashboardLink[] = [
|
||||||
{ label: 'Post', href: '/dashboard/post' },
|
{ label: 'Post', href: '/dashboard/post' },
|
||||||
{ label: 'Label', href: '/dashboard/label' },
|
{ label: 'Label', href: '/dashboard/label' },
|
||||||
{ label: 'Image', href: '/dashboard/image' },
|
{ label: 'Image', href: '/dashboard/image' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
onMount(() => loadAuthentication());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isLoading}
|
{#if state.isIdle() || state.isLoading()}
|
||||||
<div></div>
|
<div></div>
|
||||||
{:else if !isAuthenticated}
|
{:else if !isAuthenticated}
|
||||||
<ErrorPage />
|
<ErrorPage />
|
||||||
{:else}
|
{:else}
|
||||||
<div class="grid min-h-content-height grid-cols-[auto_1fr]">
|
<div class="min-h-content-height grid grid-cols-[auto_1fr]">
|
||||||
<DashboardNavbar {links} />
|
<DashboardNavbar {links} />
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Container } from '$lib/container';
|
import { Container } from '$lib/container';
|
||||||
import { ImageBloc } from '$lib/image/adapter/presenter/imageBloc';
|
import { ImageUploadedStore } from '$lib/image/adapter/presenter/ImageUploadedStore';
|
||||||
import ImageManagementPage from '$lib/image/framework/ui/ImageManagementPage.svelte';
|
import ImageManagementPage from '$lib/image/framework/ui/ImageManagementPage.svelte';
|
||||||
import { getContext, setContext } from 'svelte';
|
import { getContext, setContext } from 'svelte';
|
||||||
|
|
||||||
const container = getContext<Container>(Container.name);
|
const container = getContext<Container>(Container.name);
|
||||||
const imageBloc = container.createImageBloc();
|
const store = container.createImageUploadedStore();
|
||||||
setContext(ImageBloc.name, imageBloc);
|
setContext(ImageUploadedStore.name, store);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ImageManagementPage />
|
<ImageManagementPage />
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Container } from '$lib/container';
|
import { Container } from '$lib/container';
|
||||||
import { PostBloc } from '$lib/post/adapter/presenter/postBloc';
|
import { PostCreatedStore } from '$lib/post/adapter/presenter/postCreatedStore';
|
||||||
import PostManagementPage from '$lib/post/framework/ui/PostManagementPage.svelte';
|
import PostManagementPage from '$lib/post/framework/ui/PostManagementPage.svelte';
|
||||||
import { getContext, setContext } from 'svelte';
|
import { getContext, setContext } from 'svelte';
|
||||||
|
|
||||||
const container = getContext<Container>(Container.name);
|
const container = getContext<Container>(Container.name);
|
||||||
const postBloc = container.createPostBloc();
|
const store = container.createPostCreatedStore();
|
||||||
setContext(PostBloc.name, postBloc);
|
setContext(PostCreatedStore.name, store);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PostManagementPage />
|
<PostManagementPage />
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { PostListEventType } from '$lib/post/adapter/presenter/postListBloc';
|
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ locals }) => {
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
const { container } = locals;
|
const { container } = locals;
|
||||||
const postListBloc = container.createPostListBloc();
|
const store = container.createPostsListedStore();
|
||||||
|
const { trigger: loadPosts } = store;
|
||||||
|
|
||||||
const state = await postListBloc.dispatch({ event: PostListEventType.PostListLoadedEvent });
|
const state = await loadPosts();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dehydratedData: state.data?.map((post) => post.dehydrate()),
|
dehydratedData: state.data?.map((post) => post.dehydrate()),
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc';
|
|
||||||
import { getContext, setContext } from 'svelte';
|
import { getContext, setContext } from 'svelte';
|
||||||
import type { PageProps } from './$types';
|
import type { PageProps } from './$types';
|
||||||
import { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel';
|
import { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel';
|
||||||
import PostOverallPage from '$lib/post/framework/ui/PostOverallPage.svelte';
|
import PostOverallPage from '$lib/post/framework/ui/PostOverallPage.svelte';
|
||||||
import { Container } from '$lib/container';
|
import { Container } from '$lib/container';
|
||||||
|
import { PostsListedStore } from '$lib/post/adapter/presenter/postsListedStore';
|
||||||
|
|
||||||
const { data }: PageProps = $props();
|
const { data }: PageProps = $props();
|
||||||
const container = getContext<Container>(Container.name);
|
const container = getContext<Container>(Container.name);
|
||||||
|
|
||||||
const initialData = data.dehydratedData?.map((post) => PostInfoViewModel.rehydrate(post));
|
const initialData = data.dehydratedData?.map((post) => PostInfoViewModel.rehydrate(post));
|
||||||
const postListBloc = container.createPostListBloc(initialData);
|
const store = container.createPostsListedStore(initialData);
|
||||||
setContext(PostListBloc.name, postListBloc);
|
setContext(PostsListedStore.name, store);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PostOverallPage />
|
<PostOverallPage />
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import { PostEventType } from '$lib/post/adapter/presenter/postBloc';
|
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ locals, params }) => {
|
export const load: PageServerLoad = async ({ locals, params }) => {
|
||||||
const { container } = locals;
|
const { container } = locals;
|
||||||
const postBloc = container.createPostBloc();
|
const store = container.createPostLoadedStore();
|
||||||
|
const { trigger: loadPost } = store;
|
||||||
|
|
||||||
const state = await postBloc.dispatch({
|
const state = await loadPost(params.id);
|
||||||
event: PostEventType.PostLoadedEvent,
|
|
||||||
id: params.id,
|
|
||||||
});
|
|
||||||
if (!state.data) {
|
if (!state.data) {
|
||||||
error(404, { message: 'Post not found' });
|
error(404, { message: 'Post not found' });
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PostBloc } from '$lib/post/adapter/presenter/postBloc';
|
|
||||||
import { PostViewModel } from '$lib/post/adapter/presenter/postViewModel';
|
import { PostViewModel } from '$lib/post/adapter/presenter/postViewModel';
|
||||||
import { getContext, setContext } from 'svelte';
|
import { getContext, setContext } from 'svelte';
|
||||||
import type { PageProps } from './$types';
|
import type { PageProps } from './$types';
|
||||||
import PostContentPage from '$lib/post/framework/ui/PostContentPage.svelte';
|
import PostContentPage from '$lib/post/framework/ui/PostContentPage.svelte';
|
||||||
import { Container } from '$lib/container';
|
import { Container } from '$lib/container';
|
||||||
|
import { PostLoadedStore } from '$lib/post/adapter/presenter/PostLoadedStore';
|
||||||
|
|
||||||
const { data, params }: PageProps = $props();
|
const { data, params }: PageProps = $props();
|
||||||
const { id } = params;
|
const { id } = params;
|
||||||
const container = getContext<Container>(Container.name);
|
const container = getContext<Container>(Container.name);
|
||||||
|
|
||||||
const initialData = PostViewModel.rehydrate(data.dehydratedData!);
|
const initialData = PostViewModel.rehydrate(data.dehydratedData!);
|
||||||
const postBloc = container.createPostBloc(initialData);
|
const store = container.createPostLoadedStore(initialData);
|
||||||
setContext(PostBloc.name, postBloc);
|
setContext(PostLoadedStore.name, store);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PostContentPage {id} />
|
<PostContentPage {id} />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user