mirror of
https://github.com/ngthanhvu/kr-phim.git
synced 2026-08-10 15:27:46 +00:00
feat: add gender field to user profile and update related components
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { CornerDownLeft, Crown, Loader2, ThumbsDown, ThumbsUp, Trash2, User } from 'lucide-vue-next'
|
||||
import { CornerDownLeft, Crown, Loader2, ThumbsDown, ThumbsUp, Trash2, User, Venus, Mars, Minus } from 'lucide-vue-next'
|
||||
|
||||
const props = defineProps<{
|
||||
replies: any[]
|
||||
@@ -48,6 +48,9 @@ function handleVote(replyId: number, vote: number) {
|
||||
<span v-if="reply.userRole === 'admin' && !reply.anonymous"
|
||||
class="px-1.5 py-0.5 rounded text-[9px] font-black bg-yellow-400 text-slate-900">ADMIN</span>
|
||||
<span class="text-xs font-semibold text-white">{{ reply.userName }}</span>
|
||||
<Venus v-if="reply.userGender === 'female' && !reply.anonymous" class="size-3 text-pink-400" />
|
||||
<Mars v-if="reply.userGender === 'male' && !reply.anonymous" class="size-3 text-blue-400" />
|
||||
<Minus v-if="reply.userGender === 'other' && !reply.anonymous" class="size-3 text-purple-400" />
|
||||
<span v-if="reply.anonymous"
|
||||
class="px-1 py-0.5 rounded text-[9px] font-medium bg-white/10 text-slate-400">Ẩn
|
||||
danh</span>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ChevronDown, ChevronUp, CornerDownLeft, Crown, Loader2, MessageSquare, Pin, Send, ThumbsDown, ThumbsUp, Trash2, User } from 'lucide-vue-next'
|
||||
import { ChevronDown, ChevronUp, CornerDownLeft, Crown, Loader2, MessageSquare, Pin, Send, ThumbsDown, ThumbsUp, Trash2, User, Venus, Mars, Minus } from 'lucide-vue-next'
|
||||
|
||||
const props = defineProps<{
|
||||
source: string
|
||||
@@ -110,6 +110,7 @@ async function handleSubmitComment() {
|
||||
userName: user.value.name || 'Ẩn danh',
|
||||
userAvatar: user.value.avatar,
|
||||
userRole: user.value.role,
|
||||
userGender: user.value.gender,
|
||||
replies: [],
|
||||
timeAgo: 'Vừa xong'
|
||||
})
|
||||
@@ -229,6 +230,7 @@ async function submitReply(parentId: number) {
|
||||
userName: user.value.name || 'Ẩn danh',
|
||||
userAvatar: user.value.avatar,
|
||||
userRole: user.value.role,
|
||||
userGender: user.value.gender,
|
||||
timeAgo: 'Vừa xong',
|
||||
anonymous: isAnon,
|
||||
replies: []
|
||||
@@ -423,6 +425,9 @@ watch(() => props.slug, () => {
|
||||
<span v-if="comment.userRole === 'admin' && !comment.anonymous"
|
||||
class="px-2 py-0.5 rounded text-[10px] font-black bg-yellow-400 text-slate-900 shadow-sm">ADMIN</span>
|
||||
<span class="text-sm font-semibold text-white">{{ comment.userName }}</span>
|
||||
<Venus v-if="comment.userGender === 'female' && !comment.anonymous" class="size-3.5 text-pink-400" />
|
||||
<Mars v-if="comment.userGender === 'male' && !comment.anonymous" class="size-3.5 text-blue-400" />
|
||||
<Minus v-if="comment.userGender === 'other' && !comment.anonymous" class="size-3.5 text-purple-400" />
|
||||
<span v-if="comment.anonymous"
|
||||
class="px-1.5 py-0.5 rounded text-[10px] font-medium bg-white/10 text-slate-400">Ẩn danh</span>
|
||||
<span class="text-xs text-slate-600">{{ timeAgo(comment.createdAt) }}</span>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
export default defineNuxtRouteMiddleware(async (to) => {
|
||||
if (to.path !== '/thanh-vien') return
|
||||
|
||||
const { data: user, error } = await useFetch('/api/auth/me', {
|
||||
headers: useRequestHeaders(['cookie']),
|
||||
})
|
||||
let user = null
|
||||
try {
|
||||
user = await $fetch('/api/auth/me')
|
||||
} catch {
|
||||
return navigateTo('/')
|
||||
}
|
||||
|
||||
if (error.value?.statusCode === 401 || !user.value) {
|
||||
if (!user) {
|
||||
return navigateTo('/')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ArrowLeft, Camera, Check, Clock3, Eye, EyeOff, Heart, KeyRound, LogOut, LockKeyhole, Mail, Pencil, Settings, UserRound, X } from 'lucide-vue-next'
|
||||
import { ArrowLeft, Camera, Check, Clock3, Crown, Eye, EyeOff, Heart, KeyRound, LogOut, LockKeyhole, Mail, Pencil, Settings, UserRound, X, Venus, Mars, Minus } from 'lucide-vue-next'
|
||||
|
||||
definePageMeta({ middleware: 'auth' })
|
||||
|
||||
@@ -25,6 +25,15 @@ const displayNameInput = ref('')
|
||||
const nameLoading = ref(false)
|
||||
const nameError = ref('')
|
||||
const nameSuccess = ref('')
|
||||
const genderLoading = ref(false)
|
||||
const genderError = ref('')
|
||||
const genderSuccess = ref('')
|
||||
|
||||
const genderOptions = [
|
||||
{ value: 'male', label: 'Nam', icon: Mars },
|
||||
{ value: 'female', label: 'Nữ', icon: Venus },
|
||||
{ value: 'other', label: 'Khác', icon: Minus },
|
||||
]
|
||||
|
||||
const avatarCategories = [
|
||||
{ id: 'meme', label: 'Meme' },
|
||||
@@ -43,6 +52,8 @@ const avatarUrl = computed(() => {
|
||||
return null
|
||||
})
|
||||
|
||||
const isAdmin = computed(() => user.value?.role === 'admin')
|
||||
|
||||
const avatarGrid = computed(() => {
|
||||
const categoryMap: Record<string, string[]> = {
|
||||
meme: ['01', '02', '03', '04', '05', '06', '07', '08'],
|
||||
@@ -194,6 +205,28 @@ async function saveName() {
|
||||
}
|
||||
}
|
||||
|
||||
async function saveGender(gender: string) {
|
||||
genderLoading.value = true
|
||||
genderError.value = ''
|
||||
genderSuccess.value = ''
|
||||
|
||||
try {
|
||||
await $fetch('/api/auth/profile', {
|
||||
method: 'PUT',
|
||||
body: { gender },
|
||||
})
|
||||
genderSuccess.value = 'Cập nhật giới tính thành công!'
|
||||
await fetchUser()
|
||||
setTimeout(() => {
|
||||
genderSuccess.value = ''
|
||||
}, 1500)
|
||||
} catch (err: any) {
|
||||
genderError.value = err?.data?.message || 'Cập nhật thất bại'
|
||||
} finally {
|
||||
genderLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(user, (u) => {
|
||||
if (u) {
|
||||
displayNameInput.value = u.name || ''
|
||||
@@ -225,11 +258,17 @@ useHead({
|
||||
<aside class="w-full lg:w-64 shrink-0">
|
||||
<div class="lg:sticky lg:top-28">
|
||||
<div class="mb-4 flex items-center gap-3 rounded-xl border border-white/[0.08] bg-[#12121a] p-4 lg:hidden">
|
||||
<div v-if="avatarUrl" class="size-10 shrink-0 overflow-hidden rounded-full ring-2 ring-[#FFD166]/20">
|
||||
<img :src="avatarUrl" :alt="memberName" class="size-full object-cover">
|
||||
</div>
|
||||
<div v-else class="grid size-10 shrink-0 place-items-center rounded-full bg-[#FFD166]/10">
|
||||
<span class="text-sm font-bold text-[#FFD166]">{{ memberName.charAt(0).toUpperCase() }}</span>
|
||||
<div class="relative shrink-0">
|
||||
<div v-if="avatarUrl" class="size-10 shrink-0 overflow-hidden rounded-full"
|
||||
:class="isAdmin ? 'ring-2 ring-[#FFD166]' : 'ring-2 ring-[#FFD166]/20'">
|
||||
<img :src="avatarUrl" :alt="memberName" class="size-full object-cover">
|
||||
</div>
|
||||
<div v-else class="grid size-10 shrink-0 place-items-center rounded-full"
|
||||
:class="isAdmin ? 'bg-[#FFD166]/20 ring-2 ring-[#FFD166]' : 'bg-[#FFD166]/10 ring-2 ring-[#FFD166]/20'">
|
||||
<span class="text-sm font-bold text-[#FFD166]">{{ memberName.charAt(0).toUpperCase() }}</span>
|
||||
</div>
|
||||
<Crown v-if="isAdmin"
|
||||
class="absolute -right-1 -top-1 grid size-5 place-items-center rounded-full bg-[#0a0a0f] p-0.5 text-[#FFD166] ring-1 ring-[#FFD166]/40" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<p class="truncate text-sm font-semibold">{{ memberName }}</p>
|
||||
@@ -309,6 +348,21 @@ useHead({
|
||||
class="h-12 w-full cursor-not-allowed rounded-xl border border-white/[0.06] bg-white/[0.02] px-4 text-sm text-white/50 outline-none">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-xs font-medium text-white/40">Giới tính</label>
|
||||
<div class="flex gap-2">
|
||||
<button v-for="opt in genderOptions" :key="opt.value" type="button"
|
||||
class="flex-1 flex items-center justify-center gap-2 h-12 rounded-xl border text-sm font-semibold transition-all"
|
||||
:class="user.gender === opt.value
|
||||
? 'border-[#FFD166]/40 bg-[#FFD166]/10 text-[#FFD166]'
|
||||
: 'border-white/[0.08] bg-white/[0.04] text-white/50 hover:border-white/[0.15] hover:text-white/70'"
|
||||
:disabled="genderLoading" @click="saveGender(opt.value)">
|
||||
<component :is="opt.icon" class="size-4" />
|
||||
<span>{{ opt.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="nameError" class="flex items-center gap-2 rounded-xl bg-red-500/10 p-3 text-sm text-red-400">
|
||||
<X class="size-4 shrink-0" />
|
||||
{{ nameError }}
|
||||
@@ -318,6 +372,16 @@ useHead({
|
||||
<Check class="size-4 shrink-0" />
|
||||
{{ nameSuccess }}
|
||||
</p>
|
||||
<p v-if="genderError"
|
||||
class="flex items-center gap-2 rounded-xl bg-red-500/10 p-3 text-sm text-red-400">
|
||||
<X class="size-4 shrink-0" />
|
||||
{{ genderError }}
|
||||
</p>
|
||||
<p v-if="genderSuccess"
|
||||
class="flex items-center gap-2 rounded-xl bg-green-500/10 p-3 text-sm font-medium text-green-400">
|
||||
<Check class="size-4 shrink-0" />
|
||||
{{ genderSuccess }}
|
||||
</p>
|
||||
|
||||
<div class="flex flex-wrap gap-2 pt-2">
|
||||
<button type="button"
|
||||
@@ -343,13 +407,16 @@ useHead({
|
||||
|
||||
<div class="flex flex-col items-center gap-3 lg:w-48 lg:shrink-0">
|
||||
<div class="group/avatar relative">
|
||||
<div v-if="avatarUrl" class="size-32 overflow-hidden rounded-full ring-4 ring-[#FFD166]/20">
|
||||
<div v-if="avatarUrl" class="size-32 overflow-hidden rounded-full"
|
||||
:class="isAdmin ? 'ring-4 ring-[#FFD166]' : 'ring-4 ring-[#FFD166]/20'">
|
||||
<img :src="avatarUrl" :alt="memberName" class="size-full object-cover">
|
||||
</div>
|
||||
<div v-else
|
||||
class="grid size-32 place-items-center rounded-full bg-[#FFD166]/10 ring-4 ring-[#FFD166]/20">
|
||||
<div v-else class="grid size-32 place-items-center rounded-full"
|
||||
:class="isAdmin ? 'bg-[#FFD166]/20 ring-4 ring-[#FFD166]' : 'bg-[#FFD166]/10 ring-4 ring-[#FFD166]/20'">
|
||||
<span class="text-4xl font-black text-[#FFD166]">{{ memberName.charAt(0).toUpperCase() }}</span>
|
||||
</div>
|
||||
<Crown v-if="isAdmin"
|
||||
class="absolute -right-2 -top-2 grid size-9 place-items-center rounded-full bg-[#FFD166] p-1.5 text-[#0f111a] ring-2 ring-[#FFD166]/40" />
|
||||
<button type="button"
|
||||
class="absolute -right-1 -bottom-1 grid size-9 place-items-center rounded-full bg-[#FFD166] text-[#0f111a] shadow-lg transition hover:bg-[#FFC845]"
|
||||
@click="avatarModalOpen = true">
|
||||
|
||||
@@ -28,5 +28,6 @@ export default defineEventHandler(async (event) => {
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
avatar: user.avatar,
|
||||
gender: user.gender,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { users } from '../../database/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { getTokenFromEvent } from '../../utils/auth'
|
||||
import { getTokenFromEvent, verifyToken } from '../../utils/auth'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = useDb()
|
||||
@@ -11,24 +11,30 @@ export default defineEventHandler(async (event) => {
|
||||
throw createError({ statusCode: 401, message: 'Chưa đăng nhập' })
|
||||
}
|
||||
|
||||
const name = typeof body.name === 'string' ? body.name.trim() : ''
|
||||
|
||||
if (!name) {
|
||||
throw createError({ statusCode: 400, message: 'Tên hiển thị không được để trống' })
|
||||
}
|
||||
|
||||
if (name.length > 50) {
|
||||
throw createError({ statusCode: 400, message: 'Tên hiển thị tối đa 50 ký tự' })
|
||||
}
|
||||
|
||||
const payload = verifyToken(token)
|
||||
if (!payload) {
|
||||
throw createError({ statusCode: 401, message: 'Token không hợp lệ' })
|
||||
}
|
||||
|
||||
await db.update(users)
|
||||
.set({ name })
|
||||
.where(eq(users.id, payload.id))
|
||||
const updates: Record<string, any> = {}
|
||||
|
||||
return { message: 'Cập nhật tên thành công!' }
|
||||
const name = typeof body.name === 'string' ? body.name.trim() : ''
|
||||
if (name) {
|
||||
if (name.length > 50) {
|
||||
throw createError({ statusCode: 400, message: 'Tên hiển thị tối đa 50 ký tự' })
|
||||
}
|
||||
updates.name = name
|
||||
}
|
||||
|
||||
if (['male', 'female', 'other', ''].includes(body.gender)) {
|
||||
updates.gender = body.gender || null
|
||||
}
|
||||
|
||||
if (!Object.keys(updates).length) {
|
||||
throw createError({ statusCode: 400, message: 'Không có dữ liệu cập nhật' })
|
||||
}
|
||||
|
||||
await db.update(users).set(updates).where(eq(users.id, payload.id))
|
||||
|
||||
return { message: 'Cập nhật thành công!' }
|
||||
})
|
||||
|
||||
@@ -36,6 +36,7 @@ export default defineEventHandler(async (event) => {
|
||||
userName: users.name,
|
||||
userAvatar: users.avatar,
|
||||
userRole: users.role,
|
||||
userGender: users.gender,
|
||||
source: comments.source,
|
||||
slug: comments.slug,
|
||||
movieName: comments.movieName,
|
||||
@@ -75,6 +76,7 @@ export default defineEventHandler(async (event) => {
|
||||
userName: rep.anonymous ? 'Ẩn danh' : (rep.userName || 'Ẩn danh'),
|
||||
userAvatar: rep.anonymous ? null : rep.userAvatar,
|
||||
userRole: rep.userRole,
|
||||
userGender: rep.userGender,
|
||||
parentId: rep.parentId,
|
||||
content: rep.content,
|
||||
pinned: rep.pinned || false,
|
||||
@@ -97,6 +99,7 @@ export default defineEventHandler(async (event) => {
|
||||
userName: users.name,
|
||||
userAvatar: users.avatar,
|
||||
userRole: users.role,
|
||||
userGender: users.gender,
|
||||
parentId: comments.parentId,
|
||||
content: comments.content,
|
||||
pinned: comments.pinned,
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `users` ADD `gender` varchar(20);
|
||||
@@ -0,0 +1,566 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "mysql",
|
||||
"id": "cdff5cf9-c17b-4367-a3db-46bd089b4b44",
|
||||
"prevId": "80c783b0-5942-4747-b8da-78c937b01d64",
|
||||
"tables": {
|
||||
"comment_votes": {
|
||||
"name": "comment_votes",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"comment_id": {
|
||||
"name": "comment_id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"vote": {
|
||||
"name": "vote",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"idx_user_comment": {
|
||||
"name": "idx_user_comment",
|
||||
"columns": [
|
||||
"user_id",
|
||||
"comment_id"
|
||||
],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"comment_votes_id": {
|
||||
"name": "comment_votes_id",
|
||||
"columns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraint": {}
|
||||
},
|
||||
"comments": {
|
||||
"name": "comments",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"source": {
|
||||
"name": "source",
|
||||
"type": "varchar(50)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "''"
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(500)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"movie_name": {
|
||||
"name": "movie_name",
|
||||
"type": "varchar(500)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"parent_id": {
|
||||
"name": "parent_id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"pinned": {
|
||||
"name": "pinned",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"spoiler": {
|
||||
"name": "spoiler",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"anonymous": {
|
||||
"name": "anonymous",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"like_count": {
|
||||
"name": "like_count",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"dislike_count": {
|
||||
"name": "dislike_count",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"onUpdate": true,
|
||||
"default": "(now())"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"idx_source_slug": {
|
||||
"name": "idx_source_slug",
|
||||
"columns": [
|
||||
"source",
|
||||
"slug"
|
||||
],
|
||||
"isUnique": false
|
||||
},
|
||||
"idx_user_id": {
|
||||
"name": "idx_user_id",
|
||||
"columns": [
|
||||
"user_id"
|
||||
],
|
||||
"isUnique": false
|
||||
},
|
||||
"idx_parent_id": {
|
||||
"name": "idx_parent_id",
|
||||
"columns": [
|
||||
"parent_id"
|
||||
],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"comments_id": {
|
||||
"name": "comments_id",
|
||||
"columns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraint": {}
|
||||
},
|
||||
"movies": {
|
||||
"name": "movies",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"source": {
|
||||
"name": "source",
|
||||
"type": "varchar(50)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(500)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(500)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"origin_name": {
|
||||
"name": "origin_name",
|
||||
"type": "varchar(500)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"thumb": {
|
||||
"name": "thumb",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"poster": {
|
||||
"name": "poster",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"year": {
|
||||
"name": "year",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"time": {
|
||||
"name": "time",
|
||||
"type": "varchar(100)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"episode": {
|
||||
"name": "episode",
|
||||
"type": "varchar(100)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"episode_total": {
|
||||
"name": "episode_total",
|
||||
"type": "varchar(100)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"quality": {
|
||||
"name": "quality",
|
||||
"type": "varchar(50)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"lang": {
|
||||
"name": "lang",
|
||||
"type": "varchar(50)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "varchar(50)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"rating": {
|
||||
"name": "rating",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"views": {
|
||||
"name": "views",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"categories": {
|
||||
"name": "categories",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"countries": {
|
||||
"name": "countries",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"sources": {
|
||||
"name": "sources",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"custom_poster": {
|
||||
"name": "custom_poster",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"custom_thumb": {
|
||||
"name": "custom_thumb",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"custom_content": {
|
||||
"name": "custom_content",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"custom_episodes": {
|
||||
"name": "custom_episodes",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"active": {
|
||||
"name": "active",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"api_updated_at": {
|
||||
"name": "api_updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"synced_at": {
|
||||
"name": "synced_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"onUpdate": true,
|
||||
"default": "(now())"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"movies_id": {
|
||||
"name": "movies_id",
|
||||
"columns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraint": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "int",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(200)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"password": {
|
||||
"name": "password",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "varchar(50)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'user'"
|
||||
},
|
||||
"avatar": {
|
||||
"name": "avatar",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"gender": {
|
||||
"name": "gender",
|
||||
"type": "varchar(20)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"reset_token": {
|
||||
"name": "reset_token",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"reset_token_expires": {
|
||||
"name": "reset_token_expires",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"active": {
|
||||
"name": "active",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(now())"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"onUpdate": true,
|
||||
"default": "(now())"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"users_id": {
|
||||
"name": "users_id",
|
||||
"columns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
}
|
||||
},
|
||||
"checkConstraint": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"tables": {},
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,13 @@
|
||||
"when": 1784853783915,
|
||||
"tag": "0011_magenta_jack_flag",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 12,
|
||||
"version": "5",
|
||||
"when": 1784882595519,
|
||||
"tag": "0012_bumpy_the_watchers",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -7,6 +7,7 @@ export const users = mysqlTable('users', {
|
||||
password: varchar('password', { length: 255 }).notNull(),
|
||||
role: varchar('role', { length: 50 }).notNull().default('user'),
|
||||
avatar: text('avatar'),
|
||||
gender: varchar('gender', { length: 20 }),
|
||||
resetToken: varchar('reset_token', { length: 255 }),
|
||||
resetTokenExpires: timestamp('reset_token_expires'),
|
||||
active: boolean('active').notNull().default(true),
|
||||
|
||||
Reference in New Issue
Block a user