feat(04-01): create tagRepository with tag operations
- Add TagRepository interface with findOrCreate, getAll, getById, getByEntryId, updateEntryTags - Implement SQLiteTagRepository with case-insensitive tag lookup - updateEntryTags atomically replaces all tags for an entry - Export tagRepository singleton
This commit is contained in:
@@ -1,6 +1,17 @@
|
||||
import { eq, desc, asc, ne } from 'drizzle-orm';
|
||||
import { eq, desc, asc, ne, sql } from 'drizzle-orm';
|
||||
import { db } from './index';
|
||||
import { entries, images, type Entry, type NewEntry, type Image, type NewImage } from './schema';
|
||||
import {
|
||||
entries,
|
||||
images,
|
||||
tags,
|
||||
entryTags,
|
||||
lower,
|
||||
type Entry,
|
||||
type NewEntry,
|
||||
type Image,
|
||||
type NewImage,
|
||||
type Tag
|
||||
} from './schema';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export interface EntryRepository {
|
||||
@@ -139,3 +150,71 @@ class SQLiteImageRepository implements ImageRepository {
|
||||
}
|
||||
|
||||
export const imageRepository: ImageRepository = new SQLiteImageRepository();
|
||||
|
||||
// Tag Repository
|
||||
export interface TagRepository {
|
||||
findOrCreate(name: string): Tag;
|
||||
getAll(): Tag[];
|
||||
getById(id: string): Tag | undefined;
|
||||
getByEntryId(entryId: string): Tag[];
|
||||
updateEntryTags(entryId: string, tagNames: string[]): void;
|
||||
}
|
||||
|
||||
class SQLiteTagRepository implements TagRepository {
|
||||
findOrCreate(name: string): Tag {
|
||||
const normalizedName = name.trim();
|
||||
|
||||
// Try to find existing tag (case-insensitive)
|
||||
const existing = db
|
||||
.select()
|
||||
.from(tags)
|
||||
.where(sql`lower(${tags.name}) = lower(${normalizedName})`)
|
||||
.get();
|
||||
|
||||
if (existing) return existing;
|
||||
|
||||
// Create new tag
|
||||
const newTag: Tag = {
|
||||
id: nanoid(),
|
||||
name: normalizedName,
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
db.insert(tags).values(newTag).run();
|
||||
return newTag;
|
||||
}
|
||||
|
||||
getAll(): Tag[] {
|
||||
return db.select().from(tags).orderBy(asc(tags.name)).all();
|
||||
}
|
||||
|
||||
getById(id: string): Tag | undefined {
|
||||
return db.select().from(tags).where(eq(tags.id, id)).get();
|
||||
}
|
||||
|
||||
getByEntryId(entryId: string): Tag[] {
|
||||
return db
|
||||
.select({
|
||||
id: tags.id,
|
||||
name: tags.name,
|
||||
createdAt: tags.createdAt
|
||||
})
|
||||
.from(entryTags)
|
||||
.innerJoin(tags, eq(entryTags.tagId, tags.id))
|
||||
.where(eq(entryTags.entryId, entryId))
|
||||
.orderBy(asc(tags.name))
|
||||
.all();
|
||||
}
|
||||
|
||||
updateEntryTags(entryId: string, tagNames: string[]): void {
|
||||
// Delete existing associations
|
||||
db.delete(entryTags).where(eq(entryTags.entryId, entryId)).run();
|
||||
|
||||
// Find or create each tag and create associations
|
||||
for (const name of tagNames) {
|
||||
const tag = this.findOrCreate(name);
|
||||
db.insert(entryTags).values({ entryId, tagId: tag.id }).run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const tagRepository: TagRepository = new SQLiteTagRepository();
|
||||
|
||||
Reference in New Issue
Block a user