feat(payers): gerar share_code na aplicação e remover pgcrypto

Move a geração do share_code do PostgreSQL para a camada de aplicação,
eliminando a dependência da extensão pgcrypto no setup do banco.

- schema: drop default substr(encode(gen_random_bytes(24), 'base64'), 1, 24)
  da coluna share_code em pagadores (continua NOT NULL)
- nova util generateShareCode() em shared/lib/payers/share-code.ts
  (server-only, usa crypto.randomBytes do Node)
- chamadas explícitas em createPayerAction, ensureDefaultPagadorForUser,
  resetUserAppData e mock-data ao inserir pagadores
- migration 0028_fancy_reaper renumerada (0027 já estava ocupado por
  arquivo órfão); journal e snapshot atualizados
- remove etapa de habilitação de pgcrypto do docker-entrypoint.sh
- remove scripts/postgres/ (init.sql e enable-extensions.ts)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-04-26 22:52:36 +00:00
parent 791fec7751
commit 39f3cd8b20
12 changed files with 2938 additions and 75 deletions

View File

@@ -236,9 +236,7 @@ export const payers = pgTable(
note: text("anotacao"),
role: text("role"),
isAutoSend: boolean("is_auto_send").notNull().default(false),
shareCode: text("share_code")
.notNull()
.default(sql`substr(encode(gen_random_bytes(24), 'base64'), 1, 24)`),
shareCode: text("share_code").notNull(),
lastMailAt: timestamp("last_mail", {
mode: "date",
withTimezone: true,

View File

@@ -1,6 +1,5 @@
"use server";
import { randomBytes } from "node:crypto";
import { and, eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { z } from "zod";
@@ -17,6 +16,7 @@ import {
PAYER_ROLE_THIRD_PARTY,
PAYER_STATUS_OPTIONS,
} from "@/shared/lib/payers/constants";
import { generateShareCode } from "@/shared/lib/payers/share-code";
import { normalizeAvatarPath } from "@/shared/lib/payers/utils";
import { noteSchema, uuidSchema } from "@/shared/lib/schemas/common";
import type { ActionResult } from "@/shared/lib/types/actions";
@@ -83,12 +83,6 @@ type ShareCodeRegenerateInput = z.infer<typeof shareCodeRegenerateSchema>;
const revalidate = (userId: string) => revalidateForEntity("payers", userId);
const generateShareCode = () => {
// base64url já retorna apenas [a-zA-Z0-9_-]
// 18 bytes = 24 caracteres em base64
return randomBytes(18).toString("base64url").slice(0, 24);
};
export async function createPayerAction(
input: CreateInput,
): Promise<ActionResult> {

View File

@@ -17,6 +17,7 @@ import {
PAYER_STATUS_OPTIONS,
} from "@/shared/lib/payers/constants";
import { getAdminPayerId } from "@/shared/lib/payers/get-admin-id";
import { generateShareCode } from "@/shared/lib/payers/share-code";
import { normalizeNameFromEmail } from "@/shared/lib/payers/utils";
import { deleteS3Object } from "@/shared/lib/storage/presign";
@@ -153,6 +154,7 @@ async function resetUserAppData(
note: null,
role: PAYER_ROLE_ADMIN,
isAutoSend: false,
shareCode: generateShareCode(),
userId,
});
});

View File

@@ -6,6 +6,7 @@ import {
PAYER_ROLE_ADMIN,
PAYER_STATUS_OPTIONS,
} from "./constants";
import { generateShareCode } from "./share-code";
import { normalizeNameFromEmail } from "./utils";
const DEFAULT_STATUS = PAYER_STATUS_OPTIONS[0];
@@ -49,6 +50,7 @@ export async function ensureDefaultPagadorForUser(user: SeedUserLike) {
avatarUrl,
note: null,
isAutoSend: false,
shareCode: generateShareCode(),
userId,
});
}

View File

@@ -0,0 +1,6 @@
import "server-only";
import { randomBytes } from "node:crypto";
export const generateShareCode = (): string => {
return randomBytes(18).toString("base64url").slice(0, 24);
};