Notes route
This commit is contained in:
parent
0fe2b335ed
commit
9f706092a7
14 changed files with 546 additions and 141 deletions
|
|
@ -3,7 +3,7 @@ import { defineConfig } from "drizzle-kit";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
out: "./drizzle",
|
out: "./drizzle",
|
||||||
schema: "./src/db/schema.ts",
|
schema: "./src/db/schema",
|
||||||
dialect: "postgresql",
|
dialect: "postgresql",
|
||||||
dbCredentials: {
|
dbCredentials: {
|
||||||
url: process.env.DATABASE_URL!,
|
url: process.env.DATABASE_URL!,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
CREATE TABLE "account" (
|
CREATE SCHEMA "auth";
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "auth"."account" (
|
||||||
"id" text PRIMARY KEY NOT NULL,
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
"account_id" text NOT NULL,
|
"account_id" text NOT NULL,
|
||||||
"provider_id" text NOT NULL,
|
"provider_id" text NOT NULL,
|
||||||
|
|
@ -14,14 +16,14 @@ CREATE TABLE "account" (
|
||||||
"updated_at" timestamp NOT NULL
|
"updated_at" timestamp NOT NULL
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
CREATE TABLE "rate_limit" (
|
CREATE TABLE "auth"."rate_limit" (
|
||||||
"id" text PRIMARY KEY NOT NULL,
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
"key" text,
|
"key" text,
|
||||||
"count" integer,
|
"count" integer,
|
||||||
"last_request" integer
|
"last_request" integer
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
CREATE TABLE "session" (
|
CREATE TABLE "auth"."session" (
|
||||||
"id" text PRIMARY KEY NOT NULL,
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
"expires_at" timestamp NOT NULL,
|
"expires_at" timestamp NOT NULL,
|
||||||
"token" text NOT NULL,
|
"token" text NOT NULL,
|
||||||
|
|
@ -33,7 +35,7 @@ CREATE TABLE "session" (
|
||||||
CONSTRAINT "session_token_unique" UNIQUE("token")
|
CONSTRAINT "session_token_unique" UNIQUE("token")
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
CREATE TABLE "user" (
|
CREATE TABLE "auth"."user" (
|
||||||
"id" text PRIMARY KEY NOT NULL,
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
"name" text NOT NULL,
|
"name" text NOT NULL,
|
||||||
"email" text NOT NULL,
|
"email" text NOT NULL,
|
||||||
|
|
@ -44,7 +46,7 @@ CREATE TABLE "user" (
|
||||||
CONSTRAINT "user_email_unique" UNIQUE("email")
|
CONSTRAINT "user_email_unique" UNIQUE("email")
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
CREATE TABLE "verification" (
|
CREATE TABLE "auth"."verification" (
|
||||||
"id" text PRIMARY KEY NOT NULL,
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
"identifier" text NOT NULL,
|
"identifier" text NOT NULL,
|
||||||
"value" text NOT NULL,
|
"value" text NOT NULL,
|
||||||
|
|
@ -53,5 +55,15 @@ CREATE TABLE "verification" (
|
||||||
"updated_at" timestamp
|
"updated_at" timestamp
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
CREATE TABLE "note" (
|
||||||
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"title" text,
|
||||||
|
"content" text,
|
||||||
|
"createdAt" timestamp DEFAULT now(),
|
||||||
|
"updatedAt" timestamp,
|
||||||
|
"ownerId" text NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "auth"."account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "auth"."session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "note" ADD CONSTRAINT "note_ownerId_user_id_fk" FOREIGN KEY ("ownerId") REFERENCES "auth"."user"("id") ON DELETE no action ON UPDATE no action;
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"id": "e9e52dc2-cfab-453f-8e23-57a32259b8e9",
|
"id": "806f9895-fec6-43da-9c78-46e599e611e8",
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"dialect": "postgresql",
|
"dialect": "postgresql",
|
||||||
"tables": {
|
"tables": {
|
||||||
"public.account": {
|
"auth.account": {
|
||||||
"name": "account",
|
"name": "account",
|
||||||
"schema": "",
|
"schema": "auth",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
|
|
@ -93,6 +93,7 @@
|
||||||
"name": "account_user_id_user_id_fk",
|
"name": "account_user_id_user_id_fk",
|
||||||
"tableFrom": "account",
|
"tableFrom": "account",
|
||||||
"tableTo": "user",
|
"tableTo": "user",
|
||||||
|
"schemaTo": "auth",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"user_id"
|
"user_id"
|
||||||
],
|
],
|
||||||
|
|
@ -109,9 +110,9 @@
|
||||||
"checkConstraints": {},
|
"checkConstraints": {},
|
||||||
"isRLSEnabled": false
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
"public.rate_limit": {
|
"auth.rate_limit": {
|
||||||
"name": "rate_limit",
|
"name": "rate_limit",
|
||||||
"schema": "",
|
"schema": "auth",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
|
|
@ -146,9 +147,9 @@
|
||||||
"checkConstraints": {},
|
"checkConstraints": {},
|
||||||
"isRLSEnabled": false
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
"public.session": {
|
"auth.session": {
|
||||||
"name": "session",
|
"name": "session",
|
||||||
"schema": "",
|
"schema": "auth",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
|
|
@ -205,6 +206,7 @@
|
||||||
"name": "session_user_id_user_id_fk",
|
"name": "session_user_id_user_id_fk",
|
||||||
"tableFrom": "session",
|
"tableFrom": "session",
|
||||||
"tableTo": "user",
|
"tableTo": "user",
|
||||||
|
"schemaTo": "auth",
|
||||||
"columnsFrom": [
|
"columnsFrom": [
|
||||||
"user_id"
|
"user_id"
|
||||||
],
|
],
|
||||||
|
|
@ -229,9 +231,9 @@
|
||||||
"checkConstraints": {},
|
"checkConstraints": {},
|
||||||
"isRLSEnabled": false
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
"public.user": {
|
"auth.user": {
|
||||||
"name": "user",
|
"name": "user",
|
||||||
"schema": "",
|
"schema": "auth",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
|
|
@ -292,9 +294,9 @@
|
||||||
"checkConstraints": {},
|
"checkConstraints": {},
|
||||||
"isRLSEnabled": false
|
"isRLSEnabled": false
|
||||||
},
|
},
|
||||||
"public.verification": {
|
"auth.verification": {
|
||||||
"name": "verification",
|
"name": "verification",
|
||||||
"schema": "",
|
"schema": "auth",
|
||||||
"columns": {
|
"columns": {
|
||||||
"id": {
|
"id": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
|
|
@ -340,10 +342,77 @@
|
||||||
"policies": {},
|
"policies": {},
|
||||||
"checkConstraints": {},
|
"checkConstraints": {},
|
||||||
"isRLSEnabled": false
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.note": {
|
||||||
|
"name": "note",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"ownerId": {
|
||||||
|
"name": "ownerId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"note_ownerId_user_id_fk": {
|
||||||
|
"name": "note_ownerId_user_id_fk",
|
||||||
|
"tableFrom": "note",
|
||||||
|
"tableTo": "user",
|
||||||
|
"schemaTo": "auth",
|
||||||
|
"columnsFrom": [
|
||||||
|
"ownerId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"enums": {},
|
"enums": {},
|
||||||
"schemas": {},
|
"schemas": {
|
||||||
|
"auth": "auth"
|
||||||
|
},
|
||||||
"sequences": {},
|
"sequences": {},
|
||||||
"roles": {},
|
"roles": {},
|
||||||
"policies": {},
|
"policies": {},
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1736964339376,
|
"when": 1736970092723,
|
||||||
"tag": "0000_bright_meltdown",
|
"tag": "0000_pretty_banshee",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,141 @@
|
||||||
import { Memo } from "./note.model";
|
import { and, eq, notExists } from "drizzle-orm";
|
||||||
|
import { db } from "../../db";
|
||||||
|
import { note } from "../../db/schema/note";
|
||||||
|
import { CreateNote } from "./note.model";
|
||||||
|
|
||||||
export class Note {
|
export class NoteController {
|
||||||
constructor(
|
async createNote(new_note: CreateNote, ownerId: string) {
|
||||||
public data: Memo[] = [
|
const new_note_data = { ...new_note, ownerId: ownerId };
|
||||||
{
|
const result = await db
|
||||||
data: "Moonhalo",
|
.insert(note)
|
||||||
},
|
.values(new_note_data)
|
||||||
]
|
.returning({
|
||||||
) {}
|
id: note.id,
|
||||||
|
title: note.title,
|
||||||
add(note: Memo) {
|
content: note.content,
|
||||||
this.data.push(note);
|
createdAt: note.createdAt,
|
||||||
|
updatedAt: note.updatedAt,
|
||||||
return this.data;
|
})
|
||||||
|
.execute();
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: result,
|
||||||
|
message: "Note created successfully",
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(index: number) {
|
async getOwnerNotes(ownerId: string, limit:number=10, offset:number=0) {
|
||||||
return this.data.splice(index, 1);
|
const result = await db
|
||||||
|
.select({
|
||||||
|
id: note.id,
|
||||||
|
title: note.title,
|
||||||
|
content: note.content,
|
||||||
|
createdAt: note.createdAt,
|
||||||
|
updatedAt: note.updatedAt,
|
||||||
|
})
|
||||||
|
.from(note)
|
||||||
|
.where(and(eq(note.ownerId, ownerId), notExists(note.deletedAt)))
|
||||||
|
.limit(limit).offset(offset)
|
||||||
|
.execute();
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: result,
|
||||||
|
message: "",
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
update(index: number, note: Partial<Memo>) {
|
async getNoteById(noteId: string, ownerId: string) {
|
||||||
return (this.data[index] = { ...this.data[index], ...note });
|
const result = await db
|
||||||
|
.select({
|
||||||
|
id: note.id,
|
||||||
|
title: note.title,
|
||||||
|
content: note.content,
|
||||||
|
createdAt: note.createdAt,
|
||||||
|
updatedAt: note.updatedAt,
|
||||||
|
})
|
||||||
|
.from(note)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(note.id, noteId),
|
||||||
|
eq(note.ownerId, ownerId),
|
||||||
|
notExists(note.deletedAt)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.execute();
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: result,
|
||||||
|
message: "",
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateNoteById(
|
||||||
|
noteId: string,
|
||||||
|
updated_note: CreateNote,
|
||||||
|
ownerId: string
|
||||||
|
) {
|
||||||
|
const new_note_data = { ...updated_note, updatedAt: new Date() };
|
||||||
|
const result = await db
|
||||||
|
.update(note)
|
||||||
|
.set(new_note_data)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(note.id, noteId),
|
||||||
|
eq(note.ownerId, ownerId),
|
||||||
|
notExists(note.deletedAt)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.returning({
|
||||||
|
id: note.id,
|
||||||
|
title: note.title,
|
||||||
|
content: note.content,
|
||||||
|
createdAt: note.createdAt,
|
||||||
|
updatedAt: note.updatedAt,
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: result,
|
||||||
|
message: "Note updated successfully",
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteNoteById(noteId: string, ownerId: string) {
|
||||||
|
await db
|
||||||
|
.update(note)
|
||||||
|
.set({ deletedAt: new Date() })
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(note.id, noteId),
|
||||||
|
eq(note.ownerId, ownerId),
|
||||||
|
notExists(note.deletedAt)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.execute();
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: null,
|
||||||
|
message: "Note deleted successfully",
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteAllNotes(ownerId: string) {
|
||||||
|
await db
|
||||||
|
.update(note)
|
||||||
|
.set({ deletedAt: new Date() })
|
||||||
|
.where(and(eq(note.ownerId, ownerId), notExists(note.deletedAt)))
|
||||||
|
.execute();
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: null,
|
||||||
|
message: "Notes deleted successfully",
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,37 @@
|
||||||
|
import { createSelectSchema } from "drizzle-typebox";
|
||||||
import { t } from "elysia";
|
import { t } from "elysia";
|
||||||
|
import { note } from "../../db/schema/note";
|
||||||
|
import { InferInsertModel, InferSelectModel } from "drizzle-orm";
|
||||||
|
|
||||||
export const memoSchema = t.Object({
|
|
||||||
data: t.String(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type Memo = typeof memoSchema.static;
|
export const SelectNoteSchema = createSelectSchema(note);
|
||||||
|
|
||||||
|
export const NoteSchema = t.Omit(SelectNoteSchema, ["deletedAt", "ownerId"]);
|
||||||
|
|
||||||
|
export type Note = InferSelectModel<typeof note>;
|
||||||
|
export type CreateNote = Pick<
|
||||||
|
InferInsertModel<typeof note>,
|
||||||
|
"title" | "content"
|
||||||
|
>;
|
||||||
|
export const createNoteSchema = t.Pick(NoteSchema, [
|
||||||
|
"title",
|
||||||
|
"content",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const successGetNoteResponse = t.Object({
|
||||||
|
success:t.Boolean({default:true}),
|
||||||
|
data: t.Array(NoteSchema),
|
||||||
|
error: t.Null(),
|
||||||
|
message: t.String()
|
||||||
|
}, {
|
||||||
|
description:"Success"
|
||||||
|
})
|
||||||
|
|
||||||
|
export const successDeleteNoteResponse = t.Object({
|
||||||
|
success:t.Boolean({default:true}),
|
||||||
|
data: t.Null(),
|
||||||
|
error: t.Null(),
|
||||||
|
message: t.String({default:"Note deletion succesful"})
|
||||||
|
}, {
|
||||||
|
description:"Success"
|
||||||
|
})
|
||||||
|
|
@ -1,44 +1,129 @@
|
||||||
import { Elysia, t } from "elysia";
|
import { Elysia, error, t } from "elysia";
|
||||||
import { Note } from "./note.controller";
|
import { createNoteSchema, NoteSchema, successDeleteNoteResponse, successGetNoteResponse } from "./note.model";
|
||||||
import { memoSchema } from "./note.model";
|
import { NoteController } from "./note.controller";
|
||||||
|
import { userMiddleware } from "../../middlewares/auth-middleware";
|
||||||
|
import { commonResponses } from "../../lib/utils/common";
|
||||||
|
|
||||||
export const note = new Elysia({ prefix: "/note" })
|
export const noteRouter = new Elysia({
|
||||||
.decorate("note", new Note())
|
prefix: "/note",
|
||||||
|
name: "CRUD Operations for Notes",
|
||||||
|
"analytic":true,
|
||||||
|
tags: ["Note"],
|
||||||
|
detail: {
|
||||||
|
description: "Notes CRUD operations",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.decorate("note", new NoteController())
|
||||||
.model({
|
.model({
|
||||||
memo: t.Omit(memoSchema, ["author"]),
|
note: NoteSchema,
|
||||||
})
|
|
||||||
.get("/", ({ note }) => note.data)
|
|
||||||
.put("/", ({ note, body: { data } }) => note.add({ data }), {
|
|
||||||
body: "memo",
|
|
||||||
})
|
})
|
||||||
|
.derive(({ request }) => userMiddleware(request))
|
||||||
.get(
|
.get(
|
||||||
"/:index",
|
"",
|
||||||
({ note, params: { index }, error }) => {
|
async ({ note, user, query}) => {
|
||||||
return note.data[index] ?? error(404, "Not Found :(");
|
return await note.getOwnerNotes(user.id, query.limit, query.offset);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
params: t.Object({
|
query:t.Object({
|
||||||
index: t.Number(),
|
limit: t.Optional(t.Number()),
|
||||||
|
offset: t.Optional(t.Number())
|
||||||
}),
|
}),
|
||||||
|
response:{
|
||||||
|
200: successGetNoteResponse,
|
||||||
|
...commonResponses
|
||||||
|
},
|
||||||
|
detail:{
|
||||||
|
"description":"Get all notes of the user",
|
||||||
|
"summary":"Get all notes"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.guard({
|
.get(
|
||||||
params: t.Object({
|
":id",
|
||||||
index: t.Number(),
|
async ({ note, user, params:{id} }) => {
|
||||||
}),
|
return await note.getNoteById(id, user.id);
|
||||||
})
|
|
||||||
.delete("/:index", ({ note, params: { index }, error }) => {
|
|
||||||
if (index in note.data) return note.remove(index);
|
|
||||||
|
|
||||||
return error(422);
|
|
||||||
})
|
|
||||||
.patch(
|
|
||||||
"/:index",
|
|
||||||
({ note, params: { index }, body: { data }, error }) => {
|
|
||||||
if (index in note.data) return note.update(index, { data });
|
|
||||||
return error(422);
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
body: "memo",
|
params:t.Object({
|
||||||
|
id: t.String(),
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: successGetNoteResponse,
|
||||||
|
...commonResponses
|
||||||
|
},
|
||||||
|
detail:{
|
||||||
|
"description":"Get a note by Id",
|
||||||
|
"summary":"Get a note"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
.post(
|
||||||
|
"",
|
||||||
|
async ({ body, note, user }) => {
|
||||||
|
return await note.createNote(body, user.id);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: createNoteSchema,
|
||||||
|
response: {
|
||||||
|
200: successGetNoteResponse,
|
||||||
|
...commonResponses
|
||||||
|
},
|
||||||
|
detail:{
|
||||||
|
"description":"Create a new note",
|
||||||
|
"summary":"Create a note"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).patch(
|
||||||
|
":id",
|
||||||
|
async ({ body, note, user, params:{id} }) => {
|
||||||
|
return await note.updateNoteById(id, body, user.id);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: createNoteSchema,
|
||||||
|
params:t.Object({
|
||||||
|
id: t.String(),
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: successGetNoteResponse,
|
||||||
|
...commonResponses
|
||||||
|
},
|
||||||
|
detail:{
|
||||||
|
"description":"Update a note by Id",
|
||||||
|
"summary":"Update a note"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).delete(
|
||||||
|
":id",
|
||||||
|
async ({ note, user, params:{id} }) => {
|
||||||
|
return await note.deleteNoteById(id, user.id);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params:t.Object({
|
||||||
|
id: t.String(),
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: successDeleteNoteResponse,
|
||||||
|
...commonResponses
|
||||||
|
},
|
||||||
|
detail:{
|
||||||
|
"description":"Delete a note by Id",
|
||||||
|
"summary":"Delete a note"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.delete(
|
||||||
|
"",
|
||||||
|
async ({ note, user }) => {
|
||||||
|
return await note.deleteAllNotes(user.id);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
response: {
|
||||||
|
200: successDeleteNoteResponse,
|
||||||
|
...commonResponses
|
||||||
|
},
|
||||||
|
detail:{
|
||||||
|
"description":"Delete all notes of an user",
|
||||||
|
"summary":"Delete all notes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
import { pgTable, text, integer, timestamp, boolean } from "drizzle-orm/pg-core";
|
|
||||||
|
|
||||||
export const user = pgTable("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 = pgTable("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)
|
|
||||||
});
|
|
||||||
|
|
||||||
export const account = pgTable("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()
|
|
||||||
});
|
|
||||||
|
|
||||||
export const verification = pgTable("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')
|
|
||||||
});
|
|
||||||
|
|
||||||
export const rateLimit = pgTable("rate_limit", {
|
|
||||||
id: text("id").primaryKey(),
|
|
||||||
key: text('key'),
|
|
||||||
count: integer('count'),
|
|
||||||
lastRequest: integer('last_request')
|
|
||||||
});
|
|
||||||
56
src/db/schema/auth.ts
Normal file
56
src/db/schema/auth.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { text, integer, timestamp, boolean, pgSchema } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
export const authSchema = pgSchema('auth');
|
||||||
|
|
||||||
|
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(),
|
||||||
|
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)
|
||||||
|
});
|
||||||
|
|
||||||
|
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()
|
||||||
|
});
|
||||||
|
|
||||||
|
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')
|
||||||
|
});
|
||||||
|
|
||||||
|
export const rateLimit = authSchema.table("rate_limit", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
key: text('key'),
|
||||||
|
count: integer('count'),
|
||||||
|
lastRequest: integer('last_request')
|
||||||
|
});
|
||||||
13
src/db/schema/note.ts
Normal file
13
src/db/schema/note.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
@ -3,9 +3,8 @@ import { swagger } from "@elysiajs/swagger";
|
||||||
import { opentelemetry } from "@elysiajs/opentelemetry";
|
import { opentelemetry } from "@elysiajs/opentelemetry";
|
||||||
import { serverTiming } from "@elysiajs/server-timing";
|
import { serverTiming } from "@elysiajs/server-timing";
|
||||||
import { cors } from '@elysiajs/cors'
|
import { cors } from '@elysiajs/cors'
|
||||||
import { note } from "./api/note/note.route";
|
import { noteRouter } from "./api/note/note.route";
|
||||||
import { betterAuthView } from "./lib/auth/auth-view";
|
import { betterAuthView } from "./lib/auth/auth-view";
|
||||||
import { userMiddleware, userInfo } from "./middlewares/auth-middleware";
|
|
||||||
import { getBaseConfig, validateEnv } from "./lib/utils/env";
|
import { getBaseConfig, validateEnv } from "./lib/utils/env";
|
||||||
|
|
||||||
const baseConfig = getBaseConfig()
|
const baseConfig = getBaseConfig()
|
||||||
|
|
@ -23,10 +22,8 @@ const app = new Elysia()
|
||||||
if (code === "NOT_FOUND") return "Not Found :(";
|
if (code === "NOT_FOUND") return "Not Found :(";
|
||||||
console.error(error);
|
console.error(error);
|
||||||
})
|
})
|
||||||
.derive(({ request }) => userMiddleware(request))
|
|
||||||
.all("/api/auth/*", betterAuthView)
|
.all("/api/auth/*", betterAuthView)
|
||||||
.use(note)
|
.use(noteRouter)
|
||||||
.get("/user", ({ user, session }) => userInfo(user, session))
|
|
||||||
.get("/", () => `${baseConfig.SERVICE_NAME} Server is Running`)
|
.get("/", () => `${baseConfig.SERVICE_NAME} Server is Running`)
|
||||||
|
|
||||||
validateEnv();
|
validateEnv();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { betterAuth } from "better-auth";
|
||||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||||
import { db } from "../../db/index";
|
import { db } from "../../db/index";
|
||||||
import { openAPI } from "better-auth/plugins"
|
import { openAPI } from "better-auth/plugins"
|
||||||
import { user, account, verification, session } from "../../db/schema";
|
import { user, account, verification, session, rateLimit } from "../../db/schema/auth";
|
||||||
import { sendMail } from "../mail/mail";
|
import { sendMail } from "../mail/mail";
|
||||||
import { renderToStaticMarkup } from "react-dom/server";
|
import { renderToStaticMarkup } from "react-dom/server";
|
||||||
import { createElement } from "react";
|
import { createElement } from "react";
|
||||||
|
|
@ -15,7 +15,8 @@ export const auth = betterAuth({
|
||||||
user: user,
|
user: user,
|
||||||
session: session,
|
session: session,
|
||||||
account: account,
|
account: account,
|
||||||
verification: verification
|
verification: verification,
|
||||||
|
rateLimit: rateLimit,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
rateLimit: {
|
rateLimit: {
|
||||||
|
|
|
||||||
82
src/lib/utils/common.ts
Normal file
82
src/lib/utils/common.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { t } from "elysia";
|
||||||
|
|
||||||
|
export const commonResponses = {
|
||||||
|
400: t.Object(
|
||||||
|
{
|
||||||
|
data: t.Null(),
|
||||||
|
success: t.Boolean({ default: false }),
|
||||||
|
message: t.String({ default: "Bad Request" }),
|
||||||
|
error: t.String({
|
||||||
|
default: "Missing parameters, or invalid parameters.",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
"Bad Request. Usually due to missing parameters, or invalid parameters.",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
401: t.Object(
|
||||||
|
{
|
||||||
|
data: t.Null(),
|
||||||
|
success: t.Boolean({ default: false }),
|
||||||
|
message: t.String({ default: "Unauthorized" }),
|
||||||
|
error: t.String({
|
||||||
|
default: "User needs to sign in to access this resource",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Unauthorized. Due to missing or invalid authentication.",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
403: t.Object(
|
||||||
|
{
|
||||||
|
data: t.Null(),
|
||||||
|
success: t.Boolean({ default: false }),
|
||||||
|
message: t.String({ default: "Forbidden" }),
|
||||||
|
error: t.String({
|
||||||
|
default: "User does not have permission to access this resource",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
"Forbidden. You do not have permission to access this resource or to perform this action.",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
404: t.Object(
|
||||||
|
{
|
||||||
|
data: t.Null(),
|
||||||
|
success: t.Boolean({ default: false }),
|
||||||
|
message: t.String({ default: "Not Found" }),
|
||||||
|
error: t.String({ default: "Requested resource has not found" }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Not Found. The requested resource was not found.",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
429: t.Object(
|
||||||
|
{
|
||||||
|
data: t.Null(),
|
||||||
|
success: t.Boolean({ default: false }),
|
||||||
|
message: t.String({ default: "Too Many Requests" }),
|
||||||
|
error: t.String({
|
||||||
|
default: "RUser has exceeded the rate limit. Try again later.",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
"Too Many Requests. You have exceeded the rate limit. Try again later.",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
500: t.Object(
|
||||||
|
{
|
||||||
|
data: t.Null(),
|
||||||
|
success: t.Boolean({ default: false }),
|
||||||
|
message: t.String({ default: "Internal Server Error" }),
|
||||||
|
error: t.String({ default: "Server faced an error" }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
"Internal Server Error. This is a problem with the server that you cannot fix.",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
import { Session, User } from "better-auth/types";
|
import { Session, User } from "better-auth/types";
|
||||||
import { auth } from "../lib/auth/auth";
|
import { auth } from "../lib/auth/auth";
|
||||||
|
import { error } from "elysia";
|
||||||
|
|
||||||
// user middleware (compute user and session and pass to routes)
|
// user middleware (compute user and session and pass to routes)
|
||||||
export const userMiddleware = async (request: Request) => {
|
export const userMiddleware = async (request: Request) => {
|
||||||
const session = await auth.api.getSession({ headers: request.headers });
|
const session = await auth.api.getSession({ headers: request.headers });
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return {
|
return error("Unauthorized", 401);
|
||||||
user: null,
|
|
||||||
session: null,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue