feat: implement get label by ID functionality in label management
All checks were successful
Frontend CI / build (push) Successful in 1m38s

This commit is contained in:
SquidSpirit 2025-10-15 15:12:20 +08:00
parent d20a210857
commit 4f23151439
10 changed files with 137 additions and 1 deletions

View File

@ -31,6 +31,8 @@ import { CreatePostUseCase } from '$lib/post/application/useCase/createPostUseCa
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 { GetLabelUseCase } from '$lib/label/application/useCase/getLabelUseCase';
import { LabelLoadedStore } from '$lib/label/adapter/presenter/labelLoadedStore';
export class Container {
private useCases: UseCases;
@ -65,6 +67,10 @@ export class Container {
return new LabelsListedStore(this.useCases.getAllLabelsUseCase, initialData);
}
createLabelLoadedStore(initialData?: LabelViewModel): LabelLoadedStore {
return new LabelLoadedStore(this.useCases.getLabelUseCase, initialData);
}
createLabelCreatedStore(): LabelCreatedStore {
return new LabelCreatedStore(this.useCases.createLabelUseCase);
}
@ -145,6 +151,7 @@ class UseCases {
private _getPostUseCase?: GetPostUseCase;
private _createPostUseCase?: CreatePostUseCase;
private _getAllLabelsUseCase?: GetAllLabelsUseCase;
private _getLabelUseCase?: GetLabelUseCase;
private _createLabelUseCase?: CreateLabelUseCase;
constructor(repositories: Repositories) {
@ -181,6 +188,11 @@ class UseCases {
return this._getAllLabelsUseCase;
}
get getLabelUseCase(): GetLabelUseCase {
this._getLabelUseCase ??= new GetLabelUseCase(this.repositories.labelRepository);
return this._getLabelUseCase;
}
get createLabelUseCase(): CreateLabelUseCase {
this._createLabelUseCase ??= new CreateLabelUseCase(this.repositories.labelRepository);
return this._createLabelUseCase;

View File

@ -3,5 +3,6 @@ import type { LabelResponseDto } from '$lib/label/adapter/gateway/labelResponseD
export interface LabelApiService {
getAllLabels(): Promise<LabelResponseDto[]>;
getLabelById(id: number): Promise<LabelResponseDto | null>;
createLabel(payload: CreateLabelRequestDto): Promise<LabelResponseDto>;
}

View File

@ -14,6 +14,11 @@ export class LabelRepositoryImpl implements LabelRepository {
return dtos.map((dto) => dto.toEntity());
}
async getLabelById(id: number): Promise<Label | null> {
const dto = await this.labelApiService.getLabelById(id);
return dto?.toEntity() ?? null;
}
async createLabel(params: CreateLabelParams): Promise<Label> {
const requestDto = CreateLabelRequestDto.fromParams(params);
const responseDto = await this.labelApiService.createLabel(requestDto);

View File

@ -0,0 +1,51 @@
import { AsyncState } from '$lib/common/adapter/presenter/asyncState';
import type { BaseStore } from '$lib/common/adapter/presenter/baseStore';
import { LabelViewModel } from '$lib/label/adapter/presenter/labelViewModel';
import type { GetLabelUseCase } from '$lib/label/application/useCase/getLabelUseCase';
import { captureException } from '@sentry/sveltekit';
import { get, writable } from 'svelte/store';
type LabelState = AsyncState<LabelViewModel>;
export class LabelLoadedStore implements BaseStore<LabelState, number> {
private state = writable<LabelState>(AsyncState.idle<LabelViewModel>(null));
constructor(
private readonly getLabelUseCase: GetLabelUseCase,
initialData?: LabelViewModel
) {
if (initialData) {
this.state.set(AsyncState.idle(initialData));
}
}
get subscribe() {
return this.state.subscribe;
}
get trigger() {
return (id: number) => this.loadLabel(id);
}
async loadLabel(id: number): Promise<LabelState> {
this.state.set(AsyncState.loading<LabelViewModel>(get(this.state).data));
let result: LabelState;
try {
const label = await this.getLabelUseCase.execute(id);
if (!label) {
result = AsyncState.error(new Error('Label not found'), get(this.state).data);
this.state.set(result);
return result;
}
const labelViewModel = LabelViewModel.fromEntity(label);
result = AsyncState.success(labelViewModel);
} catch (e) {
result = AsyncState.error(e, get(this.state).data);
captureException(e);
}
this.state.set(result);
return result;
}
}

View File

@ -3,6 +3,7 @@ import type { Label } from '$lib/label/domain/entity/label';
export interface LabelRepository {
getAllLabels(): Promise<Label[]>;
getLabelById(id: number): Promise<Label | null>;
createLabel(params: CreateLabelParams): Promise<Label>;
}

View File

@ -4,7 +4,7 @@ import type { Label } from '$lib/label/domain/entity/label';
export class GetAllLabelsUseCase {
constructor(private readonly labelRepository: LabelRepository) {}
execute(): Promise<Label[]> {
async execute(): Promise<Label[]> {
return this.labelRepository.getAllLabels();
}
}

View File

@ -0,0 +1,10 @@
import type { LabelRepository } from '$lib/label/application/gateway/labelRepository';
import type { Label } from '$lib/label/domain/entity/label';
export class GetLabelUseCase {
constructor(private readonly labelRepository: LabelRepository) {}
async execute(id: number): Promise<Label | null> {
return this.labelRepository.getLabelById(id);
}
}

View File

@ -20,6 +20,22 @@ export class LabelApiServiceImpl implements LabelApiService {
return data.map(LabelResponseDto.fromJson);
}
async getLabelById(id: number): Promise<LabelResponseDto | null> {
const url = new URL(`label/${id}`, Environment.API_BASE_URL);
const response = await this.fetchFn(url);
if (response.status === 404) {
return null;
}
if (!response.ok) {
throw new HttpError(response.status, url);
}
const data = await response.json();
return LabelResponseDto.fromJson(data);
}
async createLabel(payload: CreateLabelRequestDto): Promise<LabelResponseDto> {
const url = new URL('label', Environment.API_BASE_URL);

View File

@ -0,0 +1,23 @@
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ locals, params }) => {
const { container } = locals;
const store = container.createLabelLoadedStore();
const { trigger: loadLabel } = store;
const numericId = Number(params.id);
if (isNaN(numericId)) {
throw error(400, { message: 'Invalid label ID' });
}
const state = await loadLabel(numericId);
if (!state.data) {
error(404, { message: 'Post not found' });
}
return {
dehydratedData: state.data.dehydrate(),
};
};

View File

@ -0,0 +1,17 @@
<script lang="ts">
import { getContext, setContext } from 'svelte';
import type { PageProps } from './$types';
import { Container } from '$lib/container';
import { LabelViewModel } from '$lib/label/adapter/presenter/labelViewModel';
import { LabelLoadedStore } from '$lib/label/adapter/presenter/labelLoadedStore';
const { data, params }: PageProps = $props();
const { id } = params;
const container = getContext<Container>(Container.name);
const initialData = LabelViewModel.rehydrate(data.dehydratedData!);
const store = container.createLabelLoadedStore(initialData);
setContext(LabelLoadedStore.name, store);
</script>
<div>{id}</div>