From 9b4cbdd0e37cf28c4aefcd5f8b1a9b6f75d88c6e Mon Sep 17 00:00:00 2001 From: Sanjib Sen Date: Mon, 20 Jan 2025 22:37:03 +0600 Subject: [PATCH] added prettier formatting --- README.md | 6 +- bun.lockb | Bin 194074 -> 194098 bytes docker-compose.yaml | 1 - drizzle/meta/0000_snapshot.json | 34 ++----- drizzle/meta/_journal.json | 2 +- package.json | 2 + src/api/index.ts | 6 +- src/api/routes/index.ts | 4 +- src/api/routes/note/note.controller.ts | 25 ++--- src/api/routes/note/note.model.ts | 51 +++++----- src/api/routes/note/note.route.ts | 113 +++++++++++---------- src/api/routes/note/note.test.ts | 125 +++++++++++------------ src/db/index.ts | 2 +- src/db/schema/auth.ts | 132 ++++++++++++++----------- src/db/schema/note.ts | 34 ++++--- src/emails/auth.tsx | 26 +++-- src/index.ts | 4 +- src/lib/auth/auth.ts | 34 ++++--- src/lib/mail/mail.ts | 20 +++- src/lib/storage/storage.ts | 35 +++---- src/lib/utils/common.ts | 12 +-- src/lib/utils/env.ts | 43 +++++--- test/client.ts | 28 +++--- tsconfig.json | 18 ++-- 24 files changed, 413 insertions(+), 344 deletions(-) diff --git a/README.md b/README.md index 688c87e..bebf061 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ # Elysia with Bun runtime ## Getting Started + To get started with this template, simply paste this command into your terminal: + ```bash bun create elysia ./elysia-example ``` ## Development + To start the development server run: + ```bash bun run dev ``` -Open http://localhost:3000/ with your browser to see the result. \ No newline at end of file +Open http://localhost:3000/ with your browser to see the result. diff --git a/bun.lockb b/bun.lockb index c7a287aab8af8d883f5606d858159c924f4e471b..c2de2b1d6a997d1f14822614b95d2b1588bf18eb 100755 GIT binary patch delta 16118 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%0DO4v4g3@O?||=tAAqv} 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 delta 16205 zcmeHO33yG{+TLp?hin=WLJ|>4BoY#tNoXWTj6IGRYDnCoLW9&0O`@nF(Ozx2SAV*0 z??S7tRtH)gP$|_Goy1T>X(?)Gt5j7{y=ZU!@4L@BYE45JQ&vck!Bro$lL0=cNRC-aHr#bX)K+|QHQ zbKv@j{|U^(MwLup-p0t1(!`>|5u;aH9{pm(GhY|}u+aTrrW;*YRx)Y4VQlv3D=K#) zfHak>wZRH4A4cx1NE+l|@B(lH@Sl(|?Pnp^hTtpU5by~wTVn>|8-weiv?kyn$n^Fq z9BKp(3-DB=xGb@>XjDbfgIP>zoX77&U=}~2w5Yti zxTtibobNlzdQ7LPl)@2QTmp$Lcc;#ymzJGircD5|j3!!dX}%ESDdQ-ZWo`j;P`(Q0 z;GIxfTs97k_N0!VD`z?;i8ZS)J9b)8-g)}>^Rs05#F6wjS)Qt&xw>V*NguyMLsM>F zm9TE^>VTlhx0vq;X>N4LIWak6nyij-TI(@tvtdwI_KikWYRMigazqO`yMupc*dug?HI;x?*UHbR$;ER!I*H284)rfc=az{CLNCy!hdnD#KA~E{f z$-L%S)+mJ7AU<+;Or}*0iTz|ZkHa6+ri+~0D$5#-5c}I9cel;nTs?vBs2&OzcxuzQ#N?a0D#?!pFhR3OxWA@hwOIVUB@yc8t2rsSZ3 ztW>9?2vh7#R05 zN z?I2m*#%Vo*)t>eZC5D8x1yTkiALeNO+(gc4n`32TMbRc0WRs+o;S?`QSBBFXhc%}= z%>6m+<;HH79~51$DL6CcU>RdmW5CB|I`%;7ApN7VtPrf59EuJ*X=Q)Q4~m{ppy=pK zYZW98VuRH(k2jM&+U1B8IlG-x442jIoYoUqSLqo#8s@tovEJ}AInz-q(lA^yuU(d- zH$t85(4*Hv`w((dXGKO`YmfL$F;Z6F>J-bQm4$g8C3|G$STW5puIyT{fd|X#ET^>) zx-7e>z^F{eUPw7IFD^?okh42Dt=#Bq^-j$c_ed+-DPEGUY^V8ow49TjV>OKN7}Sz| z6Ea0-Y2`RYbLq-K&&7J$4n8Diif3eXj?+99E9*FOtQJ^=b3N`M>R~zC>9kHmrw0iZ zirkiRPGrt%zmC)Hs0cL-gD?Sfoqg5h+R&id_H`r15@jHB13!$6mbTSD$Fy%{7sbFET3vw~bmrcYp=l2Jo_34tXNDf~nj9)-YoLz>7@l zffR6&X;%PHKM3GO#xl>v$BrKg=0)}eh65~pG{AJl0PV+Wc^nof1PucL+h9DH2}=PQ zPSo-gFe@+>;6-MJ(*W940KCXT<}HqB#aw1lh3nT?$mxqCL?~3ij=P*=c^A zS(o`hQ(z^)y1WGNB2&K=pne;`i;TtH_=Ey3vLA2+pxsecpBWzq$X@`wYBJqAX=^>q zgr@*D!Z%t^rhFEl{RPc`1yg?o;PsD;SRoHT8Ij9mS8bzzjj>)EL8z0E1C~ToZ~%r= z0ypEIzhlNn>-ZQjosQLZHJP=E*LpI830fx8sU*#*S`Uu5Gfanqx;FU30x~qWqY@Wc z$VPwZZO?=*T1#fItLAQ+yMtNno{ZGYTzf$#_tE+rF{AowyMEe^%wT_k&ZnORkg)9< z#oC}Ib97G7dNS?H!SsI;m=&0!d8+2=VEi{`;1BI)f|+iX);|blzH>za{@MYL;XIu{ zf_c?sA!jX(s9-h=bu5{|MOv=OY_BEIb0v5N%t5~r%*n7$+mjhwujPM&J#(Ih^m~I& z_-jo6--aD4@*bFeZwIr2yTQ!oBQP&A9sC4LyU(=#I-`7McnlGYJFX4>6=wP`k&b*) z=W|N)*E+wN%$DN~HyyZCp#wkb1XnfF4QA*=cQhl)@YOOIzhsQ2TK;#r;@?g98>OqT zJ>aGo4^P-fsbD&luDLCko@Z#e9V2m(x!rZrGMT|HTK+YrNjKXL{m%dk=&lozagSj1 z(ejO$QMcm{xj+6e{QwG@DG$`~1zJz0evp>QLWVz6n`6MZLu<+G_@Q8`hH3qcxH01I z)$#uU_u$R8@kgD}f5Jjud?rp9veB~_#tE%e@4E{dV%IeU^@Ibm0p(Pp7s2Q zc~Wa@GMi(m*58Pk+fzE8%$49dFjdR7p3Hn+1k-+{*4JR^^IV|4eY~RMYBJNjs?)C0 z@nin!MV7LHBQ1U z_RSgXUQOmIeGYoI&-XgMCJXuI^TGCJby>%f8T=7{xNew8%G3`2(ah57K&IU-T3?f? zs;~8A_ErFx_6@YY23ObxG|~}d8U|~bOgU7`HJR>(>-Y%GQJSN*eNEKIzgz%YB1?P7$Hr{h9L-KJ{u`a}hh68Q@C(dzJ#@U>vLeK81K!yY zETraL9YkAZrp+nfx)F0+!~x8_CBW;SVc1vj_RdaN^RA8=Q07MVo8H|azuN#-)CJ%d z-1sMI-MrnqdAny12HyT*Zrr@xvs>fl?cUAXy_>gt_Eh+#yR)0OdpB?QZr<+Mw|_Tp z_x77NZ})EA?)~!Z&<+5+nfG!<(qC{ z$;*%~K^pRwThx`y-^!QIzvVCe-*yXsIq2Md=pZAX>H7xejEMexQ%WRDAz%H z9a8uvw`eGfH|5LGoBZVtNR4FZ=6o5l*=wbY8qyX>iSM{YsGRanzMTAyzdQ^n zOtyMAU$%VLU(S8kEyCpiNc$mWzvmVaa`t=q^5OUVgAPuQ@iv+p68va$ozpZYOCUFE_(@NW60-c0o^tdjaOM-Y7$8IU!c-y{QDgKeeTBV z2i1_aKuY|=jaOo(d;$NyfPavt%2r>(zc1n6mu@j#9)PqTQuYbAsF1Tyz`qml57JC| z>W%!>?Y;_`p#sXppTvT_o3GD%<37f+o^SUZOj;@ z#5c$yNk@54llbg1S?hSBg#Ua^%t+C?y;{c;zmcjhv*-gA9|eD`qZ%U2=YFg_52N^e z4=>Fa-bcx2prdmTVTG)upt&3VYZ0Xio8QR~>`CsghkWI+db@l8<%BLF(Ui)U6yX+#1C5ytB( z*E%-}!Q*QF8S!*BpWg9_UoODo^bTM?8*KuF0Zj?y6Am;3B7jIB3W!$sd@I7@1%f7k z16E@IFc9EaxeaguJpdl=UNzL(Z$+>6Jf+_b>;OIlb^^PAJ-|o6USJ>aF)$C94a@=J zRrXmCk(7>L8=x(a0b~MfJGK=YAQE7E@w~H>kD7Be;j6ccFY*%i;Mcs(TPP z1RMsAsL=03bUY7@`T=~j&$EiBfn@-P33-W~!0jiJWjAM!A3)s`~euG&V45$#BddOP6W5$d3PSr31|a^0HHt=;3pLN zGw_LWof8oi4<>x&rNh_CQA<7sv!2gUz1-1uOvm3*a>2wBR)O9^k3{7+@^G zR|9$j89*~20*C~nc;w$40lp$}2@P=uI08%uW&qhh4&Vej0(|VuC;!a>9!2;AJfh%X z$G5;);5&dP$Xo(m2YB$r1FRLm3xERt1Plf4G%F0_cL)pw`21%+@F>95YY*@OkP0LL zDL^d1_cgvoMUDbUZcG9014@8 zU@CB*I(J0`H@Y9enE*PPlid(NrJ8v~Jl%-iE(ewYY`dp{UaI|%A|jBJmelfolWxR3jfVLDW*YpsLY6=0H>JcbIF<^Fqz5WrnonPbPoVbj8}AdcT~L@HbyE zG&KqTFahBOCQ;>xMK7N0SaR1cpUQu}#ltpSLsHyjRnScuq1O&&HwAHnMBUfvy zT^8ER`^n9UeFZDt|HCT}+7_w&LDn3rTOiu3;Kjk`qsHF8SI1zvnW4h#zyt5sJ6@3cmj=Byprvh)mV^=GF%Sz>7VN|dHQyh!T9rUDH`h@w`kNu<&N^yY zT{F%6$xrPk@2{h7xdnP}{NcK)BV?V-n~BcZlcm~ti`hiSc+&~nqOmXfa)C;$_si8p zGtW{(P_Z!Y2T%K&Zama)&AZFAqcHFkb1iF^de0wy@BL8fsd@|6O`Q8iN8My-dryh* zn+5BNsbTd{$$Dxk>+%cTmYU(|mC!KnM_dyo)NA}%hxDIy-tflLUm{HXjd|Oh*w`0k zu28ACjf)b z`*~K>!Q`QFL!-XJJZqbj#!2G+P^N7~2h8XO3cd5kzaMSyx){J{!NO$F3 ze&)It)6{Mlh`H)?fSDKO{W5IZ8Am~G?AsZzOUE>``>tOgntC}lSPR2ECeY`#_=V%! z!yv^|hgE86pc$Is{X#2Zv3ayjgEe0Th_`hT?>Aiak39Z)>LbG@1&A#=W_PgqF3?O9 z3#y_UptVFlmD>O@o1meSMTxUl!_&7usVh!#YA-`*i5fVYGnHUKXUl-wx{viqz)Qp$-(12)dQuY z?E#d^CDi*-T!)-JiFA z3%$6p?$o<(on{w-sf3?ZYA*BkegYUh_Lbss#>W$2kcL`f74NDx!9e7z)TU9#) zGMu`$qC=l|_Aa^xb9s&|`U z^@vkP7-Lgo)j7=U>j|f8D{O4;rZBLp5&AzRbyrifjXKd3*(53vZjNd@H^#GR6`yoJ znbc|15H@*AQYzLQ@8^8WGrPxjE^OC8$F#@Io?0A^dG7rV@P(kbjQsBJ-0m^pFYKep zv>}@RCv#px@10SrbW&_!7h=>m;n?lH-xhAPW{=C3)k{1U$Q!$TL^E`d_hZD5zpz@@ zpO>{rXMx=Jbprm_u<@_ePpMP2~Dcb0n;#{@NTJexow}C1rAqomq_vU#p^_mc*b4&kA2v zs|A;=HC1(E&Dy3|qZ+m}UjwDKGSk(rc(aMR6o(OJEAl)Psur~Js?xQoj?t2x5a9>n z%~7D!@o2R*%8_8_9F=BS&T(lAR_f z`C6J#buz`g-;*X!y`E~0_Jn8ZY^pY-nH_y<TF_wtwmj5+V;>8@Q(t-(`2o`DVAvo0BK zwFCyZe_B^yj>aCy++mKW LYJI<1tNgzKb=(FU diff --git a/docker-compose.yaml b/docker-compose.yaml index ffbf3ab..a746a02 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -10,4 +10,3 @@ services: OTEL_EXPORTER_OTLP_ENDPOINT: http://tracing:4318 OTEL_EXPORTER_OTLP_PROTOCOL: http/protobuf DATABASE_URL: ${DATABASE_URL} - diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json index 3ab4076..dfa6446 100644 --- a/drizzle/meta/0000_snapshot.json +++ b/drizzle/meta/0000_snapshot.json @@ -140,12 +140,8 @@ "tableFrom": "account", "tableTo": "user", "schemaTo": "auth", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -337,12 +333,8 @@ "tableFrom": "session", "tableTo": "user", "schemaTo": "auth", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -352,9 +344,7 @@ "session_token_unique": { "name": "session_token_unique", "nullsNotDistinct": false, - "columns": [ - "token" - ] + "columns": ["token"] } }, "policies": {}, @@ -415,9 +405,7 @@ "user_email_unique": { "name": "user_email_unique", "nullsNotDistinct": false, - "columns": [ - "email" - ] + "columns": ["email"] } }, "policies": {}, @@ -605,12 +593,8 @@ "tableFrom": "note", "tableTo": "user", "schemaTo": "auth", - "columnsFrom": [ - "ownerId" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["ownerId"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -635,4 +619,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 3ab2cdc..13ebf20 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -10,4 +10,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/package.json b/package.json index be978da..34c2c08 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.0.50", "scripts": { "test": "bun test api", + "format": "prettier . --write", "dev": "bun run --watch src/index.ts", "email": "email dev --dir src/emails", "auth:generate": "bun x @better-auth/cli generate --config src/lib/auth/auth.ts --output src/db/schema/auth.ts && drizzle-kit migrate", @@ -39,6 +40,7 @@ "devDependencies": { "@types/nodemailer": "^6.4.17", "@types/pg": "^8.11.10", + "prettier": "^3.4.2", "@types/react": "^19.0.7", "@types/react-dom": "^19.0.3", "bun-types": "latest", diff --git a/src/api/index.ts b/src/api/index.ts index 9d50552..9854a42 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -50,7 +50,7 @@ export const api = new Elysia({ }, { description: "Success", - } + }, ), 404: t.Object( { @@ -65,8 +65,8 @@ export const api = new Elysia({ }, { description: "Not found", - } + }, ), }, - } + }, ); diff --git a/src/api/routes/index.ts b/src/api/routes/index.ts index 6d3d1e2..0893eaa 100644 --- a/src/api/routes/index.ts +++ b/src/api/routes/index.ts @@ -1,6 +1,4 @@ - import { Elysia, t } from "elysia"; import { noteRouter } from "./note/note.route"; - -export const router = new Elysia().use(noteRouter) \ No newline at end of file +export const router = new Elysia().use(noteRouter); diff --git a/src/api/routes/note/note.controller.ts b/src/api/routes/note/note.controller.ts index e0b1060..89b5407 100644 --- a/src/api/routes/note/note.controller.ts +++ b/src/api/routes/note/note.controller.ts @@ -25,7 +25,7 @@ export class NoteController { }; } - async getOwnerNotes(ownerId: string, limit:number=10, offset:number=0) { + async getOwnerNotes(ownerId: string, limit: number = 10, offset: number = 0) { const result = await db .select({ id: note.id, @@ -36,7 +36,8 @@ export class NoteController { }) .from(note) .where(and(eq(note.ownerId, ownerId), isNull(note.deletedAt))) - .limit(limit).offset(offset) + .limit(limit) + .offset(offset) .execute(); return { success: true, @@ -60,14 +61,14 @@ export class NoteController { and( eq(note.id, noteId), eq(note.ownerId, ownerId), - isNull(note.deletedAt) - ) + isNull(note.deletedAt), + ), ) .execute(); let successStatus = true; - if(result.length===0){ - successStatus = false - }; + if (result.length === 0) { + successStatus = false; + } return { success: successStatus, data: result, @@ -79,7 +80,7 @@ export class NoteController { async updateNoteById( noteId: string, updated_note: CreateNoteType, - ownerId: string + ownerId: string, ) { const new_note_data = { ...updated_note, updatedAt: new Date() }; const result = await db @@ -89,8 +90,8 @@ export class NoteController { and( eq(note.id, noteId), eq(note.ownerId, ownerId), - isNull(note.deletedAt) - ) + isNull(note.deletedAt), + ), ) .returning({ id: note.id, @@ -117,8 +118,8 @@ export class NoteController { and( eq(note.id, noteId), eq(note.ownerId, ownerId), - isNull(note.deletedAt) - ) + isNull(note.deletedAt), + ), ) .execute(); return { diff --git a/src/api/routes/note/note.model.ts b/src/api/routes/note/note.model.ts index 5ca3d38..15f7d88 100644 --- a/src/api/routes/note/note.model.ts +++ b/src/api/routes/note/note.model.ts @@ -13,31 +13,34 @@ export type CreateNoteType = Pick< "title" | "content" >; -export const createNoteSchema = t.Pick(NoteSchema, [ - "title", - "content", -]); +export const createNoteSchema = t.Pick(NoteSchema, ["title", "content"]); export const getNoteResponses = { - 200: t.Object({ - success:t.Boolean({default:true}), - data: t.Array(NoteSchema), - error: t.Null(), - message: t.String() -}, { - description:"Success" -}) , - ...commonResponses -} + 200: t.Object( + { + success: t.Boolean({ default: true }), + data: t.Array(NoteSchema), + error: t.Null(), + message: t.String(), + }, + { + description: "Success", + }, + ), + ...commonResponses, +}; export const deleteNoteResponses = { - 200: t.Object({ - success:t.Boolean({default:true}), - data: t.Null(), - error: t.Null(), - message: t.String({default:"Note deletion succesful"}) -}, { - description:"Success" -}), - ...commonResponses -} \ No newline at end of file + 200: t.Object( + { + success: t.Boolean({ default: true }), + data: t.Null(), + error: t.Null(), + message: t.String({ default: "Note deletion succesful" }), + }, + { + description: "Success", + }, + ), + ...commonResponses, +}; diff --git a/src/api/routes/note/note.route.ts b/src/api/routes/note/note.route.ts index 839dacf..a677d42 100644 --- a/src/api/routes/note/note.route.ts +++ b/src/api/routes/note/note.route.ts @@ -1,12 +1,17 @@ import { Elysia, t } from "elysia"; -import { createNoteSchema, deleteNoteResponses, getNoteResponses, NoteSchema } from "./note.model"; +import { + createNoteSchema, + deleteNoteResponses, + getNoteResponses, + NoteSchema, +} from "./note.model"; import { NoteController } from "./note.controller"; import { userMiddleware } from "../../../middlewares/auth-middleware"; export const noteRouter = new Elysia({ prefix: "/note", name: "CRUD Operations for Notes", - "analytic":true, + analytic: true, tags: ["Note"], detail: { description: "Notes CRUD operations", @@ -17,47 +22,47 @@ export const noteRouter = new Elysia({ note: NoteSchema, }) .derive(({ request }) => userMiddleware(request)) - .onError(({ error, code, }) => { - console.error(error); - return { - message: "", - success: false, - data: null, - error: code.toString() - }; + .onError(({ error, code }) => { + console.error(error); + return { + message: "", + success: false, + data: null, + error: code.toString(), + }; }) .get( "", - async ({ note, user, query}) => { + async ({ note, user, query }) => { return await note.getOwnerNotes(user.id, query.limit, query.offset); }, { - query:t.Object({ + query: t.Object({ limit: t.Optional(t.Number()), - offset: t.Optional(t.Number()) + offset: t.Optional(t.Number()), }), - response:getNoteResponses, - detail:{ - "description":"Get all notes of the user", - "summary":"Get all notes" - } - } + response: getNoteResponses, + detail: { + description: "Get all notes of the user", + summary: "Get all notes", + }, + }, ) .get( "/:id", - async ({ note, user, params:{id} }) => { + async ({ note, user, params: { id } }) => { return await note.getNoteById(id, user.id); }, { - params:t.Object({ + params: t.Object({ id: t.String(), }), response: getNoteResponses, - detail:{ - "description":"Get a note by Id", - "summary":"Get a note" - } - } + detail: { + description: "Get a note by Id", + summary: "Get a note", + }, + }, ) .post( "", @@ -67,42 +72,44 @@ export const noteRouter = new Elysia({ { body: createNoteSchema, response: getNoteResponses, - detail:{ - "description":"Create a new note", - "summary":"Create a note" - } - } - ).patch( + detail: { + description: "Create a new note", + summary: "Create a note", + }, + }, + ) + .patch( "/:id", - async ({ body, note, user, params:{id} }) => { + async ({ body, note, user, params: { id } }) => { return await note.updateNoteById(id, body, user.id); }, { body: createNoteSchema, - params:t.Object({ + params: t.Object({ id: t.String(), }), response: getNoteResponses, - detail:{ - "description":"Update a note by Id", - "summary":"Update a note" - } - } - ).delete( + detail: { + description: "Update a note by Id", + summary: "Update a note", + }, + }, + ) + .delete( "/:id", - async ({ note, user, params:{id} }) => { + async ({ note, user, params: { id } }) => { return await note.deleteNoteById(id, user.id); }, { - params:t.Object({ + params: t.Object({ id: t.String(), }), response: deleteNoteResponses, - detail:{ - "description":"Delete a note by Id", - "summary":"Delete a note" - } - } + detail: { + description: "Delete a note by Id", + summary: "Delete a note", + }, + }, ) .delete( "", @@ -111,9 +118,9 @@ export const noteRouter = new Elysia({ }, { response: deleteNoteResponses, - detail:{ - "description":"Delete all notes of an user", - "summary":"Delete all notes" - } - } - ) + detail: { + description: "Delete all notes of an user", + summary: "Delete all notes", + }, + }, + ); diff --git a/src/api/routes/note/note.test.ts b/src/api/routes/note/note.test.ts index 304d241..c06f240 100644 --- a/src/api/routes/note/note.test.ts +++ b/src/api/routes/note/note.test.ts @@ -1,102 +1,105 @@ -import { describe, expect, it } from 'bun:test' -import { testClientApp } from '../../../../test/client'; +import { describe, expect, it } from "bun:test"; +import { testClientApp } from "../../../../test/client"; let noteId: string; -describe('Note', () => { +describe("Note", () => { // Create a note before tests - it('Create Note', async () => { + it("Create Note", async () => { const { data } = await testClientApp.api.note.post({ - "title": "test note", - "content": "description", - }) + title: "test note", + content: "description", + }); if (!data?.data) { - throw new Error('create note api did not return data'); + throw new Error("create note api did not return data"); } - noteId = data.data[0].id - expect(data.data[0].title).toBe('test note') - }) + noteId = data.data[0].id; + expect(data.data[0].title).toBe("test note"); + }); // Get all notes - it('Get All Notes', async () => { + it("Get All Notes", async () => { const { data } = await testClientApp.api.note.get({ - query: { limit: 1, offset: 0 } - }) - expect(data?.data[0].id).toBe(noteId) - }) + query: { limit: 1, offset: 0 }, + }); + expect(data?.data[0].id).toBe(noteId); + }); // Get single note - it('Get Created Note', async () => { - const { data, error } = await testClientApp.api.note({ id: noteId }).get() - expect(data?.data[0].id).toBe(noteId) - }) + it("Get Created Note", async () => { + const { data, error } = await testClientApp.api.note({ id: noteId }).get(); + expect(data?.data[0].id).toBe(noteId); + }); // Update note - it('Update Note', async () => { - const updatedTitle = "updated test note" - const updatedContent = "updated description" - + it("Update Note", async () => { + const updatedTitle = "updated test note"; + const updatedContent = "updated description"; + const { data } = await testClientApp.api.note({ id: noteId }).patch({ title: updatedTitle, content: updatedContent, - }) + }); - expect(data?.success).toBe(true) - expect(data?.data[0].title).toBe(updatedTitle) - expect(data?.data[0].content).toBe(updatedContent) - }) + expect(data?.success).toBe(true); + expect(data?.data[0].title).toBe(updatedTitle); + expect(data?.data[0].content).toBe(updatedContent); + }); // Delete single note - it('Delete Single Note', async () => { + it("Delete Single Note", async () => { // First create a new note to delete const { data: createData } = await testClientApp.api.note.post({ - "title": "note to delete", - "content": "this note will be deleted", - }) - const deleteNoteId = createData?.data[0].id - + title: "note to delete", + content: "this note will be deleted", + }); + const deleteNoteId = createData?.data[0].id; if (!deleteNoteId) { - throw new Error('Failed to receive noteId in delete note test'); + throw new Error("Failed to receive noteId in delete note test"); } // Delete the note - const { data: deleteData } = await testClientApp.api.note({ id: deleteNoteId }).delete() - expect(deleteData?.success).toBe(true) + const { data: deleteData } = await testClientApp.api + .note({ id: deleteNoteId }) + .delete(); + expect(deleteData?.success).toBe(true); // Verify note is deleted by trying to fetch it - const { data: verifyData } = await testClientApp.api.note({ id: deleteNoteId }).get() - expect(verifyData?.data).toHaveLength(0) - }) + const { data: verifyData } = await testClientApp.api + .note({ id: deleteNoteId }) + .get(); + expect(verifyData?.data).toHaveLength(0); + }); // Delete all notes - it('Delete All Notes', async () => { + it("Delete All Notes", async () => { // First create multiple notes await testClientApp.api.note.post({ - "title": "note 1", - "content": "content 1", - }) + title: "note 1", + content: "content 1", + }); await testClientApp.api.note.post({ - "title": "note 2", - "content": "content 2", - }) + title: "note 2", + content: "content 2", + }); // Delete all notes - const { data: deleteData } = await testClientApp.api.note.delete() - expect(deleteData?.success).toBe(true) + const { data: deleteData } = await testClientApp.api.note.delete(); + expect(deleteData?.success).toBe(true); // Verify all notes are deleted const { data: verifyData } = await testClientApp.api.note.get({ - query: { limit: 10, offset: 0 } - }) - expect(verifyData?.data).toHaveLength(0) - }) + query: { limit: 10, offset: 0 }, + }); + expect(verifyData?.data).toHaveLength(0); + }); // Error cases - it('Should handle invalid note ID', async () => { - const invalidId = 'invalid-id' - const { data } = await testClientApp.api.note({ id: invalidId }).get() - expect(data?.success).toBe(false) - expect(data?.data).toHaveLength(0) - }) -}) \ No newline at end of file + it("Should handle invalid note ID", async () => { + const invalidId = "invalid-id"; + const { data } = await testClientApp.api.note({ id: invalidId }).get(); + expect(data?.success).toBe(false); + expect(data?.data).toHaveLength(0); + }); +}); diff --git a/src/db/index.ts b/src/db/index.ts index 23aa82b..0f93f7d 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -2,6 +2,6 @@ import "dotenv/config"; import { drizzle } from "drizzle-orm/node-postgres"; import { getDbConfig } from "../lib/utils/env"; -const dbConfig = getDbConfig() +const dbConfig = getDbConfig(); export const db = drizzle(dbConfig.DATABASE_URL); diff --git a/src/db/schema/auth.ts b/src/db/schema/auth.ts index d14e28b..f6b68f3 100644 --- a/src/db/schema/auth.ts +++ b/src/db/schema/auth.ts @@ -9,74 +9,94 @@ import { export const authSchema = pgSchema("auth"); -export const user = authSchema.table( - "user", +export const user = authSchema.table("user", { + id: text("id").primaryKey(), + name: text("name").notNull(), + email: text("email").notNull().unique(), + emailVerified: boolean("email_verified").notNull(), + image: text("image"), + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), +}); + +export const session = authSchema.table( + "session", { id: text("id").primaryKey(), - name: text("name").notNull(), - email: text("email").notNull().unique(), - emailVerified: boolean("email_verified").notNull(), - image: text("image"), + expiresAt: timestamp("expires_at").notNull(), + token: text("token").notNull().unique(), + createdAt: timestamp("created_at").notNull(), + updatedAt: timestamp("updated_at").notNull(), + ipAddress: text("ip_address"), + userAgent: text("user_agent"), + userId: text("user_id") + .notNull() + .references(() => user.id), + }, + (table) => [ + index("idx_auth_session_ip_address").on(table.ipAddress), + index("idx_auth_session_userid").on(table.userId), + ], +); + +export const account = authSchema.table( + "account", + { + id: text("id").primaryKey(), + accountId: text("account_id").notNull(), + providerId: text("provider_id").notNull(), + userId: text("user_id") + .notNull() + .references(() => user.id), + accessToken: text("access_token"), + refreshToken: text("refresh_token"), + idToken: text("id_token"), + accessTokenExpiresAt: timestamp("access_token_expires_at"), + refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), + scope: text("scope"), + password: text("password"), createdAt: timestamp("created_at").notNull(), updatedAt: timestamp("updated_at").notNull(), }, + (table) => [ + index("idx_auth_account_userid").on(table.userId), + index("idx_auth_account_refreshtokenexpiresat").on( + table.refreshTokenExpiresAt, + ), + index("idx_auth_account_providerid").on(table.providerId), + ], ); -export const session = authSchema.table("session", { - id: text("id").primaryKey(), - expiresAt: timestamp("expires_at").notNull(), - token: text("token").notNull().unique(), - createdAt: timestamp("created_at").notNull(), - updatedAt: timestamp("updated_at").notNull(), - ipAddress: text("ip_address"), - userAgent: text("user_agent"), - userId: text("user_id") - .notNull() - .references(() => user.id), -}, -(table) => [index("idx_auth_session_ip_address").on(table.ipAddress), index("idx_auth_session_userid").on(table.userId)] +export const verification = authSchema.table( + "verification", + { + id: text("id").primaryKey(), + identifier: text("identifier").notNull(), + value: text("value").notNull(), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at"), + updatedAt: timestamp("updated_at"), + }, + (table) => [ + index("idx_auth_verification_identifier").on(table.identifier), + index("idx_auth_verification_expires_at").on(table.expiresAt), + ], ); -export const account = authSchema.table("account", { - id: text("id").primaryKey(), - accountId: text("account_id").notNull(), - providerId: text("provider_id").notNull(), - userId: text("user_id") - .notNull() - .references(() => user.id), - accessToken: text("access_token"), - refreshToken: text("refresh_token"), - idToken: text("id_token"), - accessTokenExpiresAt: timestamp("access_token_expires_at"), - refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), - scope: text("scope"), - password: text("password"), - createdAt: timestamp("created_at").notNull(), - updatedAt: timestamp("updated_at").notNull(), -}, -(table) => [index("idx_auth_account_userid").on(table.userId), index("idx_auth_account_refreshtokenexpiresat").on(table.refreshTokenExpiresAt), index("idx_auth_account_providerid").on(table.providerId)]); - -export const verification = authSchema.table("verification", { - id: text("id").primaryKey(), - identifier: text("identifier").notNull(), - value: text("value").notNull(), - expiresAt: timestamp("expires_at").notNull(), - createdAt: timestamp("created_at"), - updatedAt: timestamp("updated_at"), -}, -(table) => [index("idx_auth_verification_identifier").on(table.identifier), index("idx_auth_verification_expires_at").on(table.expiresAt)]); - -export const rateLimit = authSchema.table("rate_limit", { - id: text("id").primaryKey(), - key: text("key"), - count: integer("count"), - lastRequest: integer("last_request"), -}, -(table) => [index("idx_auth_ratelimit_key").on(table.key)]); +export const rateLimit = authSchema.table( + "rate_limit", + { + id: text("id").primaryKey(), + key: text("key"), + count: integer("count"), + lastRequest: integer("last_request"), + }, + (table) => [index("idx_auth_ratelimit_key").on(table.key)], +); export const jwks = authSchema.table("jwks", { id: text("id").primaryKey(), publicKey: text("public_key").notNull(), privateKey: text("private_key").notNull(), createdAt: timestamp("created_at").notNull(), -}); \ No newline at end of file +}); diff --git a/src/db/schema/note.ts b/src/db/schema/note.ts index fb803ac..3b23f8d 100644 --- a/src/db/schema/note.ts +++ b/src/db/schema/note.ts @@ -1,16 +1,26 @@ import { index, pgTable, text, timestamp } from "drizzle-orm/pg-core"; -import { createId } from '@paralleldrive/cuid2' +import { createId } from "@paralleldrive/cuid2"; import { user } from "./auth"; -export const note = pgTable("note", { - id: text("id").primaryKey().$defaultFn(()=> `note_${createId()}`), - title: text("title"), - content: text("content"), - createdAt: timestamp().notNull().defaultNow(), - updatedAt: timestamp(), - deletedAt: timestamp(), - ownerId: text().notNull().references(() => user.id) -}, +export const note = pgTable( + "note", + { + id: text("id") + .primaryKey() + .$defaultFn(() => `note_${createId()}`), + title: text("title"), + content: text("content"), + createdAt: timestamp().notNull().defaultNow(), + updatedAt: timestamp(), + deletedAt: timestamp(), + ownerId: text() + .notNull() + .references(() => user.id), + }, -(table) => [index("idx_note_ownerid").on(table.ownerId), index("idx_note_createdat").on(table.createdAt), index("idx_note_deletedat").on(table.deletedAt)] -) \ No newline at end of file + (table) => [ + index("idx_note_ownerid").on(table.ownerId), + index("idx_note_createdat").on(table.createdAt), + index("idx_note_deletedat").on(table.deletedAt), + ], +); diff --git a/src/emails/auth.tsx b/src/emails/auth.tsx index 6db98a0..c7d6888 100644 --- a/src/emails/auth.tsx +++ b/src/emails/auth.tsx @@ -1,7 +1,13 @@ -import * as React from 'react' -import { Tailwind, Section, Text } from '@react-email/components' +import * as React from "react"; +import { Tailwind, Section, Text } from "@react-email/components"; -export default function AuthEmail({ message, link }: { message: string, link: string }) { +export default function AuthEmail({ + message, + link, +}: { + message: string; + link: string; +}) { return (
@@ -12,17 +18,17 @@ export default function AuthEmail({ message, link }: { message: string, link: st Use the following Link to {message} - Link - - Thanks - + + Link + + Thanks
- ) + ); } AuthEmail.PreviewProps = { link: "https://example.com", - message: "Verify your email address" -} + message: "Verify your email address", +}; diff --git a/src/index.ts b/src/index.ts index 98bccc2..9662d9b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,7 @@ export const app = new Elysia() .use( opentelemetry({ serviceName: baseConfig.SERVICE_NAME, - }) + }), ) .use(serverTiming()) .use( @@ -28,7 +28,7 @@ export const app = new Elysia() description: `API docs for ${baseConfig.SERVICE_NAME}`, }, }, - }) + }), ) .onError(({ error, code }) => { if (code === "NOT_FOUND") diff --git a/src/lib/auth/auth.ts b/src/lib/auth/auth.ts index 2bd5698..bb8bc35 100644 --- a/src/lib/auth/auth.ts +++ b/src/lib/auth/auth.ts @@ -1,8 +1,15 @@ import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { db } from "../../db/index"; -import { jwt, openAPI } from "better-auth/plugins" -import { user, account, verification, session, rateLimit, jwks } from "../../db/schema/auth"; +import { jwt, openAPI } from "better-auth/plugins"; +import { + user, + account, + verification, + session, + rateLimit, + jwks, +} from "../../db/schema/auth"; import { sendMail } from "../mail/mail"; import { renderToStaticMarkup } from "react-dom/server"; import { createElement } from "react"; @@ -17,13 +24,13 @@ export const auth = betterAuth({ account: account, verification: verification, rateLimit: rateLimit, - jwks: jwks - } + jwks: jwks, + }, }), user: { deleteUser: { - enabled: true // [!Code Highlight] - } + enabled: true, // [!Code Highlight] + }, }, rateLimit: { window: 60, @@ -40,8 +47,8 @@ export const auth = betterAuth({ return { window: 3600 * 12, max: 10, - } - } + }; + }, }, }, emailAndPassword: { @@ -49,7 +56,9 @@ export const auth = betterAuth({ requireEmailVerification: false, sendResetPassword: async ({ user, url }, request) => { const subject = "Reset your password"; - const html = renderToStaticMarkup(createElement(AuthEmail, { message: subject, link: url })); + const html = renderToStaticMarkup( + createElement(AuthEmail, { message: subject, link: url }), + ); await sendMail({ to: user.email, subject: subject, @@ -60,20 +69,21 @@ export const auth = betterAuth({ emailVerification: { sendVerificationEmail: async ({ user, url, token }, request) => { const subject = "Verify your email address"; - const html = renderToStaticMarkup(createElement(AuthEmail, { message: subject, link: url })); + const html = renderToStaticMarkup( + createElement(AuthEmail, { message: subject, link: url }), + ); await sendMail({ to: user.email, subject: subject, html: html, }); }, - }, plugins: [ openAPI({ path: "/docs", }), - jwt() + jwt(), ], socialProviders: { /* diff --git a/src/lib/mail/mail.ts b/src/lib/mail/mail.ts index ada3212..7be4be0 100644 --- a/src/lib/mail/mail.ts +++ b/src/lib/mail/mail.ts @@ -1,14 +1,24 @@ -import nodemailer from 'nodemailer' +import nodemailer from "nodemailer"; -export async function sendMail({ to, subject, text, html }: { to: string, subject: string, text?: string, html?: string }) { +export async function sendMail({ + to, + subject, + text, + html, +}: { + to: string; + subject: string; + text?: string; + html?: string; +}) { const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST!, port: +process.env.SMTP_PORT!, auth: { user: process.env.SMTP_USER!, pass: process.env.SMTP_PASSWORD!, - } - }) + }, + }); await transporter.sendMail({ from: process.env.SMTP_FROM!, @@ -16,5 +26,5 @@ export async function sendMail({ to, subject, text, html }: { to: string, subjec subject, text: text, html: html, - }) + }); } diff --git a/src/lib/storage/storage.ts b/src/lib/storage/storage.ts index c9e3648..2cdfd75 100644 --- a/src/lib/storage/storage.ts +++ b/src/lib/storage/storage.ts @@ -1,15 +1,15 @@ -import { Client } from 'minio'; -import { Buffer } from 'buffer'; -import { getMinioConfig } from '../utils/env'; +import { Client } from "minio"; +import { Buffer } from "buffer"; +import { getMinioConfig } from "../utils/env"; // MinIO client configuration -const minioConfig = getMinioConfig() +const minioConfig = getMinioConfig(); const minioClient = new Client({ endPoint: minioConfig.MINIO_ENDPOINT_URL, useSSL: false, accessKey: minioConfig.MINIO_ACCESS_KEY, - secretKey: minioConfig.MINIO_SECRET_KEY + secretKey: minioConfig.MINIO_SECRET_KEY, }); const BUCKET_NAME = minioConfig.MINIO_BUCKET_NAME; @@ -44,7 +44,7 @@ const fileToBuffer = async (file: File | Blob): Promise => { export const uploadFileAndGetUrl = async ( filename: string, file: Buffer | File | Blob, - contentType?: string + contentType?: string, ): Promise => { try { await ensureBucket(); @@ -54,11 +54,11 @@ export const uploadFileAndGetUrl = async ( // If file is from form-data and no contentType is provided, use its type const metadata: Record = {}; - if (!contentType && 'type' in file) { + if (!contentType && "type" in file) { contentType = file.type; } if (contentType) { - metadata['Content-Type'] = contentType; + metadata["Content-Type"] = contentType; } // Upload the file @@ -67,19 +67,19 @@ export const uploadFileAndGetUrl = async ( filename, fileBuffer, fileBuffer.length, - metadata + metadata, ); // Generate and return signed URL const url = await minioClient.presignedGetObject( BUCKET_NAME, filename, - SIGNED_URL_EXPIRY + SIGNED_URL_EXPIRY, ); return url; } catch (error) { - console.error('Error uploading file:', error); + console.error("Error uploading file:", error); if (error instanceof Error) { throw new Error(`Failed to upload file: ${error.message}`); } @@ -100,13 +100,14 @@ export const getSignedUrl = async (filename: string): Promise => { const url = await minioClient.presignedGetObject( BUCKET_NAME, filename, - SIGNED_URL_EXPIRY + SIGNED_URL_EXPIRY, ); return url; } catch (error) { - console.error('Error generating signed URL:', error); - if (error instanceof Error) throw new Error(`Failed to generate signed URL: ${error.message}`); + console.error("Error generating signed URL:", error); + if (error instanceof Error) + throw new Error(`Failed to generate signed URL: ${error.message}`); throw new Error(`Failed to generate signed URL: ${error}`); } }; @@ -120,8 +121,9 @@ export const deleteFile = async (filename: string): Promise => { try { await minioClient.removeObject(BUCKET_NAME, filename); } catch (error) { - console.error('Error deleting file:', error); - if (error instanceof Error) throw new Error(`Failed to delete file: ${error.message}`); + console.error("Error deleting file:", error); + if (error instanceof Error) + throw new Error(`Failed to delete file: ${error.message}`); throw new Error(`Failed to delete file: ${error}`); } }; @@ -140,4 +142,3 @@ export const deleteFile = async (filename: string): Promise => { // Delete a file // await deleteFile('hello.txt'); // console.log('File deleted successfully'); - diff --git a/src/lib/utils/common.ts b/src/lib/utils/common.ts index 3d5bf4c..a10f7e3 100644 --- a/src/lib/utils/common.ts +++ b/src/lib/utils/common.ts @@ -11,7 +11,7 @@ export const commonResponses = { { description: "Bad Request. Usually due to missing parameters, or invalid parameters.", - } + }, ), 401: t.Object( { @@ -22,7 +22,7 @@ export const commonResponses = { }, { description: "Unauthorized. Due to missing or invalid authentication.", - } + }, ), 403: t.Object( { @@ -34,7 +34,7 @@ export const commonResponses = { { description: "Forbidden. You do not have permission to access this resource or to perform this action.", - } + }, ), 404: t.Object( { @@ -45,7 +45,7 @@ export const commonResponses = { }, { description: "Not Found. The requested resource was not found.", - } + }, ), 429: t.Object( { @@ -57,7 +57,7 @@ export const commonResponses = { { description: "Too Many Requests. You have exceeded the rate limit. Try again later.", - } + }, ), 500: t.Object( { @@ -69,6 +69,6 @@ export const commonResponses = { { description: "Internal Server Error. This is a problem with the server that you cannot fix.", - } + }, ), }; diff --git a/src/lib/utils/env.ts b/src/lib/utils/env.ts index b8f7d60..43f792c 100644 --- a/src/lib/utils/env.ts +++ b/src/lib/utils/env.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import { z } from "zod"; // Define the environment schema const envSchema = z.object({ @@ -34,33 +34,36 @@ export const validateEnv = (): EnvConfig => { const config = envSchema.safeParse(process.env); if (!config.success) { - console.warn('\n🚨 Environment Variable Warnings:'); + console.warn("\n🚨 Environment Variable Warnings:"); // Collect and categorize warnings config.error.errors.forEach((error) => { - const path = error.path.join('.'); + const path = error.path.join("."); const message = error.message; let warningMessage = `❌ ${path}: ${message}`; // Add specific functionality warnings - if (path.startsWith('DB_') || path === 'DATABASE_URL') { - warningMessage += '\n ⚠️ Database functionality may not work properly'; + if (path.startsWith("DB_") || path === "DATABASE_URL") { + warningMessage += + "\n ⚠️ Database functionality may not work properly"; } - if (path.startsWith('MINIO_')) { - warningMessage += '\n ⚠️ File storage functionality may not work properly'; + if (path.startsWith("MINIO_")) { + warningMessage += + "\n ⚠️ File storage functionality may not work properly"; } - if (path.startsWith('BETTER_AUTH_')) { - warningMessage += '\n ⚠️ Authentication functionality may not work properly'; + if (path.startsWith("BETTER_AUTH_")) { + warningMessage += + "\n ⚠️ Authentication functionality may not work properly"; } warnings.push(warningMessage); }); // Print all warnings warnings.forEach((warning) => console.warn(warning)); - console.warn('\n'); + console.warn("\n"); - throw new Error('Environment validation failed. Check warnings above.'); + throw new Error("Environment validation failed. Check warnings above."); } return config.data; @@ -74,8 +77,7 @@ export const getConfig = (): EnvConfig => { return validateEnv(); }; - -export const getBaseConfig = (): Pick => { +export const getBaseConfig = (): Pick => { const config = getConfig(); return { PORT: config.PORT, @@ -84,14 +86,20 @@ export const getBaseConfig = (): Pick => { }; // Optional: Export individual config getters with type safety -export const getDbConfig = (): Pick => { +export const getDbConfig = (): Pick => { const config = getConfig(); return { DATABASE_URL: config.DATABASE_URL, }; }; -export const getMinioConfig = (): Pick => { +export const getMinioConfig = (): Pick< + EnvConfig, + | "MINIO_ACCESS_KEY" + | "MINIO_SECRET_KEY" + | "MINIO_ENDPOINT_URL" + | "MINIO_BUCKET_NAME" +> => { const config = getConfig(); return { MINIO_ACCESS_KEY: config.MINIO_ACCESS_KEY, @@ -101,7 +109,10 @@ export const getMinioConfig = (): Pick => { +export const getAuthConfig = (): Pick< + EnvConfig, + "BETTER_AUTH_SECRET" | "BETTER_AUTH_URL" +> => { const config = getConfig(); return { BETTER_AUTH_SECRET: config.BETTER_AUTH_SECRET, diff --git a/test/client.ts b/test/client.ts index 72966db..e2d6520 100644 --- a/test/client.ts +++ b/test/client.ts @@ -1,27 +1,27 @@ -import { treaty } from '@elysiajs/eden' -import { app } from '../src' -import { getAuthConfig } from '../src/lib/utils/env'; +import { treaty } from "@elysiajs/eden"; +import { app } from "../src"; +import { getAuthConfig } from "../src/lib/utils/env"; async function getAuthToken() { - const authUrl = getAuthConfig().BETTER_AUTH_URL + const authUrl = getAuthConfig().BETTER_AUTH_URL; const response = await fetch(`${authUrl}/api/auth/sign-in/email`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json' + "Content-Type": "application/json", }, body: JSON.stringify({ email: "test@test.com", - password: "testpass123" - }) + password: "testpass123", + }), }); const cookies = response.headers.getSetCookie()[0]; - const sessionToken = cookies.split(";")[0].split("=")[1] + const sessionToken = cookies.split(";")[0].split("=")[1]; return sessionToken; } const token = await getAuthToken(); -export const testClientApp = treaty(app,{ - headers: { - Cookie: `better-auth.session_token=${token}` - } -}) \ No newline at end of file +export const testClientApp = treaty(app, { + headers: { + Cookie: `better-auth.session_token=${token}`, + }, +}); diff --git a/tsconfig.json b/tsconfig.json index 671e128..9abf5cd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,9 +9,9 @@ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "ES2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - "jsx": "react", /* Specify what JSX code is generated. */ + "jsx": "react" /* Specify what JSX code is generated. */, // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ @@ -22,16 +22,16 @@ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "ES2022", /* Specify what module code is generated. */ + "module": "ES2022" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ "types": [ "bun-types" - ], /* Specify type package names to be included without being referenced in a source file. */ + ] /* Specify type package names to be included without being referenced in a source file. */, // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ @@ -67,11 +67,11 @@ /* Interop Constraints */ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ + "strict": true /* Enable all strict type-checking options. */, // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ @@ -94,4 +94,4 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ } -} \ No newline at end of file +}