Checkpoint: Ajout de l'onglet "Signatures" dans la page Paramètres : table DB signatures (firstName, lastName, imageKey, imageUrl), migration appliquée, routes tRPC (list, upload, create, delete), composant SignaturesSection avec formulaire d'ajout (prénom+nom+upload image), grille d'affichage des signatures avec aperçu, bouton de suppression au survol
This commit is contained in:
36
server/db.ts
36
server/db.ts
@@ -32,7 +32,10 @@ import {
|
||||
AutomationRule,
|
||||
llmFieldsConfig,
|
||||
InsertLlmFieldConfig,
|
||||
LlmFieldConfig
|
||||
LlmFieldConfig,
|
||||
signatures,
|
||||
InsertSignature,
|
||||
Signature
|
||||
} from "../drizzle/schema";
|
||||
import { ENV } from './_core/env';
|
||||
|
||||
@@ -667,3 +670,34 @@ export async function initializeDefaultLlmFields(userId: number): Promise<void>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============= SIGNATURES HELPERS =============
|
||||
|
||||
export async function getSignaturesByUser(userId: number): Promise<Signature[]> {
|
||||
const db = await getDb();
|
||||
if (!db) return [];
|
||||
return db.select().from(signatures).where(eq(signatures.userId, userId));
|
||||
}
|
||||
|
||||
export async function getSignatureById(id: number): Promise<Signature | undefined> {
|
||||
const db = await getDb();
|
||||
if (!db) return undefined;
|
||||
const results = await db.select().from(signatures).where(eq(signatures.id, id));
|
||||
return results[0];
|
||||
}
|
||||
|
||||
export async function createSignature(data: InsertSignature): Promise<Signature> {
|
||||
const db = await getDb();
|
||||
if (!db) throw new Error("Database not available");
|
||||
const result = await db.insert(signatures).values(data);
|
||||
const insertId = (result[0] as any).insertId;
|
||||
const created = await getSignatureById(insertId);
|
||||
if (!created) throw new Error("Failed to retrieve created signature");
|
||||
return created;
|
||||
}
|
||||
|
||||
export async function deleteSignature(id: number): Promise<void> {
|
||||
const db = await getDb();
|
||||
if (!db) return;
|
||||
await db.delete(signatures).where(eq(signatures.id, id));
|
||||
}
|
||||
|
||||
@@ -56,6 +56,10 @@ import {
|
||||
createAutomationRule,
|
||||
updateAutomationRule,
|
||||
deleteAutomationRule,
|
||||
getSignaturesByUser,
|
||||
getSignatureById,
|
||||
createSignature,
|
||||
deleteSignature,
|
||||
} from "./db";
|
||||
import { loginLocal, hashPassword, getAzureAuthUrl, isAzureAdConfigured, generateToken } from "./auth";
|
||||
import { extractInvoicesWithMistral, generateMetadataJSON } from "./invoiceExtractor";
|
||||
@@ -1170,6 +1174,65 @@ export const appRouter = router({
|
||||
});
|
||||
}),
|
||||
}),
|
||||
|
||||
// ============= SIGNATURES ROUTES =============
|
||||
signatures: router({
|
||||
list: protectedProcedure.query(async ({ ctx }) => {
|
||||
return await getSignaturesByUser(ctx.user.id);
|
||||
}),
|
||||
|
||||
upload: protectedProcedure
|
||||
.input(z.object({
|
||||
firstName: z.string().min(1).max(100),
|
||||
lastName: z.string().min(1).max(100),
|
||||
fileName: z.string().min(1),
|
||||
fileData: z.string(), // Base64 encoded image
|
||||
mimeType: z.string().default("image/png"),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const userId = ctx.user.id;
|
||||
const fileBuffer = Buffer.from(input.fileData, "base64");
|
||||
const safeFileName = `${input.firstName}-${input.lastName}-${Date.now()}-${input.fileName}`
|
||||
.replace(/[^a-zA-Z0-9._-]/g, "_");
|
||||
const imageKey = generateStorageKey(userId, safeFileName);
|
||||
const result = await localStoragePut(imageKey, fileBuffer, input.mimeType);
|
||||
return await createSignature({
|
||||
userId,
|
||||
firstName: input.firstName,
|
||||
lastName: input.lastName,
|
||||
imageKey,
|
||||
imageUrl: result.url,
|
||||
});
|
||||
}),
|
||||
|
||||
create: protectedProcedure
|
||||
.input(z.object({
|
||||
firstName: z.string().min(1).max(100),
|
||||
lastName: z.string().min(1).max(100),
|
||||
imageKey: z.string().min(1),
|
||||
imageUrl: z.string().min(1),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
return await createSignature({
|
||||
userId: ctx.user.id,
|
||||
firstName: input.firstName,
|
||||
lastName: input.lastName,
|
||||
imageKey: input.imageKey,
|
||||
imageUrl: input.imageUrl,
|
||||
});
|
||||
}),
|
||||
|
||||
delete: protectedProcedure
|
||||
.input(z.object({ id: z.number() }))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const sig = await getSignatureById(input.id);
|
||||
if (!sig || sig.userId !== ctx.user.id) {
|
||||
throw new TRPCError({ code: "NOT_FOUND" });
|
||||
}
|
||||
await deleteSignature(input.id);
|
||||
return { success: true };
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
||||
Reference in New Issue
Block a user