From d6d0bbeae19ee9a084ea76f158b69b68cafb8d84 Mon Sep 17 00:00:00 2001 From: enpitsulin Date: Wed, 3 Dec 2025 01:05:05 +0800 Subject: [PATCH 1/7] feat: add new table --- .../migrations/0001_sparkling_magma.sql | 7 + .../migrations/meta/0001_snapshot.json | 257 ++++++++++++++++++ server/database/migrations/meta/_journal.json | 7 + server/database/schema.ts | 28 +- 4 files changed, 291 insertions(+), 8 deletions(-) create mode 100644 server/database/migrations/0001_sparkling_magma.sql create mode 100644 server/database/migrations/meta/0001_snapshot.json diff --git a/server/database/migrations/0001_sparkling_magma.sql b/server/database/migrations/0001_sparkling_magma.sql new file mode 100644 index 0000000..7c37060 --- /dev/null +++ b/server/database/migrations/0001_sparkling_magma.sql @@ -0,0 +1,7 @@ +CREATE TABLE `thoughts` ( + `id` text PRIMARY KEY NOT NULL, + `content` text NOT NULL, + `created_at` integer DEFAULT (unixepoch('now')) NOT NULL +); +--> statement-breakpoint +CREATE INDEX `thoughts_published_at_idx` ON `thoughts` (`created_at`); \ No newline at end of file diff --git a/server/database/migrations/meta/0001_snapshot.json b/server/database/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..59a1258 --- /dev/null +++ b/server/database/migrations/meta/0001_snapshot.json @@ -0,0 +1,257 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "cd4a9ecb-db7a-4424-a800-4d7d0324d605", + "prevId": "9dfc4b53-d23a-4ae9-abc8-6a3636cb9841", + "tables": { + "posts": { + "name": "posts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch('now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch('now'))" + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch('now'))" + } + }, + "indexes": { + "posts_slug_unique": { + "name": "posts_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "post_slug_idx": { + "name": "post_slug_idx", + "columns": [ + "slug" + ], + "isUnique": false + }, + "post_published_at_idx": { + "name": "post_published_at_idx", + "columns": [ + "published_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "post_tag": { + "name": "post_tag", + "columns": { + "post_id": { + "name": "post_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "post_tag_idx": { + "name": "post_tag_idx", + "columns": [ + "post_id", + "tag_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "post_tag_post_id_posts_id_fk": { + "name": "post_tag_post_id_posts_id_fk", + "tableFrom": "post_tag", + "tableTo": "posts", + "columnsFrom": [ + "post_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "post_tag_tag_id_tags_id_fk": { + "name": "post_tag_tag_id_tags_id_fk", + "tableFrom": "post_tag", + "tableTo": "tags", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tags": { + "name": "tags", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch('now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch('now'))" + } + }, + "indexes": { + "tags_name_unique": { + "name": "tags_name_unique", + "columns": [ + "name" + ], + "isUnique": true + }, + "idx_name": { + "name": "idx_name", + "columns": [ + "name" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "thoughts": { + "name": "thoughts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch('now'))" + } + }, + "indexes": { + "thoughts_published_at_idx": { + "name": "thoughts_published_at_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/server/database/migrations/meta/_journal.json b/server/database/migrations/meta/_journal.json index 117794d..0f87bc8 100644 --- a/server/database/migrations/meta/_journal.json +++ b/server/database/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1764687511012, "tag": "0000_graceful_yellow_claw", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1764695056485, + "tag": "0001_sparkling_magma", + "breakpoints": true } ] } diff --git a/server/database/schema.ts b/server/database/schema.ts index 0451189..30c0792 100644 --- a/server/database/schema.ts +++ b/server/database/schema.ts @@ -1,7 +1,9 @@ import { relations, sql } from 'drizzle-orm' import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core' -const post = sqliteTable( +// MARK: - Tables + +export const post = sqliteTable( 'posts', { id: text('id').primaryKey(), @@ -24,7 +26,7 @@ const post = sqliteTable( ], ) -const tag = sqliteTable( +export const tag = sqliteTable( 'tags', { id: text('id').primaryKey(), @@ -42,7 +44,7 @@ const tag = sqliteTable( ], ) -const postTag = sqliteTable( +export const postTag = sqliteTable( 'post_tag', { postId: text('post_id').references(() => post.id, { onDelete: 'cascade' }), @@ -53,7 +55,21 @@ const postTag = sqliteTable( ], ) -// Relations +export const thoughts = sqliteTable( + 'thoughts', + { + id: text('id').primaryKey(), + content: text('content').notNull(), + publishedAt: integer('created_at', { mode: 'timestamp' }) + .default(sql`(unixepoch('now'))`) + .notNull(), + }, + table => [ + index('thoughts_published_at_idx').on(table.publishedAt), + ], +) + +// MARK: - Relations export const postRelations = relations(post, ({ many }) => ({ postTags: many(postTag), })) @@ -73,10 +89,6 @@ export const postTagRelations = relations(postTag, ({ one }) => ({ }), })) -// Export all tables -export { post, postTag, tag } - -// Export types export type SelectPost = typeof post.$inferSelect export type InsertPost = typeof post.$inferInsert export type SelectTag = typeof tag.$inferSelect From 572fe2d5ab574a0ca5538ef398376b47aada9b96 Mon Sep 17 00:00:00 2001 From: enpitsulin Date: Wed, 3 Dec 2025 15:40:29 +0800 Subject: [PATCH 2/7] feat: implement thoughts --- .github/workflows/nuxthub.yml | 4 +- app/app.config.ts | 19 +++ app/components/modules/home/thought-item.vue | 31 +++++ .../modules/home/thought-timeline.vue | 42 ++++++ app/pages/(home)/thoughts.vue | 111 +++++++++++++++ .../admin/(dashboard)/thoughts/create.vue | 128 ++++++++++++++++++ server/api/thought/index.get.ts | 63 +++++++++ server/api/thought/index.post.ts | 12 ++ .../migrations/0001_dashing_siren.sql | 8 ++ .../migrations/0001_sparkling_magma.sql | 7 - .../migrations/meta/0001_snapshot.json | 22 ++- server/database/migrations/meta/_journal.json | 4 +- server/database/schema.ts | 10 +- server/utils/markdown.ts | 17 +++ server/utils/thought/thought.ts | 47 +++++++ shared/schema/thought.ts | 8 ++ shared/types/mdc-shim.d.ts | 21 ++- shared/types/post.ts | 6 +- shared/types/thought.ts | 21 +++ 19 files changed, 545 insertions(+), 36 deletions(-) create mode 100644 app/components/modules/home/thought-item.vue create mode 100644 app/components/modules/home/thought-timeline.vue create mode 100644 app/pages/(home)/thoughts.vue create mode 100644 app/pages/admin/(dashboard)/thoughts/create.vue create mode 100644 server/api/thought/index.get.ts create mode 100644 server/api/thought/index.post.ts create mode 100644 server/database/migrations/0001_dashing_siren.sql delete mode 100644 server/database/migrations/0001_sparkling_magma.sql create mode 100644 server/utils/markdown.ts create mode 100644 server/utils/thought/thought.ts create mode 100644 shared/schema/thought.ts create mode 100644 shared/types/thought.ts diff --git a/.github/workflows/nuxthub.yml b/.github/workflows/nuxthub.yml index 0a89ee4..ae3be4c 100644 --- a/.github/workflows/nuxthub.yml +++ b/.github/workflows/nuxthub.yml @@ -3,7 +3,7 @@ on: push jobs: deploy: - name: "Deploy to NuxtHub" + name: Deploy to NuxtHub runs-on: ubuntu-latest permissions: contents: read @@ -19,7 +19,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 22 - cache: 'pnpm' + cache: pnpm - name: Install dependencies run: pnpm install diff --git a/app/app.config.ts b/app/app.config.ts index 139e69f..58e7a2a 100644 --- a/app/app.config.ts +++ b/app/app.config.ts @@ -33,6 +33,11 @@ export default defineAppConfig({ href: '/blog', match: ['blog-page', 'blog-tag-tag', 'blog-slug', 'blog-tag-tag-page'], }, + { + label: '想法', + href: '/thoughts', + match: ['thoughts'], + }, { label: '项目', href: '/projects', @@ -77,5 +82,19 @@ export default defineAppConfig({ }, ], }, + { + label: '想法管理', + href: '/admin/thoughts/create', + match: ['admin-thoughts-create'], + icon: 'i-mingcute:lightbulb-line', + children: [ + { + label: '新建想法', + href: '/admin/thoughts/create', + match: ['admin-thoughts-create'], + icon: 'i-mingcute:add-line', + }, + ], + }, ] as AdminNavigationItem[], }) diff --git a/app/components/modules/home/thought-item.vue b/app/components/modules/home/thought-item.vue new file mode 100644 index 0000000..d1f9a06 --- /dev/null +++ b/app/components/modules/home/thought-item.vue @@ -0,0 +1,31 @@ + + + diff --git a/app/components/modules/home/thought-timeline.vue b/app/components/modules/home/thought-timeline.vue new file mode 100644 index 0000000..6340825 --- /dev/null +++ b/app/components/modules/home/thought-timeline.vue @@ -0,0 +1,42 @@ + + + diff --git a/app/pages/(home)/thoughts.vue b/app/pages/(home)/thoughts.vue new file mode 100644 index 0000000..832af71 --- /dev/null +++ b/app/pages/(home)/thoughts.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/app/pages/admin/(dashboard)/thoughts/create.vue b/app/pages/admin/(dashboard)/thoughts/create.vue new file mode 100644 index 0000000..4ccba40 --- /dev/null +++ b/app/pages/admin/(dashboard)/thoughts/create.vue @@ -0,0 +1,128 @@ + + +