feat: implement new store architecture for authentication and post management, replacing Bloc with Store pattern
Some checks failed
Frontend CI / build (push) Failing after 55s

This commit is contained in:
SquidSpirit 2025-10-15 02:57:51 +08:00
parent 063374f56f
commit 325abd1ab5
21 changed files with 306 additions and 287 deletions

View File

@ -1,15 +1,15 @@
import { AuthViewModel } from '$lib/auth/adapter/presenter/authViewModel';
import { UserViewModel } from '$lib/auth/adapter/presenter/userViewModel';
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 { get, writable } from 'svelte/store';
export type AuthState = AsyncState<AuthViewModel>;
export type AuthEvent = CurrentUserLoadedEvent;
export class AuthBloc {
private readonly state = writable<AuthState>(StateFactory.idle());
export class AuthLoadedStore implements BaseStore<AuthState> {
private readonly state = writable<AuthState>(AsyncState.idle<AuthViewModel>(null));
constructor(private readonly getCurrentUserUseCase: GetCurrentUserUseCase) {}
@ -17,24 +17,21 @@ export class AuthBloc {
return this.state.subscribe;
}
async dispatch(event: AuthEvent): Promise<AuthState> {
switch (event.event) {
case AuthEventType.CurrentUserLoadedEvent:
return this.loadCurrentUser();
}
get trigger() {
return () => this.loadCurrentUser();
}
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;
try {
const user = await this.getCurrentUserUseCase.execute();
const userViewModel = user ? UserViewModel.fromEntity(user) : null;
const authViewModel = AuthViewModel.fromEntity(userViewModel);
result = StateFactory.success(authViewModel);
result = AsyncState.success(authViewModel);
} catch (e) {
result = StateFactory.error(e);
result = AsyncState.error(e, get(this.state).data);
captureException(e);
}
@ -42,11 +39,3 @@ export class AuthBloc {
return result;
}
}
export enum AuthEventType {
CurrentUserLoadedEvent,
}
interface CurrentUserLoadedEvent {
event: AuthEventType.CurrentUserLoadedEvent;
}

View File

@ -1,60 +1,110 @@
export enum StatusType {
Idle,
Loading,
Success,
Error,
enum AsyncStateStatus {
Idle = 'idle',
Loading = 'loading',
Success = 'success',
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> {
status: StatusType.Idle;
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 idle<T>(data: T | null): IdleState<T> {
return new IdleState(data);
}
static loading<T>(data?: T): AsyncState<T> {
return {
status: StatusType.Loading,
data,
};
static loading<T>(data: T | null): LoadingState<T> {
return new LoadingState(data);
}
static success<T>(data: T): AsyncState<T> {
return {
status: StatusType.Success,
data,
};
static success<T>(data: T): SuccessState<T> {
return new SuccessState(data);
}
static error<T>(error: unknown, data?: T): AsyncState<T> {
return {
status: StatusType.Error,
error: error instanceof Error ? error : new Error('Unknown error'),
data,
};
static error<T>(error: unknown, data: T | null): ErrorState<T> {
const errorInstance = error instanceof Error ? error : new Error(String(error));
return new ErrorState(errorInstance, 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);
}
}

View 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>;
}

View File

@ -1,20 +1,21 @@
import type { AuthApiService } from '$lib/auth/adapter/gateway/authApiService';
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 { GetCurrentUserUseCase } from '$lib/auth/application/useCase/getCurrentUserUseCase';
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 { 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 { UploadImageUseCase } from '$lib/image/application/useCase/uploadImageUseCase';
import { ImageApiServiceImpl } from '$lib/image/framework/api/imageApiServiceImpl';
import type { PostApiService } from '$lib/post/adapter/gateway/postApiService';
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 { 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 { PostRepository } from '$lib/post/application/gateway/postRepository';
import { CreatePostUseCase } from '$lib/post/application/useCase/createPostUseCase';
@ -31,20 +32,24 @@ export class Container {
this.useCases = new UseCases(repositories);
}
createAuthBloc(): AuthBloc {
return new AuthBloc(this.useCases.getCurrentUserUseCase);
createAuthLoadedStore(): AuthLoadedStore {
return new AuthLoadedStore(this.useCases.getCurrentUserUseCase);
}
createImageBloc(): ImageBloc {
return new ImageBloc(this.useCases.uploadImageUseCase);
createImageUploadedStore(): ImageUploadedStore {
return new ImageUploadedStore(this.useCases.uploadImageUseCase);
}
createPostListBloc(initialData?: readonly PostInfoViewModel[]): PostListBloc {
return new PostListBloc(this.useCases.getAllPostsUseCase, initialData);
createPostsListedStore(initialData?: readonly PostInfoViewModel[]): PostsListedStore {
return new PostsListedStore(this.useCases.getAllPostsUseCase, initialData);
}
createPostBloc(initialData?: PostViewModel): PostBloc {
return new PostBloc(this.useCases.getPostUseCase, this.useCases.createPostUseCase, initialData);
createPostLoadedStore(initialData?: PostViewModel): PostLoadedStore {
return new PostLoadedStore(this.useCases.getPostUseCase, initialData);
}
createPostCreatedStore(initialData?: PostViewModel): PostCreatedStore {
return new PostCreatedStore(this.useCases.createPostUseCase, initialData);
}
}

View File

@ -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 type { UploadImageUseCase } from '$lib/image/application/useCase/uploadImageUseCase';
import { captureException } from '@sentry/sveltekit';
import { get, writable } from 'svelte/store';
export type ImageInfoState = AsyncState<ImageInfoViewModel>;
export type ImageEvent = ImageUploadedEvent;
export class ImageBloc {
private readonly state = writable<ImageInfoState>(StateFactory.idle());
export class ImageUploadedStore {
private readonly state = writable<ImageInfoState>(AsyncState.idle<ImageInfoViewModel>(null));
constructor(private readonly uploadImageUseCase: UploadImageUseCase) {}
@ -16,35 +15,23 @@ export class ImageBloc {
return this.state.subscribe;
}
async dispatch(event: ImageEvent): Promise<ImageInfoState> {
switch (event.event) {
case ImageEventType.ImageUploadedEvent:
return this.uploadImage(event.file);
}
get trigger() {
return (file: File) => this.uploadImage(file);
}
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;
try {
const imageInfo = await this.uploadImageUseCase.execute(file);
const imageInfoViewModel = ImageInfoViewModel.fromEntity(imageInfo);
result = StateFactory.success(imageInfoViewModel);
result = AsyncState.success(imageInfoViewModel);
} catch (e) {
result = StateFactory.error(e);
result = AsyncState.error(e, get(this.state).data);
captureException(e);
}
return result;
}
}
export enum ImageEventType {
ImageUploadedEvent,
}
interface ImageUploadedEvent {
event: ImageEventType.ImageUploadedEvent;
file: File;
}

View File

@ -1,19 +1,17 @@
<script lang="ts">
import { getContext } from '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 { ImageUploadedStore } from '$lib/image/adapter/presenter/ImageUploadedStore';
const imageBloc = getContext<ImageBloc>(ImageBloc.name);
const state = $derived($imageBloc);
const isLoading = $derived(state.status === StatusType.Loading);
const store = getContext<ImageUploadedStore>(ImageUploadedStore.name);
const state = $derived($store);
const { trigger: uploadImage } = store;
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;
console.log('Image URL', imageInfo.url.href);
@ -30,7 +28,7 @@
? 'The URL is copied to clipboard'
: 'The URL is printed in console',
});
} else if (state.status === StatusType.Error) {
} else if (state.isError()) {
toast.error('Failed to upload image', {
description: state.error?.message ?? 'Unknown error',
});
@ -41,7 +39,7 @@
<div class="dashboard-container mb-10">
<div class="flex flex-row items-center justify-between">
<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>
<p>Gallery is currently unavailable.</p>
</div>

View File

@ -36,9 +36,9 @@
}
await uploadImage(file);
close();
files = undefined;
fileInputErrorMessage = null;
open = false;
}
</script>

View File

@ -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;
}

View 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;
}
}

View 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;
}
}

View File

@ -1,52 +1,42 @@
import {
StateFactory,
StatusType,
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 { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel';
import type { GetAllPostsUseCase } from '$lib/post/application/useCase/getAllPostsUseCase';
import { captureException } from '@sentry/sveltekit';
import { get, writable } from 'svelte/store';
export type PostListState = AsyncState<readonly PostInfoViewModel[]>;
export type PostListEvent = PostListLoadedEvent;
export class PostListBloc {
private readonly state = writable<PostListState>({
status: StatusType.Idle,
});
export class PostsListedStore implements BaseStore<PostListState> {
private readonly state = writable<PostListState>(AsyncState.idle([]));
constructor(
private readonly getAllPostsUseCase: GetAllPostsUseCase,
initialData?: readonly PostInfoViewModel[]
) {
this.state.set({
status: StatusType.Idle,
data: initialData,
});
if (initialData) {
this.state.set(AsyncState.idle(initialData));
}
}
get subscribe() {
return this.state.subscribe;
}
async dispatch(event: PostListEvent): Promise<PostListState> {
switch (event.event) {
case PostListEventType.PostListLoadedEvent:
return this.loadPosts();
}
get trigger() {
return () => this.loadPosts();
}
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;
try {
const posts = await this.getAllPostsUseCase.execute();
const postViewModels = posts.map((post) => PostInfoViewModel.fromEntity(post));
result = StateFactory.success(postViewModels);
result = AsyncState.success(postViewModels);
} catch (e) {
result = StateFactory.error(e);
result = AsyncState.error(e, get(this.state).data);
captureException(e);
}
@ -54,11 +44,3 @@ export class PostListBloc {
return result;
}
}
export enum PostListEventType {
PostListLoadedEvent,
}
export interface PostListLoadedEvent {
event: PostListEventType.PostListLoadedEvent;
}

View File

@ -1,22 +1,23 @@
<script lang="ts">
import { PostBloc, PostEventType } from '$lib/post/adapter/presenter/postBloc';
import PostContentHeader from '$lib/post/framework/ui/PostContentHeader.svelte';
import { getContext, onMount } from 'svelte';
import markdownit from 'markdown-it';
import SafeHtml from '$lib/common/framework/ui/SafeHtml.svelte';
import generateTitle from '$lib/common/framework/ui/generateTitle';
import StructuredData from '$lib/post/framework/ui/StructuredData.svelte';
import { PostLoadedStore } from '$lib/post/adapter/presenter/PostLoadedStore';
const { id }: { id: string } = $props();
const postBloc = getContext<PostBloc>(PostBloc.name);
const state = $derived($postBloc);
const store = getContext<PostLoadedStore>(PostLoadedStore.name);
const state = $derived($store);
const { trigger: loadPost } = store;
const md = markdownit();
const parsedContent = $derived(state.data?.content ? md.render(state.data.content) : '');
const postInfo = $derived(state.data?.info);
onMount(() => postBloc.dispatch({ event: PostEventType.PostLoadedEvent, id: id }));
onMount(() => loadPost(id));
</script>
<svelte:head>

View File

@ -1,25 +1,23 @@
<script lang="ts">
import { StatusType } from '$lib/common/adapter/presenter/asyncState';
import { PostBloc, PostEventType } from '$lib/post/adapter/presenter/postBloc';
import { PostCreatedStore } from '$lib/post/adapter/presenter/postCreatedStore';
import CreatePostDialog, {
type CreatePostDialogFormParams,
} from '$lib/post/framework/ui/CreatePostDialog.svelte';
import { getContext } from 'svelte';
import { toast } from 'svelte-sonner';
const postBloc = getContext<PostBloc>(PostBloc.name);
const state = $derived($postBloc);
const isLoading = $derived(state.status === StatusType.Loading);
const store = getContext<PostCreatedStore>(PostCreatedStore.name);
const state = $derived($store);
const { trigger: createPost } = store;
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}`);
} else if (state.status === StatusType.Error) {
} else if (state.isError()) {
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="flex flex-row items-center justify-between">
<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>
<p></p>
</div>

View File

@ -1,13 +1,14 @@
<script lang="ts">
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 { getContext, onMount } from 'svelte';
const postListBloc = getContext<PostListBloc>(PostListBloc.name);
const state = $derived($postListBloc);
const store = getContext<PostsListedStore>(PostsListedStore.name);
const state = $derived($store);
const { trigger: loadPosts } = store;
onMount(() => postListBloc.dispatch({ event: PostListEventType.PostListLoadedEvent }));
onMount(() => loadPosts());
</script>
<svelte:head>

View File

@ -1,42 +1,37 @@
<script lang="ts">
import { AuthBloc, AuthEventType } from '$lib/auth/adapter/presenter/authBloc';
import { getContext, onMount, setContext } from 'svelte';
import type { LayoutProps } from './$types';
import { StatusType } from '$lib/common/adapter/presenter/asyncState';
import ErrorPage from '$lib/common/framework/ui/ErrorPage.svelte';
import DashboardNavbar from '$lib/dashboard/framework/ui/DashboardNavbar.svelte';
import type { DashboardLink } from '$lib/dashboard/framework/ui/dashboardLink';
import { Container } from '$lib/container';
import { AuthLoadedStore } from '$lib/auth/adapter/presenter/authLoadedStore';
const { children }: LayoutProps = $props();
const container = getContext<Container>(Container.name);
const store = container.createAuthLoadedStore();
setContext(AuthLoadedStore.name, store);
const authBloc = container.createAuthBloc();
setContext(AuthBloc.name, authBloc);
const state = $derived($store);
const { trigger: loadAuthentication } = store;
onMount(() => authBloc.dispatch({ event: AuthEventType.CurrentUserLoadedEvent }));
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 isAuthenticated = $derived(state.isSuccess() && state.data.isAuthenticated);
const links: DashboardLink[] = [
{ label: 'Post', href: '/dashboard/post' },
{ label: 'Label', href: '/dashboard/label' },
{ label: 'Image', href: '/dashboard/image' },
];
onMount(() => loadAuthentication());
</script>
{#if isLoading}
{#if state.isIdle() || state.isLoading()}
<div></div>
{:else if !isAuthenticated}
<ErrorPage />
{: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} />
{@render children()}
</div>

View File

@ -1,12 +1,12 @@
<script lang="ts">
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 { getContext, setContext } from 'svelte';
const container = getContext<Container>(Container.name);
const imageBloc = container.createImageBloc();
setContext(ImageBloc.name, imageBloc);
const store = container.createImageUploadedStore();
setContext(ImageUploadedStore.name, store);
</script>
<ImageManagementPage />

View File

@ -1,12 +1,12 @@
<script lang="ts">
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 { getContext, setContext } from 'svelte';
const container = getContext<Container>(Container.name);
const postBloc = container.createPostBloc();
setContext(PostBloc.name, postBloc);
const store = container.createPostCreatedStore();
setContext(PostCreatedStore.name, store);
</script>
<PostManagementPage />

View File

@ -1,11 +1,11 @@
import { PostListEventType } from '$lib/post/adapter/presenter/postListBloc';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ 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 {
dehydratedData: state.data?.map((post) => post.dehydrate()),

View File

@ -1,17 +1,17 @@
<script lang="ts">
import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc';
import { getContext, setContext } from 'svelte';
import type { PageProps } from './$types';
import { PostInfoViewModel } from '$lib/post/adapter/presenter/postInfoViewModel';
import PostOverallPage from '$lib/post/framework/ui/PostOverallPage.svelte';
import { Container } from '$lib/container';
import { PostsListedStore } from '$lib/post/adapter/presenter/postsListedStore';
const { data }: PageProps = $props();
const container = getContext<Container>(Container.name);
const initialData = data.dehydratedData?.map((post) => PostInfoViewModel.rehydrate(post));
const postListBloc = container.createPostListBloc(initialData);
setContext(PostListBloc.name, postListBloc);
const store = container.createPostsListedStore(initialData);
setContext(PostsListedStore.name, store);
</script>
<PostOverallPage />

View File

@ -1,15 +1,12 @@
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 { container } = locals;
const postBloc = container.createPostBloc();
const store = container.createPostLoadedStore();
const { trigger: loadPost } = store;
const state = await postBloc.dispatch({
event: PostEventType.PostLoadedEvent,
id: params.id,
});
const state = await loadPost(params.id);
if (!state.data) {
error(404, { message: 'Post not found' });
}

View File

@ -1,18 +1,18 @@
<script lang="ts">
import { PostBloc } from '$lib/post/adapter/presenter/postBloc';
import { PostViewModel } from '$lib/post/adapter/presenter/postViewModel';
import { getContext, setContext } from 'svelte';
import type { PageProps } from './$types';
import PostContentPage from '$lib/post/framework/ui/PostContentPage.svelte';
import { Container } from '$lib/container';
import { PostLoadedStore } from '$lib/post/adapter/presenter/PostLoadedStore';
const { data, params }: PageProps = $props();
const { id } = params;
const container = getContext<Container>(Container.name);
const initialData = PostViewModel.rehydrate(data.dehydratedData!);
const postBloc = container.createPostBloc(initialData);
setContext(PostBloc.name, postBloc);
const store = container.createPostLoadedStore(initialData);
setContext(PostLoadedStore.name, store);
</script>
<PostContentPage {id} />