From b7fcba77b7ed0f887ba26e2b0ceae19904e140cd Mon Sep 17 00:00:00 2001 From: Felipe Coutinho Date: Mon, 8 Dec 2025 14:56:50 +0000 Subject: [PATCH] =?UTF-8?q?feat(changelog):=20implementar=20funcionalidade?= =?UTF-8?q?s=20de=20leitura=20de=20atualiza=C3=A7=C3=B5es=20-=20Adiciona?= =?UTF-8?q?=20fun=C3=A7=C3=B5es=20para=20marcar=20atualiza=C3=A7=C3=B5es?= =?UTF-8?q?=20como=20lidas=20-=20Implementa=20a=20l=C3=B3gica=20para=20mar?= =?UTF-8?q?car=20todas=20as=20atualiza=C3=A7=C3=B5es=20como=20lidas=20-=20?= =?UTF-8?q?Adiciona=20suporte=20a=20logs=20de=20atualiza=C3=A7=C3=B5es=20l?= =?UTF-8?q?idas=20no=20banco=20de=20dados=20-=20Cria=20fun=C3=A7=C3=B5es?= =?UTF-8?q?=20utilit=C3=A1rias=20para=20manipula=C3=A7=C3=A3o=20de=20chang?= =?UTF-8?q?elog=20-=20Gera=20changelog=20a=20partir=20de=20commits=20do=20?= =?UTF-8?q?Git=20-=20Salva=20changelog=20em=20formato=20JSON=20na=20pasta?= =?UTF-8?q?=20p=C3=BAblica=20perf:=20adicionar=20=C3=ADndices=20de=20banco?= =?UTF-8?q?=20de=20dados=20para=20otimiza=C3=A7=C3=A3o=20de=20queries=20-?= =?UTF-8?q?=20Cria=2014=20=C3=ADndices=20compostos=20em=20tabelas=20princi?= =?UTF-8?q?pais=20(lancamentos,=20contas,=20etc)=20-=20Adiciona=20=C3=ADnd?= =?UTF-8?q?ice=20user=5Fid=20+=20period=20em=20lancamentos,=20faturas=20e?= =?UTF-8?q?=20or=C3=A7amentos=20-=20Adiciona=20=C3=ADndices=20para=20s?= =?UTF-8?q?=C3=A9ries=20de=20parcelas=20e=20transfer=C3=AAncias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../changelog/changelog-notification.tsx | 141 ++ components/feedback/feedback-dialog.tsx | 154 ++ components/header-dashboard.tsx | 22 +- .../notifications/notification-bell.tsx | 4 +- components/ui/item.tsx | 193 ++ components/ui/scroll-area.tsx | 58 + components/ui/separator.tsx | 2 +- db/schema.ts | 426 ++-- drizzle/0001_young_mister_fear.sql | 14 + drizzle/0002_slimy_flatman.sql | 9 + drizzle/meta/0001_snapshot.json | 1869 ++++++++++++++++ drizzle/meta/0002_snapshot.json | 1944 +++++++++++++++++ drizzle/meta/_journal.json | 14 + lib/changelog/actions.ts | 76 + lib/changelog/data.ts | 75 + lib/changelog/utils.ts | 29 + lib/utils.ts | 2 + package.json | 2 + pnpm-lock.yaml | 33 + public/changelog.json | 166 ++ scripts/generate-changelog.ts | 178 ++ 21 files changed, 5250 insertions(+), 161 deletions(-) create mode 100644 components/changelog/changelog-notification.tsx create mode 100644 components/feedback/feedback-dialog.tsx create mode 100644 components/ui/item.tsx create mode 100644 components/ui/scroll-area.tsx create mode 100644 drizzle/0001_young_mister_fear.sql create mode 100644 drizzle/0002_slimy_flatman.sql create mode 100644 drizzle/meta/0001_snapshot.json create mode 100644 drizzle/meta/0002_snapshot.json create mode 100644 lib/changelog/actions.ts create mode 100644 lib/changelog/data.ts create mode 100644 lib/changelog/utils.ts create mode 100644 lib/utils.ts create mode 100644 public/changelog.json create mode 100644 scripts/generate-changelog.ts diff --git a/components/changelog/changelog-notification.tsx b/components/changelog/changelog-notification.tsx new file mode 100644 index 0000000..3097195 --- /dev/null +++ b/components/changelog/changelog-notification.tsx @@ -0,0 +1,141 @@ +"use client"; + +import { Badge } from "@/components/ui/badge"; +import { Button, buttonVariants } from "@/components/ui/button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Separator } from "@/components/ui/separator"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { markAllUpdatesAsRead } from "@/lib/changelog/actions"; +import type { ChangelogEntry } from "@/lib/changelog/data"; +import { + getCategoryLabel, + groupEntriesByCategory, +} from "@/lib/changelog/utils"; +import { cn } from "@/lib/utils"; +import { RiMegaphoneLine } from "@remixicon/react"; +import { formatDistanceToNow } from "date-fns"; +import { ptBR } from "date-fns/locale"; +import { useState } from "react"; + +interface ChangelogNotificationProps { + unreadCount: number; + entries: ChangelogEntry[]; +} + +export function ChangelogNotification({ + unreadCount: initialUnreadCount, + entries, +}: ChangelogNotificationProps) { + const [unreadCount, setUnreadCount] = useState(initialUnreadCount); + const [isOpen, setIsOpen] = useState(false); + + const handleMarkAllAsRead = async () => { + const updateIds = entries.map((e) => e.id); + await markAllUpdatesAsRead(updateIds); + setUnreadCount(0); + }; + + const grouped = groupEntriesByCategory(entries); + + return ( + + + + + + + + Novidades + + +
+
+ +

Novidades

+
+ {unreadCount > 0 && ( + + )} +
+ + + + +
+ {Object.entries(grouped).map(([category, categoryEntries]) => ( +
+

+ {getCategoryLabel(category)} +

+
+ {categoryEntries.map((entry) => ( +
+
+ {entry.icon} +
+ + #{entry.id.substring(0, 7)} + +

+ {entry.title} +

+

+ {formatDistanceToNow(new Date(entry.date), { + addSuffix: true, + locale: ptBR, + })} +

+
+
+
+ ))} +
+
+ ))} + + {entries.length === 0 && ( +
+ Nenhuma atualização recente +
+ )} +
+
+
+
+ ); +} diff --git a/components/feedback/feedback-dialog.tsx b/components/feedback/feedback-dialog.tsx new file mode 100644 index 0000000..6b803d2 --- /dev/null +++ b/components/feedback/feedback-dialog.tsx @@ -0,0 +1,154 @@ +"use client"; + +import { useState } from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Button, buttonVariants } from "@/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; +import { + RiMessageLine, + RiBugLine, + RiLightbulbLine, + RiQuestionLine, + RiStarLine, + RiExternalLinkLine, +} from "@remixicon/react"; + +const GITHUB_REPO_BASE = "https://github.com/felipegcoutinho/opensheets-app"; +const GITHUB_DISCUSSIONS_BASE = `${GITHUB_REPO_BASE}/discussions/new`; +const GITHUB_ISSUES_URL = `${GITHUB_REPO_BASE}/issues/new`; + +const feedbackCategories = [ + { + id: "bug", + title: "Reportar Bug", + icon: RiBugLine, + description: "Encontrou algo que não está funcionando?", + color: "text-red-500 dark:text-red-400", + url: GITHUB_ISSUES_URL, + }, + { + id: "idea", + title: "Sugerir Feature", + icon: RiLightbulbLine, + description: "Tem uma ideia para melhorar o app?", + color: "text-yellow-500 dark:text-yellow-400", + url: `${GITHUB_DISCUSSIONS_BASE}?category=ideias`, + }, + { + id: "question", + title: "Dúvidas/Suporte", + icon: RiQuestionLine, + description: "Precisa de ajuda com alguma coisa?", + color: "text-blue-500 dark:text-blue-400", + url: `${GITHUB_DISCUSSIONS_BASE}?category=q-a`, + }, + { + id: "experience", + title: "Compartilhar Experiência", + icon: RiStarLine, + description: "Como o OpenSheets tem ajudado você?", + color: "text-purple-500 dark:text-purple-400", + url: `${GITHUB_DISCUSSIONS_BASE}?category=sua-experiencia`, + }, +]; + +export function FeedbackDialog() { + const [open, setOpen] = useState(false); + + const handleCategoryClick = (url: string) => { + window.open(url, "_blank", "noopener,noreferrer"); + setOpen(false); + }; + + return ( + + + + + + + + Enviar Feedback + + + + + + + Enviar Feedback + + + Sua opinião é muito importante! Escolha o tipo de feedback que + deseja compartilhar. + + + +
+ {feedbackCategories.map((item) => { + const Icon = item.icon; + return ( + + ); + })} +
+ +
+ +

