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
|
#!/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..."
|
echo "Rodando migrations..."
|
||||||
MIGRATED=0
|
MIGRATED=0
|
||||||
for i in 1 2 3 4 5; do
|
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,
|
"when": 1777042423451,
|
||||||
"tag": "0026_bored_eternity",
|
"tag": "0026_bored_eternity",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 28,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1777153372633,
|
||||||
|
"tag": "0028_fancy_reaper",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
PAYER_ROLE_THIRD_PARTY,
|
PAYER_ROLE_THIRD_PARTY,
|
||||||
PAYER_STATUS_OPTIONS,
|
PAYER_STATUS_OPTIONS,
|
||||||
} from "@/shared/lib/payers/constants";
|
} from "@/shared/lib/payers/constants";
|
||||||
|
import { generateShareCode } from "@/shared/lib/payers/share-code";
|
||||||
import { normalizeNameFromEmail } from "@/shared/lib/payers/utils";
|
import { normalizeNameFromEmail } from "@/shared/lib/payers/utils";
|
||||||
import {
|
import {
|
||||||
addMonthsToDate,
|
addMonthsToDate,
|
||||||
@@ -537,6 +538,7 @@ async function ensureAdminPayer(targetUser: typeof user.$inferSelect) {
|
|||||||
note: null,
|
note: null,
|
||||||
role: PAYER_ROLE_ADMIN,
|
role: PAYER_ROLE_ADMIN,
|
||||||
isAutoSend: false,
|
isAutoSend: false,
|
||||||
|
shareCode: generateShareCode(),
|
||||||
userId: targetUser.id,
|
userId: targetUser.id,
|
||||||
})
|
})
|
||||||
.returning({ id: payers.id, name: payers.name });
|
.returning({ id: payers.id, name: payers.name });
|
||||||
@@ -870,6 +872,7 @@ async function main() {
|
|||||||
note: definition.note,
|
note: definition.note,
|
||||||
role: PAYER_ROLE_THIRD_PARTY,
|
role: PAYER_ROLE_THIRD_PARTY,
|
||||||
isAutoSend: definition.isAutoSend,
|
isAutoSend: definition.isAutoSend,
|
||||||
|
shareCode: generateShareCode(),
|
||||||
userId: targetUser.id,
|
userId: targetUser.id,
|
||||||
})
|
})
|
||||||
.returning({ id: payers.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"),
|
note: text("anotacao"),
|
||||||
role: text("role"),
|
role: text("role"),
|
||||||
isAutoSend: boolean("is_auto_send").notNull().default(false),
|
isAutoSend: boolean("is_auto_send").notNull().default(false),
|
||||||
shareCode: text("share_code")
|
shareCode: text("share_code").notNull(),
|
||||||
.notNull()
|
|
||||||
.default(sql`substr(encode(gen_random_bytes(24), 'base64'), 1, 24)`),
|
|
||||||
lastMailAt: timestamp("last_mail", {
|
lastMailAt: timestamp("last_mail", {
|
||||||
mode: "date",
|
mode: "date",
|
||||||
withTimezone: true,
|
withTimezone: true,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { randomBytes } from "node:crypto";
|
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -17,6 +16,7 @@ import {
|
|||||||
PAYER_ROLE_THIRD_PARTY,
|
PAYER_ROLE_THIRD_PARTY,
|
||||||
PAYER_STATUS_OPTIONS,
|
PAYER_STATUS_OPTIONS,
|
||||||
} from "@/shared/lib/payers/constants";
|
} from "@/shared/lib/payers/constants";
|
||||||
|
import { generateShareCode } from "@/shared/lib/payers/share-code";
|
||||||
import { normalizeAvatarPath } from "@/shared/lib/payers/utils";
|
import { normalizeAvatarPath } from "@/shared/lib/payers/utils";
|
||||||
import { noteSchema, uuidSchema } from "@/shared/lib/schemas/common";
|
import { noteSchema, uuidSchema } from "@/shared/lib/schemas/common";
|
||||||
import type { ActionResult } from "@/shared/lib/types/actions";
|
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 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(
|
export async function createPayerAction(
|
||||||
input: CreateInput,
|
input: CreateInput,
|
||||||
): Promise<ActionResult> {
|
): Promise<ActionResult> {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
PAYER_STATUS_OPTIONS,
|
PAYER_STATUS_OPTIONS,
|
||||||
} from "@/shared/lib/payers/constants";
|
} from "@/shared/lib/payers/constants";
|
||||||
import { getAdminPayerId } from "@/shared/lib/payers/get-admin-id";
|
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 { normalizeNameFromEmail } from "@/shared/lib/payers/utils";
|
||||||
import { deleteS3Object } from "@/shared/lib/storage/presign";
|
import { deleteS3Object } from "@/shared/lib/storage/presign";
|
||||||
|
|
||||||
@@ -153,6 +154,7 @@ async function resetUserAppData(
|
|||||||
note: null,
|
note: null,
|
||||||
role: PAYER_ROLE_ADMIN,
|
role: PAYER_ROLE_ADMIN,
|
||||||
isAutoSend: false,
|
isAutoSend: false,
|
||||||
|
shareCode: generateShareCode(),
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
PAYER_ROLE_ADMIN,
|
PAYER_ROLE_ADMIN,
|
||||||
PAYER_STATUS_OPTIONS,
|
PAYER_STATUS_OPTIONS,
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
|
import { generateShareCode } from "./share-code";
|
||||||
import { normalizeNameFromEmail } from "./utils";
|
import { normalizeNameFromEmail } from "./utils";
|
||||||
|
|
||||||
const DEFAULT_STATUS = PAYER_STATUS_OPTIONS[0];
|
const DEFAULT_STATUS = PAYER_STATUS_OPTIONS[0];
|
||||||
@@ -49,6 +50,7 @@ export async function ensureDefaultPagadorForUser(user: SeedUserLike) {
|
|||||||
avatarUrl,
|
avatarUrl,
|
||||||
note: null,
|
note: null,
|
||||||
isAutoSend: false,
|
isAutoSend: false,
|
||||||
|
shareCode: generateShareCode(),
|
||||||
userId,
|
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