mirror of
https://github.com/acedanger/finance.git
synced 2025-12-05 22:50:12 -08:00
style: apply Biome formatting to TypeScript files (#27)
- Fix import sorting - Standardize code formatting - Apply consistent TypeScript style - Update code to match Biome configuration Part of #27
This commit is contained in:
@@ -1,12 +1,11 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { currentAccountId as currentAccountIdStore, refreshKey } from '../stores/transactionStore';
|
||||||
import type { Account } from '../types';
|
import type { Account } from '../types';
|
||||||
import { formatCurrency } from '../utils';
|
import { formatCurrency } from '../utils';
|
||||||
import { currentAccountId as currentAccountIdStore, refreshKey } from '../stores/transactionStore';
|
|
||||||
|
|
||||||
interface AccountSummaryProps {
|
type AccountSummaryProps = {};
|
||||||
// No props needed, data comes from store and fetch
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AccountSummary({}: AccountSummaryProps) {
|
export default function AccountSummary({}: AccountSummaryProps) {
|
||||||
const currentAccountId = useStore(currentAccountIdStore);
|
const currentAccountId = useStore(currentAccountIdStore);
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import type { Transaction } from '../types';
|
import type React from 'react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
// Import store atoms and actions
|
// Import store atoms and actions
|
||||||
import {
|
import {
|
||||||
currentAccountId as currentAccountIdStore,
|
|
||||||
transactionToEdit as transactionToEditStore,
|
|
||||||
cancelEditingTransaction,
|
cancelEditingTransaction,
|
||||||
|
currentAccountId as currentAccountIdStore,
|
||||||
transactionSaved,
|
transactionSaved,
|
||||||
|
transactionToEdit as transactionToEditStore,
|
||||||
} from '../stores/transactionStore';
|
} from '../stores/transactionStore';
|
||||||
|
import type { Transaction } from '../types';
|
||||||
|
|
||||||
// Remove props that now come from the store
|
// Remove props that now come from the store
|
||||||
interface AddTransactionFormProps {}
|
type AddTransactionFormProps = {};
|
||||||
|
|
||||||
export default function AddTransactionForm({}: AddTransactionFormProps) {
|
export default function AddTransactionForm({}: AddTransactionFormProps) {
|
||||||
// --- Read state from store ---
|
// --- Read state from store ---
|
||||||
@@ -87,7 +88,7 @@ export default function AddTransactionForm({}: AddTransactionFormProps) {
|
|||||||
if (!amount) {
|
if (!amount) {
|
||||||
errors.push('Amount is required');
|
errors.push('Amount is required');
|
||||||
} else {
|
} else {
|
||||||
const amountNum = parseFloat(amount);
|
const amountNum = Number.parseFloat(amount);
|
||||||
if (isNaN(amountNum)) {
|
if (isNaN(amountNum)) {
|
||||||
errors.push('Amount must be a valid number');
|
errors.push('Amount must be a valid number');
|
||||||
} else if (amountNum === 0) {
|
} else if (amountNum === 0) {
|
||||||
@@ -134,7 +135,7 @@ export default function AddTransactionForm({}: AddTransactionFormProps) {
|
|||||||
accountId: currentAccountId,
|
accountId: currentAccountId,
|
||||||
date: date, // Send as YYYY-MM-DD string
|
date: date, // Send as YYYY-MM-DD string
|
||||||
description: description.trim(),
|
description: description.trim(),
|
||||||
amount: parseFloat(amount),
|
amount: Number.parseFloat(amount),
|
||||||
};
|
};
|
||||||
|
|
||||||
const method = editingId ? 'PUT' : 'POST';
|
const method = editingId ? 'PUT' : 'POST';
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
currentAccountId as currentAccountIdStore,
|
||||||
|
refreshKey,
|
||||||
|
startEditingTransaction,
|
||||||
|
triggerRefresh,
|
||||||
|
} from '../stores/transactionStore';
|
||||||
import type { Transaction } from '../types';
|
import type { Transaction } from '../types';
|
||||||
import { formatCurrency, formatDate } from '../utils';
|
import { formatCurrency, formatDate } from '../utils';
|
||||||
import {
|
|
||||||
startEditingTransaction,
|
|
||||||
currentAccountId as currentAccountIdStore,
|
|
||||||
triggerRefresh,
|
|
||||||
refreshKey,
|
|
||||||
} from '../stores/transactionStore';
|
|
||||||
|
|
||||||
interface TransactionTableProps {}
|
type TransactionTableProps = {};
|
||||||
|
|
||||||
export default function TransactionTable({}: TransactionTableProps) {
|
export default function TransactionTable({}: TransactionTableProps) {
|
||||||
const currentAccountId = useStore(currentAccountIdStore);
|
const currentAccountId = useStore(currentAccountIdStore);
|
||||||
@@ -47,7 +47,7 @@ export default function TransactionTable({}: TransactionTableProps) {
|
|||||||
}, [currentAccountId, refreshCounter]);
|
}, [currentAccountId, refreshCounter]);
|
||||||
|
|
||||||
const sortedTransactions = [...transactions].sort(
|
const sortedTransactions = [...transactions].sort(
|
||||||
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
|
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDelete = async (txnId: string) => {
|
const handleDelete = async (txnId: string) => {
|
||||||
@@ -76,7 +76,7 @@ export default function TransactionTable({}: TransactionTableProps) {
|
|||||||
console.log(`Transaction ${txnId} deleted successfully.`);
|
console.log(`Transaction ${txnId} deleted successfully.`);
|
||||||
|
|
||||||
setTransactions((currentTransactions) =>
|
setTransactions((currentTransactions) =>
|
||||||
currentTransactions.filter((txn) => txn.id !== txnId)
|
currentTransactions.filter((txn) => txn.id !== txnId),
|
||||||
);
|
);
|
||||||
|
|
||||||
triggerRefresh();
|
triggerRefresh();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { APIRoute } from 'astro';
|
import type { APIRoute } from 'astro';
|
||||||
import { transactions, accounts } from '../../../../data/store';
|
import { accounts, transactions } from '../../../../data/store';
|
||||||
import type { Transaction } from '../../../../types';
|
import type { Transaction } from '../../../../types';
|
||||||
|
|
||||||
export const PUT: APIRoute = async ({ request, params }) => {
|
export const PUT: APIRoute = async ({ request, params }) => {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { APIRoute } from 'astro';
|
import type { APIRoute } from 'astro';
|
||||||
import { transactions, accounts } from '../../../data/store';
|
import { accounts, transactions } from '../../../data/store';
|
||||||
import type { Transaction } from '../../../types';
|
import type { Transaction } from '../../../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
347
src/server.ts
347
src/server.ts
@@ -1,11 +1,11 @@
|
|||||||
// src/server.ts
|
// src/server.ts
|
||||||
|
|
||||||
import Fastify, { FastifyInstance } from 'fastify';
|
import { IncomingMessage, Server, ServerResponse } from 'http';
|
||||||
import { Server, IncomingMessage, ServerResponse } from 'http';
|
import { type BankAccount, PrismaClient } from '@prisma/client';
|
||||||
import { PrismaClient, BankAccount } from '@prisma/client';
|
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
|
import Fastify, { type FastifyInstance } from 'fastify';
|
||||||
|
import type { ZodTypeProvider } from 'fastify-type-provider-zod';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { ZodTypeProvider } from 'fastify-type-provider-zod';
|
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
@@ -13,9 +13,9 @@ const prisma = new PrismaClient();
|
|||||||
|
|
||||||
// Base schema for common fields, useful for reuse
|
// Base schema for common fields, useful for reuse
|
||||||
const bankAccountBaseSchema = z.object({
|
const bankAccountBaseSchema = z.object({
|
||||||
name: z.string().min(1, { message: "Name cannot be empty" }),
|
name: z.string().min(1, { message: 'Name cannot be empty' }),
|
||||||
bankName: z.string().min(1, { message: "Bank name cannot be empty" }),
|
bankName: z.string().min(1, { message: 'Bank name cannot be empty' }),
|
||||||
accountNumber: z.string().min(1, { message: "Account number cannot be empty" }),
|
accountNumber: z.string().min(1, { message: 'Account number cannot be empty' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Schema for creating a bank account (all fields required)
|
// Schema for creating a bank account (all fields required)
|
||||||
@@ -23,8 +23,8 @@ const createBankAccountSchema = bankAccountBaseSchema;
|
|||||||
|
|
||||||
// Schema for request parameters containing an ID
|
// Schema for request parameters containing an ID
|
||||||
const paramsSchema = z.object({
|
const paramsSchema = z.object({
|
||||||
// Use coerce to automatically convert string param to number
|
// Use coerce to automatically convert string param to number
|
||||||
id: z.coerce.number().int().positive({ message: "ID must be a positive integer" })
|
id: z.coerce.number().int().positive({ message: 'ID must be a positive integer' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Schema for updating a bank account (all fields optional)
|
// Schema for updating a bank account (all fields optional)
|
||||||
@@ -32,7 +32,7 @@ const updateBankAccountSchema = bankAccountBaseSchema.partial(); // Makes all fi
|
|||||||
|
|
||||||
// --- Fastify Server Instance with Zod Type Provider ---
|
// --- Fastify Server Instance with Zod Type Provider ---
|
||||||
const server: FastifyInstance = Fastify({
|
const server: FastifyInstance = Fastify({
|
||||||
logger: true
|
logger: true,
|
||||||
}).withTypeProvider<ZodTypeProvider>(); // Enable Zod validation and typing
|
}).withTypeProvider<ZodTypeProvider>(); // Enable Zod validation and typing
|
||||||
|
|
||||||
// --- API Routes ---
|
// --- API Routes ---
|
||||||
@@ -41,206 +41,209 @@ const API_PREFIX = '/api/bank-account';
|
|||||||
|
|
||||||
// 1. Create Bank Account
|
// 1. Create Bank Account
|
||||||
server.post(
|
server.post(
|
||||||
`${API_PREFIX}/create`,
|
`${API_PREFIX}/create`,
|
||||||
{
|
{
|
||||||
schema: { // Define Zod schema for the request body
|
schema: {
|
||||||
body: createBankAccountSchema
|
// Define Zod schema for the request body
|
||||||
}
|
body: createBankAccountSchema,
|
||||||
},
|
},
|
||||||
async (request, reply): Promise<BankAccount> => {
|
},
|
||||||
try {
|
async (request, reply): Promise<BankAccount> => {
|
||||||
// request.body is now typed and validated by Zod!
|
try {
|
||||||
const newAccount = await prisma.bankAccount.create({
|
// request.body is now typed and validated by Zod!
|
||||||
data: request.body, // Pass validated body directly
|
const newAccount = await prisma.bankAccount.create({
|
||||||
});
|
data: request.body, // Pass validated body directly
|
||||||
reply.code(201);
|
});
|
||||||
return newAccount;
|
reply.code(201);
|
||||||
} catch (error: any) {
|
return newAccount;
|
||||||
server.log.error(error);
|
} catch (error: any) {
|
||||||
if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) {
|
server.log.error(error);
|
||||||
reply.code(409);
|
if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) {
|
||||||
const body = createBankAccountSchema.parse(request.body);
|
reply.code(409);
|
||||||
throw new Error(`Bank account with number ${body.accountNumber} already exists.`);
|
const body = createBankAccountSchema.parse(request.body);
|
||||||
}
|
throw new Error(`Bank account with number ${body.accountNumber} already exists.`);
|
||||||
reply.code(500);
|
}
|
||||||
throw new Error('Failed to create bank account.');
|
reply.code(500);
|
||||||
}
|
throw new Error('Failed to create bank account.');
|
||||||
}
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Update Bank Account
|
// 2. Update Bank Account
|
||||||
server.post(
|
server.post(
|
||||||
`${API_PREFIX}/update/:id`,
|
`${API_PREFIX}/update/:id`,
|
||||||
{
|
{
|
||||||
schema: { // Define Zod schemas for params and body
|
schema: {
|
||||||
params: paramsSchema,
|
// Define Zod schemas for params and body
|
||||||
body: updateBankAccountSchema
|
params: paramsSchema,
|
||||||
}
|
body: updateBankAccountSchema,
|
||||||
},
|
},
|
||||||
async (request, reply): Promise<BankAccount> => {
|
},
|
||||||
try {
|
async (request, reply): Promise<BankAccount> => {
|
||||||
// request.params.id is now a validated number
|
try {
|
||||||
// request.body is now a validated partial object
|
// request.params.id is now a validated number
|
||||||
const { id } = request.params;
|
// request.body is now a validated partial object
|
||||||
const updateData = request.body;
|
const { id } = request.params;
|
||||||
|
const updateData = request.body;
|
||||||
|
|
||||||
// Prevent updating with an empty object
|
// Prevent updating with an empty object
|
||||||
if (Object.keys(updateData).length === 0) {
|
if (Object.keys(updateData).length === 0) {
|
||||||
reply.code(400);
|
reply.code(400);
|
||||||
throw new Error("Request body cannot be empty for update.");
|
throw new Error('Request body cannot be empty for update.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedAccount = await prisma.bankAccount.update({
|
const updatedAccount = await prisma.bankAccount.update({
|
||||||
where: { id: id }, // Use the validated numeric ID
|
where: { id: id }, // Use the validated numeric ID
|
||||||
data: updateData,
|
data: updateData,
|
||||||
});
|
});
|
||||||
return updatedAccount;
|
return updatedAccount;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
server.log.error(error);
|
server.log.error(error);
|
||||||
if (error.code === 'P2025') { // Record to update not found
|
if (error.code === 'P2025') {
|
||||||
reply.code(404);
|
// Record to update not found
|
||||||
throw new Error(`Bank account with ID ${request.params.id} not found.`);
|
reply.code(404);
|
||||||
}
|
throw new Error(`Bank account with ID ${request.params.id} not found.`);
|
||||||
if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) {
|
}
|
||||||
reply.code(409);
|
if (error.code === 'P2002' && error.meta?.target?.includes('accountNumber')) {
|
||||||
// Access accountNumber safely as it's optional in update
|
reply.code(409);
|
||||||
const attemptedNumber = request.body.accountNumber || '(unchanged)';
|
// Access accountNumber safely as it's optional in update
|
||||||
throw new Error(`Bank account with number ${attemptedNumber} already exists.`);
|
const attemptedNumber = request.body.accountNumber || '(unchanged)';
|
||||||
}
|
throw new Error(`Bank account with number ${attemptedNumber} already exists.`);
|
||||||
// Handle Zod validation errors specifically if needed (though Fastify usually does)
|
}
|
||||||
if (error instanceof z.ZodError) {
|
// Handle Zod validation errors specifically if needed (though Fastify usually does)
|
||||||
reply.code(400);
|
if (error instanceof z.ZodError) {
|
||||||
throw new Error(`Validation Error: ${error.errors.map(e => e.message).join(', ')}`);
|
reply.code(400);
|
||||||
}
|
throw new Error(`Validation Error: ${error.errors.map((e) => e.message).join(', ')}`);
|
||||||
reply.code(500);
|
}
|
||||||
throw new Error('Failed to update bank account.');
|
reply.code(500);
|
||||||
}
|
throw new Error('Failed to update bank account.');
|
||||||
}
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Delete Bank Account
|
// 3. Delete Bank Account
|
||||||
server.delete(
|
server.delete(
|
||||||
`${API_PREFIX}/delete/:id`,
|
`${API_PREFIX}/delete/:id`,
|
||||||
{
|
{
|
||||||
schema: { // Define Zod schema for params
|
schema: {
|
||||||
params: paramsSchema
|
// Define Zod schema for params
|
||||||
}
|
params: paramsSchema,
|
||||||
},
|
},
|
||||||
async (request, reply): Promise<{ message: string; deletedAccount: BankAccount }> => {
|
},
|
||||||
try {
|
async (request, reply): Promise<{ message: string; deletedAccount: BankAccount }> => {
|
||||||
// request.params.id is now a validated number
|
try {
|
||||||
const { id } = request.params;
|
// request.params.id is now a validated number
|
||||||
|
const { id } = request.params;
|
||||||
|
|
||||||
const deletedAccount = await prisma.bankAccount.delete({
|
const deletedAccount = await prisma.bankAccount.delete({
|
||||||
where: { id: id }, // Use the validated numeric ID
|
where: { id: id }, // Use the validated numeric ID
|
||||||
});
|
});
|
||||||
return { message: `Bank account with ID ${id} deleted successfully.`, deletedAccount };
|
return { message: `Bank account with ID ${id} deleted successfully.`, deletedAccount };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
server.log.error(error);
|
server.log.error(error);
|
||||||
if (error.code === 'P2025') { // Record to delete not found
|
if (error.code === 'P2025') {
|
||||||
reply.code(404);
|
// Record to delete not found
|
||||||
throw new Error(`Bank account with ID ${request.params.id} not found.`);
|
reply.code(404);
|
||||||
}
|
throw new Error(`Bank account with ID ${request.params.id} not found.`);
|
||||||
// Handle Zod validation errors
|
}
|
||||||
if (error instanceof z.ZodError) {
|
// Handle Zod validation errors
|
||||||
reply.code(400);
|
if (error instanceof z.ZodError) {
|
||||||
throw new Error(`Validation Error: ${error.errors.map(e => e.message).join(', ')}`);
|
reply.code(400);
|
||||||
}
|
throw new Error(`Validation Error: ${error.errors.map((e) => e.message).join(', ')}`);
|
||||||
reply.code(500);
|
}
|
||||||
throw new Error('Failed to delete bank account.');
|
reply.code(500);
|
||||||
}
|
throw new Error('Failed to delete bank account.');
|
||||||
}
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// 4. Get All Bank Accounts
|
// 4. Get All Bank Accounts
|
||||||
server.get(
|
server.get(`${API_PREFIX}/`, async (request, reply): Promise<BankAccount[]> => {
|
||||||
`${API_PREFIX}/`,
|
// No input validation needed for getting all items usually
|
||||||
async (request, reply): Promise<BankAccount[]> => {
|
try {
|
||||||
// No input validation needed for getting all items usually
|
const accounts = await prisma.bankAccount.findMany({
|
||||||
try {
|
orderBy: { createdAt: 'desc' },
|
||||||
const accounts = await prisma.bankAccount.findMany({
|
});
|
||||||
orderBy: { createdAt: 'desc' }
|
return accounts;
|
||||||
});
|
} catch (error: any) {
|
||||||
return accounts;
|
server.log.error(error);
|
||||||
} catch (error: any) {
|
reply.code(500);
|
||||||
server.log.error(error);
|
throw new Error('Failed to retrieve bank accounts.');
|
||||||
reply.code(500);
|
}
|
||||||
throw new Error('Failed to retrieve bank accounts.');
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Optional: Get Single Bank Account by ID
|
// Optional: Get Single Bank Account by ID
|
||||||
server.get(
|
server.get(
|
||||||
`${API_PREFIX}/:id`,
|
`${API_PREFIX}/:id`,
|
||||||
{
|
{
|
||||||
schema: { // Define Zod schema for params
|
schema: {
|
||||||
params: paramsSchema
|
// Define Zod schema for params
|
||||||
}
|
params: paramsSchema,
|
||||||
},
|
},
|
||||||
async (request, reply): Promise<BankAccount> => {
|
},
|
||||||
try {
|
async (request, reply): Promise<BankAccount> => {
|
||||||
// request.params.id is now a validated number
|
try {
|
||||||
const { id } = request.params;
|
// request.params.id is now a validated number
|
||||||
|
const { id } = request.params;
|
||||||
|
|
||||||
const account = await prisma.bankAccount.findUnique({
|
const account = await prisma.bankAccount.findUnique({
|
||||||
where: { id: id }, // Use the validated numeric ID
|
where: { id: id }, // Use the validated numeric ID
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
reply.code(404);
|
reply.code(404);
|
||||||
throw new Error(`Bank account with ID ${id} not found.`);
|
throw new Error(`Bank account with ID ${id} not found.`);
|
||||||
}
|
}
|
||||||
return account;
|
return account;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Handle Zod validation errors (though should be caught by Fastify earlier)
|
// Handle Zod validation errors (though should be caught by Fastify earlier)
|
||||||
if (error instanceof z.ZodError) {
|
if (error instanceof z.ZodError) {
|
||||||
reply.code(400);
|
reply.code(400);
|
||||||
throw new Error(`Validation Error: ${error.errors.map(e => e.message).join(', ')}`);
|
throw new Error(`Validation Error: ${error.errors.map((e) => e.message).join(', ')}`);
|
||||||
}
|
}
|
||||||
// If Prisma throws or other errors occur after validation
|
// If Prisma throws or other errors occur after validation
|
||||||
if (!reply.sent) {
|
if (!reply.sent) {
|
||||||
// Specific check for Prisma's RecordNotFound (though findUnique returns null, not throws P2025 by default)
|
// Specific check for Prisma's RecordNotFound (though findUnique returns null, not throws P2025 by default)
|
||||||
// The !account check above handles the "not found" case for findUnique
|
// The !account check above handles the "not found" case for findUnique
|
||||||
|
|
||||||
server.log.error(error); // Log other unexpected errors
|
server.log.error(error); // Log other unexpected errors
|
||||||
reply.code(500);
|
reply.code(500);
|
||||||
throw new Error('Failed to retrieve bank account.');
|
throw new Error('Failed to retrieve bank account.');
|
||||||
}
|
}
|
||||||
// If reply already sent (e.g., 404), just rethrow the original error
|
// If reply already sent (e.g., 404), just rethrow the original error
|
||||||
throw error;
|
throw error;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// --- Graceful Shutdown ---
|
// --- Graceful Shutdown ---
|
||||||
const gracefulShutdown = async (signal: string) => {
|
const gracefulShutdown = async (signal: string) => {
|
||||||
console.log(`*^! Received signal ${signal}. Shutting down...`);
|
console.log(`*^! Received signal ${signal}. Shutting down...`);
|
||||||
try {
|
try {
|
||||||
await server.close();
|
await server.close();
|
||||||
console.log('Fastify server closed.');
|
console.log('Fastify server closed.');
|
||||||
await prisma.$disconnect();
|
await prisma.$disconnect();
|
||||||
console.log('Prisma client disconnected.');
|
console.log('Prisma client disconnected.');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error during shutdown:', err);
|
console.error('Error during shutdown:', err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
||||||
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
||||||
|
|
||||||
// --- Start Server (unchanged) ---
|
// --- Start Server (unchanged) ---
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
try {
|
try {
|
||||||
const host = process.env.API_HOST || '0.0.0.0';
|
const host = process.env.API_HOST || '0.0.0.0';
|
||||||
const port = parseInt(process.env.API_PORT || '3000', 10);
|
const port = Number.parseInt(process.env.API_PORT || '3000', 10);
|
||||||
await server.listen({ port, host });
|
await server.listen({ port, host });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
server.log.error(err);
|
server.log.error(err);
|
||||||
await prisma.$disconnect();
|
await prisma.$disconnect();
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
start();
|
start();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { GET as listAccounts } from '../pages/api/accounts/index';
|
|
||||||
import { GET as getAccount } from '../pages/api/accounts/[id]/index';
|
import { GET as getAccount } from '../pages/api/accounts/[id]/index';
|
||||||
import { GET as listTransactions } from '../pages/api/accounts/[id]/transactions/index';
|
import { GET as listTransactions } from '../pages/api/accounts/[id]/transactions/index';
|
||||||
|
import { GET as listAccounts } from '../pages/api/accounts/index';
|
||||||
import { createMockAPIContext } from './setup';
|
import { createMockAPIContext } from './setup';
|
||||||
|
|
||||||
describe('Accounts API', () => {
|
describe('Accounts API', () => {
|
||||||
@@ -48,7 +48,7 @@ describe('Accounts API', () => {
|
|||||||
|
|
||||||
it('should return empty array for account with no transactions', async () => {
|
it('should return empty array for account with no transactions', async () => {
|
||||||
const response = await listTransactions(
|
const response = await listTransactions(
|
||||||
createMockAPIContext({ params: { id: '999' } }) as any
|
createMockAPIContext({ params: { id: '999' } }) as any,
|
||||||
);
|
);
|
||||||
const transactions = await response.json();
|
const transactions = await response.json();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import type { APIContext } from 'astro';
|
||||||
import { beforeEach } from 'vitest';
|
import { beforeEach } from 'vitest';
|
||||||
import { accounts, transactions } from '../data/store';
|
import { accounts, transactions } from '../data/store';
|
||||||
import type { APIContext } from 'astro';
|
|
||||||
|
|
||||||
// Create a mock APIContext factory
|
// Create a mock APIContext factory
|
||||||
export function createMockAPIContext<T extends Record<string, string> = Record<string, string>>({
|
export function createMockAPIContext<T extends Record<string, string> = Record<string, string>>({
|
||||||
@@ -43,7 +43,7 @@ beforeEach(() => {
|
|||||||
name: 'Test Savings',
|
name: 'Test Savings',
|
||||||
last4: '5678',
|
last4: '5678',
|
||||||
balance: 5000.0,
|
balance: 5000.0,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Reset transactions to initial state
|
// Reset transactions to initial state
|
||||||
@@ -62,6 +62,6 @@ beforeEach(() => {
|
|||||||
date: '2025-04-24',
|
date: '2025-04-24',
|
||||||
description: 'Test Transaction 2',
|
description: 'Test Transaction 2',
|
||||||
amount: 100.0,
|
amount: 100.0,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
// - Add load testing for API endpoints
|
// - Add load testing for API endpoints
|
||||||
// - Implement test data factories
|
// - Implement test data factories
|
||||||
|
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { POST as createTransaction } from '../pages/api/transactions/index';
|
|
||||||
import {
|
|
||||||
PUT as updateTransaction,
|
|
||||||
DELETE as deleteTransaction,
|
|
||||||
} from '../pages/api/transactions/[id]/index';
|
|
||||||
import { accounts, transactions } from '../data/store';
|
import { accounts, transactions } from '../data/store';
|
||||||
|
import {
|
||||||
|
DELETE as deleteTransaction,
|
||||||
|
PUT as updateTransaction,
|
||||||
|
} from '../pages/api/transactions/[id]/index';
|
||||||
|
import { POST as createTransaction } from '../pages/api/transactions/index';
|
||||||
import type { Transaction } from '../types';
|
import type { Transaction } from '../types';
|
||||||
import { createMockAPIContext } from './setup';
|
import { createMockAPIContext } from './setup';
|
||||||
|
|
||||||
@@ -273,7 +273,7 @@ describe('Transactions API', () => {
|
|||||||
const initialCount = transactions.length;
|
const initialCount = transactions.length;
|
||||||
|
|
||||||
const response = await deleteTransaction(
|
const response = await deleteTransaction(
|
||||||
createMockAPIContext({ params: { id: '1' } }) as any
|
createMockAPIContext({ params: { id: '1' } }) as any,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(response.status).toBe(204);
|
expect(response.status).toBe(204);
|
||||||
@@ -292,7 +292,7 @@ describe('Transactions API', () => {
|
|||||||
|
|
||||||
it('should return 404 for non-existent transaction', async () => {
|
it('should return 404 for non-existent transaction', async () => {
|
||||||
const response = await deleteTransaction(
|
const response = await deleteTransaction(
|
||||||
createMockAPIContext({ params: { id: '999' } }) as any
|
createMockAPIContext({ params: { id: '999' } }) as any,
|
||||||
);
|
);
|
||||||
|
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
@@ -313,7 +313,7 @@ describe('Transactions API', () => {
|
|||||||
transactions.push(testTransaction);
|
transactions.push(testTransaction);
|
||||||
|
|
||||||
const response = await deleteTransaction(
|
const response = await deleteTransaction(
|
||||||
createMockAPIContext({ params: { id: 'test-delete' } }) as any
|
createMockAPIContext({ params: { id: 'test-delete' } }) as any,
|
||||||
);
|
);
|
||||||
|
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
|
|||||||
Reference in New Issue
Block a user