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 c7a287a..c2de2b1 100755
Binary files a/bun.lockb and b/bun.lockb differ
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
+}