mirror of
https://github.com/ngthanhvu/kr-phim.git
synced 2026-08-10 13: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">
|
<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<{
|
const props = defineProps<{
|
||||||
replies: any[]
|
replies: any[]
|
||||||
@@ -48,6 +48,9 @@ function handleVote(replyId: number, vote: number) {
|
|||||||
<span v-if="reply.userRole === 'admin' && !reply.anonymous"
|
<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>
|
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>
|
<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"
|
<span v-if="reply.anonymous"
|
||||||
class="px-1 py-0.5 rounded text-[9px] font-medium bg-white/10 text-slate-400">Ẩn
|
class="px-1 py-0.5 rounded text-[9px] font-medium bg-white/10 text-slate-400">Ẩn
|
||||||
danh</span>
|
danh</span>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<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<{
|
const props = defineProps<{
|
||||||
source: string
|
source: string
|
||||||
@@ -110,6 +110,7 @@ async function handleSubmitComment() {
|
|||||||
userName: user.value.name || 'Ẩn danh',
|
userName: user.value.name || 'Ẩn danh',
|
||||||
userAvatar: user.value.avatar,
|
userAvatar: user.value.avatar,
|
||||||
userRole: user.value.role,
|
userRole: user.value.role,
|
||||||
|
userGender: user.value.gender,
|
||||||
replies: [],
|
replies: [],
|
||||||
timeAgo: 'Vừa xong'
|
timeAgo: 'Vừa xong'
|
||||||
})
|
})
|
||||||
@@ -229,6 +230,7 @@ async function submitReply(parentId: number) {
|
|||||||
userName: user.value.name || 'Ẩn danh',
|
userName: user.value.name || 'Ẩn danh',
|
||||||
userAvatar: user.value.avatar,
|
userAvatar: user.value.avatar,
|
||||||
userRole: user.value.role,
|
userRole: user.value.role,
|
||||||
|
userGender: user.value.gender,
|
||||||
timeAgo: 'Vừa xong',
|
timeAgo: 'Vừa xong',
|
||||||
anonymous: isAnon,
|
anonymous: isAnon,
|
||||||
replies: []
|
replies: []
|
||||||
@@ -423,6 +425,9 @@ watch(() => props.slug, () => {
|
|||||||
<span v-if="comment.userRole === 'admin' && !comment.anonymous"
|
<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>
|
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>
|
<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"
|
<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>
|
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>
|
<span class="text-xs text-slate-600">{{ timeAgo(comment.createdAt) }}</span>
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
export default defineNuxtRouteMiddleware(async (to) => {
|
export default defineNuxtRouteMiddleware(async (to) => {
|
||||||
if (to.path !== '/thanh-vien') return
|
if (to.path !== '/thanh-vien') return
|
||||||
|
|
||||||
const { data: user, error } = await useFetch('/api/auth/me', {
|
let user = null
|
||||||
headers: useRequestHeaders(['cookie']),
|
try {
|
||||||
})
|
user = await $fetch('/api/auth/me')
|
||||||
|
} catch {
|
||||||
|
return navigateTo('/')
|
||||||
|
}
|
||||||
|
|
||||||
if (error.value?.statusCode === 401 || !user.value) {
|
if (!user) {
|
||||||
return navigateTo('/')
|
return navigateTo('/')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<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' })
|
definePageMeta({ middleware: 'auth' })
|
||||||
|
|
||||||
@@ -25,6 +25,15 @@ const displayNameInput = ref('')
|
|||||||
const nameLoading = ref(false)
|
const nameLoading = ref(false)
|
||||||
const nameError = ref('')
|
const nameError = ref('')
|
||||||
const nameSuccess = 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 = [
|
const avatarCategories = [
|
||||||
{ id: 'meme', label: 'Meme' },
|
{ id: 'meme', label: 'Meme' },
|
||||||
@@ -43,6 +52,8 @@ const avatarUrl = computed(() => {
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isAdmin = computed(() => user.value?.role === 'admin')
|
||||||
|
|
||||||
const avatarGrid = computed(() => {
|
const avatarGrid = computed(() => {
|
||||||
const categoryMap: Record<string, string[]> = {
|
const categoryMap: Record<string, string[]> = {
|
||||||
meme: ['01', '02', '03', '04', '05', '06', '07', '08'],
|
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) => {
|
watch(user, (u) => {
|
||||||
if (u) {
|
if (u) {
|
||||||
displayNameInput.value = u.name || ''
|
displayNameInput.value = u.name || ''
|
||||||
@@ -225,11 +258,17 @@ useHead({
|
|||||||
<aside class="w-full lg:w-64 shrink-0">
|
<aside class="w-full lg:w-64 shrink-0">
|
||||||
<div class="lg:sticky lg:top-28">
|
<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 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">
|
<div class="relative shrink-0">
|
||||||
<img :src="avatarUrl" :alt="memberName" class="size-full object-cover">
|
<div v-if="avatarUrl" class="size-10 shrink-0 overflow-hidden rounded-full"
|
||||||
</div>
|
:class="isAdmin ? 'ring-2 ring-[#FFD166]' : 'ring-2 ring-[#FFD166]/20'">
|
||||||
<div v-else class="grid size-10 shrink-0 place-items-center rounded-full bg-[#FFD166]/10">
|
<img :src="avatarUrl" :alt="memberName" class="size-full object-cover">
|
||||||
<span class="text-sm font-bold text-[#FFD166]">{{ memberName.charAt(0).toUpperCase() }}</span>
|
</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>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<p class="truncate text-sm font-semibold">{{ memberName }}</p>
|
<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">
|
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>
|
||||||
|
|
||||||
|
<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">
|
<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" />
|
<X class="size-4 shrink-0" />
|
||||||
{{ nameError }}
|
{{ nameError }}
|
||||||
@@ -318,6 +372,16 @@ useHead({
|
|||||||
<Check class="size-4 shrink-0" />
|
<Check class="size-4 shrink-0" />
|
||||||
{{ nameSuccess }}
|
{{ nameSuccess }}
|
||||||
</p>
|
</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">
|
<div class="flex flex-wrap gap-2 pt-2">
|
||||||
<button type="button"
|
<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="flex flex-col items-center gap-3 lg:w-48 lg:shrink-0">
|
||||||
<div class="group/avatar relative">
|
<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">
|
<img :src="avatarUrl" :alt="memberName" class="size-full object-cover">
|
||||||
</div>
|
</div>
|
||||||
<div v-else
|
<div v-else class="grid size-32 place-items-center rounded-full"
|
||||||
class="grid size-32 place-items-center rounded-full bg-[#FFD166]/10 ring-4 ring-[#FFD166]/20">
|
: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>
|
<span class="text-4xl font-black text-[#FFD166]">{{ memberName.charAt(0).toUpperCase() }}</span>
|
||||||
</div>
|
</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"
|
<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]"
|
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">
|
@click="avatarModalOpen = true">
|
||||||
|
|||||||
@@ -28,5 +28,6 @@ export default defineEventHandler(async (event) => {
|
|||||||
name: user.name,
|
name: user.name,
|
||||||
role: user.role,
|
role: user.role,
|
||||||
avatar: user.avatar,
|
avatar: user.avatar,
|
||||||
|
gender: user.gender,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { users } from '../../database/schema'
|
import { users } from '../../database/schema'
|
||||||
import { eq } from 'drizzle-orm'
|
import { eq } from 'drizzle-orm'
|
||||||
import { getTokenFromEvent } from '../../utils/auth'
|
import { getTokenFromEvent, verifyToken } from '../../utils/auth'
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const db = useDb()
|
const db = useDb()
|
||||||
@@ -11,24 +11,30 @@ export default defineEventHandler(async (event) => {
|
|||||||
throw createError({ statusCode: 401, message: 'Chưa đăng nhập' })
|
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)
|
const payload = verifyToken(token)
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
throw createError({ statusCode: 401, message: 'Token không hợp lệ' })
|
throw createError({ statusCode: 401, message: 'Token không hợp lệ' })
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.update(users)
|
const updates: Record<string, any> = {}
|
||||||
.set({ name })
|
|
||||||
.where(eq(users.id, payload.id))
|
|
||||||
|
|
||||||
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,
|
userName: users.name,
|
||||||
userAvatar: users.avatar,
|
userAvatar: users.avatar,
|
||||||
userRole: users.role,
|
userRole: users.role,
|
||||||
|
userGender: users.gender,
|
||||||
source: comments.source,
|
source: comments.source,
|
||||||
slug: comments.slug,
|
slug: comments.slug,
|
||||||
movieName: comments.movieName,
|
movieName: comments.movieName,
|
||||||
@@ -75,6 +76,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
userName: rep.anonymous ? 'Ẩn danh' : (rep.userName || 'Ẩn danh'),
|
userName: rep.anonymous ? 'Ẩn danh' : (rep.userName || 'Ẩn danh'),
|
||||||
userAvatar: rep.anonymous ? null : rep.userAvatar,
|
userAvatar: rep.anonymous ? null : rep.userAvatar,
|
||||||
userRole: rep.userRole,
|
userRole: rep.userRole,
|
||||||
|
userGender: rep.userGender,
|
||||||
parentId: rep.parentId,
|
parentId: rep.parentId,
|
||||||
content: rep.content,
|
content: rep.content,
|
||||||
pinned: rep.pinned || false,
|
pinned: rep.pinned || false,
|
||||||
@@ -97,6 +99,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
userName: users.name,
|
userName: users.name,
|
||||||
userAvatar: users.avatar,
|
userAvatar: users.avatar,
|
||||||
userRole: users.role,
|
userRole: users.role,
|
||||||
|
userGender: users.gender,
|
||||||
parentId: comments.parentId,
|
parentId: comments.parentId,
|
||||||
content: comments.content,
|
content: comments.content,
|
||||||
pinned: comments.pinned,
|
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,
|
"when": 1784853783915,
|
||||||
"tag": "0011_magenta_jack_flag",
|
"tag": "0011_magenta_jack_flag",
|
||||||
"breakpoints": true
|
"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(),
|
password: varchar('password', { length: 255 }).notNull(),
|
||||||
role: varchar('role', { length: 50 }).notNull().default('user'),
|
role: varchar('role', { length: 50 }).notNull().default('user'),
|
||||||
avatar: text('avatar'),
|
avatar: text('avatar'),
|
||||||
|
gender: varchar('gender', { length: 20 }),
|
||||||
resetToken: varchar('reset_token', { length: 255 }),
|
resetToken: varchar('reset_token', { length: 255 }),
|
||||||
resetTokenExpires: timestamp('reset_token_expires'),
|
resetTokenExpires: timestamp('reset_token_expires'),
|
||||||
active: boolean('active').notNull().default(true),
|
active: boolean('active').notNull().default(true),
|
||||||
|
|||||||
Reference in New Issue
Block a user