mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10afef9fec | ||
|
|
fd4d90a53e | ||
|
|
a24406271c |
1
.github/workflows/docker-publish.yml
vendored
1
.github/workflows/docker-publish.yml
vendored
@@ -13,6 +13,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
DOCKER_IMAGE_NAME: openmonetis
|
DOCKER_IMAGE_NAME: openmonetis
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
quality:
|
quality:
|
||||||
|
|||||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -5,6 +5,9 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -7,6 +7,21 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2.3.2] - 2026-04-04
|
||||||
|
|
||||||
|
### Segurança
|
||||||
|
|
||||||
|
- Tokens: removido aceite de tokens sem expiração (`expiresAt NULL`); tokens criados via settings agora expiram em 1 ano
|
||||||
|
- Tokens: corrigido refresh que sobrescrevia hash e invalidava access token anterior; verify agora valida JWT por assinatura
|
||||||
|
- xlsx: desabilitado parsing de fórmulas (`cellFormula: false`) para mitigar CVE-2024-44294
|
||||||
|
- CSP: expandida Content-Security-Policy com `default-src`, `script-src`, `style-src`, `img-src`, `font-src` e `connect-src`
|
||||||
|
- Headers: adicionados `Referrer-Policy` e `X-Permitted-Cross-Domain-Policies`
|
||||||
|
- API: rotas autenticadas agora retornam `401 JSON` em vez de redirect `302` para clientes não autenticados
|
||||||
|
- Health: removido campo `version` da resposta do `/api/health`
|
||||||
|
- robots.txt: simplificado para não expor mapa de rotas internas
|
||||||
|
- Sitemap: corrigida URL com protocolo duplicado (`https://https://`)
|
||||||
|
- Criado `security.txt` (RFC 9116)
|
||||||
|
|
||||||
## [2.3.1] - 2026-04-03
|
## [2.3.1] - 2026-04-03
|
||||||
|
|
||||||
### Corrigido
|
### Corrigido
|
||||||
|
|||||||
@@ -44,7 +44,23 @@ const nextConfig: NextConfig = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Content-Security-Policy",
|
key: "Content-Security-Policy",
|
||||||
value: "frame-ancestors 'none';",
|
value: [
|
||||||
|
"default-src 'self'",
|
||||||
|
"script-src 'self' 'unsafe-inline' https://umami.felipecoutinho.com",
|
||||||
|
"style-src 'self' 'unsafe-inline'",
|
||||||
|
"img-src 'self' https://lh3.googleusercontent.com data: blob:",
|
||||||
|
"font-src 'self'",
|
||||||
|
"connect-src 'self' https://umami.felipecoutinho.com",
|
||||||
|
"frame-ancestors 'none'",
|
||||||
|
].join("; "),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Referrer-Policy",
|
||||||
|
value: "strict-origin-when-cross-origin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "X-Permitted-Cross-Domain-Policies",
|
||||||
|
value: "none",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "Permissions-Policy",
|
key: "Permissions-Policy",
|
||||||
|
|||||||
212
package.json
212
package.json
@@ -1,108 +1,108 @@
|
|||||||
{
|
{
|
||||||
"name": "openmonetis",
|
"name": "openmonetis",
|
||||||
"version": "2.3.1",
|
"version": "2.3.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@10.33.0",
|
"packageManager": "pnpm@10.33.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbopack",
|
||||||
"db:seed": "tsx scripts/mock-data.ts",
|
"db:seed": "tsx scripts/mock-data.ts",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "biome check .",
|
"lint": "biome check .",
|
||||||
"lint:deadcode": "knip --reporter compact",
|
"lint:deadcode": "knip --reporter compact",
|
||||||
"lint:fix": "biome check --write .",
|
"lint:fix": "biome check --write .",
|
||||||
"env:setup": "bash scripts/setup-env.sh",
|
"env:setup": "bash scripts/setup-env.sh",
|
||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:migrate": "drizzle-kit migrate",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
"db:push": "drizzle-kit push",
|
"db:push": "drizzle-kit push",
|
||||||
"db:extensions": "tsx scripts/postgres/enable-extensions.ts",
|
"db:extensions": "tsx scripts/postgres/enable-extensions.ts",
|
||||||
"db:studio": "drizzle-kit studio",
|
"db:studio": "drizzle-kit studio",
|
||||||
"docker:up": "docker compose up --build",
|
"docker:up": "docker compose up --build",
|
||||||
"postinstall": "cp node_modules/pdfjs-dist/build/pdf.worker.min.mjs public/pdf.worker.min.mjs",
|
"postinstall": "cp node_modules/pdfjs-dist/build/pdf.worker.min.mjs public/pdf.worker.min.mjs",
|
||||||
"docker:up:db": "docker compose up -d db",
|
"docker:up:db": "docker compose up -d db",
|
||||||
"docker:up:d": "docker compose up --build -d",
|
"docker:up:d": "docker compose up --build -d",
|
||||||
"docker:down": "docker compose down",
|
"docker:down": "docker compose down",
|
||||||
"docker:down:volumes": "docker compose down -v",
|
"docker:down:volumes": "docker compose down -v",
|
||||||
"docker:logs": "docker compose logs -f",
|
"docker:logs": "docker compose logs -f",
|
||||||
"docker:logs:app": "docker compose logs -f app",
|
"docker:logs:app": "docker compose logs -f app",
|
||||||
"docker:logs:db": "docker compose logs -f db",
|
"docker:logs:db": "docker compose logs -f db",
|
||||||
"docker:restart": "docker compose restart",
|
"docker:restart": "docker compose restart",
|
||||||
"docker:rebuild": "docker compose up --build --force-recreate",
|
"docker:rebuild": "docker compose up --build --force-recreate",
|
||||||
"backup": "bash scripts/backup.sh"
|
"backup": "bash scripts/backup.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "^3.0.65",
|
"@ai-sdk/anthropic": "^3.0.65",
|
||||||
"@ai-sdk/google": "^3.0.55",
|
"@ai-sdk/google": "^3.0.55",
|
||||||
"@ai-sdk/openai": "^3.0.49",
|
"@ai-sdk/openai": "^3.0.49",
|
||||||
"@aws-sdk/client-s3": "^3.1022.0",
|
"@aws-sdk/client-s3": "^3.1022.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.1022.0",
|
"@aws-sdk/s3-request-presigner": "^3.1022.0",
|
||||||
"@better-auth/passkey": "^1.5.6",
|
"@better-auth/passkey": "^1.5.6",
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@openrouter/ai-sdk-provider": "^2.3.3",
|
"@openrouter/ai-sdk-provider": "^2.3.3",
|
||||||
"@radix-ui/react-alert-dialog": "1.1.15",
|
"@radix-ui/react-alert-dialog": "1.1.15",
|
||||||
"@radix-ui/react-avatar": "1.1.11",
|
"@radix-ui/react-avatar": "1.1.11",
|
||||||
"@radix-ui/react-checkbox": "1.3.3",
|
"@radix-ui/react-checkbox": "1.3.3",
|
||||||
"@radix-ui/react-collapsible": "1.1.12",
|
"@radix-ui/react-collapsible": "1.1.12",
|
||||||
"@radix-ui/react-dialog": "1.1.15",
|
"@radix-ui/react-dialog": "1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "2.1.16",
|
"@radix-ui/react-dropdown-menu": "2.1.16",
|
||||||
"@radix-ui/react-hover-card": "^1.1.15",
|
"@radix-ui/react-hover-card": "^1.1.15",
|
||||||
"@radix-ui/react-label": "2.1.8",
|
"@radix-ui/react-label": "2.1.8",
|
||||||
"@radix-ui/react-popover": "^1.1.15",
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
"@radix-ui/react-progress": "1.1.8",
|
"@radix-ui/react-progress": "1.1.8",
|
||||||
"@radix-ui/react-radio-group": "^1.3.8",
|
"@radix-ui/react-radio-group": "^1.3.8",
|
||||||
"@radix-ui/react-select": "2.2.6",
|
"@radix-ui/react-select": "2.2.6",
|
||||||
"@radix-ui/react-separator": "1.1.8",
|
"@radix-ui/react-separator": "1.1.8",
|
||||||
"@radix-ui/react-slot": "1.2.4",
|
"@radix-ui/react-slot": "1.2.4",
|
||||||
"@radix-ui/react-switch": "1.2.6",
|
"@radix-ui/react-switch": "1.2.6",
|
||||||
"@radix-ui/react-tabs": "1.1.13",
|
"@radix-ui/react-tabs": "1.1.13",
|
||||||
"@radix-ui/react-toggle": "1.1.10",
|
"@radix-ui/react-toggle": "1.1.10",
|
||||||
"@radix-ui/react-toggle-group": "1.1.11",
|
"@radix-ui/react-toggle-group": "1.1.11",
|
||||||
"@radix-ui/react-tooltip": "1.2.8",
|
"@radix-ui/react-tooltip": "1.2.8",
|
||||||
"@remixicon/react": "4.9.0",
|
"@remixicon/react": "4.9.0",
|
||||||
"@tanstack/react-query": "^5.96.2",
|
"@tanstack/react-query": "^5.96.2",
|
||||||
"@tanstack/react-table": "8.21.3",
|
"@tanstack/react-table": "8.21.3",
|
||||||
"@tanstack/react-virtual": "^3.13.23",
|
"@tanstack/react-virtual": "^3.13.23",
|
||||||
"ai": "^6.0.143",
|
"ai": "^6.0.143",
|
||||||
"better-auth": "1.5.6",
|
"better-auth": "1.5.6",
|
||||||
"canvas-confetti": "^1.9.4",
|
"canvas-confetti": "^1.9.4",
|
||||||
"class-variance-authority": "0.7.1",
|
"class-variance-authority": "0.7.1",
|
||||||
"clsx": "2.1.1",
|
"clsx": "2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"drizzle-orm": "0.45.2",
|
"drizzle-orm": "0.45.2",
|
||||||
"jspdf": "^4.2.1",
|
"jspdf": "^4.2.1",
|
||||||
"jspdf-autotable": "^5.0.7",
|
"jspdf-autotable": "^5.0.7",
|
||||||
"next": "16.2.2",
|
"next": "16.2.2",
|
||||||
"next-themes": "0.4.6",
|
"next-themes": "0.4.6",
|
||||||
"pdfjs-dist": "^5.6.205",
|
"pdfjs-dist": "^5.6.205",
|
||||||
"pg": "8.20.0",
|
"pg": "8.20.0",
|
||||||
"radix-ui": "^1.4.3",
|
"radix-ui": "^1.4.3",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
"react-day-picker": "^9.14.0",
|
"react-day-picker": "^9.14.0",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "19.2.4",
|
||||||
"recharts": "3.8.1",
|
"recharts": "3.8.1",
|
||||||
"resend": "^6.10.0",
|
"resend": "^6.10.0",
|
||||||
"sonner": "2.0.7",
|
"sonner": "2.0.7",
|
||||||
"tailwind-merge": "3.5.0",
|
"tailwind-merge": "3.5.0",
|
||||||
"vaul": "1.1.2",
|
"vaul": "1.1.2",
|
||||||
"xlsx": "^0.18.5",
|
"xlsx": "^0.18.5",
|
||||||
"zod": "4.3.6"
|
"zod": "4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.4.10",
|
"@biomejs/biome": "2.4.10",
|
||||||
"@tailwindcss/postcss": "4.2.2",
|
"@tailwindcss/postcss": "4.2.2",
|
||||||
"@types/canvas-confetti": "^1.9.0",
|
"@types/canvas-confetti": "^1.9.0",
|
||||||
"@types/node": "25.5.0",
|
"@types/node": "25.5.0",
|
||||||
"@types/pg": "^8.20.0",
|
"@types/pg": "^8.20.0",
|
||||||
"@types/react": "19.2.14",
|
"@types/react": "19.2.14",
|
||||||
"@types/react-dom": "19.2.3",
|
"@types/react-dom": "19.2.3",
|
||||||
"dotenv": "^17.4.0",
|
"dotenv": "^17.4.0",
|
||||||
"drizzle-kit": "0.31.10",
|
"drizzle-kit": "0.31.10",
|
||||||
"knip": "^6.3.0",
|
"knip": "^6.3.0",
|
||||||
"tailwindcss": "4.2.2",
|
"tailwindcss": "4.2.2",
|
||||||
"tsx": "4.21.0",
|
"tsx": "4.21.0",
|
||||||
"typescript": "6.0.2"
|
"typescript": "6.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
public/.well-known/security.txt
Normal file
4
public/.well-known/security.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Contact: https://github.com/felipegcoutinho/openmonetis/security/advisories
|
||||||
|
Expires: 2027-04-04T00:00:00.000Z
|
||||||
|
Preferred-Languages: pt-BR, en
|
||||||
|
Canonical: https://openmonetis.com/.well-known/security.txt
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { attachments } from "@/db/schema";
|
import { attachments } from "@/db/schema";
|
||||||
import { getUserId } from "@/shared/lib/auth/server";
|
import { getOptionalUserSession } from "@/shared/lib/auth/server";
|
||||||
import { db } from "@/shared/lib/db";
|
import { db } from "@/shared/lib/db";
|
||||||
import { createPresignedGetUrl } from "@/shared/lib/storage/presign";
|
import { createPresignedGetUrl } from "@/shared/lib/storage/presign";
|
||||||
|
|
||||||
@@ -13,7 +13,19 @@ export async function GET(
|
|||||||
_request: Request,
|
_request: Request,
|
||||||
{ params }: { params: Promise<{ attachmentId: string }> },
|
{ params }: { params: Promise<{ attachmentId: string }> },
|
||||||
) {
|
) {
|
||||||
const [userId, { attachmentId }] = await Promise.all([getUserId(), params]);
|
const [session, { attachmentId }] = await Promise.all([
|
||||||
|
getOptionalUserSession(),
|
||||||
|
params,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Não autenticado" },
|
||||||
|
{ status: 401, headers: PRIVATE_RESPONSE_HEADERS },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = session.user.id;
|
||||||
|
|
||||||
const [row] = await db
|
const [row] = await db
|
||||||
.select({ fileKey: attachments.fileKey })
|
.select({ fileKey: attachments.fileKey })
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { NextResponse } from "next/server";
|
|||||||
import { apiTokens } from "@/db/schema";
|
import { apiTokens } from "@/db/schema";
|
||||||
import {
|
import {
|
||||||
extractBearerToken,
|
extractBearerToken,
|
||||||
hashToken,
|
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
verifyJwt,
|
verifyJwt,
|
||||||
} from "@/shared/lib/auth/api-token";
|
} from "@/shared/lib/auth/api-token";
|
||||||
@@ -59,11 +58,11 @@ export async function POST(request: Request) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Atualizar hash do token e último uso
|
// Atualizar último uso e expiração (sem sobrescrever tokenHash,
|
||||||
|
// pois o JWT é auto-verificável por assinatura)
|
||||||
await db
|
await db
|
||||||
.update(apiTokens)
|
.update(apiTokens)
|
||||||
.set({
|
.set({
|
||||||
tokenHash: hashToken(result.accessToken),
|
|
||||||
lastUsedAt: new Date(),
|
lastUsedAt: new Date(),
|
||||||
lastUsedIp:
|
lastUsedIp:
|
||||||
request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ||
|
request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ||
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { and, eq, gt, isNull } from "drizzle-orm";
|
import { and, eq, gt, isNull } from "drizzle-orm";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { apiTokens } from "@/db/schema";
|
import { apiTokens } from "@/db/schema";
|
||||||
import { extractBearerToken, hashToken } from "@/shared/lib/auth/api-token";
|
import { extractBearerToken, verifyJwt } from "@/shared/lib/auth/api-token";
|
||||||
import { db } from "@/shared/lib/db";
|
import { db } from "@/shared/lib/db";
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
@@ -17,21 +17,21 @@ export async function POST(request: Request) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validar token os_xxx via hash lookup
|
// Verificar JWT (assinatura + expiração)
|
||||||
if (!token.startsWith("os_")) {
|
const payload = verifyJwt(token);
|
||||||
|
|
||||||
|
if (!payload || payload.type !== "api_access") {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ valid: false, error: "Formato de token inválido" },
|
{ valid: false, error: "Token inválido ou expirado" },
|
||||||
{ status: 401 },
|
{ status: 401 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash do token para buscar no DB
|
// Buscar token no banco por tokenId para checar revogação
|
||||||
const tokenHash = hashToken(token);
|
|
||||||
|
|
||||||
// Buscar token no banco
|
|
||||||
const tokenRecord = await db.query.apiTokens.findFirst({
|
const tokenRecord = await db.query.apiTokens.findFirst({
|
||||||
where: and(
|
where: and(
|
||||||
eq(apiTokens.tokenHash, tokenHash),
|
eq(apiTokens.id, payload.tokenId),
|
||||||
|
eq(apiTokens.userId, payload.sub),
|
||||||
isNull(apiTokens.revokedAt),
|
isNull(apiTokens.revokedAt),
|
||||||
gt(apiTokens.expiresAt, new Date()),
|
gt(apiTokens.expiresAt, new Date()),
|
||||||
),
|
),
|
||||||
@@ -39,7 +39,7 @@ export async function POST(request: Request) {
|
|||||||
|
|
||||||
if (!tokenRecord) {
|
if (!tokenRecord) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ valid: false, error: "Token inválido ou revogado" },
|
{ valid: false, error: "Token revogado ou não encontrado" },
|
||||||
{ status: 401 },
|
{ status: 401 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { version as APP_VERSION } from "@/package.json";
|
|
||||||
import { db } from "@/shared/lib/db";
|
import { db } from "@/shared/lib/db";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,7 +19,6 @@ export async function GET() {
|
|||||||
{
|
{
|
||||||
status: "ok",
|
status: "ok",
|
||||||
name: "OpenMonetis",
|
name: "OpenMonetis",
|
||||||
version: APP_VERSION,
|
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
{ status: 200 },
|
{ status: 200 },
|
||||||
@@ -33,7 +31,6 @@ export async function GET() {
|
|||||||
{
|
{
|
||||||
status: "error",
|
status: "error",
|
||||||
name: "OpenMonetis",
|
name: "OpenMonetis",
|
||||||
version: APP_VERSION,
|
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
message: "Database connection failed",
|
message: "Database connection failed",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { and, eq, gt, isNull, or } from "drizzle-orm";
|
import { and, eq, gt, isNull } from "drizzle-orm";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { apiTokens, inboxItems } from "@/db/schema";
|
import { apiTokens, inboxItems } from "@/db/schema";
|
||||||
@@ -63,7 +63,7 @@ export async function POST(request: Request) {
|
|||||||
where: and(
|
where: and(
|
||||||
eq(apiTokens.tokenHash, tokenHash),
|
eq(apiTokens.tokenHash, tokenHash),
|
||||||
isNull(apiTokens.revokedAt),
|
isNull(apiTokens.revokedAt),
|
||||||
or(isNull(apiTokens.expiresAt), gt(apiTokens.expiresAt, new Date())),
|
gt(apiTokens.expiresAt, new Date()),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { and, eq, gt, isNull, or } from "drizzle-orm";
|
import { and, eq, gt, isNull } from "drizzle-orm";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { apiTokens, inboxItems } from "@/db/schema";
|
import { apiTokens, inboxItems } from "@/db/schema";
|
||||||
@@ -56,7 +56,7 @@ export async function POST(request: Request) {
|
|||||||
where: and(
|
where: and(
|
||||||
eq(apiTokens.tokenHash, tokenHash),
|
eq(apiTokens.tokenHash, tokenHash),
|
||||||
isNull(apiTokens.revokedAt),
|
isNull(apiTokens.revokedAt),
|
||||||
or(isNull(apiTokens.expiresAt), gt(apiTokens.expiresAt, new Date())),
|
gt(apiTokens.expiresAt, new Date()),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
fetchSavedInsights,
|
fetchSavedInsights,
|
||||||
savedInsightsPeriodSchema,
|
savedInsightsPeriodSchema,
|
||||||
} from "@/features/insights/queries";
|
} from "@/features/insights/queries";
|
||||||
import { getUserId } from "@/shared/lib/auth/server";
|
import { getOptionalUserSession } from "@/shared/lib/auth/server";
|
||||||
|
|
||||||
const PRIVATE_RESPONSE_HEADERS = {
|
const PRIVATE_RESPONSE_HEADERS = {
|
||||||
"Cache-Control": "private, no-store",
|
"Cache-Control": "private, no-store",
|
||||||
@@ -25,8 +25,18 @@ export async function GET(request: Request) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = await getUserId();
|
const session = await getOptionalUserSession();
|
||||||
const insights = await fetchSavedInsights(userId, validatedPeriod.data);
|
if (!session?.user) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Não autenticado" },
|
||||||
|
{ status: 401, headers: PRIVATE_RESPONSE_HEADERS },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const insights = await fetchSavedInsights(
|
||||||
|
session.user.id,
|
||||||
|
validatedPeriod.data,
|
||||||
|
);
|
||||||
|
|
||||||
return NextResponse.json(insights, {
|
return NextResponse.json(insights, {
|
||||||
headers: PRIVATE_RESPONSE_HEADERS,
|
headers: PRIVATE_RESPONSE_HEADERS,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { fetchTransactionAttachments } from "@/features/transactions/attachment-queries";
|
import { fetchTransactionAttachments } from "@/features/transactions/attachment-queries";
|
||||||
import { getUserId } from "@/shared/lib/auth/server";
|
import { getOptionalUserSession } from "@/shared/lib/auth/server";
|
||||||
|
|
||||||
const PRIVATE_RESPONSE_HEADERS = {
|
const PRIVATE_RESPONSE_HEADERS = {
|
||||||
"Cache-Control": "private, no-store",
|
"Cache-Control": "private, no-store",
|
||||||
@@ -10,7 +10,19 @@ export async function GET(
|
|||||||
_request: Request,
|
_request: Request,
|
||||||
{ params }: { params: Promise<{ transactionId: string }> },
|
{ params }: { params: Promise<{ transactionId: string }> },
|
||||||
) {
|
) {
|
||||||
const [userId, { transactionId }] = await Promise.all([getUserId(), params]);
|
const [session, { transactionId }] = await Promise.all([
|
||||||
|
getOptionalUserSession(),
|
||||||
|
params,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Não autenticado" },
|
||||||
|
{ status: 401, headers: PRIVATE_RESPONSE_HEADERS },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = session.user.id;
|
||||||
const attachments = await fetchTransactionAttachments(userId, transactionId);
|
const attachments = await fetchTransactionAttachments(userId, transactionId);
|
||||||
|
|
||||||
return NextResponse.json(attachments, {
|
return NextResponse.json(attachments, {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { fetchInstallmentAnticipations } from "@/features/transactions/anticipation-queries";
|
import { fetchInstallmentAnticipations } from "@/features/transactions/anticipation-queries";
|
||||||
import { getUserId } from "@/shared/lib/auth/server";
|
import { getOptionalUserSession } from "@/shared/lib/auth/server";
|
||||||
|
|
||||||
const PRIVATE_RESPONSE_HEADERS = {
|
const PRIVATE_RESPONSE_HEADERS = {
|
||||||
"Cache-Control": "private, no-store",
|
"Cache-Control": "private, no-store",
|
||||||
@@ -11,7 +11,19 @@ export async function GET(
|
|||||||
{ params }: { params: Promise<{ seriesId: string }> },
|
{ params }: { params: Promise<{ seriesId: string }> },
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const [userId, { seriesId }] = await Promise.all([getUserId(), params]);
|
const [session, { seriesId }] = await Promise.all([
|
||||||
|
getOptionalUserSession(),
|
||||||
|
params,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Não autenticado" },
|
||||||
|
{ status: 401, headers: PRIVATE_RESPONSE_HEADERS },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = session.user.id;
|
||||||
const anticipations = await fetchInstallmentAnticipations(userId, seriesId);
|
const anticipations = await fetchInstallmentAnticipations(userId, seriesId);
|
||||||
|
|
||||||
return NextResponse.json(anticipations, {
|
return NextResponse.json(anticipations, {
|
||||||
|
|||||||
@@ -6,25 +6,7 @@ export default function robots(): MetadataRoute.Robots {
|
|||||||
{
|
{
|
||||||
userAgent: "*",
|
userAgent: "*",
|
||||||
allow: "/",
|
allow: "/",
|
||||||
disallow: [
|
disallow: "/api/",
|
||||||
"/dashboard",
|
|
||||||
"/transactions",
|
|
||||||
"/accounts",
|
|
||||||
"/cards",
|
|
||||||
"/categories",
|
|
||||||
"/budgets",
|
|
||||||
"/payers",
|
|
||||||
"/notes",
|
|
||||||
"/insights",
|
|
||||||
"/calendar",
|
|
||||||
"/attachments",
|
|
||||||
"/settings",
|
|
||||||
"/reports",
|
|
||||||
"/inbox",
|
|
||||||
"/login",
|
|
||||||
"/signup",
|
|
||||||
"/api/",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { MetadataRoute } from "next";
|
import type { MetadataRoute } from "next";
|
||||||
|
|
||||||
const BASE_URL = process.env.PUBLIC_DOMAIN
|
const BASE_URL = process.env.PUBLIC_DOMAIN
|
||||||
? `https://${process.env.PUBLIC_DOMAIN}`
|
? `https://${process.env.PUBLIC_DOMAIN.replace(/^https?:\/\//, "")}`
|
||||||
: "https://openmonetis.com";
|
: "https://openmonetis.com";
|
||||||
|
|
||||||
export default function sitemap(): MetadataRoute.Sitemap {
|
export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
|
|||||||
@@ -649,7 +649,7 @@ export async function createApiTokenAction(
|
|||||||
name: validated.name,
|
name: validated.name,
|
||||||
tokenHash,
|
tokenHash,
|
||||||
tokenPrefix,
|
tokenPrefix,
|
||||||
expiresAt: null, // No expiration for now
|
expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1 ano
|
||||||
})
|
})
|
||||||
.returning({ id: apiTokens.id });
|
.returning({ id: apiTokens.id });
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ export function parseXls(buffer: ArrayBuffer): ImportStatement {
|
|||||||
const workbook = XLSX.read(new Uint8Array(buffer), {
|
const workbook = XLSX.read(new Uint8Array(buffer), {
|
||||||
type: "array",
|
type: "array",
|
||||||
cellDates: false,
|
cellDates: false,
|
||||||
|
cellFormula: false,
|
||||||
|
cellNF: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!workbook.SheetNames.length) {
|
if (!workbook.SheetNames.length) {
|
||||||
|
|||||||
Reference in New Issue
Block a user