diff --git a/bun.lockb b/bun.lockb index f058f2d..64132c0 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/prisma/migrations/20240602214206_init/migration.sql b/prisma/migrations/20240602214206_init/migration.sql new file mode 100644 index 0000000..8dc6741 --- /dev/null +++ b/prisma/migrations/20240602214206_init/migration.sql @@ -0,0 +1,13 @@ +-- CreateTable +CREATE TABLE "todos" ( + "id" TEXT NOT NULL PRIMARY KEY, + "title" TEXT NOT NULL, + "done" BOOLEAN NOT NULL DEFAULT false, + "owner" TEXT NOT NULL DEFAULT 'Anonymous', + "avatar" TEXT DEFAULT 'https://abre.ai/jUTD', + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "todos_title_key" ON "todos"("title"); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..e5e5c47 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "sqlite" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a086d26..081b841 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -7,4 +7,14 @@ datasource db { url = env("DATABASE_URL") } -// Model definitions +model Todo { + id String @id + title String @unique + done Boolean @default(false) + owner String @default("Anonymous") + avatar String? @default("https://abre.ai/jUTD") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("todos") +} diff --git a/prisma/table.sql b/prisma/table.sql new file mode 100644 index 0000000..a7eeb6e Binary files /dev/null and b/prisma/table.sql differ diff --git a/src/app.ts b/src/app.ts index 3aec509..87ebc00 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,7 +2,7 @@ import { Hono } from "hono"; import { cors } from "hono/cors"; import { BrowserRouter } from "routes/index"; -export const App = new Hono(); +export const App = new Hono({ strict: true }); App.use(cors({ origin: "*" })); App.route("/", BrowserRouter(App)); diff --git a/src/entities/index.ts b/src/entities/index.ts deleted file mode 100644 index 4986cce..0000000 --- a/src/entities/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export abstract class Services { - abstract create(resource: unknown): Promise; - abstract read(): Promise; - abstract readOne(id: string): Promise; - abstract update(id: string): Promise; - abstract delete(id: string): Promise; -} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts new file mode 100644 index 0000000..b99516d --- /dev/null +++ b/src/interfaces/index.ts @@ -0,0 +1,4 @@ +import type { Todo } from "@prisma/client"; + +export type HttpError = { message: string }; +export type HttpResponse = Todo | Todo[]; diff --git a/src/main.ts b/src/main.ts index abe4c1d..b1e367a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import { log } from "config/logger"; +import { log } from "utils/logger"; import { App } from "./app"; Bun.serve({ diff --git a/src/routes/index.ts b/src/routes/index.ts index 42968ec..95c365d 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -1,57 +1,40 @@ -import { isEmpty } from "radash"; import type { Hono } from "hono"; -import services from "services/index"; +import * as services from "services/index"; export const BrowserRouter = (Router: Hono) => { Router.post("/", async (context) => { const data = await context.req.json(); const response = await services.create(data); - if (response.status !== 201) - return context.json({ error: "Failed to create resource" }, 500); - - return context.body(response.data, 201); + return context.json(response); }); Router.get("/", async (context) => { - const response = await services.read(); - - if (isEmpty(response.data)) - return context.json({ message: "No resources registered" }, 200); + const response = await services.findMany(); - return context.json(response.data, 200); + return context.json(response, 200); }); Router.get("/:id", async (context) => { const id = context.req.param("id"); + const response = await services.findOne(id); - const response = await services.readOne(id); - - if (isEmpty(response.data)) - return context.json({ error: "Resource not found" }, 404); - - return context.json(response.data, 200); + return context.json(response); }); Router.put("/:id", async (context) => { const id = context.req.param("id"); - const response = await services.update(id); - - if (isEmpty(response.data)) - return context.json({ error: "Resource not found" }, 200); + const { title, done } = await context.req.json(); + const response = await services.update(id, { title, done }); - return context.json(response.data, 200); + return context.json(response); }); Router.delete("/:id", async (context) => { const id = context.req.param("id"); - const response = await services.delete(id); - - if (response.status !== 200) { - return context.json({ error: "Failed to delete resource" }, 500); - } + const response = await services.destroy(id); - return context.body(response.data, 200); + return context.json(response); }); return Router; diff --git a/src/services/create/index.ts b/src/services/create/index.ts new file mode 100644 index 0000000..4eee346 --- /dev/null +++ b/src/services/create/index.ts @@ -0,0 +1,28 @@ +import { tryit, uid } from "radash"; +import { HTTPException } from "hono/http-exception"; +import type { Todo } from "@prisma/client"; +import { db } from "config/orm/client"; +import type { HttpError, HttpResponse } from "src/interfaces/index"; + +export const create = async ({ + title, + done, + owner, + avatar +}: Todo): Promise => { + const [error, todo] = await tryit(() => + db.todo.create({ + data: { + id: uid(10), + title, + done, + owner, + avatar + } + }) + )(); + + if (error) throw new HTTPException(409, { message: "Todo Already Exists" }); + + return todo; +}; diff --git a/src/services/delete/index.ts b/src/services/delete/index.ts new file mode 100644 index 0000000..a1b6cff --- /dev/null +++ b/src/services/delete/index.ts @@ -0,0 +1,15 @@ +import { HTTPException } from "hono/http-exception"; +import { tryit } from "radash"; +import { db } from "config/orm/client"; +import type { HttpResponse, HttpError } from "src/interfaces/index"; +import { findMany } from "services/read/findMany"; + +export const destroy = async ( + id: string +): Promise => { + const [error] = await tryit(() => db.todo.delete({ where: { id } }))(); + + if (error) throw new HTTPException(404, { message: "Todo Not Found" }); + + return findMany(); +}; diff --git a/src/services/index.ts b/src/services/index.ts index 5e3b641..e3cf851 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,32 +1,7 @@ -import { uid } from "radash"; -import { db } from "config/orm/client"; -import type { Services } from "entities/index"; +import { findMany } from "./read/findMany"; +import { findOne } from "./read/findOne"; +import { update } from "./update"; +import { create } from "./create"; +import { destroy } from "./delete"; -class Service implements Services { - public async create(resource: unknown) { - // Implement the create method here - return { status: 201, data: null }; - } - - public async read() { - // Implement the read method here - return { status: 200, data: [] }; - } - - public async readOne(id: string) { - // Implement the readOne method here - return { status: 200, data: {} }; - } - - public async update(id: string) { - // Implement the update method here - return { status: 200, data: {} }; - } - - public async delete(id: string) { - // Implement the delete method here - return { status: 200, data: null }; - } -} - -export default new Service(); +export { findMany, findOne, update, create, destroy }; diff --git a/src/services/read/findMany.ts b/src/services/read/findMany.ts new file mode 100644 index 0000000..da367f0 --- /dev/null +++ b/src/services/read/findMany.ts @@ -0,0 +1,9 @@ +import { tryit } from "radash"; +import { db } from "config/orm/client"; +import type { HttpResponse } from "src/interfaces/index"; + +export const findMany = async (): Promise => { + const [_, todos] = await tryit(db.todo.findMany)(); + + return todos ?? []; +}; diff --git a/src/services/read/findOne.ts b/src/services/read/findOne.ts new file mode 100644 index 0000000..57ab98f --- /dev/null +++ b/src/services/read/findOne.ts @@ -0,0 +1,18 @@ +import { tryit } from "radash"; +import { HTTPException } from "hono/http-exception"; +import type { HttpResponse, HttpError } from "src/interfaces/index"; +import { db } from "config/orm/client"; + +export const findOne = async ( + id: string +): Promise => { + const [error, todo] = await tryit(() => + db.todo.findUniqueOrThrow({ + where: { id } + }) + )(); + + if (error) throw new HTTPException(404, { message: "Todo Not Found" }); + + return todo; +}; diff --git a/src/services/update/index.ts b/src/services/update/index.ts new file mode 100644 index 0000000..29bcc82 --- /dev/null +++ b/src/services/update/index.ts @@ -0,0 +1,24 @@ +import { tryit } from "radash"; +import { HTTPException } from "hono/http-exception"; +import type { HttpResponse, HttpError } from "src/interfaces/index"; +import { db } from "config/orm/client"; + +type Fields = { title: string; done: boolean }; + +export const update = async ( + id: string, + data: Fields +): Promise => { + const [error, todo] = await tryit(() => + db.todo.update({ + where: { id }, + data: { + ...data + } + }) + )(); + + if (error) throw new HTTPException(404, { message: "Todo Not Found" }); + + return todo; +}; diff --git a/src/config/logger.ts b/src/utils/logger.ts similarity index 87% rename from src/config/logger.ts rename to src/utils/logger.ts index fde8d70..2d79086 100644 --- a/src/config/logger.ts +++ b/src/utils/logger.ts @@ -16,6 +16,6 @@ export const log = { chalk.underline("http://localhost:3000"), repository: chalk.hex("#0ba95a").bold("Repository: ") + - chalk.underline("https://github.com/username/repo") + chalk.underline("https://github.com/brittof/galxe") } } satisfies Logger; diff --git a/tsconfig.json b/tsconfig.json index 3b7e3f4..810bc58 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "forceConsistentCasingInFileNames": true, "skipLibCheck": true, "allowImportingTsExtensions": true, + "useUnknownInCatchVariables": true, "noEmit": true, "baseUrl": ".", "paths": { @@ -22,7 +23,10 @@ "src/config/*" ], "entities/*": [ - "src/entities/*" + "src/interfaces/*" + ], + "utils/*": [ + "src/utils/*" ] } }