@yimingliao/cms 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@yimingliao/cms",
3
+ "version": "0.0.1",
4
+ "author": "Yiming Liao",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "prisma"
17
+ ],
18
+ "scripts": {
19
+ "type": "tsc --noEmit",
20
+ "build": "tsup",
21
+ "prepublishOnly": "yarn build"
22
+ },
23
+ "dependencies": {
24
+ "argon2": "^0.44.0",
25
+ "jsonwebtoken": "^9.0.3",
26
+ "mime-types": "^3.0.2",
27
+ "ulid": "^3.0.2"
28
+ },
29
+ "devDependencies": {
30
+ "@prisma/client": "6.5.0",
31
+ "@types/jsonwebtoken": "^9.0.10",
32
+ "@types/mime-types": "^3.0.1",
33
+ "next": "^16.1.6",
34
+ "prisma": "6.5.0",
35
+ "tsup": "^8.5.1",
36
+ "typescript": "^5.9.3"
37
+ }
38
+ }
@@ -0,0 +1,84 @@
1
+ model Admin {
2
+ id String @id @default(ulid())
3
+
4
+ // core
5
+ role AdminRole @default(ADMIN) @map("role")
6
+ email String @unique
7
+ passwordHash String @map("password_hash")
8
+
9
+ // better seo
10
+ socialLinks String[] @map("social_links")
11
+
12
+ // -----------------------------------------------------------------------
13
+ // relations: AdminRefreshToken
14
+ // -----------------------------------------------------------------------
15
+ adminRefreshTokens AdminRefreshToken[]
16
+
17
+ // -----------------------------------------------------------------------
18
+ // relations: File
19
+ // -----------------------------------------------------------------------
20
+ avatarImage File? @relation(fields: [avatarImageId], references: [id], name: "avatar_image", onDelete: Restrict)
21
+ avatarImageId String? @map("avatar_image_id")
22
+
23
+ // -----------------------------------------------------------------------
24
+ // relations: Post
25
+ // -----------------------------------------------------------------------
26
+ posts Post[]
27
+
28
+ // -----------------------------------------------------------------------
29
+ // relations: AdminTranslation
30
+ // -----------------------------------------------------------------------
31
+ translations AdminTranslation[]
32
+
33
+ // -----------------------------------------------------------------------
34
+ // timestamps
35
+ // -----------------------------------------------------------------------
36
+ createdAt DateTime @default(now()) @map("created_at")
37
+ updatedAt DateTime @updatedAt @map("updated_at")
38
+ emailVerifiedAt DateTime? @map("email_verified_at")
39
+
40
+ @@map("admins")
41
+ }
42
+
43
+ model AdminTranslation {
44
+ id String @id @default(ulid())
45
+
46
+ // core
47
+ locale String
48
+
49
+ // text
50
+ name String?
51
+
52
+ // better seo
53
+ authorName String? @map("author_name")
54
+ description String?
55
+ jobTitle String? @map("job_title")
56
+ url String?
57
+ worksFor String? @map("works_for")
58
+ knowsAbout String[] @map("knows_about")
59
+ homeLocation String? @map("home_location")
60
+ nationality String?
61
+
62
+ // -----------------------------------------------------------------------
63
+ // relations: Admin
64
+ // -----------------------------------------------------------------------
65
+ admin Admin @relation(fields: [adminId], references: [id], onDelete: Cascade)
66
+ adminId String @map("admin_id")
67
+
68
+ // -----------------------------------------------------------------------
69
+ // timestamps
70
+ // -----------------------------------------------------------------------
71
+ createdAt DateTime @default(now()) @map("created_at")
72
+ updatedAt DateTime @updatedAt @map("updated_at")
73
+
74
+ @@unique([adminId, locale])
75
+ @@map("admin_translations")
76
+ }
77
+
78
+ enum AdminRole {
79
+ SUPER_ADMIN
80
+ ADMIN
81
+ EDITOR
82
+
83
+ @@map("admin_role")
84
+ }
@@ -0,0 +1,27 @@
1
+ model AdminRefreshToken {
2
+ id String @id @default(ulid())
3
+
4
+ // core
5
+ tokenHash String @unique @map("token")
6
+ email String
7
+
8
+ // device info
9
+ ip String
10
+ deviceInfo Json? @map("device_info")
11
+
12
+ // -----------------------------------------------------------------------
13
+ // relations: Admin
14
+ // -----------------------------------------------------------------------
15
+ admin Admin @relation(fields: [adminId], references: [id], onDelete: Cascade)
16
+ adminId String @map("admin_id")
17
+
18
+ // -----------------------------------------------------------------------
19
+ // timestamps
20
+ // -----------------------------------------------------------------------
21
+ createdAt DateTime @default(now()) @map("created_at")
22
+ expiresAt DateTime @map("expires_at")
23
+
24
+ @@index([adminId])
25
+ @@index([expiresAt])
26
+ @@map("admin_refresh_tokens")
27
+ }
@@ -0,0 +1,105 @@
1
+ model File {
2
+ id String @id @default(ulid())
3
+
4
+ // core
5
+ key String @unique
6
+ checksum String
7
+
8
+ // file info
9
+ originalName String @map("original_name")
10
+ size Int @default(0)
11
+ extension String
12
+ mimeType String @map("mime_type")
13
+ type FileType @default(OTHER)
14
+
15
+ // media info
16
+ width Int?
17
+ height Int?
18
+ duration Float?
19
+
20
+ // states
21
+ isLocked Boolean @default(false) @map("is_locked")
22
+
23
+ // -----------------------------------------------------------------------
24
+ // relations: Folder
25
+ // -----------------------------------------------------------------------
26
+ folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
27
+ folderId String? @map("folder_id")
28
+
29
+ // -----------------------------------------------------------------------
30
+ // relations: Admin
31
+ // -----------------------------------------------------------------------
32
+ adminAsAvatarImage Admin[] @relation(name: "avatar_image")
33
+
34
+ // -----------------------------------------------------------------------
35
+ // relations: Post
36
+ // -----------------------------------------------------------------------
37
+ postsAsCoverImage Post[] @relation(name: "cover_image")
38
+ postsAsContentImage Post[] @relation("content_images")
39
+
40
+ // -----------------------------------------------------------------------
41
+ // relations: FileTranslation
42
+ // -----------------------------------------------------------------------
43
+ translations FileTranslation[]
44
+
45
+ // -----------------------------------------------------------------------
46
+ // timestamps
47
+ // -----------------------------------------------------------------------
48
+ createdAt DateTime @default(now()) @map("created_at")
49
+ updatedAt DateTime @updatedAt @map("updated_at")
50
+ deletedAt DateTime? @map("deleted_at")
51
+
52
+ // -----------------------------------------------------------------------
53
+ // --- custom fields
54
+ // -----------------------------------------------------------------------
55
+ // multi images (in post)
56
+ postsAsImages1 Post[] @relation("images1")
57
+ postsAsImages2 Post[] @relation("images2")
58
+
59
+ // single images (in post)
60
+ postsAsImage1 Post[] @relation(name: "image1")
61
+ postsAsImage2 Post[] @relation(name: "image2")
62
+ postsAsImage3 Post[] @relation(name: "image3")
63
+ postsAsImage4 Post[] @relation(name: "image4")
64
+
65
+ @@index([checksum])
66
+ @@index([deletedAt])
67
+ @@map("files")
68
+ }
69
+
70
+ model FileTranslation {
71
+ id String @id @default(ulid())
72
+
73
+ // core
74
+ locale String
75
+
76
+ // text
77
+ name String?
78
+ alt String?
79
+
80
+ // -----------------------------------------------------------------------
81
+ // relations: File
82
+ // -----------------------------------------------------------------------
83
+ file File @relation(fields: [fileId], references: [id], onDelete: Cascade)
84
+ fileId String @map("file_id")
85
+
86
+ // -----------------------------------------------------------------------
87
+ // timestamps
88
+ // -----------------------------------------------------------------------
89
+ createdAt DateTime @default(now()) @map("created_at")
90
+ updatedAt DateTime @updatedAt @map("updated_at")
91
+
92
+ @@unique([fileId, locale])
93
+ @@map("file_translations")
94
+ }
95
+
96
+ enum FileType {
97
+ IMAGE
98
+ AUDIO
99
+ VIDEO
100
+ DOCUMENT
101
+ ARCHIVE
102
+ OTHER
103
+
104
+ @@map("file_type")
105
+ }
@@ -0,0 +1,33 @@
1
+ model Folder {
2
+ id String @id @default(ulid())
3
+
4
+ // core
5
+ key String @unique
6
+ name String
7
+
8
+ // states
9
+ isLocked Boolean @default(false) @map("is_locked")
10
+
11
+ // -----------------------------------------------------------------------
12
+ // relations: Folder
13
+ // -----------------------------------------------------------------------
14
+ parentFolder Folder? @relation("parent_sub_folder", fields: [parentFolderId], references: [id], onDelete: Restrict)
15
+ parentFolderId String? @map("parent_id")
16
+
17
+ // sub folders
18
+ subFolders Folder[] @relation("parent_sub_folder")
19
+
20
+ // -----------------------------------------------------------------------
21
+ // relations: File
22
+ // -----------------------------------------------------------------------
23
+ files File[]
24
+
25
+ // -----------------------------------------------------------------------
26
+ // timestamps
27
+ // -----------------------------------------------------------------------
28
+ createdAt DateTime @default(now()) @map("created_at")
29
+ updatedAt DateTime @updatedAt @map("updated_at")
30
+
31
+ @@index([parentFolderId])
32
+ @@map("folders")
33
+ }
@@ -0,0 +1,86 @@
1
+ model SeoMetadata {
2
+ id String @id @default(ulid())
3
+
4
+ // core
5
+ locale String
6
+
7
+ // -----------------------------------------------------------------------
8
+ // Basic
9
+ // -----------------------------------------------------------------------
10
+ title String?
11
+ description String?
12
+
13
+ // identity
14
+ author String?
15
+
16
+ // url
17
+ canonical String?
18
+ alternate Json[]
19
+
20
+ robots String?
21
+
22
+ // -----------------------------------------------------------------------
23
+ // Open Graph
24
+ // -----------------------------------------------------------------------
25
+ ogTitle String? @map("og_title")
26
+ ogDescription String? @map("og_description")
27
+
28
+ // url
29
+ ogUrl String? @map("og_url")
30
+
31
+ // identity
32
+ ogType String? @map("og_type")
33
+ ogSiteName String? @map("og_site_name")
34
+
35
+ ogImage String? @map("og_image")
36
+ ogImageAlt String? @map("og_image_alt")
37
+ ogImageType String? @map("og_image_type")
38
+ ogImageWidth Int? @map("og_image_width")
39
+ ogImageHeight Int? @map("og_image_height")
40
+
41
+ // locale
42
+ ogLocale String? @map("og_locale")
43
+ ogLocaleAlternate Json[] @map("og_locale_alternate")
44
+
45
+ // article
46
+ ogArticlePublishedTime DateTime? @map("og_article_published_time")
47
+ ogArticleModifiedTime DateTime? @map("og_article_modified_time")
48
+ ogArticleAuthor String? @map("og_article_author")
49
+ ogArticleSection String? @map("og_article_section")
50
+ ogArticleTag Json[] @map("og_article_tag")
51
+
52
+ // -----------------------------------------------------------------------
53
+ // Twitter
54
+ // -----------------------------------------------------------------------
55
+ twitterCard String? @map("twitter_card")
56
+
57
+ // identity
58
+ twitterSite String? @map("twitter_site")
59
+ twitterCreator String? @map("twitter_creator")
60
+
61
+ twitterImage String? @map("twitter_image")
62
+ twitterImageAlt String? @map("twitter_image_alt")
63
+ twitterImageType String? @map("twitter_image_type")
64
+ twitterImageWidth Int? @map("twitter_image_width")
65
+ twitterImageHeight Int? @map("twitter_image_height")
66
+
67
+ // -----------------------------------------------------------------------
68
+ // JSON-LD
69
+ // -----------------------------------------------------------------------
70
+ jsonLd Json[] @map("json_ld")
71
+
72
+ // -----------------------------------------------------------------------
73
+ // relations: Post
74
+ // -----------------------------------------------------------------------
75
+ post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
76
+ postId String @map("post_id")
77
+
78
+ // -----------------------------------------------------------------------
79
+ // timestamps
80
+ // -----------------------------------------------------------------------
81
+ createdAt DateTime @default(now()) @map("created_at")
82
+ updatedAt DateTime @updatedAt @map("updated_at")
83
+
84
+ @@unique([postId, locale])
85
+ @@map("seo_metadata")
86
+ }
@@ -0,0 +1,191 @@
1
+ model Post {
2
+ id String @id @default(ulid())
3
+
4
+ // core
5
+ type PostType @default(POST) @map("type")
6
+
7
+ // -----------------------------------------------------------------------
8
+ // states
9
+ // -----------------------------------------------------------------------
10
+ isLocked Boolean @default(false) @map("is_locked")
11
+
12
+ isActive Boolean @default(true) @map("is_active")
13
+
14
+ isIndexActive Boolean @default(false) @map("is_index_active")
15
+ index Int?
16
+
17
+ isSlugActive Boolean @default(false) @map("is_slug_active")
18
+ slug String
19
+
20
+ isFeatured Boolean @default(false) @map("is_featured")
21
+ isShownOnHome Boolean @default(false) @map("is_show_on_home")
22
+
23
+ // -----------------------------------------------------------------------
24
+ // relations: Admin
25
+ // -----------------------------------------------------------------------
26
+ author Admin? @relation(fields: [authorId], references: [id], onDelete: SetNull)
27
+ authorId String? @map("author_id")
28
+
29
+ // -----------------------------------------------------------------------
30
+ // relations: Post
31
+ // -----------------------------------------------------------------------
32
+ // topic (1-many)
33
+ topic Post? @relation(fields: [topicId], references: [id], name: "post_topic", onDelete: Restrict)
34
+ topicId String? @map("topic_id")
35
+ postsInTopic Post[] @relation("post_topic")
36
+
37
+ // category (many-many)
38
+ parents Post[] @relation("post_parents")
39
+ children Post[] @relation("post_parents")
40
+
41
+ // tags (many-many)
42
+ tags Post[] @relation("post_tags")
43
+ taggedPosts Post[] @relation("post_tags")
44
+
45
+ // relatedPosts (many-many)
46
+ relatedPosts Post[] @relation("related_posts")
47
+ referencingPosts Post[] @relation("related_posts")
48
+
49
+ // -----------------------------------------------------------------------
50
+ // relations: File
51
+ // -----------------------------------------------------------------------
52
+ coverImage File? @relation(fields: [coverImageId], references: [id], name: "cover_image", onDelete: Restrict)
53
+ coverImageId String? @map("cover_image_id")
54
+
55
+ // content images
56
+ contentImages File[] @relation("content_images")
57
+
58
+ // -----------------------------------------------------------------------
59
+ // relations: SeoMetadata
60
+ // -----------------------------------------------------------------------
61
+ seoMetadatas SeoMetadata[]
62
+
63
+ // -----------------------------------------------------------------------
64
+ // relations: PostTranslation
65
+ // -----------------------------------------------------------------------
66
+ translations PostTranslation[]
67
+
68
+ // -----------------------------------------------------------------------
69
+ // timestamps
70
+ // -----------------------------------------------------------------------
71
+ createdAt DateTime @default(now()) @map("created_at")
72
+ updatedAt DateTime @updatedAt @map("updated_at")
73
+ deletedAt DateTime? @map("deleted_at")
74
+
75
+ // -----------------------------------------------------------------------
76
+ // --- custom fields
77
+ // -----------------------------------------------------------------------
78
+ // states
79
+ state1 Boolean @default(false)
80
+ state2 Boolean @default(false)
81
+ state3 Boolean @default(false)
82
+ state4 Boolean @default(false)
83
+ state5 Boolean @default(false)
84
+ state6 Boolean @default(false)
85
+ state7 Boolean @default(false)
86
+ state8 Boolean @default(false)
87
+ state9 Boolean @default(false)
88
+ state10 Boolean @default(false)
89
+
90
+ // multi images
91
+ images1 File[] @relation("images1")
92
+ images2 File[] @relation("images2")
93
+
94
+ // single images
95
+ image1 File? @relation(fields: [image1Id], references: [id], name: "image1", onDelete: Restrict)
96
+ image1Id String? @map("image1_id")
97
+ image2 File? @relation(fields: [image2Id], references: [id], name: "image2", onDelete: Restrict)
98
+ image2Id String? @map("image2_id")
99
+ image3 File? @relation(fields: [image3Id], references: [id], name: "image3", onDelete: Restrict)
100
+ image3Id String? @map("image3_id")
101
+ image4 File? @relation(fields: [image4Id], references: [id], name: "image4", onDelete: Restrict)
102
+ image4Id String? @map("image4_id")
103
+
104
+ // text
105
+ text1 String?
106
+ text2 String?
107
+ text3 String?
108
+ text4 String?
109
+ text5 String?
110
+ text6 String?
111
+ text7 String?
112
+ text8 String?
113
+ text9 String?
114
+ text10 String?
115
+
116
+ // json array
117
+ data1 Json[] @default([])
118
+ data2 Json[] @default([])
119
+ data3 Json[] @default([])
120
+ data4 Json[] @default([])
121
+
122
+ @@index([type, slug])
123
+ @@map("posts")
124
+ }
125
+
126
+ model PostTranslation {
127
+ id String @id @default(ulid())
128
+
129
+ // core
130
+ locale String
131
+
132
+ // text
133
+ title String?
134
+ subtitle String?
135
+ summary String?
136
+ description String?
137
+ content String?
138
+
139
+ // better seo
140
+ externalLinks Json[] @default([]) @map("external_links")
141
+ faq Json[] @default([])
142
+ toc Json[] @default([])
143
+
144
+ // extra
145
+ readTime Int? @map("read_time")
146
+ wordCount Int @default(0) @map("word_count")
147
+
148
+ // -----------------------------------------------------------------------
149
+ // relations: Post
150
+ // -----------------------------------------------------------------------
151
+ post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
152
+ postId String @map("post_id")
153
+
154
+ // -----------------------------------------------------------------------
155
+ // timestamps
156
+ // -----------------------------------------------------------------------
157
+ createdAt DateTime @default(now()) @map("created_at")
158
+ updatedAt DateTime @updatedAt @map("updated_at")
159
+
160
+ // -----------------------------------------------------------------------
161
+ // --- custom fields
162
+ // -----------------------------------------------------------------------
163
+ // text
164
+ text1 String?
165
+ text2 String?
166
+ text3 String?
167
+ text4 String?
168
+ text5 String?
169
+ text6 String?
170
+ text7 String?
171
+ text8 String?
172
+ text9 String?
173
+ text10 String?
174
+
175
+ // json array
176
+ data1 Json[] @default([])
177
+ data2 Json[] @default([])
178
+ data3 Json[] @default([])
179
+ data4 Json[] @default([])
180
+
181
+ @@unique([postId, locale])
182
+ @@map("post_translations")
183
+ }
184
+
185
+ enum PostType {
186
+ TOPIC
187
+ CATEGORY
188
+ POST
189
+ TAG
190
+ PAGE
191
+ }
@@ -0,0 +1,29 @@
1
+ datasource db {
2
+ provider = "postgresql"
3
+ url = env("DATABASE_URL")
4
+ }
5
+
6
+ generator client {
7
+ provider = "prisma-client-js"
8
+ previewFeatures = ["prismaSchemaFolder"]
9
+ }
10
+
11
+ /**
12
+ * CLI (On `test` database):
13
+ * *
14
+ * *
15
+ * - Push
16
+ * yarn cross-env DATABASE_URL="postgresql://test:test@localhost:5432/test?schema=public" prisma db push
17
+ * *
18
+ * *
19
+ * - Push (Force reset)
20
+ * yarn cross-env DATABASE_URL="postgresql://test:test@localhost:5432/test?schema=public" prisma db push --force-reset
21
+ * *
22
+ * *
23
+ * - Migrate status
24
+ * yarn cross-env DATABASE_URL="postgresql://test:test@localhost:5432/test?schema=public" prisma migrate status
25
+ * *
26
+ * *
27
+ * - Migrate reset
28
+ * yarn cross-env DATABASE_URL="postgresql://test:test@localhost:5432/test?schema=public" prisma migrate reset
29
+ */