+ Você será redirecionado para o GitHub Discussions onde poderá + escrever seu feedback. É necessário ter uma conta no GitHub. +

+
+
+
+ ); +} diff --git a/components/header-dashboard.tsx b/components/header-dashboard.tsx index 72c414e..a4ed90c 100644 --- a/components/header-dashboard.tsx +++ b/components/header-dashboard.tsx @@ -1,29 +1,41 @@ +import { ChangelogNotification } from "@/components/changelog/changelog-notification"; +import { FeedbackDialog } from "@/components/feedback/feedback-dialog"; import { NotificationBell } from "@/components/notifications/notification-bell"; import { SidebarTrigger } from "@/components/ui/sidebar"; +import { getUser } from "@/lib/auth/server"; +import { getUnreadUpdates } from "@/lib/changelog/data"; import type { DashboardNotificationsSnapshot } from "@/lib/dashboard/notifications"; -import LogoutButton from "./auth/logout-button"; import { AnimatedThemeToggler } from "./animated-theme-toggler"; -import { PrivacyModeToggle } from "./privacy-mode-toggle"; +import LogoutButton from "./auth/logout-button"; import { CalculatorDialogButton } from "./calculadora/calculator-dialog"; +import { PrivacyModeToggle } from "./privacy-mode-toggle"; type SiteHeaderProps = { notificationsSnapshot: DashboardNotificationsSnapshot; }; -export function SiteHeader({ notificationsSnapshot }: SiteHeaderProps) { +export async function SiteHeader({ notificationsSnapshot }: SiteHeaderProps) { + const user = await getUser(); + const { unreadCount, allEntries } = await getUnreadUpdates(user.id); + return (
- - | + + | + +
diff --git a/components/notifications/notification-bell.tsx b/components/notifications/notification-bell.tsx index a7be2dc..a0745a2 100644 --- a/components/notifications/notification-bell.tsx +++ b/components/notifications/notification-bell.tsx @@ -103,7 +103,7 @@ export function NotificationBell({ - Notificações + Pagamentos para os próximos 5 dias. - Notificações + Notificações | Próximos 5 dias. {hasNotifications && ( {totalCount} {totalCount === 1 ? "item" : "itens"} diff --git a/components/ui/item.tsx b/components/ui/item.tsx new file mode 100644 index 0000000..d97de21 --- /dev/null +++ b/components/ui/item.tsx @@ -0,0 +1,193 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" +import { Separator } from "@/components/ui/separator" + +function ItemGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function ItemSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +const itemVariants = cva( + "group/item flex items-center border border-transparent text-sm rounded-md transition-colors [a]:hover:bg-accent/50 [a]:transition-colors duration-100 flex-wrap outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", + { + variants: { + variant: { + default: "bg-transparent", + outline: "border-border", + muted: "bg-muted/50", + }, + size: { + default: "p-4 gap-4 ", + sm: "py-3 px-4 gap-2.5", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Item({ + className, + variant = "default", + size = "default", + asChild = false, + ...props +}: React.ComponentProps<"div"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : "div" + return ( + + ) +} + +const itemMediaVariants = cva( + "flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none group-has-[[data-slot=item-description]]/item:translate-y-0.5", + { + variants: { + variant: { + default: "bg-transparent", + icon: "size-8 border rounded-sm bg-muted [&_svg:not([class*='size-'])]:size-4", + image: + "size-10 rounded-sm overflow-hidden [&_img]:size-full [&_img]:object-cover", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function ItemMedia({ + className, + variant = "default", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function ItemContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function ItemTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function ItemDescription({ className, ...props }: React.ComponentProps<"p">) { + return ( +

a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4", + className + )} + {...props} + /> + ) +} + +function ItemActions({ className, ...props }: React.ComponentProps<"div">) { + return ( +

+ ) +} + +function ItemHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function ItemFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Item, + ItemMedia, + ItemContent, + ItemActions, + ItemGroup, + ItemSeparator, + ItemTitle, + ItemDescription, + ItemHeader, + ItemFooter, +} diff --git a/components/ui/scroll-area.tsx b/components/ui/scroll-area.tsx new file mode 100644 index 0000000..8e4fa13 --- /dev/null +++ b/components/ui/scroll-area.tsx @@ -0,0 +1,58 @@ +"use client" + +import * as React from "react" +import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" + +import { cn } from "@/lib/utils" + +function ScrollArea({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + + ) +} + +function ScrollBar({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { ScrollArea, ScrollBar } diff --git a/components/ui/separator.tsx b/components/ui/separator.tsx index 94189d9..275381c 100644 --- a/components/ui/separator.tsx +++ b/components/ui/separator.tsx @@ -3,7 +3,7 @@ import * as React from "react" import * as SeparatorPrimitive from "@radix-ui/react-separator" -import { cn } from "@/lib/utils/ui" +import { cn } from "@/lib/utils" function Separator({ className, diff --git a/db/schema.ts b/db/schema.ts index 77f4b02..1c52cc0 100644 --- a/db/schema.ts +++ b/db/schema.ts @@ -102,49 +102,67 @@ export const verification = pgTable("verification", { // ===================== PUBLIC TABLES ===================== -export const contas = pgTable("contas", { - id: uuid("id") - .primaryKey() - .default(sql`gen_random_uuid()`), - name: text("nome").notNull(), - accountType: text("tipo_conta").notNull(), - note: text("anotacao"), - status: text("status").notNull(), - logo: text("logo").notNull(), - initialBalance: numeric("saldo_inicial", { precision: 12, scale: 2 }) - .notNull() - .default("0"), - excludeFromBalance: boolean("excluir_do_saldo") - .notNull() - .default(false), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - createdAt: timestamp("created_at", { - mode: "date", - withTimezone: true, +export const contas = pgTable( + "contas", + { + id: uuid("id") + .primaryKey() + .default(sql`gen_random_uuid()`), + name: text("nome").notNull(), + accountType: text("tipo_conta").notNull(), + note: text("anotacao"), + status: text("status").notNull(), + logo: text("logo").notNull(), + initialBalance: numeric("saldo_inicial", { precision: 12, scale: 2 }) + .notNull() + .default("0"), + excludeFromBalance: boolean("excluir_do_saldo") + .notNull() + .default(false), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + createdAt: timestamp("created_at", { + mode: "date", + withTimezone: true, + }) + .notNull() + .defaultNow(), + }, + (table) => ({ + userIdStatusIdx: index("contas_user_id_status_idx").on( + table.userId, + table.status + ), }) - .notNull() - .defaultNow(), -}); +); -export const categorias = pgTable("categorias", { - id: uuid("id") - .primaryKey() - .default(sql`gen_random_uuid()`), - name: text("nome").notNull(), - type: text("tipo").notNull(), - icon: text("icone"), - createdAt: timestamp("created_at", { - mode: "date", - withTimezone: true, +export const categorias = pgTable( + "categorias", + { + id: uuid("id") + .primaryKey() + .default(sql`gen_random_uuid()`), + name: text("nome").notNull(), + type: text("tipo").notNull(), + icon: text("icone"), + createdAt: timestamp("created_at", { + mode: "date", + withTimezone: true, + }) + .notNull() + .defaultNow(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + }, + (table) => ({ + userIdTypeIdx: index("categorias_user_id_type_idx").on( + table.userId, + table.type + ), }) - .notNull() - .defaultNow(), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), -}); +); export const pagadores = pgTable( "pagadores", @@ -180,6 +198,14 @@ export const pagadores = pgTable( uniqueShareCode: uniqueIndex("pagadores_share_code_key").on( table.shareCode ), + userIdStatusIdx: index("pagadores_user_id_status_idx").on( + table.userId, + table.status + ), + userIdRoleIdx: index("pagadores_user_id_role_idx").on( + table.userId, + table.role + ), }) ); @@ -214,73 +240,104 @@ export const pagadorShares = pgTable( }) ); -export const cartoes = pgTable("cartoes", { - id: uuid("id") - .primaryKey() - .default(sql`gen_random_uuid()`), - name: text("nome").notNull(), - closingDay: text("dt_fechamento").notNull(), - dueDay: text("dt_vencimento").notNull(), - note: text("anotacao"), - limit: numeric("limite", { precision: 10, scale: 2 }), - brand: text("bandeira"), - logo: text("logo"), - status: text("status").notNull(), - createdAt: timestamp("created_at", { - mode: "date", - withTimezone: true, +export const cartoes = pgTable( + "cartoes", + { + id: uuid("id") + .primaryKey() + .default(sql`gen_random_uuid()`), + name: text("nome").notNull(), + closingDay: text("dt_fechamento").notNull(), + dueDay: text("dt_vencimento").notNull(), + note: text("anotacao"), + limit: numeric("limite", { precision: 10, scale: 2 }), + brand: text("bandeira"), + logo: text("logo"), + status: text("status").notNull(), + createdAt: timestamp("created_at", { + mode: "date", + withTimezone: true, + }) + .notNull() + .defaultNow(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + contaId: uuid("conta_id") + .notNull() + .references(() => contas.id, { onDelete: "cascade", onUpdate: "cascade" }), + }, + (table) => ({ + userIdStatusIdx: index("cartoes_user_id_status_idx").on( + table.userId, + table.status + ), }) - .notNull() - .defaultNow(), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - contaId: uuid("conta_id") - .notNull() - .references(() => contas.id, { onDelete: "cascade", onUpdate: "cascade" }), -}); +); -export const faturas = pgTable("faturas", { - id: uuid("id") - .primaryKey() - .default(sql`gen_random_uuid()`), - paymentStatus: text("status_pagamento"), - period: text("periodo"), - createdAt: timestamp("created_at", { - mode: "date", - withTimezone: true, +export const faturas = pgTable( + "faturas", + { + id: uuid("id") + .primaryKey() + .default(sql`gen_random_uuid()`), + paymentStatus: text("status_pagamento"), + period: text("periodo"), + createdAt: timestamp("created_at", { + mode: "date", + withTimezone: true, + }) + .notNull() + .defaultNow(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + cartaoId: uuid("cartao_id").references(() => cartoes.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + }, + (table) => ({ + userIdPeriodIdx: index("faturas_user_id_period_idx").on( + table.userId, + table.period + ), + cartaoIdPeriodIdx: index("faturas_cartao_id_period_idx").on( + table.cartaoId, + table.period + ), }) - .notNull() - .defaultNow(), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - cartaoId: uuid("cartao_id").references(() => cartoes.id, { - onDelete: "cascade", - onUpdate: "cascade", - }), -}); +); -export const orcamentos = pgTable("orcamentos", { - id: uuid("id") - .primaryKey() - .default(sql`gen_random_uuid()`), - amount: numeric("valor", { precision: 10, scale: 2 }).notNull(), - period: text("periodo").notNull(), - createdAt: timestamp("created_at", { - mode: "date", - withTimezone: true, +export const orcamentos = pgTable( + "orcamentos", + { + id: uuid("id") + .primaryKey() + .default(sql`gen_random_uuid()`), + amount: numeric("valor", { precision: 10, scale: 2 }).notNull(), + period: text("periodo").notNull(), + createdAt: timestamp("created_at", { + mode: "date", + withTimezone: true, + }) + .notNull() + .defaultNow(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + categoriaId: uuid("categoria_id").references(() => categorias.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + }, + (table) => ({ + userIdPeriodIdx: index("orcamentos_user_id_period_idx").on( + table.userId, + table.period + ), }) - .notNull() - .defaultNow(), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - categoriaId: uuid("categoria_id").references(() => categorias.id, { - onDelete: "cascade", - onUpdate: "cascade", - }), -}); +); export const anotacoes = pgTable("anotacoes", { id: uuid("id") @@ -301,6 +358,31 @@ export const anotacoes = pgTable("anotacoes", { .references(() => user.id, { onDelete: "cascade" }), }); +export const userUpdateLog = pgTable( + "user_update_log", + { + id: uuid("id") + .primaryKey() + .default(sql`gen_random_uuid()`), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + updateId: text("update_id").notNull(), // commit hash + readAt: timestamp("read_at", { + mode: "date", + withTimezone: true, + }) + .notNull() + .defaultNow(), + }, + (table) => ({ + userIdUpdateIdIdx: uniqueIndex("user_update_log_user_update_idx").on( + table.userId, + table.updateId + ), + }) +); + export const savedInsights = pgTable( "saved_insights", { @@ -379,58 +461,87 @@ export const installmentAnticipations = pgTable( }) ); -export const lancamentos = pgTable("lancamentos", { - id: uuid("id") - .primaryKey() - .default(sql`gen_random_uuid()`), - condition: text("condicao").notNull(), - name: text("nome").notNull(), - paymentMethod: text("forma_pagamento").notNull(), - note: text("anotacao"), - amount: numeric("valor", { precision: 12, scale: 2 }).notNull(), - purchaseDate: date("data_compra", { mode: "date" }).notNull(), - transactionType: text("tipo_transacao").notNull(), - installmentCount: smallint("qtde_parcela"), - period: text("periodo").notNull(), - currentInstallment: smallint("parcela_atual"), - recurrenceCount: integer("qtde_recorrencia"), - dueDate: date("data_vencimento", { mode: "date" }), - boletoPaymentDate: date("dt_pagamento_boleto", { mode: "date" }), - isSettled: boolean("realizado").default(false), - isDivided: boolean("dividido").default(false), - isAnticipated: boolean("antecipado").default(false), - anticipationId: uuid("antecipacao_id").references( - () => installmentAnticipations.id, - { onDelete: "set null" } - ), - createdAt: timestamp("created_at", { - mode: "date", - withTimezone: true, +export const lancamentos = pgTable( + "lancamentos", + { + id: uuid("id") + .primaryKey() + .default(sql`gen_random_uuid()`), + condition: text("condicao").notNull(), + name: text("nome").notNull(), + paymentMethod: text("forma_pagamento").notNull(), + note: text("anotacao"), + amount: numeric("valor", { precision: 12, scale: 2 }).notNull(), + purchaseDate: date("data_compra", { mode: "date" }).notNull(), + transactionType: text("tipo_transacao").notNull(), + installmentCount: smallint("qtde_parcela"), + period: text("periodo").notNull(), + currentInstallment: smallint("parcela_atual"), + recurrenceCount: integer("qtde_recorrencia"), + dueDate: date("data_vencimento", { mode: "date" }), + boletoPaymentDate: date("dt_pagamento_boleto", { mode: "date" }), + isSettled: boolean("realizado").default(false), + isDivided: boolean("dividido").default(false), + isAnticipated: boolean("antecipado").default(false), + anticipationId: uuid("antecipacao_id").references( + () => installmentAnticipations.id, + { onDelete: "set null" } + ), + createdAt: timestamp("created_at", { + mode: "date", + withTimezone: true, + }) + .notNull() + .defaultNow(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + cartaoId: uuid("cartao_id").references(() => cartoes.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + contaId: uuid("conta_id").references(() => contas.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + categoriaId: uuid("categoria_id").references(() => categorias.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + pagadorId: uuid("pagador_id").references(() => pagadores.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + seriesId: uuid("series_id"), + transferId: uuid("transfer_id"), + }, + (table) => ({ + // Índice composto mais importante: userId + period (usado em quase todas as queries do dashboard) + userIdPeriodIdx: index("lancamentos_user_id_period_idx").on( + table.userId, + table.period + ), + // Índice para queries ordenadas por data de compra + userIdPurchaseDateIdx: index("lancamentos_user_id_purchase_date_idx").on( + table.userId, + table.purchaseDate + ), + // Índice para buscar parcelas de uma série + seriesIdIdx: index("lancamentos_series_id_idx").on(table.seriesId), + // Índice para buscar transferências relacionadas + transferIdIdx: index("lancamentos_transfer_id_idx").on(table.transferId), + // Índice para filtrar por condição (aberto, realizado, cancelado) + userIdConditionIdx: index("lancamentos_user_id_condition_idx").on( + table.userId, + table.condition + ), + // Índice para queries de cartão específico + cartaoIdPeriodIdx: index("lancamentos_cartao_id_period_idx").on( + table.cartaoId, + table.period + ), }) - .notNull() - .defaultNow(), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - cartaoId: uuid("cartao_id").references(() => cartoes.id, { - onDelete: "cascade", - onUpdate: "cascade", - }), - contaId: uuid("conta_id").references(() => contas.id, { - onDelete: "cascade", - onUpdate: "cascade", - }), - categoriaId: uuid("categoria_id").references(() => categorias.id, { - onDelete: "cascade", - onUpdate: "cascade", - }), - pagadorId: uuid("pagador_id").references(() => pagadores.id, { - onDelete: "cascade", - onUpdate: "cascade", - }), - seriesId: uuid("series_id"), - transferId: uuid("transfer_id"), -}); +); export const userRelations = relations(user, ({ many, one }) => ({ accounts: many(account), @@ -444,6 +555,7 @@ export const userRelations = relations(user, ({ many, one }) => ({ orcamentos: many(orcamentos), pagadores: many(pagadores), installmentAnticipations: many(installmentAnticipations), + updateLogs: many(userUpdateLog), })); export const accountRelations = relations(account, ({ one }) => ({ @@ -551,6 +663,13 @@ export const savedInsightsRelations = relations(savedInsights, ({ one }) => ({ }), })); +export const userUpdateLogRelations = relations(userUpdateLog, ({ one }) => ({ + user: one(user, { + fields: [userUpdateLog.userId], + references: [user.id], + }), +})); + export const lancamentosRelations = relations(lancamentos, ({ one }) => ({ user: one(user, { fields: [lancamentos.userId], @@ -616,3 +735,4 @@ export type SavedInsight = typeof savedInsights.$inferSelect; export type Lancamento = typeof lancamentos.$inferSelect; export type InstallmentAnticipation = typeof installmentAnticipations.$inferSelect; +export type UserUpdateLog = typeof userUpdateLog.$inferSelect; diff --git a/drizzle/0001_young_mister_fear.sql b/drizzle/0001_young_mister_fear.sql new file mode 100644 index 0000000..9ee048e --- /dev/null +++ b/drizzle/0001_young_mister_fear.sql @@ -0,0 +1,14 @@ +CREATE INDEX "cartoes_user_id_status_idx" ON "cartoes" USING btree ("user_id","status");--> statement-breakpoint +CREATE INDEX "categorias_user_id_type_idx" ON "categorias" USING btree ("user_id","tipo");--> statement-breakpoint +CREATE INDEX "contas_user_id_status_idx" ON "contas" USING btree ("user_id","status");--> statement-breakpoint +CREATE INDEX "faturas_user_id_period_idx" ON "faturas" USING btree ("user_id","periodo");--> statement-breakpoint +CREATE INDEX "faturas_cartao_id_period_idx" ON "faturas" USING btree ("cartao_id","periodo");--> statement-breakpoint +CREATE INDEX "lancamentos_user_id_period_idx" ON "lancamentos" USING btree ("user_id","periodo");--> statement-breakpoint +CREATE INDEX "lancamentos_user_id_purchase_date_idx" ON "lancamentos" USING btree ("user_id","data_compra");--> statement-breakpoint +CREATE INDEX "lancamentos_series_id_idx" ON "lancamentos" USING btree ("series_id");--> statement-breakpoint +CREATE INDEX "lancamentos_transfer_id_idx" ON "lancamentos" USING btree ("transfer_id");--> statement-breakpoint +CREATE INDEX "lancamentos_user_id_condition_idx" ON "lancamentos" USING btree ("user_id","condicao");--> statement-breakpoint +CREATE INDEX "lancamentos_cartao_id_period_idx" ON "lancamentos" USING btree ("cartao_id","periodo");--> statement-breakpoint +CREATE INDEX "orcamentos_user_id_period_idx" ON "orcamentos" USING btree ("user_id","periodo");--> statement-breakpoint +CREATE INDEX "pagadores_user_id_status_idx" ON "pagadores" USING btree ("user_id","status");--> statement-breakpoint +CREATE INDEX "pagadores_user_id_role_idx" ON "pagadores" USING btree ("user_id","role"); \ No newline at end of file diff --git a/drizzle/0002_slimy_flatman.sql b/drizzle/0002_slimy_flatman.sql new file mode 100644 index 0000000..4ce0cc0 --- /dev/null +++ b/drizzle/0002_slimy_flatman.sql @@ -0,0 +1,9 @@ +CREATE TABLE "user_update_log" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" text NOT NULL, + "update_id" text NOT NULL, + "read_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "user_update_log" ADD CONSTRAINT "user_update_log_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "user_update_log_user_update_idx" ON "user_update_log" USING btree ("user_id","update_id"); \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..e4fed9e --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,1869 @@ +{ + "id": "e055789e-96d3-4108-8b8c-7c4a1491677c", + "prevId": "8255a4f5-92ef-42dc-bd7f-959b6b2dae0f", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "accountId": { + "name": "accountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idToken": { + "name": "idToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessTokenExpiresAt": { + "name": "accessTokenExpiresAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refreshTokenExpiresAt": { + "name": "refreshTokenExpiresAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.anotacoes": { + "name": "anotacoes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "descricao": { + "name": "descricao", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tipo": { + "name": "tipo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'nota'" + }, + "tasks": { + "name": "tasks", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "anotacoes_user_id_user_id_fk": { + "name": "anotacoes_user_id_user_id_fk", + "tableFrom": "anotacoes", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cartoes": { + "name": "cartoes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "nome": { + "name": "nome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dt_fechamento": { + "name": "dt_fechamento", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dt_vencimento": { + "name": "dt_vencimento", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anotacao": { + "name": "anotacao", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "limite": { + "name": "limite", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "bandeira": { + "name": "bandeira", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conta_id": { + "name": "conta_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "cartoes_user_id_status_idx": { + "name": "cartoes_user_id_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cartoes_user_id_user_id_fk": { + "name": "cartoes_user_id_user_id_fk", + "tableFrom": "cartoes", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cartoes_conta_id_contas_id_fk": { + "name": "cartoes_conta_id_contas_id_fk", + "tableFrom": "cartoes", + "tableTo": "contas", + "columnsFrom": [ + "conta_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.categorias": { + "name": "categorias", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "nome": { + "name": "nome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tipo": { + "name": "tipo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "icone": { + "name": "icone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "categorias_user_id_type_idx": { + "name": "categorias_user_id_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tipo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "categorias_user_id_user_id_fk": { + "name": "categorias_user_id_user_id_fk", + "tableFrom": "categorias", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contas": { + "name": "contas", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "nome": { + "name": "nome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tipo_conta": { + "name": "tipo_conta", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anotacao": { + "name": "anotacao", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "saldo_inicial": { + "name": "saldo_inicial", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "excluir_do_saldo": { + "name": "excluir_do_saldo", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "contas_user_id_status_idx": { + "name": "contas_user_id_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "contas_user_id_user_id_fk": { + "name": "contas_user_id_user_id_fk", + "tableFrom": "contas", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.faturas": { + "name": "faturas", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "status_pagamento": { + "name": "status_pagamento", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "periodo": { + "name": "periodo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cartao_id": { + "name": "cartao_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "faturas_user_id_period_idx": { + "name": "faturas_user_id_period_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "periodo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "faturas_cartao_id_period_idx": { + "name": "faturas_cartao_id_period_idx", + "columns": [ + { + "expression": "cartao_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "periodo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "faturas_user_id_user_id_fk": { + "name": "faturas_user_id_user_id_fk", + "tableFrom": "faturas", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "faturas_cartao_id_cartoes_id_fk": { + "name": "faturas_cartao_id_cartoes_id_fk", + "tableFrom": "faturas", + "tableTo": "cartoes", + "columnsFrom": [ + "cartao_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.installment_anticipations": { + "name": "installment_anticipations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "series_id": { + "name": "series_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "periodo_antecipacao": { + "name": "periodo_antecipacao", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data_antecipacao": { + "name": "data_antecipacao", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "parcelas_antecipadas": { + "name": "parcelas_antecipadas", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "valor_total": { + "name": "valor_total", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": true + }, + "qtde_parcelas": { + "name": "qtde_parcelas", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "desconto": { + "name": "desconto", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "lancamento_id": { + "name": "lancamento_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pagador_id": { + "name": "pagador_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "categoria_id": { + "name": "categoria_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "anotacao": { + "name": "anotacao", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "installment_anticipations_series_id_idx": { + "name": "installment_anticipations_series_id_idx", + "columns": [ + { + "expression": "series_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "installment_anticipations_user_id_idx": { + "name": "installment_anticipations_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "installment_anticipations_lancamento_id_lancamentos_id_fk": { + "name": "installment_anticipations_lancamento_id_lancamentos_id_fk", + "tableFrom": "installment_anticipations", + "tableTo": "lancamentos", + "columnsFrom": [ + "lancamento_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "installment_anticipations_pagador_id_pagadores_id_fk": { + "name": "installment_anticipations_pagador_id_pagadores_id_fk", + "tableFrom": "installment_anticipations", + "tableTo": "pagadores", + "columnsFrom": [ + "pagador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "installment_anticipations_categoria_id_categorias_id_fk": { + "name": "installment_anticipations_categoria_id_categorias_id_fk", + "tableFrom": "installment_anticipations", + "tableTo": "categorias", + "columnsFrom": [ + "categoria_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "installment_anticipations_user_id_user_id_fk": { + "name": "installment_anticipations_user_id_user_id_fk", + "tableFrom": "installment_anticipations", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lancamentos": { + "name": "lancamentos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "condicao": { + "name": "condicao", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nome": { + "name": "nome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "forma_pagamento": { + "name": "forma_pagamento", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anotacao": { + "name": "anotacao", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "valor": { + "name": "valor", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": true + }, + "data_compra": { + "name": "data_compra", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "tipo_transacao": { + "name": "tipo_transacao", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "qtde_parcela": { + "name": "qtde_parcela", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "periodo": { + "name": "periodo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parcela_atual": { + "name": "parcela_atual", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "qtde_recorrencia": { + "name": "qtde_recorrencia", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "data_vencimento": { + "name": "data_vencimento", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "dt_pagamento_boleto": { + "name": "dt_pagamento_boleto", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "realizado": { + "name": "realizado", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "dividido": { + "name": "dividido", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "antecipado": { + "name": "antecipado", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "antecipacao_id": { + "name": "antecipacao_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cartao_id": { + "name": "cartao_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conta_id": { + "name": "conta_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "categoria_id": { + "name": "categoria_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pagador_id": { + "name": "pagador_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "series_id": { + "name": "series_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "transfer_id": { + "name": "transfer_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "lancamentos_user_id_period_idx": { + "name": "lancamentos_user_id_period_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "periodo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "lancamentos_user_id_purchase_date_idx": { + "name": "lancamentos_user_id_purchase_date_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "data_compra", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "lancamentos_series_id_idx": { + "name": "lancamentos_series_id_idx", + "columns": [ + { + "expression": "series_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "lancamentos_transfer_id_idx": { + "name": "lancamentos_transfer_id_idx", + "columns": [ + { + "expression": "transfer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "lancamentos_user_id_condition_idx": { + "name": "lancamentos_user_id_condition_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "condicao", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "lancamentos_cartao_id_period_idx": { + "name": "lancamentos_cartao_id_period_idx", + "columns": [ + { + "expression": "cartao_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "periodo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "lancamentos_antecipacao_id_installment_anticipations_id_fk": { + "name": "lancamentos_antecipacao_id_installment_anticipations_id_fk", + "tableFrom": "lancamentos", + "tableTo": "installment_anticipations", + "columnsFrom": [ + "antecipacao_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "lancamentos_user_id_user_id_fk": { + "name": "lancamentos_user_id_user_id_fk", + "tableFrom": "lancamentos", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "lancamentos_cartao_id_cartoes_id_fk": { + "name": "lancamentos_cartao_id_cartoes_id_fk", + "tableFrom": "lancamentos", + "tableTo": "cartoes", + "columnsFrom": [ + "cartao_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "lancamentos_conta_id_contas_id_fk": { + "name": "lancamentos_conta_id_contas_id_fk", + "tableFrom": "lancamentos", + "tableTo": "contas", + "columnsFrom": [ + "conta_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "lancamentos_categoria_id_categorias_id_fk": { + "name": "lancamentos_categoria_id_categorias_id_fk", + "tableFrom": "lancamentos", + "tableTo": "categorias", + "columnsFrom": [ + "categoria_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "lancamentos_pagador_id_pagadores_id_fk": { + "name": "lancamentos_pagador_id_pagadores_id_fk", + "tableFrom": "lancamentos", + "tableTo": "pagadores", + "columnsFrom": [ + "pagador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.orcamentos": { + "name": "orcamentos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "valor": { + "name": "valor", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + }, + "periodo": { + "name": "periodo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "categoria_id": { + "name": "categoria_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "orcamentos_user_id_period_idx": { + "name": "orcamentos_user_id_period_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "periodo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "orcamentos_user_id_user_id_fk": { + "name": "orcamentos_user_id_user_id_fk", + "tableFrom": "orcamentos", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "orcamentos_categoria_id_categorias_id_fk": { + "name": "orcamentos_categoria_id_categorias_id_fk", + "tableFrom": "orcamentos", + "tableTo": "categorias", + "columnsFrom": [ + "categoria_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pagador_shares": { + "name": "pagador_shares", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pagador_id": { + "name": "pagador_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "shared_with_user_id": { + "name": "shared_with_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'read'" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pagador_shares_unique": { + "name": "pagador_shares_unique", + "columns": [ + { + "expression": "pagador_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_with_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pagador_shares_pagador_id_pagadores_id_fk": { + "name": "pagador_shares_pagador_id_pagadores_id_fk", + "tableFrom": "pagador_shares", + "tableTo": "pagadores", + "columnsFrom": [ + "pagador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pagador_shares_shared_with_user_id_user_id_fk": { + "name": "pagador_shares_shared_with_user_id_user_id_fk", + "tableFrom": "pagador_shares", + "tableTo": "user", + "columnsFrom": [ + "shared_with_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pagador_shares_created_by_user_id_user_id_fk": { + "name": "pagador_shares_created_by_user_id_user_id_fk", + "tableFrom": "pagador_shares", + "tableTo": "user", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pagadores": { + "name": "pagadores", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "nome": { + "name": "nome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anotacao": { + "name": "anotacao", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_auto_send": { + "name": "is_auto_send", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "share_code": { + "name": "share_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "substr(encode(gen_random_bytes(24), 'base64'), 1, 24)" + }, + "last_mail": { + "name": "last_mail", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "pagadores_share_code_key": { + "name": "pagadores_share_code_key", + "columns": [ + { + "expression": "share_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pagadores_user_id_status_idx": { + "name": "pagadores_user_id_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pagadores_user_id_role_idx": { + "name": "pagadores_user_id_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pagadores_user_id_user_id_fk": { + "name": "pagadores_user_id_user_id_fk", + "tableFrom": "pagadores", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.saved_insights": { + "name": "saved_insights", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period": { + "name": "period", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "saved_insights_user_period_idx": { + "name": "saved_insights_user_period_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "saved_insights_user_id_user_id_fk": { + "name": "saved_insights_user_id_user_id_fk", + "tableFrom": "saved_insights", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..3b0ab3a --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,1944 @@ +{ + "id": "fae7a1d0-2ecd-4c31-aa31-c37de46403da", + "prevId": "e055789e-96d3-4108-8b8c-7c4a1491677c", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "accountId": { + "name": "accountId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accessToken": { + "name": "accessToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refreshToken": { + "name": "refreshToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idToken": { + "name": "idToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accessTokenExpiresAt": { + "name": "accessTokenExpiresAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refreshTokenExpiresAt": { + "name": "refreshTokenExpiresAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.anotacoes": { + "name": "anotacoes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "titulo": { + "name": "titulo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "descricao": { + "name": "descricao", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tipo": { + "name": "tipo", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'nota'" + }, + "tasks": { + "name": "tasks", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "anotacoes_user_id_user_id_fk": { + "name": "anotacoes_user_id_user_id_fk", + "tableFrom": "anotacoes", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cartoes": { + "name": "cartoes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "nome": { + "name": "nome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dt_fechamento": { + "name": "dt_fechamento", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dt_vencimento": { + "name": "dt_vencimento", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anotacao": { + "name": "anotacao", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "limite": { + "name": "limite", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "bandeira": { + "name": "bandeira", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conta_id": { + "name": "conta_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "cartoes_user_id_status_idx": { + "name": "cartoes_user_id_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cartoes_user_id_user_id_fk": { + "name": "cartoes_user_id_user_id_fk", + "tableFrom": "cartoes", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cartoes_conta_id_contas_id_fk": { + "name": "cartoes_conta_id_contas_id_fk", + "tableFrom": "cartoes", + "tableTo": "contas", + "columnsFrom": [ + "conta_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.categorias": { + "name": "categorias", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "nome": { + "name": "nome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tipo": { + "name": "tipo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "icone": { + "name": "icone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "categorias_user_id_type_idx": { + "name": "categorias_user_id_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tipo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "categorias_user_id_user_id_fk": { + "name": "categorias_user_id_user_id_fk", + "tableFrom": "categorias", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contas": { + "name": "contas", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "nome": { + "name": "nome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tipo_conta": { + "name": "tipo_conta", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anotacao": { + "name": "anotacao", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "saldo_inicial": { + "name": "saldo_inicial", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "excluir_do_saldo": { + "name": "excluir_do_saldo", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "contas_user_id_status_idx": { + "name": "contas_user_id_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "contas_user_id_user_id_fk": { + "name": "contas_user_id_user_id_fk", + "tableFrom": "contas", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.faturas": { + "name": "faturas", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "status_pagamento": { + "name": "status_pagamento", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "periodo": { + "name": "periodo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cartao_id": { + "name": "cartao_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "faturas_user_id_period_idx": { + "name": "faturas_user_id_period_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "periodo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "faturas_cartao_id_period_idx": { + "name": "faturas_cartao_id_period_idx", + "columns": [ + { + "expression": "cartao_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "periodo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "faturas_user_id_user_id_fk": { + "name": "faturas_user_id_user_id_fk", + "tableFrom": "faturas", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "faturas_cartao_id_cartoes_id_fk": { + "name": "faturas_cartao_id_cartoes_id_fk", + "tableFrom": "faturas", + "tableTo": "cartoes", + "columnsFrom": [ + "cartao_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.installment_anticipations": { + "name": "installment_anticipations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "series_id": { + "name": "series_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "periodo_antecipacao": { + "name": "periodo_antecipacao", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data_antecipacao": { + "name": "data_antecipacao", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "parcelas_antecipadas": { + "name": "parcelas_antecipadas", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "valor_total": { + "name": "valor_total", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": true + }, + "qtde_parcelas": { + "name": "qtde_parcelas", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "desconto": { + "name": "desconto", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "lancamento_id": { + "name": "lancamento_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pagador_id": { + "name": "pagador_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "categoria_id": { + "name": "categoria_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "anotacao": { + "name": "anotacao", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "installment_anticipations_series_id_idx": { + "name": "installment_anticipations_series_id_idx", + "columns": [ + { + "expression": "series_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "installment_anticipations_user_id_idx": { + "name": "installment_anticipations_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "installment_anticipations_lancamento_id_lancamentos_id_fk": { + "name": "installment_anticipations_lancamento_id_lancamentos_id_fk", + "tableFrom": "installment_anticipations", + "tableTo": "lancamentos", + "columnsFrom": [ + "lancamento_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "installment_anticipations_pagador_id_pagadores_id_fk": { + "name": "installment_anticipations_pagador_id_pagadores_id_fk", + "tableFrom": "installment_anticipations", + "tableTo": "pagadores", + "columnsFrom": [ + "pagador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "installment_anticipations_categoria_id_categorias_id_fk": { + "name": "installment_anticipations_categoria_id_categorias_id_fk", + "tableFrom": "installment_anticipations", + "tableTo": "categorias", + "columnsFrom": [ + "categoria_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "installment_anticipations_user_id_user_id_fk": { + "name": "installment_anticipations_user_id_user_id_fk", + "tableFrom": "installment_anticipations", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.lancamentos": { + "name": "lancamentos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "condicao": { + "name": "condicao", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "nome": { + "name": "nome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "forma_pagamento": { + "name": "forma_pagamento", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anotacao": { + "name": "anotacao", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "valor": { + "name": "valor", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": true + }, + "data_compra": { + "name": "data_compra", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "tipo_transacao": { + "name": "tipo_transacao", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "qtde_parcela": { + "name": "qtde_parcela", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "periodo": { + "name": "periodo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parcela_atual": { + "name": "parcela_atual", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "qtde_recorrencia": { + "name": "qtde_recorrencia", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "data_vencimento": { + "name": "data_vencimento", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "dt_pagamento_boleto": { + "name": "dt_pagamento_boleto", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "realizado": { + "name": "realizado", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "dividido": { + "name": "dividido", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "antecipado": { + "name": "antecipado", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "antecipacao_id": { + "name": "antecipacao_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cartao_id": { + "name": "cartao_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "conta_id": { + "name": "conta_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "categoria_id": { + "name": "categoria_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pagador_id": { + "name": "pagador_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "series_id": { + "name": "series_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "transfer_id": { + "name": "transfer_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "lancamentos_user_id_period_idx": { + "name": "lancamentos_user_id_period_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "periodo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "lancamentos_user_id_purchase_date_idx": { + "name": "lancamentos_user_id_purchase_date_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "data_compra", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "lancamentos_series_id_idx": { + "name": "lancamentos_series_id_idx", + "columns": [ + { + "expression": "series_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "lancamentos_transfer_id_idx": { + "name": "lancamentos_transfer_id_idx", + "columns": [ + { + "expression": "transfer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "lancamentos_user_id_condition_idx": { + "name": "lancamentos_user_id_condition_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "condicao", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "lancamentos_cartao_id_period_idx": { + "name": "lancamentos_cartao_id_period_idx", + "columns": [ + { + "expression": "cartao_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "periodo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "lancamentos_antecipacao_id_installment_anticipations_id_fk": { + "name": "lancamentos_antecipacao_id_installment_anticipations_id_fk", + "tableFrom": "lancamentos", + "tableTo": "installment_anticipations", + "columnsFrom": [ + "antecipacao_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "lancamentos_user_id_user_id_fk": { + "name": "lancamentos_user_id_user_id_fk", + "tableFrom": "lancamentos", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "lancamentos_cartao_id_cartoes_id_fk": { + "name": "lancamentos_cartao_id_cartoes_id_fk", + "tableFrom": "lancamentos", + "tableTo": "cartoes", + "columnsFrom": [ + "cartao_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "lancamentos_conta_id_contas_id_fk": { + "name": "lancamentos_conta_id_contas_id_fk", + "tableFrom": "lancamentos", + "tableTo": "contas", + "columnsFrom": [ + "conta_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "lancamentos_categoria_id_categorias_id_fk": { + "name": "lancamentos_categoria_id_categorias_id_fk", + "tableFrom": "lancamentos", + "tableTo": "categorias", + "columnsFrom": [ + "categoria_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "lancamentos_pagador_id_pagadores_id_fk": { + "name": "lancamentos_pagador_id_pagadores_id_fk", + "tableFrom": "lancamentos", + "tableTo": "pagadores", + "columnsFrom": [ + "pagador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.orcamentos": { + "name": "orcamentos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "valor": { + "name": "valor", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + }, + "periodo": { + "name": "periodo", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "categoria_id": { + "name": "categoria_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "orcamentos_user_id_period_idx": { + "name": "orcamentos_user_id_period_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "periodo", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "orcamentos_user_id_user_id_fk": { + "name": "orcamentos_user_id_user_id_fk", + "tableFrom": "orcamentos", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "orcamentos_categoria_id_categorias_id_fk": { + "name": "orcamentos_categoria_id_categorias_id_fk", + "tableFrom": "orcamentos", + "tableTo": "categorias", + "columnsFrom": [ + "categoria_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pagador_shares": { + "name": "pagador_shares", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pagador_id": { + "name": "pagador_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "shared_with_user_id": { + "name": "shared_with_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'read'" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pagador_shares_unique": { + "name": "pagador_shares_unique", + "columns": [ + { + "expression": "pagador_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_with_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pagador_shares_pagador_id_pagadores_id_fk": { + "name": "pagador_shares_pagador_id_pagadores_id_fk", + "tableFrom": "pagador_shares", + "tableTo": "pagadores", + "columnsFrom": [ + "pagador_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pagador_shares_shared_with_user_id_user_id_fk": { + "name": "pagador_shares_shared_with_user_id_user_id_fk", + "tableFrom": "pagador_shares", + "tableTo": "user", + "columnsFrom": [ + "shared_with_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pagador_shares_created_by_user_id_user_id_fk": { + "name": "pagador_shares_created_by_user_id_user_id_fk", + "tableFrom": "pagador_shares", + "tableTo": "user", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pagadores": { + "name": "pagadores", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "nome": { + "name": "nome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anotacao": { + "name": "anotacao", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_auto_send": { + "name": "is_auto_send", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "share_code": { + "name": "share_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "substr(encode(gen_random_bytes(24), 'base64'), 1, 24)" + }, + "last_mail": { + "name": "last_mail", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "pagadores_share_code_key": { + "name": "pagadores_share_code_key", + "columns": [ + { + "expression": "share_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pagadores_user_id_status_idx": { + "name": "pagadores_user_id_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pagadores_user_id_role_idx": { + "name": "pagadores_user_id_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pagadores_user_id_user_id_fk": { + "name": "pagadores_user_id_user_id_fk", + "tableFrom": "pagadores", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.saved_insights": { + "name": "saved_insights", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period": { + "name": "period", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "saved_insights_user_period_idx": { + "name": "saved_insights_user_period_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "saved_insights_user_id_user_id_fk": { + "name": "saved_insights_user_id_user_id_fk", + "tableFrom": "saved_insights", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ipAddress": { + "name": "ipAddress", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userAgent": { + "name": "userAgent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_update_log": { + "name": "user_update_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "update_id": { + "name": "update_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "read_at": { + "name": "read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_update_log_user_update_idx": { + "name": "user_update_log_user_update_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "update_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_update_log_user_id_user_id_fk": { + "name": "user_update_log_user_id_user_id_fk", + "tableFrom": "user_update_log", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expiresAt": { + "name": "expiresAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index f178c4a..abef811 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -8,6 +8,20 @@ "when": 1762993507299, "tag": "0000_flashy_manta", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1765199006435, + "tag": "0001_young_mister_fear", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1765200545692, + "tag": "0002_slimy_flatman", + "breakpoints": true } ] } \ No newline at end of file diff --git a/lib/changelog/actions.ts b/lib/changelog/actions.ts new file mode 100644 index 0000000..d728d3a --- /dev/null +++ b/lib/changelog/actions.ts @@ -0,0 +1,76 @@ +"use server"; + +import { userUpdateLog } from "@/db/schema"; +import { successResult, type ActionResult } from "@/lib/actions/types"; +import { getUser } from "@/lib/auth/server"; +import { db } from "@/lib/db"; +import { and, eq } from "drizzle-orm"; +import { handleActionError } from "../actions/helpers"; + +export async function markUpdateAsRead( + updateId: string +): Promise { + try { + const user = await getUser(); + + // Check if already marked as read + const existing = await db + .select() + .from(userUpdateLog) + .where( + and( + eq(userUpdateLog.userId, user.id), + eq(userUpdateLog.updateId, updateId) + ) + ) + .limit(1); + + if (existing.length > 0) { + return successResult("Já marcado como lido"); + } + + await db.insert(userUpdateLog).values({ + userId: user.id, + updateId, + }); + + return successResult("Marcado como lido"); + } catch (error) { + return handleActionError(error); + } +} + +export async function markAllUpdatesAsRead( + updateIds: string[] +): Promise { + try { + const user = await getUser(); + + // Get existing read updates + const existing = await db + .select() + .from(userUpdateLog) + .where(eq(userUpdateLog.userId, user.id)); + + const existingIds = new Set(existing.map((log) => log.updateId)); + + // Filter out already read updates + const newUpdateIds = updateIds.filter((id) => !existingIds.has(id)); + + if (newUpdateIds.length === 0) { + return successResult("Todos já marcados como lidos"); + } + + // Insert new read logs + await db.insert(userUpdateLog).values( + newUpdateIds.map((updateId) => ({ + userId: user.id, + updateId, + })) + ); + + return successResult("Todas as atualizações marcadas como lidas"); + } catch (error) { + return handleActionError(error); + } +} diff --git a/lib/changelog/data.ts b/lib/changelog/data.ts new file mode 100644 index 0000000..38fe6b6 --- /dev/null +++ b/lib/changelog/data.ts @@ -0,0 +1,75 @@ +import { db } from "@/lib/db"; +import { userUpdateLog } from "@/db/schema"; +import { eq } from "drizzle-orm"; +import fs from "fs"; +import path from "path"; + +export interface ChangelogEntry { + id: string; + type: string; + title: string; + date: string; + icon: string; + category: string; +} + +export interface Changelog { + version: string; + generatedAt: string; + entries: ChangelogEntry[]; +} + +export function getChangelog(): Changelog { + try { + const changelogPath = path.join(process.cwd(), "public", "changelog.json"); + + if (!fs.existsSync(changelogPath)) { + return { + version: "1.0.0", + generatedAt: new Date().toISOString(), + entries: [], + }; + } + + const content = fs.readFileSync(changelogPath, "utf-8"); + return JSON.parse(content); + } catch (error) { + console.error("Error reading changelog:", error); + return { + version: "1.0.0", + generatedAt: new Date().toISOString(), + entries: [], + }; + } +} + +export async function getUnreadUpdates(userId: string) { + const changelog = getChangelog(); + + if (changelog.entries.length === 0) { + return { + unreadCount: 0, + unreadEntries: [], + allEntries: [], + }; + } + + // Get read updates from database + const readLogs = await db + .select() + .from(userUpdateLog) + .where(eq(userUpdateLog.userId, userId)); + + const readUpdateIds = new Set(readLogs.map((log) => log.updateId)); + + // Filter unread entries + const unreadEntries = changelog.entries.filter( + (entry) => !readUpdateIds.has(entry.id) + ); + + return { + unreadCount: unreadEntries.length, + unreadEntries, + allEntries: changelog.entries, + }; +} diff --git a/lib/changelog/utils.ts b/lib/changelog/utils.ts new file mode 100644 index 0000000..97501a1 --- /dev/null +++ b/lib/changelog/utils.ts @@ -0,0 +1,29 @@ +import type { ChangelogEntry } from "./data"; + +export function getCategoryLabel(category: string): string { + const labels: Record = { + feature: "Novidades", + bugfix: "Correções", + performance: "Performance", + documentation: "Documentação", + style: "Interface", + refactor: "Melhorias", + test: "Testes", + chore: "Manutenção", + other: "Outros", + }; + return labels[category] || "Outros"; +} + +export function groupEntriesByCategory(entries: ChangelogEntry[]) { + return entries.reduce( + (acc, entry) => { + if (!acc[entry.category]) { + acc[entry.category] = []; + } + acc[entry.category].push(entry); + return acc; + }, + {} as Record + ); +} diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..b282626 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,2 @@ +// Re-export from lib/utils/ui.ts for shadcn compatibility +export { cn } from "./utils/ui"; diff --git a/package.json b/package.json index 917c8e7..b8bf7de 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "dev": "next dev --turbopack", "dev-env": "tsx scripts/dev.ts", + "prebuild": "tsx scripts/generate-changelog.ts", "build": "next build", "start": "next start", "lint": "eslint .", @@ -42,6 +43,7 @@ "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-progress": "1.1.8", "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "2.2.6", "@radix-ui/react-separator": "1.1.8", "@radix-ui/react-slot": "1.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 708bffa..82c7814 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: '@radix-ui/react-radio-group': specifier: ^1.3.8 version: 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-select': specifier: 2.2.6 version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -1457,6 +1460,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-select@2.2.6': resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} peerDependencies: @@ -5238,6 +5254,23 @@ snapshots: '@types/react': 19.2.7 '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@radix-ui/number': 1.1.1 diff --git a/public/changelog.json b/public/changelog.json new file mode 100644 index 0000000..4eb78bc --- /dev/null +++ b/public/changelog.json @@ -0,0 +1,166 @@ +{ + "version": "1.0.0", + "generatedAt": "2025-12-08T14:32:17.605Z", + "entries": [ + { + "id": "7a4a947e3fa4f78f174d1042906828045cbf6eaf", + "type": "fix", + "title": "atualizar dependências do projeto", + "date": "2025-12-07 18:50:00 +0000", + "icon": "🐛", + "category": "bugfix" + }, + { + "id": "244534921b9b10fbff79777a024da17a45722bce", + "type": "fix", + "title": "replace session cookie validation with actual session check in proxy middleware", + "date": "2025-12-07 09:50:55 -0300", + "icon": "🐛", + "category": "bugfix" + }, + { + "id": "de3d99a3b1a398ae01eec0f65f03309648cbe24d", + "type": "fix", + "title": "add error handling for internal server error in login form", + "date": "2025-12-06 07:35:25 -0300", + "icon": "🐛", + "category": "bugfix" + }, + { + "id": "9d03387079d9ff867d0309522d5cb8989075bc2f", + "type": "fix", + "title": "adjust padding and layout in various dashboard widgets for improved UI consistency", + "date": "2025-12-02 13:54:13 +0000", + "icon": "🐛", + "category": "bugfix" + }, + { + "id": "c834648d395e58a6fb62c620a0c5e2ee4d1b8a4f", + "type": "fix", + "title": "corrige condição de análise de gastos parcelados", + "date": "2025-12-01 00:16:50 +0000", + "icon": "🐛", + "category": "bugfix" + }, + { + "id": "47038ae687e5c6d611009171a5730f3c1477aa78", + "type": "fix", + "title": "corrige timezone e seleção de parcelas na análise de parcelas", + "date": "2025-11-29 18:26:28 +0000", + "icon": "🐛", + "category": "bugfix" + }, + { + "id": "cf5a0b7745bf2ade4970e7e15c29bdb643955878", + "type": "feat", + "title": "implement category history widget and loading state for category history page", + "date": "2025-11-28 13:42:21 +0000", + "icon": "✨", + "category": "feature" + }, + { + "id": "bf1a310c286e39664908ca989ffda0d3cea4ef3c", + "type": "feat", + "title": "add AI coding assistant instructions and update Node.js version requirement in README", + "date": "2025-11-28 01:30:09 -0300", + "icon": "✨", + "category": "feature" + }, + { + "id": "2d8d677bcc85d863b2aee58b0c9144a62588173a", + "type": "fix", + "title": "update dependencies to latest versions", + "date": "2025-11-25 14:17:58 +0000", + "icon": "🐛", + "category": "bugfix" + }, + { + "id": "a34d92f3bd7ceb96285bc32f1f2ff2eb79052170", + "type": "feat", + "title": "aprimora a exibição do cartão de parcelas e ajusta a lógica de busca", + "date": "2025-11-23 14:52:22 -0300", + "icon": "✨", + "category": "feature" + }, + { + "id": "e8a343a6dd1f2426d484afe2902b05cfc65ea32d", + "type": "feat", + "title": "adiciona integração com Speed Insights", + "date": "2025-11-23 12:32:38 -0300", + "icon": "✨", + "category": "feature" + }, + { + "id": "9fbe722d00aa0105fc3a37e0d19555e1aaf27928", + "type": "feat", + "title": "adicionar estrutura para gerenciamento de mudanças de código", + "date": "2025-11-23 12:26:05 -0300", + "icon": "✨", + "category": "feature" + }, + { + "id": "3ce8541a5699317c747c629e1c0e07d579458633", + "type": "fix", + "title": "corrige a grafia de \"OpenSheets\" para \"Opensheets\"", + "date": "2025-11-22 20:29:25 -0300", + "icon": "🐛", + "category": "bugfix" + }, + { + "id": "ac24961e4b97bfb58a52e1b95f3d9696fe1e5d86", + "type": "refactor", + "title": "substitui '•' por '-' em textos de exibição", + "date": "2025-11-22 12:58:57 -0300", + "icon": "♻️", + "category": "refactor" + }, + { + "id": "8c5313119dafaf3a33ab4bffeeb40d7f0278eb08", + "type": "feat", + "title": "atualiza fontes e altera avatar SVG", + "date": "2025-11-22 12:49:56 -0300", + "icon": "✨", + "category": "feature" + }, + { + "id": "4d076772e623cc3cb1a51f94551125ad9b791841", + "type": "refactor", + "title": "Relocate `PrivacyProvider` to the dashboard layout and update `tsconfig` `jsx` compiler option.", + "date": "2025-11-21 09:40:41 -0300", + "icon": "♻️", + "category": "refactor" + }, + { + "id": "3d8772e55f2d25b757b0b3fe398f7db2fafcb745", + "type": "feat", + "title": "adiciona tipos para d3-array e ajusta configurações do TypeScript", + "date": "2025-11-17 20:58:05 -0300", + "icon": "✨", + "category": "feature" + }, + { + "id": "a7736b7ab9249dd0e82b30f71ca74530dad0fdb0", + "type": "feat", + "title": "adicionar babel-plugin-react-compiler como dependência", + "date": "2025-11-17 19:55:21 -0300", + "icon": "✨", + "category": "feature" + }, + { + "id": "835d94f140670888df920834ab2b77eb365362ce", + "type": "chore", + "title": "add package-lock.json for dependency version locking", + "date": "2025-11-17 19:45:01 +0000", + "icon": "🔧", + "category": "chore" + }, + { + "id": "fcd4ebc608e7d0e9f6f0eb106ba7f53177d28d05", + "type": "feat", + "title": "melhorar UX/UI e segurança do módulo de ajustes de usuário", + "date": "2025-11-17 19:43:50 +0000", + "icon": "✨", + "category": "feature" + } + ] +} \ No newline at end of file diff --git a/scripts/generate-changelog.ts b/scripts/generate-changelog.ts new file mode 100644 index 0000000..f5ab37c --- /dev/null +++ b/scripts/generate-changelog.ts @@ -0,0 +1,178 @@ +import { execSync } from "child_process"; +import fs from "fs"; +import path from "path"; + +interface ChangelogEntry { + id: string; + type: string; + title: string; + date: string; + icon: string; + category: string; +} + +function getIcon(type: string): string { + const icons: Record = { + feat: "✨", + fix: "🐛", + perf: "🚀", + docs: "📝", + style: "🎨", + refactor: "♻️", + test: "🧪", + chore: "🔧", + }; + return icons[type] || "📦"; +} + +function getCategory(type: string): string { + const categories: Record = { + feat: "feature", + fix: "bugfix", + perf: "performance", + docs: "documentation", + style: "style", + refactor: "refactor", + test: "test", + chore: "chore", + }; + return categories[type] || "other"; +} + +function getCategoryLabel(category: string): string { + const labels: Record = { + feature: "Novidades", + bugfix: "Correções", + performance: "Performance", + documentation: "Documentação", + style: "Interface", + refactor: "Melhorias", + test: "Testes", + chore: "Manutenção", + other: "Outros", + }; + return labels[category] || "Outros"; +} + +function generateChangelog() { + try { + console.log("🔍 Gerando changelog dos últimos commits...\n"); + + // Pega commits dos últimos 30 dias + const gitCommand = + 'git log --since="30 days ago" --pretty=format:"%H|%s|%ai" --no-merges'; + + let output: string; + try { + output = execSync(gitCommand, { encoding: "utf-8" }); + } catch (error) { + console.warn("⚠️ Não foi possível acessar o Git. Gerando changelog vazio."); + output = ""; + } + + if (!output.trim()) { + console.log("ℹ️ Nenhum commit encontrado nos últimos 30 dias."); + const emptyChangelog = { + version: "1.0.0", + generatedAt: new Date().toISOString(), + entries: [], + }; + + const publicDir = path.join(process.cwd(), "public"); + if (!fs.existsSync(publicDir)) { + fs.mkdirSync(publicDir, { recursive: true }); + } + + fs.writeFileSync( + path.join(publicDir, "changelog.json"), + JSON.stringify(emptyChangelog, null, 2) + ); + return; + } + + const commits = output + .split("\n") + .filter((line) => line.trim()) + .map((line) => { + const [hash, message, date] = line.split("|"); + return { hash, message, date }; + }); + + console.log(`📝 Processando ${commits.length} commits...\n`); + + // Parseia conventional commits + const entries: ChangelogEntry[] = commits + .map((commit) => { + // Match conventional commit format: type: message + const match = commit.message.match( + /^(feat|fix|perf|docs|style|refactor|test|chore):\s*(.+)$/ + ); + + if (!match) { + // Ignora commits que não seguem o padrão + return null; + } + + const [, type, title] = match; + + return { + id: commit.hash, + type, + title: title.trim(), + date: commit.date, + icon: getIcon(type), + category: getCategory(type), + }; + }) + .filter((entry): entry is ChangelogEntry => entry !== null); + + console.log(`✅ ${entries.length} commits válidos encontrados\n`); + + // Agrupa por categoria + const grouped = entries.reduce( + (acc, entry) => { + if (!acc[entry.category]) { + acc[entry.category] = []; + } + acc[entry.category].push(entry); + return acc; + }, + {} as Record + ); + + // Mostra resumo + Object.entries(grouped).forEach(([category, items]) => { + console.log( + `${getIcon(items[0].type)} ${getCategoryLabel(category)}: ${items.length}` + ); + }); + + // Pega versão do package.json + const packageJson = JSON.parse( + fs.readFileSync(path.join(process.cwd(), "package.json"), "utf-8") + ); + + const changelog = { + version: packageJson.version || "1.0.0", + generatedAt: new Date().toISOString(), + entries: entries.slice(0, 20), // Limita a 20 mais recentes + }; + + // Salva em public/changelog.json + const publicDir = path.join(process.cwd(), "public"); + if (!fs.existsSync(publicDir)) { + fs.mkdirSync(publicDir, { recursive: true }); + } + + const changelogPath = path.join(publicDir, "changelog.json"); + fs.writeFileSync(changelogPath, JSON.stringify(changelog, null, 2)); + + console.log(`\n✅ Changelog gerado com sucesso em: ${changelogPath}`); + } catch (error) { + console.error("❌ Erro ao gerar changelog:", error); + // Não falha o build, apenas avisa + process.exit(0); + } +} + +generateChangelog();