From 5448dbb7ebcbb1c92972ffb8952926f5f768dc52 Mon Sep 17 00:00:00 2001 From: MOHAN Date: Fri, 17 Jul 2026 23:41:37 +0530 Subject: [PATCH] Add Sheets-first mirror mode --- .../migration.sql | 2 + prisma/schema.prisma | 1 + src/google/google.controller.ts | 6 +++ src/google/google.service.ts | 41 ++++++++++++++++++- 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 prisma/migrations/20260717181500_google_data_system_mode/migration.sql diff --git a/prisma/migrations/20260717181500_google_data_system_mode/migration.sql b/prisma/migrations/20260717181500_google_data_system_mode/migration.sql new file mode 100644 index 0000000..ddca47c --- /dev/null +++ b/prisma/migrations/20260717181500_google_data_system_mode/migration.sql @@ -0,0 +1,2 @@ +-- Track user-selected data ownership/system-of-record posture. +ALTER TABLE "GoogleConnection" ADD COLUMN "dataSystemMode" TEXT NOT NULL DEFAULT 'backend_db'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 81ab969..38c35d3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -664,6 +664,7 @@ model GoogleConnection { driveMirrorStatus String @default("not_started") driveMirrorSpreadsheetUrl String? driveMirrorLastSyncedAt DateTime? + dataSystemMode String @default("backend_db") isConnected Boolean @default(true) connectedAt DateTime @default(now()) lastSyncedAt DateTime? diff --git a/src/google/google.controller.ts b/src/google/google.controller.ts index c07d68e..326b5cb 100644 --- a/src/google/google.controller.ts +++ b/src/google/google.controller.ts @@ -30,4 +30,10 @@ export class GoogleController { status(@CurrentUser() userId: string) { return this.googleService.getStatus(userId); } + + @Post("data-system-mode") + @HttpCode(200) + dataSystemMode(@CurrentUser() userId: string, @Body() body: { mode: "backend_db" | "google_sheets_mirror" }) { + return this.googleService.updateDataSystemMode(userId, body.mode); + } } diff --git a/src/google/google.service.ts b/src/google/google.service.ts index c44ad27..6738e7d 100644 --- a/src/google/google.service.ts +++ b/src/google/google.service.ts @@ -58,7 +58,7 @@ export class GoogleService { const { data } = await oauth2.userinfo.get(); const googleEmail = data.email ?? ""; - await this.prisma.googleConnection.upsert({ + await (this.prisma as any).googleConnection.upsert({ where: { userId }, update: { googleEmail, @@ -71,6 +71,7 @@ export class GoogleService { driveMirrorStatus: "pending_first_sync", driveMirrorSpreadsheetUrl: null, driveMirrorLastSyncedAt: null, + dataSystemMode: "backend_db", }, create: { userId, @@ -80,6 +81,7 @@ export class GoogleService { isConnected: true, driveMirrorEnabled: false, driveMirrorStatus: "pending_first_sync", + dataSystemMode: "backend_db", }, }); @@ -93,7 +95,7 @@ export class GoogleService { } async getStatus(userId: string) { - const gc = await this.prisma.googleConnection.findUnique({ where: { userId } }); + const gc = await (this.prisma as any).googleConnection.findUnique({ where: { userId } }); if (!gc || !gc.isConnected) return { connected: false }; return { connected: true, @@ -107,6 +109,41 @@ export class GoogleService { lastSyncedAt: gc.driveMirrorLastSyncedAt ?? gc.lastSyncedAt, ownership: "user_google_drive", }, + dataSystem: { + mode: gc.dataSystemMode, + operationalSystemOfRecord: "ledgerone_backend_db", + userOwnedMirror: gc.dataSystemMode === "google_sheets_mirror", + note: gc.dataSystemMode === "google_sheets_mirror" + ? "LedgerOne writes a best-effort user-owned Google Sheets mirror while the application database remains the operational store." + : "LedgerOne uses the application database as the operational store. Google Sheets can be enabled as a user-owned mirror.", + }, + }; + } + + async updateDataSystemMode(userId: string, mode: "backend_db" | "google_sheets_mirror") { + if (!["backend_db", "google_sheets_mirror"].includes(mode)) { + throw new BadRequestException("Unsupported data system mode."); + } + const connection = await (this.prisma as any).googleConnection.findUnique({ where: { userId } }); + if (!connection || !connection.isConnected) { + throw new BadRequestException("Google account must be connected before enabling Sheets-first mirror mode."); + } + + const updated = await (this.prisma as any).googleConnection.update({ + where: { userId }, + data: { + dataSystemMode: mode, + driveMirrorEnabled: mode === "google_sheets_mirror" ? true : connection.driveMirrorEnabled, + driveMirrorStatus: mode === "google_sheets_mirror" && !connection.spreadsheetId ? "pending_first_sync" : connection.driveMirrorStatus, + }, + }); + + return { + mode: updated.dataSystemMode, + driveMirrorEnabled: updated.driveMirrorEnabled, + status: updated.driveMirrorStatus, + operationalSystemOfRecord: "ledgerone_backend_db", + userOwnedMirror: updated.dataSystemMode === "google_sheets_mirror", }; } }