From 78c312f279f78361882fda23a64a672c1992c68e Mon Sep 17 00:00:00 2001 From: Joe Arndt Date: Mon, 9 Feb 2026 13:53:42 -0600 Subject: [PATCH] added expenses resource --- bruno/Common Cents/Categories/folder.bru | 1 + .../Common Cents/Expenses/LOC DEL Expense.bru | 11 --- .../Expenses/LOC DELETE Expense.bru | 15 ++++ .../Expenses/LOC GET Expense By ID.bru | 6 +- .../Expenses/LOC GET Expenses.bru | 2 +- .../Expenses/LOC PATCH Expense Full.bru | 23 ------ .../Expenses/LOC POST Expense Full.bru | 23 ------ .../Expenses/LOC POST Expense Partial.bru | 20 ----- .../Expenses/LOC POST Expense.bru | 38 +++++++++ .../Common Cents/Expenses/LOC PUT Expense.bru | 21 +++++ bruno/Common Cents/Expenses/folder.bru | 9 ++- bruno/Common Cents/Merchants/folder.bru | 1 + bruno/Common Cents/Sub-categories/folder.bru | 1 + bruno/Common Cents/Tags/folder.bru | 1 + common-cents.db | Bin 45056 -> 114688 bytes src/app.module.ts | 7 +- src/categories/entities/category.entity.ts | 2 +- src/expenses/dto/create-expense.dto.ts | 10 +++ src/expenses/dto/update-expense.dto.ts | 6 ++ src/expenses/entities/expense.entity.ts | 39 +++++++++ src/expenses/expense-data.service.ts | 34 ++++++++ src/expenses/expenses.controller.ts | 75 ++++++++++++++++++ src/expenses/expenses.module.ts | 10 +++ src/expenses/expenses.service.ts | 35 ++++++++ src/merchants/entities/merchant.entity.ts | 2 +- .../entities/sub-category.entity.ts | 2 +- src/tags/entities/tag.entity.ts | 2 +- 27 files changed, 310 insertions(+), 86 deletions(-) delete mode 100644 bruno/Common Cents/Expenses/LOC DEL Expense.bru create mode 100644 bruno/Common Cents/Expenses/LOC DELETE Expense.bru delete mode 100644 bruno/Common Cents/Expenses/LOC PATCH Expense Full.bru delete mode 100644 bruno/Common Cents/Expenses/LOC POST Expense Full.bru delete mode 100644 bruno/Common Cents/Expenses/LOC POST Expense Partial.bru create mode 100644 bruno/Common Cents/Expenses/LOC POST Expense.bru create mode 100644 bruno/Common Cents/Expenses/LOC PUT Expense.bru create mode 100644 src/expenses/dto/create-expense.dto.ts create mode 100644 src/expenses/dto/update-expense.dto.ts create mode 100644 src/expenses/entities/expense.entity.ts create mode 100644 src/expenses/expense-data.service.ts create mode 100644 src/expenses/expenses.controller.ts create mode 100644 src/expenses/expenses.module.ts create mode 100644 src/expenses/expenses.service.ts diff --git a/bruno/Common Cents/Categories/folder.bru b/bruno/Common Cents/Categories/folder.bru index c32401e..ae65a21 100644 --- a/bruno/Common Cents/Categories/folder.bru +++ b/bruno/Common Cents/Categories/folder.bru @@ -1,5 +1,6 @@ meta { name: Categories + seq: 2 } auth { diff --git a/bruno/Common Cents/Expenses/LOC DEL Expense.bru b/bruno/Common Cents/Expenses/LOC DEL Expense.bru deleted file mode 100644 index dfb6024..0000000 --- a/bruno/Common Cents/Expenses/LOC DEL Expense.bru +++ /dev/null @@ -1,11 +0,0 @@ -meta { - name: LOC DEL Expense - type: http - seq: 3 -} - -delete { - url: {{localBaseUrl}}/expenses/2aa94170-2c57-4c4f-a1e7-13544ba72917 - body: none - auth: inherit -} diff --git a/bruno/Common Cents/Expenses/LOC DELETE Expense.bru b/bruno/Common Cents/Expenses/LOC DELETE Expense.bru new file mode 100644 index 0000000..6725687 --- /dev/null +++ b/bruno/Common Cents/Expenses/LOC DELETE Expense.bru @@ -0,0 +1,15 @@ +meta { + name: LOC DELETE Expense + type: http + seq: 5 +} + +delete { + url: {{localBaseUrl}}/{{resourcePath}}/{{resourceId}} + body: none + auth: inherit +} + +vars:pre-request { + resourceId: 41294f87-ab77-4fdd-9509-6968e6533962 +} diff --git a/bruno/Common Cents/Expenses/LOC GET Expense By ID.bru b/bruno/Common Cents/Expenses/LOC GET Expense By ID.bru index 935e4ee..c591a98 100644 --- a/bruno/Common Cents/Expenses/LOC GET Expense By ID.bru +++ b/bruno/Common Cents/Expenses/LOC GET Expense By ID.bru @@ -5,7 +5,11 @@ meta { } get { - url: {{localBaseUrl}}/expenses/2aa94170-2c57-4c4f-a1e7-13544ba72917 + url: {{localBaseUrl}}/{{resourcePath}}/{{resourceId}} body: none auth: inherit } + +vars:pre-request { + resourceId: 1d6d2842-b271-489b-bd93-e3ceaee5a139 +} diff --git a/bruno/Common Cents/Expenses/LOC GET Expenses.bru b/bruno/Common Cents/Expenses/LOC GET Expenses.bru index b2e8e2c..17a85b6 100644 --- a/bruno/Common Cents/Expenses/LOC GET Expenses.bru +++ b/bruno/Common Cents/Expenses/LOC GET Expenses.bru @@ -5,7 +5,7 @@ meta { } get { - url: {{localBaseUrl}}/expenses + url: {{localBaseUrl}}/{{resourcePath}} body: none auth: inherit } diff --git a/bruno/Common Cents/Expenses/LOC PATCH Expense Full.bru b/bruno/Common Cents/Expenses/LOC PATCH Expense Full.bru deleted file mode 100644 index e9a5f02..0000000 --- a/bruno/Common Cents/Expenses/LOC PATCH Expense Full.bru +++ /dev/null @@ -1,23 +0,0 @@ -meta { - name: LOC PATCH Expense Full - type: http - seq: 6 -} - -patch { - url: {{localBaseUrl}}/expenses/0708e7f7-3a2a-4b93-81da-38954925ca78 - body: json - auth: inherit -} - -body:json { - { - "date": "2025-12-15", - "cents": 888, - "categoryId": "1", - "merchantId": "1", - "subcategoryIds": ["2"], - "tagIds": ["2"], - "description": "PATCHED Desc." - } -} diff --git a/bruno/Common Cents/Expenses/LOC POST Expense Full.bru b/bruno/Common Cents/Expenses/LOC POST Expense Full.bru deleted file mode 100644 index d0e6451..0000000 --- a/bruno/Common Cents/Expenses/LOC POST Expense Full.bru +++ /dev/null @@ -1,23 +0,0 @@ -meta { - name: LOC POST Expense Full - type: http - seq: 4 -} - -post { - url: {{localBaseUrl}}/expenses - body: json - auth: inherit -} - -body:json { - { - "date": "2025-12-01", - "cents": 987, - "categoryId": "1", - "merchantId": "1", - "subcategoryIds": ["1","2"], - "tagIds": ["1","2"], - "description": "NEW FULL expense" - } -} diff --git a/bruno/Common Cents/Expenses/LOC POST Expense Partial.bru b/bruno/Common Cents/Expenses/LOC POST Expense Partial.bru deleted file mode 100644 index bfc7302..0000000 --- a/bruno/Common Cents/Expenses/LOC POST Expense Partial.bru +++ /dev/null @@ -1,20 +0,0 @@ -meta { - name: LOC POST Expense Partial - type: http - seq: 5 -} - -post { - url: {{localBaseUrl}}/expenses - body: json - auth: inherit -} - -body:json { - { - "date": "2025-12-01", - "cents": 987, - "categoryId": "1", - "description": "NEW PARTIAL expense" - } -} diff --git a/bruno/Common Cents/Expenses/LOC POST Expense.bru b/bruno/Common Cents/Expenses/LOC POST Expense.bru new file mode 100644 index 0000000..a458095 --- /dev/null +++ b/bruno/Common Cents/Expenses/LOC POST Expense.bru @@ -0,0 +1,38 @@ +meta { + name: LOC POST Expense + type: http + seq: 3 +} + +post { + url: {{localBaseUrl}}/{{resourcePath}} + body: json + auth: inherit +} + +body:json { + { + "year": "2026", + "month": "01", + "day": "02", + "cents": 1000, + "description": "With cat, subcat and merchant and two tags", + "category": { + "id": "a73fbfdd-1949-4be9-8cdc-ea9197b9b8b6" + }, + "subCategory": { + "id": "270ceaea-9cb8-4c6a-846f-ea35ed4d12f7" + }, + "merchant": { + "id": "61246db4-3110-4fe2-bdab-d819b8fd0705" + }, + "tags": [ + { + "id": "2616f724-f3ce-46df-8b30-897f147f6b74" + }, + { + "id": "16ed84e0-2d17-45c7-ada8-6fe7f8cc8720" + } + ] + } +} diff --git a/bruno/Common Cents/Expenses/LOC PUT Expense.bru b/bruno/Common Cents/Expenses/LOC PUT Expense.bru new file mode 100644 index 0000000..7042846 --- /dev/null +++ b/bruno/Common Cents/Expenses/LOC PUT Expense.bru @@ -0,0 +1,21 @@ +meta { + name: LOC PUT Expense + type: http + seq: 4 +} + +put { + url: {{localBaseUrl}}/{{resourcePath}} + body: json + auth: inherit +} + +body:json { + { + "id": "0254a07d-9dfa-4c06-b7b3-48d30efff991", + "description": "Cat, no sub-cat or merchant", + "category": { + "id": "db434e98-5f32-4202-b69c-eb7d1d248b3e" + } + } +} diff --git a/bruno/Common Cents/Expenses/folder.bru b/bruno/Common Cents/Expenses/folder.bru index 9a3fbf0..5e0ac02 100644 --- a/bruno/Common Cents/Expenses/folder.bru +++ b/bruno/Common Cents/Expenses/folder.bru @@ -1,4 +1,11 @@ meta { name: Expenses - seq: 2 +} + +auth { + mode: inherit +} + +vars:pre-request { + resourcePath: expenses } diff --git a/bruno/Common Cents/Merchants/folder.bru b/bruno/Common Cents/Merchants/folder.bru index 2ffb460..dc13882 100644 --- a/bruno/Common Cents/Merchants/folder.bru +++ b/bruno/Common Cents/Merchants/folder.bru @@ -1,5 +1,6 @@ meta { name: Merchants + seq: 3 } auth { diff --git a/bruno/Common Cents/Sub-categories/folder.bru b/bruno/Common Cents/Sub-categories/folder.bru index a7f720c..47ece91 100644 --- a/bruno/Common Cents/Sub-categories/folder.bru +++ b/bruno/Common Cents/Sub-categories/folder.bru @@ -1,5 +1,6 @@ meta { name: Sub-categories + seq: 4 } auth { diff --git a/bruno/Common Cents/Tags/folder.bru b/bruno/Common Cents/Tags/folder.bru index aa07bea..ba06b7e 100644 --- a/bruno/Common Cents/Tags/folder.bru +++ b/bruno/Common Cents/Tags/folder.bru @@ -1,5 +1,6 @@ meta { name: Tags + seq: 5 } auth { diff --git a/common-cents.db b/common-cents.db index 656df02793aebdfd293419782f8ff440411bd172..8458d51efdbffc595ac0c755511bc6ef36986968 100644 GIT binary patch literal 114688 zcmeI5&u<&&na4>{rYu|1*l`lpQCv@$sxc9dHSheM>?SHJ(Saa`vLz84A6nv_nRjdf z5oJqs93S=|J2rwI7R44Q3iMLsxCJ)A<{t?5QWVW$(MyWWVbNO_SfJU%9@-+nK0|V- zAt_p;#B~z+OGupI^ZuCU{k+fna^CmhyzlCjOY2P`hnrg)yeaFM6Pau_^8;DVWHK+( z|MT?!9Q}{be~tb}=|6sDf)UnqOl!E+ZCLiU#XPWD)L(ShnFgHv6Ibicu_uA zTn~!!+kDHv&bQ>tD~oTGSFXxSm8)`fc}=d?E?qh=7w-tZwHqlBu(8=_Uf(k$;CJ?v z`=Zg@F3RhTrnn}yk{1cYw!gK0qq)A>=%%D=`rH-bJ9<0O zX{w9Wh02?9@!^S!(e;b{SwxjfIZ;a~$vcxyTNl>V#dH`m4Uc(l7-$w#sTG)-qiUMd zpV9cBF2fTK*QJwfOP@JAUszT4Gwm2g*T%bezH@KZ8Y?JD_QA=dv3JA!i%{t8`TBz+ zRJ-bR`@clj)&{vpNM9E-9E{!H#q3_Nw}|MKMQpvF7`Dm}vgw z(rhtbm_IWNS4_69#b?jVzK|*bixcE_F34krGiS1QXIpq;d3+q{#S)7jEfUrA)KKNB zYcP#5FCY{ub3N1Z^+6;vXY&R3)G#E}!PhlTl2 z{*Lw*nk0|5ypC>|s%mh~Rn-Yxl{vyzZQWru4I~Ck;Q5Eu=!S?!M~!A(i_cEYPNz!1 z;sj6aTzD>N0-2L0J5W5E3B_T;^mI;vhUiUtd($T0zap5*xN4}1uB%)zLZ&LN zsd|d%xjOyyfb!_OCj9?Tys-iR2!H?xfB*=900@8p2!H?xfWYG<5V`f!%ow!%|7qq2 zTK@ku^8u0npBDXt|Nn7TH?9Q&AOHd&00JNY0w4eaAOHd&a2N=DtNj1n@&BI=1A3GJ z1V8`;KmY_l00ck)1V8`;KmY`uU;?MJIrnI5hchXgn>m`?;-mfieOSopU;9{XxpdDJC-HM-l{{Mq$2Y@eMP^1I_ z5C8!X009sH0T2KI5C8!X009u#k3jVNzv|F~{k~%Wj?i#Gf*gA5FQEN+)=M z00@8p2!H?xfB*=900@A<;UKV6J#wt}%*m4@yjH8F8A#Cv>Xyn>E%(_!tT(UAv|Rgn znSMV_N0~PQc|&aZ*LkD;p?P~#Zt`o}X?Ai;v)bFP- zYOCh$Y`Z-3wM<6UOoOYmT)Z2EwEnxVTC{MyM@z^%fv$=$3|*IX%ENM>(Jt`k<;G@I znrIbzd2_2aQV00@8p2!H?xfB*=900@8p2*d<1{*OuE2m&Ag0w4eaAOHd& z00JNY0w4eahnE1J|3AESi;{r=2!H?xfB*=900@8p2!H?xeA5UV==uLNkM*a1{(q8C zR}HC1QaUXpTWUMQS#qogZC~ z_Kb9OMd-fZLYSQCZa2|#Lv;QBwE91G|2udEBLxV600@8p2!H?xfB*=900@8p2&5+v z`Tx_*613L;Pctvj^8crq3uur3Kh8*>OCL-Bm0n}S1OX5L0T2KI5C8!X009sH0T2KI z5cqlsJe4bDyT1L_?+9`yax)3h?adJ>H#6Ri)Aj#E=5C8!X009sH0T2KI5C8!X0D)8lp3SlB>Mc+4d&m7x=afD%jr~vMl<}Up zH2!a||9?6oeJF*}=~S0QBoF`r5C8!X009sH0T2KI5C8!XI6MTFXyyKz>V%=`JCnpCXZN;`-pKG?N+M&xV5jciW6)j*k#qn*02i#GtP}rg4`;M)tOSGPUs?5^t z?YBhh{MQ6?ZQTzn#Si_E*7>J5$5Cy?V!mzLw9LO}*l99rum4xbNCinqA0D3SLrFjY z1V8`;KmY_l00ck)1V8`;Kp+KyH%4oniF6x(ROA21|DQRL$w>b^ z^+gIUhyns200JNY0w4eaAOHd&00JNY0^dFYJ5P-qt37k_bh%bnyOhUQ?=Y@Yht@8FNke_YyC!ZeX}8J+}1-cO2u5mRSZwK zisJ{qBDhPwde8M7&!V3U_<{?rxW4BohHr7jF|3f{=%xsafN7!qR<^B^Qp;pi%`~`b zlO{pPDZXkcp6%)6?+f@xE>VcxA~TToo~sPR~FwWuUwUvDp%#|@|s+&UAlB$ zF5VG*Yd2CNU}Lkc<@N?s(`oiysEq-)0uI4tg}Nz(0G zUc6dkPr&?gb#-l}yjWe6ix-#bh9(3z1L66W9?~?D7do!vFrTTsC|_J&sVu%;Z58%h z@;W7XrE;;dQmM{YR(BQXuqw&RRe7OusX{QT%W`>sZIJ}@SG&9r0q)Aq%YDk&p6P4c z5}IQ>x~zcRhW>(z(C>M@jq>Qoy2m+Vh#v)RmgBM|Rw-+XJmDeC-I zbF=li-ZG;tzICVGv5z_%5FO8CvcHLJT$#%k%G1NL>SoY6d5)6(G_ub7H(M6l5qA!! zpH%BC&*uwYtv(2B?qaJx$_J^B<2&Q-SYc7g?vz?}zqs&bo!bFTr3y#5s$)iva#@xc z3Ss$VE3Lky-sIP|qqh^C`dX|mRNj<}4^Lc-T1VXDbhC&|mvW-kTatGsowhEltBdI{ zW*Q#z+%TYDf2q`DP0dj?&FR-=d{CF+iHGab$+o4>oSiSMD*Kss45QBaRr=1oS!=AI zDA@-mlg8dHdw=}Gp61_;9--P*uiIHC`r$UnJwp1rpy6Qb{w`?ug1y~ON4`hTGhbev zFE3OQePKs3&=U?fG`C-k*hG>&GRY2h`Q+DH5+!|+bn-9l$R#qtx$I75*95(Qo>Wu~ zpT0IYc_D1m^2|_kIgjfv?uNVl@zfr+I5d}DTO7zMDq<{bx018x+A7X>Rb)c-bY>fl z&;rZSOePGEJTkh`ZLx6RUDp63Kf%fa^-fe3jgZ&#>oFs9>k+^^9YiYxo`u!%JB$zbwj{IPz-lv8+)cUM` zH4??^ZSiry!4!E&n~O7yth!{+Exm` z9lA-aRTr<+D%2IUGAos4i}}L*nPIqMvUM#!duH~9R0&v|Ah&Zt9xI$Vlf66J!V}Bm z<47--So~;_sHUfeDpy?^(=p}+ghFMmXL`Oqh-Bt$zTlo3hJ-r!x<+qL&wO{kXpEvw z?3_I_RycJkdv~T)d=k;$(cVInruWh@Lbf&&QsLVRd*-2vai^HLSsWM!eVwyYC5->YkO6i{#`r%uO70yEe8o}8 zeQ%-Fb^K)e{+pdMs)o1|xxYX%nk4*5Th_T&=kSC~VW;Xkz5#Q|->B|vKpEz%G!?=e z&tqI;ju|rHm@YTCJ}}dj=&E~7Jr3x6JEflaFJ&?#_rKb#F3qx!+>E~N6`CME&@B7N zD=i;TrPZi<8&Kc3aBoZAZ!mkvzIzXU15zpnu^jjVwsAj@@Ujf_lAYe7H52`)XDPpd H{(%1n!_<=O delta 430 zcmZo@U~hQ9G(lQWgn@y98;Ci8m=TD%Ch8a~iZJLEE#l>~X5i%IVc-wpKhAfOUzhhK zuPg5kJ}+J#o*$b91#WS>HA-`_i(6VUHU>5dLRq#YiAg!BC8@au`9+CEmGQ-;N%6^v zC8_E8MU~h^xh6C7giC9BbF+!ZTQV|a=B1=o6c^@XmZZj;1I4^mCvW7Hn0$a+N}Rip zgI!!!ma)+kYzfTR$p<;bCu{J!GHdE{PCmycGx<4>5F_XGb?X^}%=mvW@QbUeGB!qn zwbTM#3F1LqiR?CHHrPdCnn}#;;=;m=joJ_|C8n1orn5}u=ksIMRArgGmRCW=5GcZ0 z$cXHy+|;7vjKsVW7|k?UmOmKiGA5wQ^f;NJ0nM<4QDIX-JpaT1{>gduvo~{Y_|CtD gF@=9}iu{~K2?Co154`7}xIu8!hc5ojUHWAT01ME6>i_@% diff --git a/src/app.module.ts b/src/app.module.ts index fd2edf1..d831a58 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -9,12 +9,14 @@ import { CategoriesModule } from './categories/categories.module'; import { Category } from './categories/entities/category.entity'; import { SubCategoriesModule } from './sub-categories/sub-categories.module'; import { SubCategory } from './sub-categories/entities/sub-category.entity'; +import { ExpensesModule } from './expenses/expenses.module'; +import { Expense } from './expenses/entities/expense.entity'; const sqliteConfig: TypeOrmModuleOptions = { synchronize: true, // typeorm -h (schema:sync) type: 'sqlite', database: 'common-cents.db', - entities: [Merchant, Tag, Category, SubCategory] + entities: [Merchant, Tag, Category, SubCategory, Expense] } @Module({ @@ -23,7 +25,8 @@ const sqliteConfig: TypeOrmModuleOptions = { MerchantsModule, TagsModule, CategoriesModule, - SubCategoriesModule + SubCategoriesModule, + ExpensesModule ], controllers: [AppController], providers: [] diff --git a/src/categories/entities/category.entity.ts b/src/categories/entities/category.entity.ts index 25ee60c..806866f 100644 --- a/src/categories/entities/category.entity.ts +++ b/src/categories/entities/category.entity.ts @@ -5,6 +5,6 @@ export class Category { @PrimaryGeneratedColumn('uuid') id: string; - @Column() + @Column({ unique: true }) name: string; } diff --git a/src/expenses/dto/create-expense.dto.ts b/src/expenses/dto/create-expense.dto.ts new file mode 100644 index 0000000..52a8f38 --- /dev/null +++ b/src/expenses/dto/create-expense.dto.ts @@ -0,0 +1,10 @@ +import { Category } from '../../categories/entities/category.entity'; + +export class CreateExpenseDto { + year: string; + month: string; + day: string; + cents: number; + description: string; + category: Category +} diff --git a/src/expenses/dto/update-expense.dto.ts b/src/expenses/dto/update-expense.dto.ts new file mode 100644 index 0000000..e5be379 --- /dev/null +++ b/src/expenses/dto/update-expense.dto.ts @@ -0,0 +1,6 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateExpenseDto } from './create-expense.dto'; + +export class UpdateExpenseDto extends PartialType(CreateExpenseDto) { + id: string; +} diff --git a/src/expenses/entities/expense.entity.ts b/src/expenses/entities/expense.entity.ts new file mode 100644 index 0000000..36a116d --- /dev/null +++ b/src/expenses/entities/expense.entity.ts @@ -0,0 +1,39 @@ +import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; +import { Tag } from '../../tags/entities/tag.entity'; +import { SubCategory } from '../../sub-categories/entities/sub-category.entity'; +import { Category } from '../../categories/entities/category.entity'; +import { Merchant } from '../../merchants/entities/merchant.entity'; + +@Entity() +export class Expense { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + year: string; + + @Column() + month: string; + + @Column() + day: string; + + @Column() + cents: number; + + @Column({ nullable: true }) + description: string; + + @ManyToOne(() => Category, { eager: true }) + category: Category; + + @ManyToOne(() => SubCategory, { nullable: true, eager: true }) + subCategory: SubCategory; + + @ManyToOne(() => Merchant, { nullable: true, eager: true }) + merchant: Merchant; + + @ManyToMany(() => Tag, { nullable: true, eager: true }) + @JoinTable() + tags: Tag[]; +} diff --git a/src/expenses/expense-data.service.ts b/src/expenses/expense-data.service.ts new file mode 100644 index 0000000..ceff180 --- /dev/null +++ b/src/expenses/expense-data.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource, Repository } from 'typeorm'; +import { Expense } from './entities/expense.entity'; +import { UpdateExpenseDto } from './dto/update-expense.dto'; +import { CreateExpenseDto } from './dto/create-expense.dto'; + +@Injectable() +export class ExpenseDataService { + private expenses: Repository; + + public constructor(private dataSource: DataSource) { + this.expenses = this.dataSource.getRepository(Expense); + } + + public async getAll(): Promise { + return await this.expenses.find(); + } + + public async getById(id: string): Promise { + return await this.expenses.findOneBy({ id }); + } + + public async create(expense: CreateExpenseDto): Promise { + return await this.expenses.save(expense); + } + + public async update(expense: UpdateExpenseDto): Promise { + return await this.expenses.save(expense); + } + + public async delete(id: string): Promise { + await this.expenses.delete({ id }); + } +} diff --git a/src/expenses/expenses.controller.ts b/src/expenses/expenses.controller.ts new file mode 100644 index 0000000..98350b9 --- /dev/null +++ b/src/expenses/expenses.controller.ts @@ -0,0 +1,75 @@ +import { + Controller, + Get, + Post, + Put, + Delete, + Body, + Param, + HttpCode, + HttpStatus, + BadRequestException, + NotFoundException, + InternalServerErrorException +} from '@nestjs/common'; +import { ExpensesService } from './expenses.service'; +import { CreateExpenseDto } from './dto/create-expense.dto'; +import { UpdateExpenseDto } from './dto/update-expense.dto'; +import { Expense } from './entities/expense.entity'; + +@Controller('expenses') +export class ExpensesController { + constructor(private readonly expensesService: ExpensesService) { } + + @Get() + @HttpCode(HttpStatus.OK) + public async findAll(): Promise { + return await this.expensesService.findAll(); + } + + @Get(':id') + @HttpCode(HttpStatus.OK) + public async findOne(@Param('id') id: string): Promise { + if (!id) { + throw new BadRequestException('No ID provided.'); + } + + try { + return await this.expensesService.findById(id); + } + catch (error) { + throw new NotFoundException(error); + } + } + + @Post() + @HttpCode(HttpStatus.CREATED) + public async create(@Body() expense: CreateExpenseDto): Promise { + if (!expense) { + throw new BadRequestException('Expense name cannot be empty.'); + } + + try { + return await this.expensesService.create(expense); + } + catch (error) { + throw new InternalServerErrorException(error); + } + } + + @Put() + @HttpCode(HttpStatus.OK) + public async update(@Body() expense: UpdateExpenseDto): Promise { + if (!expense.id) { + throw new BadRequestException('Expense ID cannot be empty.'); + } + + return await this.expensesService.update(expense); + } + + @Delete(':id') + @HttpCode(HttpStatus.NO_CONTENT) + public async remove(@Param('id') id: string): Promise { + return await this.expensesService.remove(id); + } +} diff --git a/src/expenses/expenses.module.ts b/src/expenses/expenses.module.ts new file mode 100644 index 0000000..2179916 --- /dev/null +++ b/src/expenses/expenses.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ExpensesService } from './expenses.service'; +import { ExpensesController } from './expenses.controller'; +import { ExpenseDataService } from './expense-data.service'; + +@Module({ + controllers: [ExpensesController], + providers: [ExpensesService, ExpenseDataService], +}) +export class ExpensesModule { } diff --git a/src/expenses/expenses.service.ts b/src/expenses/expenses.service.ts new file mode 100644 index 0000000..4e822e6 --- /dev/null +++ b/src/expenses/expenses.service.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@nestjs/common'; +import { CreateExpenseDto } from './dto/create-expense.dto'; +import { UpdateExpenseDto } from './dto/update-expense.dto'; +import { ExpenseDataService } from './expense-data.service'; +import { Expense } from './entities/expense.entity'; + +@Injectable() +export class ExpensesService { + public constructor(private expenseDataService: ExpenseDataService) { } + + public async findAll(): Promise { + return await this.expenseDataService.getAll(); + } + + public async findById(id: string): Promise { + const expense = await this.expenseDataService.getById(id); + if (!expense) { + throw new Error('No expense found'); + } + + return expense; + } + + public async create(expense: CreateExpenseDto): Promise { + return await this.expenseDataService.create(expense); + } + + public async update(expense: UpdateExpenseDto): Promise { + return await this.expenseDataService.update(expense); + } + + public async remove(id: string): Promise { + await this.expenseDataService.delete(id); + } +} diff --git a/src/merchants/entities/merchant.entity.ts b/src/merchants/entities/merchant.entity.ts index 0a5297f..87a61f9 100644 --- a/src/merchants/entities/merchant.entity.ts +++ b/src/merchants/entities/merchant.entity.ts @@ -5,6 +5,6 @@ export class Merchant { @PrimaryGeneratedColumn('uuid') id: string; - @Column() + @Column({ unique: true }) name: string; } diff --git a/src/sub-categories/entities/sub-category.entity.ts b/src/sub-categories/entities/sub-category.entity.ts index 87f85ae..f6bf282 100644 --- a/src/sub-categories/entities/sub-category.entity.ts +++ b/src/sub-categories/entities/sub-category.entity.ts @@ -5,6 +5,6 @@ export class SubCategory { @PrimaryGeneratedColumn('uuid') id: string; - @Column() + @Column({ unique: true }) name: string; } diff --git a/src/tags/entities/tag.entity.ts b/src/tags/entities/tag.entity.ts index fdf7a2d..ed84b59 100644 --- a/src/tags/entities/tag.entity.ts +++ b/src/tags/entities/tag.entity.ts @@ -5,6 +5,6 @@ export class Tag { @PrimaryGeneratedColumn('uuid') id: string; - @Column() + @Column({ unique: true }) name: string; }