feat(logo): integração Logo.dev para logos automáticos de estabelecimentos

- Nova tabela `establishment_logos` no schema (userId + nameKey → domain)
- Utilitários: `buildLogoDevUrl`, `toNameKey`, `logoQueryKeys`, `LOGO_DEV_TOKEN`
- `EstablishmentLogo`: exibe logo via Logo.dev com fallback para iniciais; hover mostra ícone de edição
- `EstablishmentLogoPicker`: popover para buscar e fixar domínio Logo.dev por estabelecimento
- API routes: `GET /api/logo/mapping` e `GET /api/logo/search`
- Server actions/queries para persistência do mapeamento por usuário
- CSP: libera `https://img.logo.dev` em `img-src`
- `.env.example`: variáveis `NEXT_PUBLIC_LOGO_DEV_TOKEN` e `LOGO_DEV_SECRET_KEY`

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-04-14 00:26:48 +00:00
parent 1161e97d9e
commit 679ea752bb
12 changed files with 579 additions and 11 deletions

View File

@@ -721,6 +721,7 @@ export const userRelations = relations(user, ({ many, one }) => ({
installmentAnticipations: many(installmentAnticipations),
apiTokens: many(apiTokens),
inboxItems: many(inboxItems),
establishmentLogos: many(establishmentLogos),
}));
export const accountRelations = relations(account, ({ one }) => ({
@@ -955,6 +956,25 @@ export const importCategoryMappings = pgTable(
}),
);
export const establishmentLogos = pgTable(
"establishment_logos",
{
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
nameKey: text("name_key").notNull(),
domain: text("domain").notNull(),
updatedAt: timestamp("updated_at", { mode: "date", withTimezone: true })
.notNull()
.defaultNow(),
},
(table) => ({
pk: primaryKey({ columns: [table.userId, table.nameKey] }),
}),
);
export type EstablishmentLogo = typeof establishmentLogos.$inferSelect;
export type User = typeof user.$inferSelect;
export type NewUser = typeof user.$inferInsert;
export type Account = typeof account.$inferSelect;
@@ -1004,3 +1024,13 @@ export const transactionAttachmentsRelations = relations(
export type Attachment = typeof attachments.$inferSelect;
export type TransactionAttachment = typeof transactionAttachments.$inferSelect;
export const establishmentLogosRelations = relations(
establishmentLogos,
({ one }) => ({
user: one(user, {
fields: [establishmentLogos.userId],
references: [user.id],
}),
}),
);