feat(03-01): add images table and repository
- Add images table with entryId foreign key and cascade delete - Add Image and NewImage types - Add ImageRepository with create, getById, getByEntryId, delete, deleteByEntryId - Install sharp for thumbnail generation
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { eq, desc, asc, ne } from 'drizzle-orm';
|
||||
import { db } from './index';
|
||||
import { entries, type Entry, type NewEntry } from './schema';
|
||||
import { entries, images, type Entry, type NewEntry, type Image, type NewImage } from './schema';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export interface EntryRepository {
|
||||
@@ -86,3 +86,56 @@ class SQLiteEntryRepository implements EntryRepository {
|
||||
|
||||
// Singleton instance
|
||||
export const entryRepository: EntryRepository = new SQLiteEntryRepository();
|
||||
|
||||
// Image Repository
|
||||
export interface ImageRepository {
|
||||
create(image: Omit<NewImage, 'id' | 'createdAt'>): Image;
|
||||
getById(id: string): Image | undefined;
|
||||
getByEntryId(entryId: string): Image[];
|
||||
delete(id: string): boolean;
|
||||
deleteByEntryId(entryId: string): number;
|
||||
}
|
||||
|
||||
class SQLiteImageRepository implements ImageRepository {
|
||||
create(image: Omit<NewImage, 'id' | 'createdAt'>): Image {
|
||||
const newImage: NewImage = {
|
||||
...image,
|
||||
id: nanoid(),
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
db.insert(images).values(newImage).run();
|
||||
return this.getById(newImage.id)!;
|
||||
}
|
||||
|
||||
getById(id: string): Image | undefined {
|
||||
return db.select().from(images).where(eq(images.id, id)).get();
|
||||
}
|
||||
|
||||
getByEntryId(entryId: string): Image[] {
|
||||
return db
|
||||
.select()
|
||||
.from(images)
|
||||
.where(eq(images.entryId, entryId))
|
||||
.orderBy(asc(images.createdAt))
|
||||
.all();
|
||||
}
|
||||
|
||||
delete(id: string): boolean {
|
||||
const existing = this.getById(id);
|
||||
if (!existing) return false;
|
||||
|
||||
db.delete(images).where(eq(images.id, id)).run();
|
||||
return true;
|
||||
}
|
||||
|
||||
deleteByEntryId(entryId: string): number {
|
||||
const existing = this.getByEntryId(entryId);
|
||||
if (existing.length === 0) return 0;
|
||||
|
||||
db.delete(images).where(eq(images.entryId, entryId)).run();
|
||||
return existing.length;
|
||||
}
|
||||
}
|
||||
|
||||
export const imageRepository: ImageRepository = new SQLiteImageRepository();
|
||||
|
||||
@@ -20,3 +20,19 @@ export const entries = sqliteTable('entries', {
|
||||
// Type inference for TypeScript
|
||||
export type Entry = typeof entries.$inferSelect;
|
||||
export type NewEntry = typeof entries.$inferInsert;
|
||||
|
||||
// Images table: attachments linked to entries
|
||||
export const images = sqliteTable('images', {
|
||||
id: text('id').primaryKey(), // nanoid
|
||||
entryId: text('entry_id')
|
||||
.notNull()
|
||||
.references(() => entries.id, { onDelete: 'cascade' }),
|
||||
filename: text('filename').notNull(), // original filename for display
|
||||
ext: text('ext').notNull(), // file extension without dot: jpg, png, etc.
|
||||
createdAt: text('created_at')
|
||||
.notNull()
|
||||
.$defaultFn(() => new Date().toISOString())
|
||||
});
|
||||
|
||||
export type Image = typeof images.$inferSelect;
|
||||
export type NewImage = typeof images.$inferInsert;
|
||||
|
||||
Reference in New Issue
Block a user