mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-11-01 18:19:43 +00:00
feat(backend): add standalone data plane
Replace the Supabase application data plane with the Node/PostgreSQL API, migrate mobile, admin, web, and MCP callers, add controlled cutover tooling, and define hardened Dokploy infrastructure.
This commit is contained in:
1 parent
01bb8225ae
commit
006761d3b0
294 files changed
+16259
-6138
No files matched your search
@@ -0,0 +1,44 @@
|
||||
import { randomBytes, randomUUID } from 'node:crypto';
|
||||
import type { CoupleRepository } from './repository.js';
|
||||
import { CoupleError, type CoupleMutationResponse, type CoupleStateResponse } from './types.js';
|
||||
|
||||
const inviteAlphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
||||
|
||||
export function generateInviteCode(bytes: Uint8Array = randomBytes(8)): string {
|
||||
return Array.from(bytes, (byte) => inviteAlphabet[byte % inviteAlphabet.length]).join('').slice(0, 8);
|
||||
}
|
||||
|
||||
export class CoupleService {
|
||||
constructor(private readonly repository: CoupleRepository) {}
|
||||
|
||||
getState(userId: string): Promise<CoupleStateResponse> {
|
||||
return this.repository.getState(userId);
|
||||
}
|
||||
|
||||
async create(userId: string): Promise<CoupleMutationResponse> {
|
||||
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||
const inviteCode = generateInviteCode();
|
||||
try {
|
||||
const couple = await this.repository.create(userId, randomUUID(), inviteCode);
|
||||
return { success: true, couple_id: couple.id, invite_code: couple.invite_code };
|
||||
} catch (error) {
|
||||
if (!(error instanceof CoupleError) || error.code !== 'invite_code_collision') throw error;
|
||||
}
|
||||
}
|
||||
throw new Error('Unable to allocate a unique invite code');
|
||||
}
|
||||
|
||||
async join(userId: string, inviteCode: string): Promise<CoupleMutationResponse> {
|
||||
const canonicalCode = inviteCode.trim().toUpperCase();
|
||||
if (!/^[A-Z0-9]{8}$/.test(canonicalCode)) {
|
||||
throw new CoupleError('invalid_invite_code', 'Invalid invite code format', 400);
|
||||
}
|
||||
const couple = await this.repository.join(userId, canonicalCode);
|
||||
return { success: true, couple_id: couple.id };
|
||||
}
|
||||
|
||||
async cancel(userId: string): Promise<CoupleMutationResponse> {
|
||||
await this.repository.cancel(userId);
|
||||
return { success: true, couple_id: null };
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user