Integra módulo de Resellers (agente eden-commercial)
Resolve conflitos de merge (users.ts, audit.ts, br-documents.ts, pnpm-lock/workspace.yaml — o worktree partiu de uma base anterior à existência do módulo de auth/companies, então recriou versões próprias de arquivos que já existiam no main; mantido o conteúdo do main, incorporando só a função nova assignUserToReseller). Adiciona resellersRepo ao barrel @eden/database e ResellersModule ao app.module.ts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,8 @@ export * as sessionsRepo from "./repositories/sessions.js";
|
||||
export * as auditRepo from "./repositories/audit.js";
|
||||
export * as companiesRepo from "./repositories/companies.js";
|
||||
export * as productsRepo from "./repositories/products.js";
|
||||
export * as resellersRepo from "./repositories/resellers.js";
|
||||
export type * from "./repositories/types.js";
|
||||
export type { Company } from "./repositories/companies.js";
|
||||
export type { Product, CreateProductInput, UpdateProductInput } from "./repositories/products.js";
|
||||
export type { Reseller, CreateResellerInput, UpdateResellerInput } from "./repositories/resellers.js";
|
||||
|
||||
@@ -20,12 +20,13 @@ function canonicalJson(value: unknown): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends one event to the audit_log hash chain. Never call this inside a
|
||||
* larger transaction that might roll back for unrelated reasons without
|
||||
* considering that the chain must stay contiguous — a rolled-back event
|
||||
* never happened, which is fine (nothing was recorded), but a *committed*
|
||||
* event must never be skipped when computing previous_event_hash for the
|
||||
* next one. See .claude/skills/eden-security/references/audit.md.
|
||||
* Appends one event to the audit_log hash chain (see baseline-schema
|
||||
* migration and .claude/skills/eden-security/references/audit.md). Never
|
||||
* call this inside a larger transaction that might roll back for
|
||||
* unrelated reasons without considering that the chain must stay
|
||||
* contiguous — a rolled-back event never happened (fine, nothing was
|
||||
* recorded), but a *committed* event must never be skipped when
|
||||
* computing previous_event_hash for the next one.
|
||||
*/
|
||||
export async function appendAuditEvent(input: AuditEventInput): Promise<void> {
|
||||
const { rows } = await query<{ event_hash: string }>(
|
||||
|
||||
71
packages/database/src/repositories/resellers.ts
Normal file
71
packages/database/src/repositories/resellers.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { query } from "../pool.js";
|
||||
|
||||
export interface Reseller {
|
||||
id: string;
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
created_by: string | null;
|
||||
name: string;
|
||||
document: string | null;
|
||||
contact_email: string | null;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export async function listResellers(includeInactive = false): Promise<Reseller[]> {
|
||||
const { rows } = await query<Reseller>(
|
||||
includeInactive
|
||||
? "SELECT * FROM resellers ORDER BY created_at ASC"
|
||||
: "SELECT * FROM resellers WHERE active = true ORDER BY created_at ASC",
|
||||
);
|
||||
return rows;
|
||||
}
|
||||
|
||||
export async function getResellerById(id: string): Promise<Reseller | null> {
|
||||
const { rows } = await query<Reseller>("SELECT * FROM resellers WHERE id = $1", [id]);
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
|
||||
export interface CreateResellerInput {
|
||||
name: string;
|
||||
document?: string | null;
|
||||
contactEmail?: string | null;
|
||||
active?: boolean;
|
||||
createdBy?: string | null;
|
||||
}
|
||||
|
||||
export async function createReseller(input: CreateResellerInput): Promise<Reseller> {
|
||||
const { rows } = await query<Reseller>(
|
||||
`INSERT INTO resellers (name, document, contact_email, active, created_by)
|
||||
VALUES ($1, $2, $3, COALESCE($4, true), $5)
|
||||
RETURNING *`,
|
||||
[
|
||||
input.name,
|
||||
input.document ?? null,
|
||||
input.contactEmail ?? null,
|
||||
input.active ?? null,
|
||||
input.createdBy ?? null,
|
||||
],
|
||||
);
|
||||
const reseller = rows[0];
|
||||
if (!reseller) throw new Error("failed to create reseller");
|
||||
return reseller;
|
||||
}
|
||||
|
||||
export type UpdateResellerInput = Partial<Omit<CreateResellerInput, "createdBy">>;
|
||||
|
||||
export async function updateReseller(
|
||||
id: string,
|
||||
input: UpdateResellerInput,
|
||||
): Promise<Reseller | null> {
|
||||
const { rows } = await query<Reseller>(
|
||||
`UPDATE resellers SET
|
||||
name = COALESCE($2, name),
|
||||
document = COALESCE($3, document),
|
||||
contact_email = COALESCE($4, contact_email),
|
||||
active = COALESCE($5, active)
|
||||
WHERE id = $1
|
||||
RETURNING *`,
|
||||
[id, input.name ?? null, input.document ?? null, input.contactEmail ?? null, input.active ?? null],
|
||||
);
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
@@ -124,3 +124,21 @@ export async function updateUserProfile(
|
||||
);
|
||||
return rows[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reseller-scoping: sets or clears which reseller a user belongs to
|
||||
* (`users.reseller_id`, nullable — eden.md §1.7, §7). Resolved and
|
||||
* enforced server-side only; callers must never accept `reseller_id`
|
||||
* from client input for authorization decisions (docs/adr/0004,
|
||||
* docs/architecture.md).
|
||||
*/
|
||||
export async function assignUserToReseller(
|
||||
userId: string,
|
||||
resellerId: string | null,
|
||||
): Promise<boolean> {
|
||||
const { rowCount } = await query("UPDATE users SET reseller_id = $2 WHERE id = $1", [
|
||||
userId,
|
||||
resellerId,
|
||||
]);
|
||||
return (rowCount ?? 0) > 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user