Add update method to ReviewCommentRepository, updateReviewComment tRPC procedure, and inline edit UI in CommentThread. Edit button appears on user-authored comments (not agent comments) when unresolved. Uses the existing CommentForm with a new initialValue prop.
128 lines
3.6 KiB
TypeScript
128 lines
3.6 KiB
TypeScript
/**
|
|
* Drizzle Review Comment Repository Adapter
|
|
*
|
|
* Implements ReviewCommentRepository interface using Drizzle ORM.
|
|
*/
|
|
|
|
import { eq, asc } from 'drizzle-orm';
|
|
import { nanoid } from 'nanoid';
|
|
import type { DrizzleDatabase } from '../../index.js';
|
|
import { reviewComments, type ReviewComment } from '../../schema.js';
|
|
import type { ReviewCommentRepository, CreateReviewCommentData } from '../review-comment-repository.js';
|
|
|
|
export class DrizzleReviewCommentRepository implements ReviewCommentRepository {
|
|
constructor(private db: DrizzleDatabase) {}
|
|
|
|
async create(data: CreateReviewCommentData): Promise<ReviewComment> {
|
|
const now = new Date();
|
|
const id = nanoid();
|
|
await this.db.insert(reviewComments).values({
|
|
id,
|
|
phaseId: data.phaseId,
|
|
filePath: data.filePath,
|
|
lineNumber: data.lineNumber,
|
|
lineType: data.lineType,
|
|
body: data.body,
|
|
author: data.author ?? 'user',
|
|
parentCommentId: data.parentCommentId ?? null,
|
|
resolved: false,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
});
|
|
const rows = await this.db
|
|
.select()
|
|
.from(reviewComments)
|
|
.where(eq(reviewComments.id, id))
|
|
.limit(1);
|
|
return rows[0]!;
|
|
}
|
|
|
|
async createReply(parentCommentId: string, body: string, author?: string): Promise<ReviewComment> {
|
|
// Fetch parent comment to copy context fields
|
|
const parentRows = await this.db
|
|
.select()
|
|
.from(reviewComments)
|
|
.where(eq(reviewComments.id, parentCommentId))
|
|
.limit(1);
|
|
const parent = parentRows[0];
|
|
if (!parent) {
|
|
throw new Error(`Parent comment not found: ${parentCommentId}`);
|
|
}
|
|
|
|
const now = new Date();
|
|
const id = nanoid();
|
|
await this.db.insert(reviewComments).values({
|
|
id,
|
|
phaseId: parent.phaseId,
|
|
filePath: parent.filePath,
|
|
lineNumber: parent.lineNumber,
|
|
lineType: parent.lineType,
|
|
body,
|
|
author: author ?? 'user',
|
|
parentCommentId,
|
|
resolved: false,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
});
|
|
const rows = await this.db
|
|
.select()
|
|
.from(reviewComments)
|
|
.where(eq(reviewComments.id, id))
|
|
.limit(1);
|
|
return rows[0]!;
|
|
}
|
|
|
|
async findByPhaseId(phaseId: string): Promise<ReviewComment[]> {
|
|
return this.db
|
|
.select()
|
|
.from(reviewComments)
|
|
.where(eq(reviewComments.phaseId, phaseId))
|
|
.orderBy(asc(reviewComments.createdAt));
|
|
}
|
|
|
|
async update(id: string, body: string): Promise<ReviewComment | null> {
|
|
await this.db
|
|
.update(reviewComments)
|
|
.set({ body, updatedAt: new Date() })
|
|
.where(eq(reviewComments.id, id));
|
|
const rows = await this.db
|
|
.select()
|
|
.from(reviewComments)
|
|
.where(eq(reviewComments.id, id))
|
|
.limit(1);
|
|
return rows[0] ?? null;
|
|
}
|
|
|
|
async resolve(id: string): Promise<ReviewComment | null> {
|
|
await this.db
|
|
.update(reviewComments)
|
|
.set({ resolved: true, updatedAt: new Date() })
|
|
.where(eq(reviewComments.id, id));
|
|
const rows = await this.db
|
|
.select()
|
|
.from(reviewComments)
|
|
.where(eq(reviewComments.id, id))
|
|
.limit(1);
|
|
return rows[0] ?? null;
|
|
}
|
|
|
|
async unresolve(id: string): Promise<ReviewComment | null> {
|
|
await this.db
|
|
.update(reviewComments)
|
|
.set({ resolved: false, updatedAt: new Date() })
|
|
.where(eq(reviewComments.id, id));
|
|
const rows = await this.db
|
|
.select()
|
|
.from(reviewComments)
|
|
.where(eq(reviewComments.id, id))
|
|
.limit(1);
|
|
return rows[0] ?? null;
|
|
}
|
|
|
|
async delete(id: string): Promise<void> {
|
|
await this.db
|
|
.delete(reviewComments)
|
|
.where(eq(reviewComments.id, id));
|
|
}
|
|
}
|