added prettier formatting
This commit is contained in:
parent
cbb30d05a6
commit
9b4cbdd0e3
24 changed files with 413 additions and 344 deletions
|
|
@ -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.
|
||||
Open http://localhost:3000/ with your browser to see the result.
|
||||
|
|
|
|||
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -10,4 +10,3 @@ services:
|
|||
OTEL_EXPORTER_OTLP_ENDPOINT: http://tracing:4318
|
||||
OTEL_EXPORTER_OTLP_PROTOCOL: http/protobuf
|
||||
DATABASE_URL: ${DATABASE_URL}
|
||||
|
||||
|
|
|
|||
|
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@
|
|||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
}
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
|
||||
import { Elysia, t } from "elysia";
|
||||
import { noteRouter } from "./note/note.route";
|
||||
|
||||
|
||||
export const router = new Elysia().use(noteRouter)
|
||||
export const router = new Elysia().use(noteRouter);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
200: t.Object(
|
||||
{
|
||||
success: t.Boolean({ default: true }),
|
||||
data: t.Null(),
|
||||
error: t.Null(),
|
||||
message: t.String({ default: "Note deletion succesful" }),
|
||||
},
|
||||
{
|
||||
description: "Success",
|
||||
},
|
||||
),
|
||||
...commonResponses,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
)
|
||||
(table) => [
|
||||
index("idx_note_ownerid").on(table.ownerId),
|
||||
index("idx_note_createdat").on(table.createdAt),
|
||||
index("idx_note_deletedat").on(table.deletedAt),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Tailwind>
|
||||
<Section className="flex justify-center items-center w-full min-h-screen font-sans">
|
||||
|
|
@ -12,17 +18,17 @@ export default function AuthEmail({ message, link }: { message: string, link: st
|
|||
<Text className="text-gray-500 my-0">
|
||||
Use the following Link to {message}
|
||||
</Text>
|
||||
<a href={link} className="text-blue-400 font-bold pt-2">Link</a>
|
||||
<Text className="text-gray-600 text-xs">
|
||||
Thanks
|
||||
</Text>
|
||||
<a href={link} className="text-blue-400 font-bold pt-2">
|
||||
Link
|
||||
</a>
|
||||
<Text className="text-gray-600 text-xs">Thanks</Text>
|
||||
</Section>
|
||||
</Section>
|
||||
</Tailwind>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
AuthEmail.PreviewProps = {
|
||||
link: "https://example.com",
|
||||
message: "Verify your email address"
|
||||
}
|
||||
message: "Verify your email address",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Buffer> => {
|
|||
export const uploadFileAndGetUrl = async (
|
||||
filename: string,
|
||||
file: Buffer | File | Blob,
|
||||
contentType?: string
|
||||
contentType?: string,
|
||||
): Promise<string> => {
|
||||
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<string, string> = {};
|
||||
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<string> => {
|
|||
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<void> => {
|
|||
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<void> => {
|
|||
// Delete a file
|
||||
// await deleteFile('hello.txt');
|
||||
// console.log('File deleted successfully');
|
||||
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
}
|
||||
},
|
||||
),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<EnvConfig, 'PORT' | 'SERVICE_NAME'> => {
|
||||
export const getBaseConfig = (): Pick<EnvConfig, "PORT" | "SERVICE_NAME"> => {
|
||||
const config = getConfig();
|
||||
return {
|
||||
PORT: config.PORT,
|
||||
|
|
@ -84,14 +86,20 @@ export const getBaseConfig = (): Pick<EnvConfig, 'PORT' | 'SERVICE_NAME'> => {
|
|||
};
|
||||
|
||||
// Optional: Export individual config getters with type safety
|
||||
export const getDbConfig = (): Pick<EnvConfig, 'DATABASE_URL'> => {
|
||||
export const getDbConfig = (): Pick<EnvConfig, "DATABASE_URL"> => {
|
||||
const config = getConfig();
|
||||
return {
|
||||
DATABASE_URL: config.DATABASE_URL,
|
||||
};
|
||||
};
|
||||
|
||||
export const getMinioConfig = (): Pick<EnvConfig, 'MINIO_ACCESS_KEY' | 'MINIO_SECRET_KEY' | 'MINIO_ENDPOINT_URL' | 'MINIO_BUCKET_NAME'> => {
|
||||
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<EnvConfig, 'MINIO_ACCESS_KEY' | 'MINIO_SE
|
|||
};
|
||||
};
|
||||
|
||||
export const getAuthConfig = (): Pick<EnvConfig, 'BETTER_AUTH_SECRET' | 'BETTER_AUTH_URL'> => {
|
||||
export const getAuthConfig = (): Pick<
|
||||
EnvConfig,
|
||||
"BETTER_AUTH_SECRET" | "BETTER_AUTH_URL"
|
||||
> => {
|
||||
const config = getConfig();
|
||||
return {
|
||||
BETTER_AUTH_SECRET: config.BETTER_AUTH_SECRET,
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
}
|
||||
})
|
||||
export const testClientApp = treaty(app, {
|
||||
headers: {
|
||||
Cookie: `better-auth.session_token=${token}`,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue