mirror of
https://github.com/felipegcoutinho/openmonetis.git
synced 2026-05-09 11:01:45 +00:00
fix(segurança): endurecer autenticação e rotas privadas
This commit is contained in:
@@ -131,6 +131,46 @@ export async function createInstallmentAnticipationAction(
|
||||
const user = await getUser();
|
||||
const data = createAnticipationSchema.parse(input);
|
||||
|
||||
if (data.payerId || data.categoryId) {
|
||||
const [payer, category] = await Promise.all([
|
||||
data.payerId
|
||||
? db
|
||||
.select({ id: payers.id })
|
||||
.from(payers)
|
||||
.where(
|
||||
and(eq(payers.id, data.payerId), eq(payers.userId, user.id)),
|
||||
)
|
||||
.limit(1)
|
||||
: Promise.resolve([]),
|
||||
data.categoryId
|
||||
? db
|
||||
.select({ id: categories.id })
|
||||
.from(categories)
|
||||
.where(
|
||||
and(
|
||||
eq(categories.id, data.categoryId),
|
||||
eq(categories.userId, user.id),
|
||||
),
|
||||
)
|
||||
.limit(1)
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
|
||||
if (data.payerId && payer.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Pagador inválido para esta conta.",
|
||||
};
|
||||
}
|
||||
|
||||
if (data.categoryId && category.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Categoria inválida para esta conta.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Validar parcelas selecionadas
|
||||
const installments = await db.query.transactions.findMany({
|
||||
where: and(
|
||||
|
||||
99
src/features/transactions/anticipation-queries.ts
Normal file
99
src/features/transactions/anticipation-queries.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { and, desc, eq } from "drizzle-orm";
|
||||
import {
|
||||
categories,
|
||||
installmentAnticipations,
|
||||
payers,
|
||||
transactions,
|
||||
} from "@/db/schema";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import { uuidSchema } from "@/shared/lib/schemas/common";
|
||||
|
||||
export type InstallmentAnticipationListItem = {
|
||||
id: string;
|
||||
anticipationPeriod: string;
|
||||
anticipationDate: string;
|
||||
installmentCount: number;
|
||||
totalAmount: string;
|
||||
discount: string;
|
||||
transactionId: string;
|
||||
note: string | null;
|
||||
transaction: {
|
||||
isSettled: boolean | null;
|
||||
} | null;
|
||||
payer: {
|
||||
name: string;
|
||||
} | null;
|
||||
category: {
|
||||
name: string;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export async function fetchInstallmentAnticipations(
|
||||
userId: string,
|
||||
seriesId: string,
|
||||
): Promise<InstallmentAnticipationListItem[]> {
|
||||
const validatedSeriesId = uuidSchema("Série").parse(seriesId);
|
||||
|
||||
const anticipations = await db
|
||||
.select({
|
||||
id: installmentAnticipations.id,
|
||||
anticipationPeriod: installmentAnticipations.anticipationPeriod,
|
||||
anticipationDate: installmentAnticipations.anticipationDate,
|
||||
installmentCount: installmentAnticipations.installmentCount,
|
||||
totalAmount: installmentAnticipations.totalAmount,
|
||||
discount: installmentAnticipations.discount,
|
||||
transactionId: installmentAnticipations.transactionId,
|
||||
note: installmentAnticipations.note,
|
||||
transactionRecordId: transactions.id,
|
||||
transactionIsSettled: transactions.isSettled,
|
||||
payerName: payers.name,
|
||||
categoryName: categories.name,
|
||||
})
|
||||
.from(installmentAnticipations)
|
||||
.leftJoin(
|
||||
transactions,
|
||||
and(
|
||||
eq(installmentAnticipations.transactionId, transactions.id),
|
||||
eq(transactions.userId, userId),
|
||||
),
|
||||
)
|
||||
.leftJoin(
|
||||
payers,
|
||||
and(
|
||||
eq(installmentAnticipations.payerId, payers.id),
|
||||
eq(payers.userId, userId),
|
||||
),
|
||||
)
|
||||
.leftJoin(
|
||||
categories,
|
||||
and(
|
||||
eq(installmentAnticipations.categoryId, categories.id),
|
||||
eq(categories.userId, userId),
|
||||
),
|
||||
)
|
||||
.where(
|
||||
and(
|
||||
eq(installmentAnticipations.seriesId, validatedSeriesId),
|
||||
eq(installmentAnticipations.userId, userId),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(installmentAnticipations.createdAt));
|
||||
|
||||
return anticipations.map((anticipation) => ({
|
||||
id: anticipation.id,
|
||||
anticipationPeriod: anticipation.anticipationPeriod,
|
||||
anticipationDate: anticipation.anticipationDate.toISOString(),
|
||||
installmentCount: anticipation.installmentCount,
|
||||
totalAmount: anticipation.totalAmount,
|
||||
discount: anticipation.discount,
|
||||
transactionId: anticipation.transactionId,
|
||||
note: anticipation.note,
|
||||
transaction: anticipation.transactionRecordId
|
||||
? { isSettled: anticipation.transactionIsSettled }
|
||||
: null,
|
||||
payer: anticipation.payerName ? { name: anticipation.payerName } : null,
|
||||
category: anticipation.categoryName
|
||||
? { name: anticipation.categoryName }
|
||||
: null,
|
||||
}));
|
||||
}
|
||||
66
src/features/transactions/attachment-queries.ts
Normal file
66
src/features/transactions/attachment-queries.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { attachments, transactionAttachments, transactions } from "@/db/schema";
|
||||
import { db } from "@/shared/lib/db";
|
||||
import { createPresignedGetUrl } from "@/shared/lib/storage/presign";
|
||||
|
||||
export type TransactionAttachmentListItem = {
|
||||
attachmentId: string;
|
||||
fileName: string;
|
||||
fileSize: number;
|
||||
mimeType: string;
|
||||
createdAt: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export async function fetchTransactionAttachments(
|
||||
userId: string,
|
||||
transactionId: string,
|
||||
): Promise<TransactionAttachmentListItem[]> {
|
||||
const [transaction] = await db
|
||||
.select({ id: transactions.id })
|
||||
.from(transactions)
|
||||
.where(
|
||||
and(eq(transactions.id, transactionId), eq(transactions.userId, userId)),
|
||||
);
|
||||
|
||||
if (!transaction) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const rows = await db
|
||||
.select({
|
||||
attachmentId: transactionAttachments.attachmentId,
|
||||
fileName: attachments.fileName,
|
||||
fileSize: attachments.fileSize,
|
||||
mimeType: attachments.mimeType,
|
||||
fileKey: attachments.fileKey,
|
||||
createdAt: attachments.createdAt,
|
||||
})
|
||||
.from(transactionAttachments)
|
||||
.innerJoin(
|
||||
transactions,
|
||||
and(
|
||||
eq(transactionAttachments.transactionId, transactions.id),
|
||||
eq(transactions.userId, userId),
|
||||
),
|
||||
)
|
||||
.innerJoin(
|
||||
attachments,
|
||||
and(
|
||||
eq(transactionAttachments.attachmentId, attachments.id),
|
||||
eq(attachments.userId, userId),
|
||||
),
|
||||
)
|
||||
.where(eq(transactionAttachments.transactionId, transactionId));
|
||||
|
||||
return Promise.all(
|
||||
rows.map(async (row) => ({
|
||||
attachmentId: row.attachmentId,
|
||||
fileName: row.fileName,
|
||||
fileSize: row.fileSize,
|
||||
mimeType: row.mimeType,
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
url: await createPresignedGetUrl(row.fileKey),
|
||||
})),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user