mirror of
https://github.com/ngthanhvu/kr-phim.git
synced 2026-08-10 16:47:46 +00:00
feat: add movie management API and database schema
- Implemented movie CRUD operations with endpoints for creating, retrieving, updating, and deleting movies. - Added pagination and filtering capabilities for movie retrieval. - Integrated Redis caching for improved performance on movie data retrieval. - Created database schema for movies with necessary fields and types. - Added migration files for initial movie table creation and subsequent updates. - Configured database connection using Drizzle ORM with MySQL. - Established Redis connection utility for caching.
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import { movies } from '../../database/schema'
|
||||
import { useRedis } from '../../utils/redis'
|
||||
|
||||
export default defineEventHandler(async () => {
|
||||
const db = useDb()
|
||||
|
||||
await db.delete(movies)
|
||||
|
||||
try {
|
||||
const redis = useRedis()
|
||||
const keys = await redis.keys('cinek:public:*')
|
||||
if (keys.length) await redis.del(...keys)
|
||||
} catch {}
|
||||
|
||||
return { success: true }
|
||||
})
|
||||
@@ -0,0 +1,47 @@
|
||||
import { movies } from '../../database/schema'
|
||||
import { desc, like, sql } from 'drizzle-orm'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = useDb()
|
||||
const query = getQuery(event)
|
||||
const page = Math.max(Number(query.page) || 1, 1)
|
||||
const limit = Math.min(Math.max(Number(query.limit) || 20, 1), 100)
|
||||
const offset = (page - 1) * limit
|
||||
const keyword = typeof query.keyword === 'string' ? query.keyword.trim() : ''
|
||||
const status = typeof query.status === 'string' ? query.status : ''
|
||||
|
||||
let whereClause = undefined
|
||||
if (keyword && status === 'active') {
|
||||
whereClause = sql`${like(movies.name, `%${keyword}%`)} AND ${movies.active} = true`
|
||||
} else if (keyword) {
|
||||
whereClause = like(movies.name, `%${keyword}%`)
|
||||
} else if (status === 'active') {
|
||||
whereClause = eq(movies.active, true)
|
||||
} else if (status === 'inactive') {
|
||||
whereClause = eq(movies.active, false)
|
||||
}
|
||||
|
||||
const [items, countResult] = await Promise.all([
|
||||
db
|
||||
.select()
|
||||
.from(movies)
|
||||
.where(whereClause)
|
||||
.orderBy(desc(movies.syncedAt))
|
||||
.limit(limit)
|
||||
.offset(offset),
|
||||
db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(movies)
|
||||
.where(whereClause),
|
||||
])
|
||||
|
||||
const total = countResult[0]?.count ?? 0
|
||||
|
||||
return {
|
||||
items,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
import { movies } from '../../../database/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = useDb()
|
||||
const id = Number(getRouterParam(event, 'id'))
|
||||
|
||||
if (!id) {
|
||||
throw createError({ statusCode: 400, message: 'Thiếu ID phim' })
|
||||
}
|
||||
|
||||
const result = await db.select().from(movies).where(eq(movies.id, id)).limit(1)
|
||||
if (!result.length) {
|
||||
throw createError({ statusCode: 404, message: 'Không tìm thấy phim' })
|
||||
}
|
||||
|
||||
return result[0]
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
import { movies } from '../../../database/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = useDb()
|
||||
const id = Number(getRouterParam(event, 'id'))
|
||||
const body = await readBody(event)
|
||||
|
||||
if (!id) {
|
||||
throw createError({ statusCode: 400, message: 'Thiếu ID phim' })
|
||||
}
|
||||
|
||||
const existing = await db.select().from(movies).where(eq(movies.id, id)).limit(1)
|
||||
if (!existing.length) {
|
||||
throw createError({ statusCode: 404, message: 'Không tìm thấy phim' })
|
||||
}
|
||||
|
||||
const updates: Record<string, any> = {}
|
||||
|
||||
if (typeof body.active === 'boolean') {
|
||||
updates.active = body.active
|
||||
}
|
||||
|
||||
if (!Object.keys(updates).length) {
|
||||
throw createError({ statusCode: 400, message: 'Không có dữ liệu cập nhật' })
|
||||
}
|
||||
|
||||
await db.update(movies).set(updates).where(eq(movies.id, id))
|
||||
|
||||
try {
|
||||
const redis = useRedis()
|
||||
const keys = await redis.keys('cinek:public:*')
|
||||
if (keys.length) {
|
||||
await redis.del(...keys)
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return { success: true, id, ...updates }
|
||||
})
|
||||
@@ -0,0 +1,48 @@
|
||||
import { movies } from '../../../database/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { useRedis } from '../../../utils/redis'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = useDb()
|
||||
const id = Number(getRouterParam(event, 'id'))
|
||||
const body = await readBody(event)
|
||||
|
||||
if (!id) {
|
||||
throw createError({ statusCode: 400, message: 'Thiếu ID phim' })
|
||||
}
|
||||
|
||||
const existing = await db.select().from(movies).where(eq(movies.id, id)).limit(1)
|
||||
if (!existing.length) {
|
||||
throw createError({ statusCode: 404, message: 'Không tìm thấy phim' })
|
||||
}
|
||||
|
||||
const updates: Record<string, any> = {}
|
||||
|
||||
for (const field of ['customPoster', 'customThumb', 'customContent'] as const) {
|
||||
if (field in body) {
|
||||
updates[field] = typeof body[field] === 'string' && body[field].trim()
|
||||
? body[field].trim()
|
||||
: null
|
||||
}
|
||||
}
|
||||
|
||||
if ('customEpisodes' in body) {
|
||||
updates.customEpisodes = Array.isArray(body.customEpisodes) && body.customEpisodes.length
|
||||
? body.customEpisodes.filter((ep: any) => ep.name?.trim())
|
||||
: null
|
||||
}
|
||||
|
||||
if (!Object.keys(updates).length) {
|
||||
throw createError({ statusCode: 400, message: 'Không có dữ liệu cập nhật' })
|
||||
}
|
||||
|
||||
await db.update(movies).set(updates).where(eq(movies.id, id))
|
||||
|
||||
try {
|
||||
const redis = useRedis()
|
||||
const keys = await redis.keys('cinek:public:*')
|
||||
if (keys.length) await redis.del(...keys)
|
||||
} catch {}
|
||||
|
||||
return { success: true, id }
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
import { movies } from '../../database/schema'
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
export default defineEventHandler(async () => {
|
||||
const db = useDb()
|
||||
|
||||
const [stats] = await db.select({
|
||||
total: sql<number>`count(*)`,
|
||||
active: sql<number>`SUM(CASE WHEN ${movies.active} = true THEN 1 ELSE 0 END)`,
|
||||
inactive: sql<number>`SUM(CASE WHEN ${movies.active} = false THEN 1 ELSE 0 END)`,
|
||||
}).from(movies)
|
||||
|
||||
return {
|
||||
total: stats?.total ?? 0,
|
||||
active: stats?.active ?? 0,
|
||||
inactive: stats?.inactive ?? 0,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,104 @@
|
||||
import { movies } from '../../database/schema'
|
||||
import { eq, and } from 'drizzle-orm'
|
||||
import { getOphimKoreanMovies, getNguoncKoreanMovies, getKkphimKoreanMovies } from '../../utils/movies'
|
||||
|
||||
const SYNC_PAGES = 10
|
||||
const ALL_SOURCES = ['ophim', 'nguonc', 'kkphim'] as const
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = useDb()
|
||||
const body = await readBody(event).catch(() => ({})) || {}
|
||||
const requestedSources = Array.isArray(body.sources) ? body.sources : ALL_SOURCES
|
||||
const sources = requestedSources.filter((s: string) => ALL_SOURCES.includes(s as any))
|
||||
|
||||
if (!sources.length) {
|
||||
throw createError({ statusCode: 400, message: 'Nguồn không hợp lệ' })
|
||||
}
|
||||
|
||||
const allMovies: any[] = []
|
||||
const fetchers: Record<string, (page: number) => Promise<any>> = {
|
||||
ophim: getOphimKoreanMovies,
|
||||
nguonc: getNguoncKoreanMovies,
|
||||
kkphim: getKkphimKoreanMovies,
|
||||
}
|
||||
|
||||
for (const sourceName of sources) {
|
||||
const fetcher = fetchers[sourceName]
|
||||
if (!fetcher) continue
|
||||
for (let page = 1; page <= SYNC_PAGES; page++) {
|
||||
try {
|
||||
const result = await fetcher(page)
|
||||
for (const movie of result.items) {
|
||||
allMovies.push({ ...movie, source: sourceName })
|
||||
}
|
||||
if (!result.items.length) break
|
||||
} catch {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let created = 0
|
||||
let updated = 0
|
||||
|
||||
for (const movie of allMovies) {
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(movies)
|
||||
.where(and(eq(movies.source, movie.source), eq(movies.slug, movie.slug)))
|
||||
.limit(1)
|
||||
|
||||
if (existing.length) {
|
||||
await db
|
||||
.update(movies)
|
||||
.set({
|
||||
name: movie.name,
|
||||
originName: movie.originName || null,
|
||||
thumb: movie.thumb || null,
|
||||
poster: movie.poster || null,
|
||||
year: movie.year || null,
|
||||
time: movie.time || null,
|
||||
episode: movie.episode || null,
|
||||
quality: movie.quality || null,
|
||||
lang: movie.lang || null,
|
||||
type: movie.type || null,
|
||||
rating: movie.rating || null,
|
||||
categories: movie.categories || null,
|
||||
countries: movie.countries || null,
|
||||
sources: movie.sources || null,
|
||||
syncedAt: new Date(),
|
||||
})
|
||||
.where(eq(movies.id, existing[0].id))
|
||||
updated++
|
||||
} else {
|
||||
await db.insert(movies).values({
|
||||
source: movie.source,
|
||||
slug: movie.slug,
|
||||
name: movie.name,
|
||||
originName: movie.originName || null,
|
||||
thumb: movie.thumb || null,
|
||||
poster: movie.poster || null,
|
||||
year: movie.year || null,
|
||||
time: movie.time || null,
|
||||
episode: movie.episode || null,
|
||||
quality: movie.quality || null,
|
||||
lang: movie.lang || null,
|
||||
type: movie.type || null,
|
||||
rating: movie.rating || null,
|
||||
categories: movie.categories || null,
|
||||
countries: movie.countries || null,
|
||||
sources: movie.sources || null,
|
||||
active: false,
|
||||
})
|
||||
created++
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
sources,
|
||||
total: allMovies.length,
|
||||
created,
|
||||
updated,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user