mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 02:51:46 +00:00
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:
@@ -1,15 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Habilitando extensão pgcrypto..."
|
||||
node -e "
|
||||
const { Client } = require('/app/migrate/node_modules/pg');
|
||||
const c = new Client({ connectionString: process.env.DATABASE_URL });
|
||||
c.connect()
|
||||
.then(() => c.query('CREATE EXTENSION IF NOT EXISTS pgcrypto'))
|
||||
.then(() => c.end())
|
||||
.catch((e) => { console.error('Aviso pgcrypto:', e.message); process.exit(0); });
|
||||
"
|
||||
|
||||
echo "Rodando migrations..."
|
||||
MIGRATED=0
|
||||
for i in 1 2 3 4 5; do
|
||||
|
||||
1
drizzle/0028_fancy_reaper.sql
Normal file
1
drizzle/0028_fancy_reaper.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "pagadores" ALTER COLUMN "share_code" DROP DEFAULT;
|
||||
2915
drizzle/meta/0028_snapshot.json
Normal file
2915
drizzle/meta/0028_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -190,6 +190,13 @@
|
||||
"when": 1777042423451,
|
||||
"tag": "0026_bored_eternity",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 28,
|
||||
"version": "7",
|
||||
"when": 1777153372633,
|
||||
"tag": "0028_fancy_reaper",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
PAYER_ROLE_THIRD_PARTY,
|
||||
PAYER_STATUS_OPTIONS,
|
||||
} from "@/shared/lib/payers/constants";
|
||||
import { generateShareCode } from "@/shared/lib/payers/share-code";
|
||||
import { normalizeNameFromEmail } from "@/shared/lib/payers/utils";
|
||||
import {
|
||||
addMonthsToDate,
|
||||
@@ -537,6 +538,7 @@ async function ensureAdminPayer(targetUser: typeof user.$inferSelect) {
|
||||
note: null,
|
||||
role: PAYER_ROLE_ADMIN,
|
||||
isAutoSend: false,
|
||||
shareCode: generateShareCode(),
|
||||
userId: targetUser.id,
|
||||
})
|
||||
.returning({ id: payers.id, name: payers.name });
|
||||
@@ -870,6 +872,7 @@ async function main() {
|
||||
note: definition.note,
|
||||
role: PAYER_ROLE_THIRD_PARTY,
|
||||
isAutoSend: definition.isAutoSend,
|
||||
shareCode: generateShareCode(),
|
||||
userId: targetUser.id,
|
||||
})
|
||||
.returning({ id: payers.id });
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { config } from "dotenv";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { Pool } from "pg";
|
||||
|
||||
// Load environment variables from .env
|
||||
config();
|
||||
|
||||
async function initDatabase() {
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
|
||||
if (!databaseUrl) {
|
||||
console.error("DATABASE_URL environment variable is required");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const pool = new Pool({ connectionString: databaseUrl });
|
||||
const db = drizzle(pool);
|
||||
|
||||
try {
|
||||
console.log("🔧 Initializing database extensions...");
|
||||
|
||||
// Read and execute init.sql as a single query
|
||||
const initSqlPath = path.join(
|
||||
process.cwd(),
|
||||
"scripts",
|
||||
"postgres",
|
||||
"init.sql",
|
||||
);
|
||||
const initSql = fs.readFileSync(initSqlPath, "utf-8");
|
||||
|
||||
console.log("Executing init.sql...");
|
||||
await db.execute(initSql);
|
||||
|
||||
console.log("✅ Database initialization completed");
|
||||
} catch (error) {
|
||||
console.error("❌ Database initialization failed:", error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
|
||||
initDatabase();
|
||||
@@ -1,10 +0,0 @@
|
||||
-- Script de inicialização do PostgreSQL para Docker
|
||||
-- Este script é executado automaticamente quando o banco é criado pela primeira vez
|
||||
|
||||
-- Habilitar extensão pgcrypto (necessária para gen_random_bytes usado pelo Drizzle)
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
-- Log de sucesso
|
||||
DO $$
|
||||
BEGIN
|
||||
RAISE NOTICE '✅ Extensão pgcrypto habilitada com sucesso';
|
||||
END $$;
|
||||
@@ -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,
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
6
src/shared/lib/payers/share-code.ts
Normal file
6
src/shared/lib/payers/share-code.ts
Normal 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);
|
||||
};
|
||||
Reference in New Issue
Block a user