Skip to content

Commit 33a53a5

Browse files
authored
Fix/date added (Sudashiii#55)
1 parent f023fb0 commit 33a53a5

12 files changed

Lines changed: 124 additions & 24 deletions

File tree

sake/src/lib/client/routes/updateLibraryBookMetadata.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface UpdateLibraryBookMetadataRequest {
2222
googleBooksId?: string | null;
2323
openLibraryKey?: string | null;
2424
amazonAsin?: string | null;
25+
createdAt?: string | null;
2526
}
2627

2728
interface UpdateLibraryBookMetadataResponse {

sake/src/lib/features/library/components/LibraryDetailOverviewTab/LibraryDetailOverviewTab.svelte

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,17 @@
154154
<div>
155155
<p class="detail-v2-caption">Date Added</p>
156156
<div class="detail-v2-date-added">
157-
<strong>{formatDate(selectedBook.createdAt)}</strong>
158-
{#if selectedBook.createdAt}
159-
<span>{formatTime(selectedBook.createdAt)}</span>
157+
{#if isEditingMetadata}
158+
<input
159+
class="detail-v2-input"
160+
type="datetime-local"
161+
bind:value={metadataDraft.createdAt}
162+
/>
163+
{:else}
164+
<strong>{formatDate(selectedBook.createdAt)}</strong>
165+
{#if selectedBook.createdAt}
166+
<span>{formatTime(selectedBook.createdAt)}</span>
167+
{/if}
160168
{/if}
161169
</div>
162170
</div>

sake/src/lib/features/library/controllers/libraryDetailController.svelte.ts

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { toastStore } from '$lib/client/stores/toastStore.svelte';
22
import { ZUI } from '$lib/client/zui';
33
import {
4+
parseDateTimeLocalInputValue,
45
parseNullableNumber,
56
toDraftText,
7+
toDateTimeLocalInputValue,
68
type DetailTab,
79
type LibraryView,
810
type MetadataDraft
@@ -77,7 +79,8 @@ export class LibraryDetailController {
7779
openLibraryKey: '',
7880
amazonAsin: '',
7981
externalRating: '',
80-
externalRatingCount: ''
82+
externalRatingCount: '',
83+
createdAt: ''
8184
});
8285

8386
constructor(private readonly options: LibraryDetailControllerOptions) {}
@@ -157,7 +160,10 @@ export class LibraryDetailController {
157160
return;
158161
}
159162

160-
const updatedBook = this.options.applyBookMetadataUpdate(result.value.book);
163+
const updatedBook = this.options.applyBookMetadataUpdate({
164+
...result.value.book,
165+
createdAt: this.selectedBook?.createdAt ?? null
166+
});
161167
if (updatedBook) {
162168
this.selectedBook = updatedBook;
163169
}
@@ -208,6 +214,7 @@ export class LibraryDetailController {
208214
if (!this.selectedBook || !this.selectedBookDetail || this.isSavingMetadata) {
209215
return;
210216
}
217+
const selectedBookId = this.selectedBook.id;
211218

212219
const title = this.metadataDraft.title.trim();
213220
if (!title) {
@@ -228,7 +235,13 @@ export class LibraryDetailController {
228235
}
229236

230237
this.isSavingMetadata = true;
231-
const updateResult = await ZUI.updateLibraryBookMetadata(this.selectedBook.id, {
238+
const createdAt = parseDateTimeLocalInputValue(this.metadataDraft.createdAt);
239+
if (this.metadataDraft.createdAt.trim() && createdAt === null) {
240+
this.isSavingMetadata = false;
241+
toastStore.add('Date added must be a valid date and time', 'error');
242+
return;
243+
}
244+
const updateResult = await ZUI.updateLibraryBookMetadata(selectedBookId, {
232245
title,
233246
author: this.metadataDraft.author.trim() || null,
234247
publisher: this.metadataDraft.publisher.trim() || null,
@@ -248,7 +261,8 @@ export class LibraryDetailController {
248261
openLibraryKey: this.metadataDraft.openLibraryKey.trim() || null,
249262
amazonAsin: this.metadataDraft.amazonAsin.trim() || null,
250263
externalRating: parseNullableNumber(this.metadataDraft.externalRating),
251-
externalRatingCount: parseNullableNumber(this.metadataDraft.externalRatingCount)
264+
externalRatingCount: parseNullableNumber(this.metadataDraft.externalRatingCount),
265+
createdAt
252266
});
253267
this.isSavingMetadata = false;
254268

@@ -257,10 +271,41 @@ export class LibraryDetailController {
257271
return;
258272
}
259273

260-
const detailResult = await ZUI.getLibraryBookDetail(this.selectedBook.id);
274+
const detailResult = await ZUI.getLibraryBookDetail(selectedBookId);
261275
if (detailResult.ok) {
262276
this.selectedBookDetail = detailResult.value;
263277
this.initializeMetadataDraft(detailResult.value);
278+
279+
const updatedBook = this.options.applyBookMetadataUpdate({
280+
id: selectedBookId,
281+
zLibId: this.selectedBook.zLibId,
282+
title: detailResult.value.title,
283+
author: detailResult.value.author,
284+
publisher: detailResult.value.publisher,
285+
series: detailResult.value.series,
286+
seriesIndex: detailResult.value.seriesIndex,
287+
volume: detailResult.value.volume,
288+
edition: detailResult.value.edition,
289+
identifier: detailResult.value.identifier,
290+
pages: detailResult.value.pages,
291+
description: detailResult.value.description,
292+
googleBooksId: detailResult.value.googleBooksId,
293+
openLibraryKey: detailResult.value.openLibraryKey,
294+
amazonAsin: detailResult.value.amazonAsin,
295+
externalRating: detailResult.value.externalRating,
296+
externalRatingCount: detailResult.value.externalRatingCount,
297+
cover: this.metadataDraft.cover.trim() || null,
298+
extension: this.selectedBook.extension,
299+
filesize: this.selectedBook.filesize,
300+
language: this.metadataDraft.language.trim() || null,
301+
year: detailResult.value.year,
302+
month: detailResult.value.month,
303+
day: detailResult.value.day,
304+
createdAt
305+
});
306+
if (updatedBook) {
307+
this.selectedBook = updatedBook;
308+
}
264309
}
265310

266311
await this.options.loadLibrary();
@@ -548,7 +593,8 @@ export class LibraryDetailController {
548593
openLibraryKey: toDraftText(detail.openLibraryKey),
549594
amazonAsin: toDraftText(detail.amazonAsin),
550595
externalRating: toDraftText(detail.externalRating),
551-
externalRatingCount: toDraftText(detail.externalRatingCount)
596+
externalRatingCount: toDraftText(detail.externalRatingCount),
597+
createdAt: toDateTimeLocalInputValue(this.selectedBook?.createdAt ?? null)
552598
};
553599
}
554600

sake/src/lib/features/library/controllers/libraryListController.svelte.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export interface LibraryMetadataUpdate {
5454
year: number | null;
5555
month: number | null;
5656
day: number | null;
57+
createdAt: string | null;
5758
}
5859

5960
type BulkActionResult = { ok: true } | { ok: false; message: string };
@@ -642,7 +643,8 @@ export class LibraryListController {
642643
language: updated.language,
643644
year: updated.year,
644645
month: updated.month,
645-
day: updated.day
646+
day: updated.day,
647+
createdAt: updated.createdAt
646648
};
647649

648650
this.books = [...this.books.slice(0, index), updatedBook, ...this.books.slice(index + 1)];

sake/src/lib/features/library/view/formatting.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,30 @@ export function formatDateTime(dateStr: string): string {
8484
}).format(new Date(dateStr));
8585
}
8686

87+
export function toDateTimeLocalInputValue(dateStr: string | null | undefined): string {
88+
if (!dateStr) {
89+
return '';
90+
}
91+
92+
const date = new Date(dateStr);
93+
if (Number.isNaN(date.getTime())) {
94+
return '';
95+
}
96+
97+
const localDate = new Date(date.getTime() - date.getTimezoneOffset() * 60_000);
98+
return localDate.toISOString().slice(0, 16);
99+
}
100+
101+
export function parseDateTimeLocalInputValue(value: string): string | null {
102+
const trimmed = value.trim();
103+
if (!trimmed) {
104+
return null;
105+
}
106+
107+
const date = new Date(trimmed);
108+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
109+
}
110+
87111
export function formatLibraryPublicationDate(parts: {
88112
year: number | null | undefined;
89113
month: number | null | undefined;

sake/src/lib/features/library/view/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,5 @@ export type MetadataDraft = {
6767
amazonAsin: string;
6868
externalRating: string;
6969
externalRatingCount: string;
70+
createdAt: string;
7071
};

sake/src/lib/server/application/use-cases/ImportLibraryBookCoverUseCase.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ function toUpdateMetadataInput(existing: Awaited<ReturnType<BookRepositoryPort['
5353
language: existing.language,
5454
year: existing.year,
5555
month: existing.month,
56-
day: existing.day
56+
day: existing.day,
57+
createdAt: existing.createdAt
5758
};
5859
}
5960

sake/src/lib/server/application/use-cases/RefetchLibraryBookMetadataUseCase.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,8 @@ export class RefetchLibraryBookMetadataUseCase {
150150
language: existingBook.language,
151151
year: nextPublicationDate.year,
152152
month: nextPublicationDate.month,
153-
day: nextPublicationDate.day
153+
day: nextPublicationDate.day,
154+
createdAt: existingBook.createdAt
154155
});
155156

156157
return apiOk({

sake/src/lib/server/application/use-cases/UpdateLibraryBookMetadataUseCase.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { validatePublicationDateParts } from '$lib/utils/publicationDate';
88

99
interface UpdateLibraryBookMetadataInput {
1010
bookId: number;
11-
metadata: {
12-
title?: string;
11+
metadata: {
12+
title?: string;
1313
author?: string | null;
1414
publisher?: string | null;
1515
series?: string | null;
@@ -25,11 +25,12 @@ interface UpdateLibraryBookMetadataInput {
2525
month?: number | null;
2626
day?: number | null;
2727
externalRating?: number | null;
28-
externalRatingCount?: number | null;
29-
googleBooksId?: string | null;
30-
openLibraryKey?: string | null;
31-
amazonAsin?: string | null;
32-
};
28+
externalRatingCount?: number | null;
29+
googleBooksId?: string | null;
30+
openLibraryKey?: string | null;
31+
amazonAsin?: string | null;
32+
createdAt?: string | null;
33+
};
3334
}
3435

3536
interface UpdateLibraryBookMetadataResult {
@@ -113,7 +114,8 @@ export class UpdateLibraryBookMetadataUseCase {
113114
language: input.metadata.language === undefined ? existing.language : input.metadata.language,
114115
year: nextPublicationDate.year,
115116
month: nextPublicationDate.month,
116-
day: nextPublicationDate.day
117+
day: nextPublicationDate.day,
118+
createdAt: input.metadata.createdAt === undefined ? existing.createdAt : input.metadata.createdAt
117119
});
118120

119121
if (shouldDeleteManagedCover) {

sake/src/lib/server/domain/entities/Book.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,5 @@ export interface UpdateBookMetadataInput {
7878
year: number | null;
7979
month: number | null;
8080
day: number | null;
81+
createdAt: string | null;
8182
}

0 commit comments

Comments
 (0)