Add TypeORM DB and Resources #2

Merged
joe merged 8 commits from add-sqlite into master 2026-02-09 00:10:26 +00:00
15 changed files with 1972 additions and 321 deletions
Showing only changes of commit ccc6540ae8 - Show all commits

1927
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -24,8 +24,11 @@
"@nestjs/core": "^11.0.1",
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/typeorm": "^11.0.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
"rxjs": "^7.8.1",
"sqlite3": "^5.1.7",
"typeorm": "^0.3.28"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",

View file

@ -1,11 +1,10 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { ExpensesService } from './services/expenses.service';
import { ExpensesController } from './controllers/expenses/expenses.controller';
import { MerchantsModule } from './merchants/merchants.module';
@Module({
imports: [],
controllers: [AppController, ExpensesController],
providers: [ExpensesService]
imports: [MerchantsModule],
controllers: [AppController],
providers: []
})
export class AppModule {}

View file

@ -1,24 +0,0 @@
import { PartialType } from '@nestjs/mapped-types';
export class CreateExpenseDto {
date: Date;
cents: number;
categoryId: string;
merchantId?: string;
subcategoryIds?: string[];
tagIds?: string[];
description?: string;
}
export class UpdateExpenseDto extends PartialType(CreateExpenseDto) {}
export class GetExpenseDto {
id: string;
date: Date;
cents: number;
category: string;
merchant?: string;
subcategories?: string[];
tags?: string[];
description?: string;
}

View file

@ -1,30 +0,0 @@
export class Expense {
id: string;
date: Date;
cents: number;
categoryId: string;
merchantId?: string;
subcategoryIds: string[];
tagIds: string[];
description?: string;
}
export class Category {
id: string;
category: string;
}
export class SubCategory {
id: string;
subcategory: string;
}
export class Merchant {
id: string;
merchant: string;
}
export class Tag {
id: string;
tag: string;
}

View file

@ -1,44 +0,0 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, NotFoundException, HttpCode } from '@nestjs/common';
import { ExpensesService } from '../../services/expenses.service';
import { CreateExpenseDto, UpdateExpenseDto } from './expense.dto';
@Controller('expenses')
export class ExpensesController {
constructor(private readonly expensesService: ExpensesService) {}
@Post()
create(@Body() createExpenseDto: CreateExpenseDto) {
return this.expensesService.create(createExpenseDto);
}
@Get()
findAll() {
return this.expensesService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
const expense = this.expensesService.findOne(id);
if (!expense) {
throw new NotFoundException();
}
return expense;
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateExpenseDto: UpdateExpenseDto) {
const expense = this.expensesService.update(id, updateExpenseDto);
if (!expense) {
throw new NotFoundException();
}
return expense;
}
@Delete(':id')
@HttpCode(204)
remove(@Param('id') id: string) {
return this.expensesService.remove(id);
}
}

View file

@ -0,0 +1 @@
export class CreateMerchantDto {}

View file

@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateMerchantDto } from './create-merchant.dto';
export class UpdateMerchantDto extends PartialType(CreateMerchantDto) {}

View file

@ -0,0 +1 @@
export class Merchant {}

View file

@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { MerchantsController } from './merchants.controller';
import { MerchantsService } from './merchants.service';
describe('MerchantsController', () => {
let controller: MerchantsController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [MerchantsController],
providers: [MerchantsService]
}).compile();
controller = module.get<MerchantsController>(MerchantsController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View file

@ -0,0 +1,34 @@
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { MerchantsService } from './merchants.service';
import { CreateMerchantDto } from './dto/create-merchant.dto';
import { UpdateMerchantDto } from './dto/update-merchant.dto';
@Controller('merchants')
export class MerchantsController {
constructor(private readonly merchantsService: MerchantsService) {}
@Post()
create(@Body() createMerchantDto: CreateMerchantDto) {
return this.merchantsService.create(createMerchantDto);
}
@Get()
findAll() {
return this.merchantsService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.merchantsService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateMerchantDto: UpdateMerchantDto) {
return this.merchantsService.update(+id, updateMerchantDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.merchantsService.remove(+id);
}
}

View file

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { MerchantsService } from './merchants.service';
import { MerchantsController } from './merchants.controller';
@Module({
controllers: [MerchantsController],
providers: [MerchantsService]
})
export class MerchantsModule {}

View file

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { MerchantsService } from './merchants.service';
describe('MerchantsService', () => {
let service: MerchantsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MerchantsService]
}).compile();
service = module.get<MerchantsService>(MerchantsService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View file

@ -0,0 +1,26 @@
import { Injectable } from '@nestjs/common';
import { CreateMerchantDto } from './dto/create-merchant.dto';
import { UpdateMerchantDto } from './dto/update-merchant.dto';
@Injectable()
export class MerchantsService {
create(createMerchantDto: CreateMerchantDto) {
return 'This action adds a new merchant';
}
findAll() {
return `This action returns all merchants`;
}
findOne(id: number) {
return `This action returns a #${id} merchant`;
}
update(id: number, updateMerchantDto: UpdateMerchantDto) {
return `This action updates a #${id} merchant`;
}
remove(id: number) {
return `This action removes a #${id} merchant`;
}
}

View file

@ -1,141 +0,0 @@
import { Injectable } from '@nestjs/common';
import { Category, Expense, Merchant, SubCategory, Tag } from '../controllers/expenses/expense.entities';
import { CreateExpenseDto, GetExpenseDto, UpdateExpenseDto } from '../controllers/expenses/expense.dto';
import { randomUUID } from 'node:crypto';
@Injectable()
export class ExpensesService {
private categories: Category[] = [
{
id: '1',
category: 'Category 1'
}
];
private merchants: Merchant[] = [
{
id: '1',
merchant: 'Merchant 1'
}
];
private subcategories: SubCategory[] = [
{
id: '1',
subcategory: 'Subcategory 1'
},
{
id: '2',
subcategory: 'Subcategory 2'
}
];
private tags: Tag[] = [
{
id: '1',
tag: 'Tag 1'
},
{
id: '2',
tag: 'Tag 2'
}
];
private expenses: Expense[] = [
{
id: '2aa94170-2c57-4c4f-a1e7-13544ba72917',
date: new Date('2025-12-01'),
cents: 15443,
categoryId: '1',
merchantId: '1',
subcategoryIds: ['1', '2'],
tagIds: ['1', '2'],
description: 'Full existing expense'
},
{
id: '0708e7f7-3a2a-4b93-81da-38954925ca78',
date: new Date('2025-12-02'),
cents: 8723,
categoryId: '1',
subcategoryIds: [],
tagIds: [],
description: 'Partial existing expense'
}
];
public create(createExpenseDto: CreateExpenseDto) {
const expense = {
id: randomUUID(),
date: new Date(createExpenseDto.date),
cents: createExpenseDto.cents,
categoryId: createExpenseDto.categoryId,
merchantId: createExpenseDto.merchantId,
subcategoryIds: createExpenseDto.subcategoryIds ?? [],
tagIds: createExpenseDto.tagIds ?? [],
description: createExpenseDto.description
} as Expense;
this.expenses.push(expense);
return this.mapExpense(expense);
}
public findAll() {
return this.expenses.map((expense) => {
return this.mapExpense(expense);
});
}
public findOne(id: string) {
const expense = this.getExpense(id);
return expense ? this.mapExpense(expense) : undefined;
}
public update(id: string, updateExpenseDto: UpdateExpenseDto) {
let index;
const expense = this.expenses.find((exp, idx) => {
if (exp.id === id) {
index = idx;
return exp;
}
});
if (expense && index) {
this.expenses[index] = {
...expense,
...updateExpenseDto
};
return this.mapExpense(this.expenses[index]);
}
return undefined;
}
public remove(id: string) {
this.expenses = this.expenses.filter((expense) => expense.id !== id);
}
private getExpense(id: string) {
return this.expenses.find((expense) => expense.id === id);
}
private getSubcategories(ids: string[]) {
return this.subcategories.filter((sub) => ids.includes(sub.id)).map((s) => s.subcategory);
}
private getTags(ids: string[]) {
return this.tags.filter((tag) => ids.includes(tag.id)).map((t) => t.tag);
}
private mapExpense(expense: Expense) {
const category = this.categories.find((category) => category.id === expense.categoryId);
const merchant = this.merchants.find((merchant) => merchant.id === expense.merchantId);
return {
id: expense.id,
date: expense.date,
cents: expense.cents,
category: category?.category,
merchant: merchant?.merchant,
subcategories: this.getSubcategories(expense.subcategoryIds),
tags: this.getTags(expense.tagIds),
description: expense.description
} as GetExpenseDto;
}
}