diff --git a/apps/golinks-v2/migrations/0001_create_golinks_table.sql b/apps/golinks-v2/migrations/0001_create_golinks_table.sql new file mode 100644 index 0000000..71e7315 --- /dev/null +++ b/apps/golinks-v2/migrations/0001_create_golinks_table.sql @@ -0,0 +1,13 @@ +-- CreateTable +CREATE TABLE "GoLink" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "slug" TEXT NOT NULL, + "targetUrl" TEXT NOT NULL DEFAULT 'https://wiki.andreijiroh.xyz/golinks', + "is_active" BOOLEAN NOT NULL DEFAULT true, + "created_on" TEXT NOT NULL, + "updated_on" TEXT NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "GoLink_slug_key" ON "GoLink"("slug"); + diff --git a/apps/golinks-v2/package.json b/apps/golinks-v2/package.json index d140c13..1eeed90 100644 --- a/apps/golinks-v2/package.json +++ b/apps/golinks-v2/package.json @@ -6,9 +6,16 @@ "deploy": "wrangler deploy", "dev": "wrangler dev", "start": "wrangler dev", - "cf-typegen": "wrangler types" + "types:gen": "wrangler types", + "migrations:apply": "wrangler d1 migrations apply golinks-db --local", + "migrations:apply-dev": "wrangler d1 migrations apply golinks-db --local", + "migrations:apply-prod": "wrangler d1 migrations apply golinks-db --remote", + "exec:dev": "wrangler d1 execute golinks-db --local", + "exec:prod": "wrangler d1 execute golinks-db --remote" }, "dependencies": { + "@prisma/adapter-d1": "^5.16.2", + "@prisma/client": "^5.16.2", "chanfana": "^2.0.2", "hono": "^4.4.7", "zod": "^3.23.8" @@ -17,6 +24,7 @@ "@cloudflare/workers-types": "^4.20240712.0", "@types/node": "20.8.3", "@types/service-worker-mock": "^2.0.1", + "prisma": "^5.16.2", "typescript": "^5.5.3", "wrangler": "^3.64.0" } diff --git a/apps/golinks-v2/prisma/schema.prisma b/apps/golinks-v2/prisma/schema.prisma new file mode 100644 index 0000000..60cb6d6 --- /dev/null +++ b/apps/golinks-v2/prisma/schema.prisma @@ -0,0 +1,21 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" + previewFeatures = ["driverAdapters"] +} + +datasource db { + provider = "sqlite" + url = env("DATABASE_URL") +} + +model GoLink { + id Int @id @default(autoincrement()) + slug String @unique + targetUrl String @default("https://wiki.andreijiroh.xyz/golinks") + is_active Boolean @default(true) + created_on String + updated_on String +} diff --git a/apps/golinks-v2/src/api/golinks.ts b/apps/golinks-v2/src/api/golinks.ts new file mode 100644 index 0000000..3dff331 --- /dev/null +++ b/apps/golinks-v2/src/api/golinks.ts @@ -0,0 +1,88 @@ +import { OpenAPIRoute, Num, Bool } from "chanfana" +import { z } from "zod" +import { GoLinks } from "types" +import { getGoLinks } from "lib/db"; + +export class GoLinkCreate extends OpenAPIRoute { + schema = { + tags: ["golinks"], + summary: "Create a golink", + request: { + body: { + content: { + "application/json": { + schema: GoLinks + } + } + } + }, + security: [ + { + adminApiKey: [] + } + ], + responses: { + "200": { + description: "Returns the generated golink" + } + } + } + + async handle(c) { + const data = await this.getValidatedData(); + const linkToCreate = data.body + + return { + success: true, + data: linkToCreate + } + } +} + +export class GoLinkList extends OpenAPIRoute { + schema = { + tags: ["golinks"], + summary: "List all golinks", + request: { + query: z.object({ + page: Num({ + description: "Page number", + default: 0, + }), + isActive: Bool({ + description: "Filter by is_active status", + required: false + }) + }) + }, + responses: { + "200": { + description: "Returns a list of golinks", + content: { + "application/json": { + schema: z.object({ + series: z.object({ + success: Bool(), + result: z.object({ + links: GoLinks.array() + }) + }) + }) + } + } + } + } + }; + + async handle(c) { + const data = await this.getValidatedData() + const { page, isActive } = data.query + + const links = await getGoLinks(c.env.golinks, page, isActive) + + return { + success: true, + links + } + } +} diff --git a/apps/golinks-v2/src/index.ts b/apps/golinks-v2/src/index.ts new file mode 100644 index 0000000..768adc6 --- /dev/null +++ b/apps/golinks-v2/src/index.ts @@ -0,0 +1,29 @@ +import { GoLinkCreate, GoLinkList } from "api/golinks"; +import { fromHono } from "chanfana"; +import { Hono } from "hono"; +import { Env } from "./types" + +// Start a Hono app +const app = new Hono<{ Bindings: Env }>(); + +// Setup OpenAPI registry +const openapi = fromHono(app, { + docs_url: "/api/docs", +}); + +// Register OpenAPI endpoints +openapi.get("/api/links", GoLinkList) +openapi.post("/api/links", GoLinkCreate) + +openapi.registry.registerComponent( + "securitySchemea", + "adminApiKey", + { + type: "apiKey", + name: "X-Golinks-Admin-Key", + in: "header" + } +) + +// Export the Hono app +export default app; diff --git a/apps/golinks-v2/src/types.ts b/apps/golinks-v2/src/types.ts new file mode 100644 index 0000000..3674566 --- /dev/null +++ b/apps/golinks-v2/src/types.ts @@ -0,0 +1,23 @@ +import { DateTime, Int, Str } from "chanfana"; +import { z } from "zod"; + +export const Task = z.object({ + name: Str({ example: "lorem" }), + slug: Str(), + description: Str({ required: false }), + completed: z.boolean().default(false), + due_date: DateTime(), +}); + +export const GoLinks = z.object({ + id: Int({ required: false }), + slug: Str({ required: true }), + targetUrl: Str({ example: "https://example.com" }), + is_active: z.boolean().default(true), + created_on: DateTime({ default: Date.now() }), + updated_on: DateTime({ default: Date.now() }) +}) + +export type Env = { + golinks: D1Database; +} diff --git a/apps/prisma/schema.prisma b/apps/prisma/schema.prisma new file mode 100644 index 0000000..31dbd18 --- /dev/null +++ b/apps/prisma/schema.prisma @@ -0,0 +1,8 @@ +model GoLink { + id Int @id @default(autoincrement()) + slug String @unique + targetUrl String @default("https://example.com") + created_on DateTime + updated_on DateTime + is_active Boolean @default(true) +} diff --git a/yarn.lock b/yarn.lock index 65a42c5..a6a1378 100644 --- a/yarn.lock +++ b/yarn.lock @@ -139,6 +139,13 @@ __metadata: languageName: node linkType: hard +"@cloudflare/workers-types@npm:4.20240614.0": + version: 4.20240614.0 + resolution: "@cloudflare/workers-types@npm:4.20240614.0" + checksum: 10c0/e088ba3df4a54822d53f602b7f362c1e82d7c9b2f5d74e9b1b98006221802093e4830a6c140a8e04a3730916ccf6929598c0d9575761b62a9161a3d242ceb8bf + languageName: node + linkType: hard + "@cloudflare/workers-types@npm:^4.20240605.0": version: 4.20240605.0 resolution: "@cloudflare/workers-types@npm:4.20240605.0" @@ -438,6 +445,83 @@ __metadata: languageName: node linkType: hard +"@prisma/adapter-d1@npm:^5.16.2": + version: 5.16.2 + resolution: "@prisma/adapter-d1@npm:5.16.2" + dependencies: + "@cloudflare/workers-types": "npm:4.20240614.0" + "@prisma/driver-adapter-utils": "npm:5.16.2" + checksum: 10c0/5afed818201a990ea5eec66d31586d73d02162ce9d1090db90421c2b92344f6e3caae2386e67c6b4f86cd1777fcd8d02f5b5711e56a66d161caba42ca5dc6fab + languageName: node + linkType: hard + +"@prisma/client@npm:^5.16.2": + version: 5.16.2 + resolution: "@prisma/client@npm:5.16.2" + peerDependencies: + prisma: "*" + peerDependenciesMeta: + prisma: + optional: true + checksum: 10c0/4aefff22a3ad5ac3a7b6298c41949e4962b328a13d9f9e9095b530ce3ad931a1052716450d464850f240e6fa109b6db5719a9cf0f868e75ad44c39ad2409f2df + languageName: node + linkType: hard + +"@prisma/debug@npm:5.16.2": + version: 5.16.2 + resolution: "@prisma/debug@npm:5.16.2" + checksum: 10c0/63dac1010cb8304f512b84c903209f4f5b3d8a359284b576101d11e457af9af4eb57e78a1445e101e9e4a6801cdd04e299f12dd0a50eb26fd785387f6c0986b7 + languageName: node + linkType: hard + +"@prisma/driver-adapter-utils@npm:5.16.2": + version: 5.16.2 + resolution: "@prisma/driver-adapter-utils@npm:5.16.2" + dependencies: + "@prisma/debug": "npm:5.16.2" + checksum: 10c0/66e6d99d72a0acb42411dd8991f83e3e844b24376404041f78451dc85ff19b16bc61e08b16d44a776a7d8fa6bdbb417543c3f2c75d15751451fb137df497bd4d + languageName: node + linkType: hard + +"@prisma/engines-version@npm:5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303": + version: 5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303 + resolution: "@prisma/engines-version@npm:5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303" + checksum: 10c0/de02aed86429fbae0c98dbcb35baa9e1583a37aa222692a832a24153ed173a5559257c31296ecbd8ae5a8a99887f3aaf0797d819ca45e562905ce0bea411fe75 + languageName: node + linkType: hard + +"@prisma/engines@npm:5.16.2": + version: 5.16.2 + resolution: "@prisma/engines@npm:5.16.2" + dependencies: + "@prisma/debug": "npm:5.16.2" + "@prisma/engines-version": "npm:5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303" + "@prisma/fetch-engine": "npm:5.16.2" + "@prisma/get-platform": "npm:5.16.2" + checksum: 10c0/4e059639779402c9446b2d4795f7cc6b17ef1e9eb85f000a8b591d45fc755fbe66d49b6acec7ed2fd2847b4ffd48fefdc961a9e2439a24de53e59447e03179b5 + languageName: node + linkType: hard + +"@prisma/fetch-engine@npm:5.16.2": + version: 5.16.2 + resolution: "@prisma/fetch-engine@npm:5.16.2" + dependencies: + "@prisma/debug": "npm:5.16.2" + "@prisma/engines-version": "npm:5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303" + "@prisma/get-platform": "npm:5.16.2" + checksum: 10c0/00b63683df0278bdcfa4b88756189359dbe03ac5a08f33db29cba839fd9f5c97818608ac9ddc4bc99e407957e7227a5bb6dbb08d64c709835af097a607da78aa + languageName: node + linkType: hard + +"@prisma/get-platform@npm:5.16.2": + version: 5.16.2 + resolution: "@prisma/get-platform@npm:5.16.2" + dependencies: + "@prisma/debug": "npm:5.16.2" + checksum: 10c0/4a9cb31411944e59f8fd678c38e40de8f52dcb931a63f95bd33b951157c5d61229d5d0c5928fd9450a49edcb0723cabf239e65c6f277488af7cb36d45bb125c4 + languageName: node + linkType: hard + "@sindresorhus/is@npm:^4.0.0": version: 4.6.0 resolution: "@sindresorhus/is@npm:4.6.0" @@ -938,10 +1022,13 @@ __metadata: resolution: "cloudflare-workers-openapi@workspace:apps/golinks-v2" dependencies: "@cloudflare/workers-types": "npm:^4.20240712.0" + "@prisma/adapter-d1": "npm:^5.16.2" + "@prisma/client": "npm:^5.16.2" "@types/node": "npm:20.8.3" "@types/service-worker-mock": "npm:^2.0.1" chanfana: "npm:^2.0.2" hono: "npm:^4.4.7" + prisma: "npm:^5.16.2" typescript: "npm:^5.5.3" wrangler: "npm:^3.64.0" zod: "npm:^3.23.8" @@ -2083,6 +2170,17 @@ __metadata: languageName: node linkType: hard +"prisma@npm:^5.16.2": + version: 5.16.2 + resolution: "prisma@npm:5.16.2" + dependencies: + "@prisma/engines": "npm:5.16.2" + bin: + prisma: build/index.js + checksum: 10c0/0820150bfe2f9372b34b97be694554fe8ceb51542e467df0afed94575c425556b534bf8080d8526544c5d3ec339c43072360abfe6b14319b65a0480b116aa983 + languageName: node + linkType: hard + "proc-log@npm:^3.0.0": version: 3.0.0 resolution: "proc-log@npm:3.0.0"