diff --git a/typescript/rest-hono-cf-d1/README.md b/typescript/rest-hono-cf-d1/README.md index a0c98fd..a33818e 100644 --- a/typescript/rest-hono-cf-d1/README.md +++ b/typescript/rest-hono-cf-d1/README.md @@ -21,17 +21,25 @@ npm install ### 2. Create and seed the database -Run the command to create your D1 database +> [!IMPORTANT] +> Update [wrangler.toml](./wrangler.toml) database_id -``` +Run the command to create your production D1 database + +```bash npx wrangler d1 create test-sutando ``` -Update [wrangler.toml](./wrangler.toml) database_id + +Run the command to create your local D1 database + +```bash +npx knex-cloudflare-d1 setup +``` Import test data to local database ``` -npx wrangler d1 execute test-sutando --local --file=./migrations/0000_lucky_gideon.sql +npx sutando migrate:run ``` ### 3. Start the REST API server @@ -67,7 +75,8 @@ You can access the REST API of the server using the following endpoints: - `/users`: Create a new user - Body: - `email: String` (required): The email address of the user - - `name: String` (optional): The name of the user + - `first_name: String` (optional): The first name of the user + - `last_name: String` (optional): The last name of the user ### `PUT` diff --git a/typescript/rest-hono-cf-d1/migrations/0000_lucky_gideon.sql b/typescript/rest-hono-cf-d1/migrations/0000_lucky_gideon.sql deleted file mode 100644 index 4359f70..0000000 --- a/typescript/rest-hono-cf-d1/migrations/0000_lucky_gideon.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE `posts` (`id` varchar(255), `author_id` varchar(255) default '', `title` varchar(255), `content` varchar(255), `published` boolean default '0', `views_count` integer default '0', `created_at` datetime, `updated_at` datetime); -INSERT INTO posts VALUES('b0a99e86-417d-42ad-a710-d2d74f8074a8','6ad9a43f-33a4-4958-a342-cfb578850228','Check out Sutando on GitHub','https://github.com/sutandojs/sutando',1,0,'2024-04-15 03:06:39','2024-04-15 03:06:39'); -INSERT INTO posts VALUES('2bd14692-a69d-480d-8697-fd6a536d266e','99f6bcfa-6195-47dc-8e83-c653c99b2f27','Sutando Documentation','https://sutando.org',1,0,'2024-04-15 03:06:39','2024-04-15 03:06:39'); -INSERT INTO posts VALUES('66553927-ddbe-435d-ac2e-f6cd381cb516','7c753e30-f53a-45ca-b902-67bb0cbe4ef0','Contribute to Sutando on GitHub','https://github.com/sutandojs/sutando/issues',1,0,'2024-04-15 03:06:40','2024-04-15 03:06:40'); -INSERT INTO posts VALUES('7a59a34c-6f4a-4c6b-99ff-45c4333d2627','7c753e30-f53a-45ca-b902-67bb0cbe4ef0','Pull Requests for Sutando','https://github.com/sutandojs/sutando/pulls',0,0,'2024-04-15 03:06:40','2024-04-15 03:06:40'); -CREATE TABLE `users` (`id` varchar(255), `name` varchar(255), `email` varchar(255), `created_at` datetime, `updated_at` datetime); -INSERT INTO users VALUES('6ad9a43f-33a4-4958-a342-cfb578850228','Alice','alice@sutando.org','2024-04-15 03:06:38','2024-04-15 03:06:38'); -INSERT INTO users VALUES('99f6bcfa-6195-47dc-8e83-c653c99b2f27','Nilu','nilu@sutando.org','2024-04-15 03:06:39','2024-04-15 03:06:39'); -INSERT INTO users VALUES('7c753e30-f53a-45ca-b902-67bb0cbe4ef0','Mahmoud','mahmoud@sutando.org','2024-04-15 03:06:39','2024-04-15 03:06:39'); -CREATE UNIQUE INDEX `posts_id_unique` on `posts` (`id`); -CREATE UNIQUE INDEX `users_id_unique` on `users` (`id`); \ No newline at end of file diff --git a/typescript/rest-hono-cf-d1/migrations/2025_01_19_200032_create_users_table.cjs b/typescript/rest-hono-cf-d1/migrations/2025_01_19_200032_create_users_table.cjs new file mode 100644 index 0000000..839db24 --- /dev/null +++ b/typescript/rest-hono-cf-d1/migrations/2025_01_19_200032_create_users_table.cjs @@ -0,0 +1,23 @@ +const { Migration } = require("sutando"); + +module.exports = class extends Migration { + /** + * Run the migrations. + */ + async up(schema) { + await schema.createTable("users", (table) => { + table.increments("id"); + table.string("first_name"); + table.string("last_name"); + table.string("email"); + table.timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + async down(schema) { + await schema.dropTableIfExists("users"); + } +}; diff --git a/typescript/rest-hono-cf-d1/migrations/2025_01_19_200038_create_posts_table.cjs b/typescript/rest-hono-cf-d1/migrations/2025_01_19_200038_create_posts_table.cjs new file mode 100644 index 0000000..908a041 --- /dev/null +++ b/typescript/rest-hono-cf-d1/migrations/2025_01_19_200038_create_posts_table.cjs @@ -0,0 +1,25 @@ +const { Migration, sutando } = require("sutando"); + +module.exports = class extends Migration { + /** + * Run the migrations. + */ + async up(schema) { + await schema.createTable("posts", (table) => { + table.uuid('id'); + table.integer("author_id").unsigned().notNullable(); + table.string("title", 30); + table.string("content"); + table.timestamps(); + table.timestamp("published_at"); + table.integer("views_count").defaultTo(0); + }); + } + + /** + * Reverse the migrations. + */ + async down(schema) { + await schema.dropTableIfExists("posts"); + } +}; diff --git a/typescript/rest-hono-cf-d1/package.json b/typescript/rest-hono-cf-d1/package.json index 8c9c7cf..9f67f4f 100644 --- a/typescript/rest-hono-cf-d1/package.json +++ b/typescript/rest-hono-cf-d1/package.json @@ -5,8 +5,8 @@ }, "dependencies": { "hono": "^4.2.3", - "knex-cloudflare-d1": "^0.1.1", - "sutando": "1.5.4", + "knex-cloudflare-d1": "github:jjjrmy/knex-cloudflare-d1#master", + "sutando": "^1.7.1-dev.7", "uuid": "^9.0.1" }, "devDependencies": { diff --git a/typescript/rest-hono-cf-d1/src/index.ts b/typescript/rest-hono-cf-d1/src/index.ts index e4d85a6..9b25c56 100644 --- a/typescript/rest-hono-cf-d1/src/index.ts +++ b/typescript/rest-hono-cf-d1/src/index.ts @@ -2,7 +2,8 @@ import { Hono } from 'hono'; import { ModelNotFoundError, sutando } from 'sutando'; import { Post, User } from './models'; import type { OrderByDirection } from 'sutando'; -import ClientD1 from 'knex-cloudflare-d1'; +// @ts-ignore +import config from "../sutando.config.cjs"; type Bindings = { DB: D1Database @@ -10,12 +11,17 @@ type Bindings = { const app = new Hono(); +app.get('/', (c) => { + return c.text("Hello World"); +}); + app.post(`/users`, async (c) => { const body = await c.req.json(); - const { name, email } = body; + const { first_name, last_name, email } = body; const user = new User({ - name, + first_name, + last_name, email, }); await user.save(); @@ -48,7 +54,7 @@ app.put('/posts/:id/views', async (c) => { app.get('/users', async (c) => { const users = await User.query().withCount({ - posts: q => q.where('published', true) + posts: q => q.whereNotNull('published_at') }).get(); return c.json(users); }); @@ -58,7 +64,7 @@ app.get('/users/:id/drafts', async (c) => { const drafts = await User.query() .with({ - posts: q => q.where('published', false), + posts: q => q.whereNull('published_at'), }) .findOrFail(id); @@ -106,7 +112,7 @@ app.put('/publish/:id', async (c) => { const { id } = c.req.param(); const post = await Post.query().findOrFail(id); - post.published = !post.published; + post.published_at = new Date(); await post.save(); return c.json(post); @@ -127,11 +133,10 @@ app.onError((err, c) => { export default { fetch: (req: Request, env: Bindings) => { sutando.addConnection({ - client: ClientD1, + ... config, connection: { database: env.DB }, - useNullAsDefault: true, }); return app.fetch(req, env) diff --git a/typescript/rest-hono-cf-d1/src/models.ts b/typescript/rest-hono-cf-d1/src/models.ts index a9fd104..94de786 100644 --- a/typescript/rest-hono-cf-d1/src/models.ts +++ b/typescript/rest-hono-cf-d1/src/models.ts @@ -1,27 +1,24 @@ import { Model, HasUniqueIds } from 'sutando'; import { v4 as uuid } from 'uuid'; -const BaseModel = HasUniqueIds(Model) as typeof Model; +const UuidModel = HasUniqueIds(Model) as typeof Model; -export class User extends BaseModel { +export class User extends Model { table = 'users'; - id!: string; - name!: string; + id!: number; + first_name!: string; + last_name!: string; email!: string; created_at!: Date; updated_at!: Date; - newUniqueId(): string { - return uuid(); - } - relationPosts() { return this.hasMany(Post, 'author_id'); } } -export class Post extends BaseModel { +export class Post extends UuidModel { table = 'posts'; id!: string; @@ -30,11 +27,11 @@ export class Post extends BaseModel { content!: string; created_at!: Date; updated_at!: Date; - published!: boolean; + published_at!: Date; views_count!: number; casts = { - published: 'boolean' + published_at: 'datetime' } newUniqueId(): string { diff --git a/typescript/rest-hono-cf-d1/sutando.config.cjs b/typescript/rest-hono-cf-d1/sutando.config.cjs new file mode 100644 index 0000000..be8c8ab --- /dev/null +++ b/typescript/rest-hono-cf-d1/sutando.config.cjs @@ -0,0 +1,15 @@ +module.exports = { + client: require('knex-cloudflare-d1'), + connection: { + database: "test-sutando", // From Wrangler Binding + local: true, // Toggles `--local` flag on `wrangler d1 exec` command (Default as false) + }, + useNullAsDefault: true, + migrations: { + table: 'migrations', + path: './migrations', + }, + models: { + path: './models' + } + }; \ No newline at end of file diff --git a/typescript/rest-hono-cf-d1/sutando.config.d.ts b/typescript/rest-hono-cf-d1/sutando.config.d.ts new file mode 100644 index 0000000..85d0297 --- /dev/null +++ b/typescript/rest-hono-cf-d1/sutando.config.d.ts @@ -0,0 +1,17 @@ +declare const config: { + client: any; + connection: { + database: string; + wranglerPath: string; + }; + useNullAsDefault: boolean; + migrations: { + table: string; + path: string; + }; + models: { + path: string; + }; +}; + +export default config; \ No newline at end of file diff --git a/typescript/rest-hono-cf-d1/wrangler.toml b/typescript/rest-hono-cf-d1/wrangler.toml index 1a6b329..716a885 100644 --- a/typescript/rest-hono-cf-d1/wrangler.toml +++ b/typescript/rest-hono-cf-d1/wrangler.toml @@ -1,6 +1,7 @@ name = "rest-hono-cf-d1" -compatibility_date = "2023-12-01" -node_compat = true +compatibility_date = "2024-12-05" +compatibility_flags = ["nodejs_compat"] +main = "src/index.ts" # [vars] # MY_VAR = "my-variable"