From 4a91b0367825f79755474149b794f25189347aa2 Mon Sep 17 00:00:00 2001 From: thanhvudaynee Date: Thu, 21 May 2026 10:15:02 +0700 Subject: [PATCH] feat: implement core movie streaming features including dashboard, watch history, authentication, and dynamic media pages --- .env.example | 2 + app/components/AppHeader.vue | 358 ++++++++++++- app/composables/useMovieLibrary.ts | 149 ++++++ app/composables/useSupabaseAuth.ts | 86 +++ app/composables/useWatchHistory.ts | 167 ++++++ app/pages/index.vue | 79 +-- app/pages/lich-su.vue | 102 ++++ app/pages/phim/[slug].vue | 113 +++- app/pages/thanh-vien.vue | 93 ++++ app/pages/xem/[slug].vue | 805 +++++++++++++++++++++++++++-- app/pages/yeu-thich.vue | 94 ++++ app/plugins/supabase.ts | 18 + database.sql | 104 ++++ nuxt.config.ts | 6 + package-lock.json | 104 +++- package.json | 2 + 16 files changed, 2204 insertions(+), 78 deletions(-) create mode 100644 .env.example create mode 100644 app/composables/useMovieLibrary.ts create mode 100644 app/composables/useSupabaseAuth.ts create mode 100644 app/composables/useWatchHistory.ts create mode 100644 app/pages/lich-su.vue create mode 100644 app/pages/thanh-vien.vue create mode 100644 app/pages/yeu-thich.vue create mode 100644 app/plugins/supabase.ts create mode 100644 database.sql diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e602c48 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +SUPABASE_URL= +SUPABASE_KEY= \ No newline at end of file diff --git a/app/components/AppHeader.vue b/app/components/AppHeader.vue index c225c4e..91b2b53 100644 --- a/app/components/AppHeader.vue +++ b/app/components/AppHeader.vue @@ -1,10 +1,44 @@ @@ -55,8 +196,47 @@ watch(() => route.path, () => { class="w-full bg-transparent text-sm text-white outline-none placeholder:text-slate-400"> + diff --git a/app/pages/thanh-vien.vue b/app/pages/thanh-vien.vue new file mode 100644 index 0000000..2756458 --- /dev/null +++ b/app/pages/thanh-vien.vue @@ -0,0 +1,93 @@ + + + diff --git a/app/pages/xem/[slug].vue b/app/pages/xem/[slug].vue index 2140018..6542742 100644 --- a/app/pages/xem/[slug].vue +++ b/app/pages/xem/[slug].vue @@ -1,11 +1,65 @@ + + diff --git a/app/plugins/supabase.ts b/app/plugins/supabase.ts new file mode 100644 index 0000000..31afb73 --- /dev/null +++ b/app/plugins/supabase.ts @@ -0,0 +1,18 @@ +import { createClient } from '@supabase/supabase-js' + +export default defineNuxtPlugin(() => { + const config = useRuntimeConfig() + const supabase = createClient(config.public.supabaseUrl, config.public.supabaseKey, { + auth: { + persistSession: import.meta.client, + autoRefreshToken: import.meta.client, + detectSessionInUrl: import.meta.client, + }, + }) + + return { + provide: { + supabase, + }, + } +}) diff --git a/database.sql b/database.sql new file mode 100644 index 0000000..6b9d440 --- /dev/null +++ b/database.sql @@ -0,0 +1,104 @@ +create table public.watch_history ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references auth.users(id) on delete cascade, + source text not null default '', + slug text not null, + name text not null, + origin_name text, + thumb text, + poster text, + episode_name text, + episode_index integer not null default 0, + server_index integer not null default 0, + progress_seconds integer not null default 0, + duration_seconds integer not null default 0, + updated_at timestamptz not null default now(), + unique (user_id, source, slug) +); + +alter table public.watch_history + add column if not exists progress_seconds integer not null default 0, + add column if not exists duration_seconds integer not null default 0; + +alter table public.watch_history enable row level security; + +create policy "Users can read own watch history" +on public.watch_history for select +using (auth.uid() = user_id); + +create policy "Users can insert own watch history" +on public.watch_history for insert +with check (auth.uid() = user_id); + +create policy "Users can update own watch history" +on public.watch_history for update +using (auth.uid() = user_id) +with check (auth.uid() = user_id); + +create policy "Users can delete own watch history" +on public.watch_history for delete +using (auth.uid() = user_id); + +create table public.favorite_movies ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references auth.users(id) on delete cascade, + source text not null default '', + slug text not null, + name text not null, + origin_name text, + thumb text, + poster text, + updated_at timestamptz not null default now(), + unique (user_id, source, slug) +); + +alter table public.favorite_movies enable row level security; + +create policy "Users can read own favorite movies" +on public.favorite_movies for select +using (auth.uid() = user_id); + +create policy "Users can insert own favorite movies" +on public.favorite_movies for insert +with check (auth.uid() = user_id); + +create policy "Users can update own favorite movies" +on public.favorite_movies for update +using (auth.uid() = user_id) +with check (auth.uid() = user_id); + +create policy "Users can delete own favorite movies" +on public.favorite_movies for delete +using (auth.uid() = user_id); + +create table public.watch_later_movies ( + id uuid primary key default gen_random_uuid(), + user_id uuid not null references auth.users(id) on delete cascade, + source text not null default '', + slug text not null, + name text not null, + origin_name text, + thumb text, + poster text, + updated_at timestamptz not null default now(), + unique (user_id, source, slug) +); + +alter table public.watch_later_movies enable row level security; + +create policy "Users can read own watch later movies" +on public.watch_later_movies for select +using (auth.uid() = user_id); + +create policy "Users can insert own watch later movies" +on public.watch_later_movies for insert +with check (auth.uid() = user_id); + +create policy "Users can update own watch later movies" +on public.watch_later_movies for update +using (auth.uid() = user_id) +with check (auth.uid() = user_id); + +create policy "Users can delete own watch later movies" +on public.watch_later_movies for delete +using (auth.uid() = user_id); diff --git a/nuxt.config.ts b/nuxt.config.ts index 1c246db..390d66b 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -4,6 +4,12 @@ import tailwindcss from '@tailwindcss/vite' export default defineNuxtConfig({ compatibilityDate: '2025-07-15', devtools: { enabled: true }, + runtimeConfig: { + public: { + supabaseUrl: process.env.SUPABASE_URL, + supabaseKey: process.env.SUPABASE_KEY, + }, + }, devServer: { host: '0.0.0.0', port: 3002, diff --git a/package-lock.json b/package-lock.json index ec7a524..c2db6f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,9 @@ "name": "kr-phim", "hasInstallScript": true, "dependencies": { + "@supabase/supabase-js": "^2.106.0", "@tailwindcss/vite": "^4.3.0", + "hls.js": "^1.6.16", "lucide-vue-next": "^1.0.0", "nuxt": "^4.4.5", "tailwindcss": "^4.3.0", @@ -3392,6 +3394,90 @@ "integrity": "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==", "license": "CC0-1.0" }, + "node_modules/@supabase/auth-js": { + "version": "2.106.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.106.0.tgz", + "integrity": "sha512-JY7602OvjK2l3BjsQkpePpxR+6P0iG37gCrZNWAMhAuNh1iFnhGRwj/y5EshUG0INMPGFrj0UA9MErQ/kOEKFg==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.106.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.106.0.tgz", + "integrity": "sha512-ADIkJYH5w7HbnGVAAlCbyKoLF5QdfyezBLfYXpUqhxZOacK6YepOvnP/8p4p+50bhTPWp6VhDxu19KO7e/qU2g==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/phoenix": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.2.tgz", + "integrity": "sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A==", + "license": "MIT" + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.106.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.106.0.tgz", + "integrity": "sha512-vNKFAXQrtmUn7J3LbN+uMlt0jciAwRIBpdy6Do4DKrpf1xj0kJhbqXTX4y8ziewWUEEx8G5GPnDmprXXaO9f3w==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.106.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.106.0.tgz", + "integrity": "sha512-mYZoaYpkyjlecixbvxCu0h3jw12uHfEcUqNdaRATNI8zQVI5arels+VJzAGcHwNiD+/Juv0OXIuk+M7SHsdI4A==", + "license": "MIT", + "dependencies": { + "@supabase/phoenix": "^0.4.2", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.106.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.106.0.tgz", + "integrity": "sha512-BHc3nIjD3zfdDxBenphXrLJSoQ+qwo24VD96cVzmjBFbQVk5krvwRNUXrA5ozPplA3Vhlst2d/hy9R9ViqH2lg==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.106.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.106.0.tgz", + "integrity": "sha512-OOoo3sLj9iVXNp6b+fkyOfFeQrvvNy7nQbaONNf72dOaictUeS39hFDS9argIRTag6M3ZxIypNWcrDAwLgUihQ==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.106.0", + "@supabase/functions-js": "2.106.0", + "@supabase/postgrest-js": "2.106.0", + "@supabase/realtime-js": "2.106.0", + "@supabase/storage-js": "2.106.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@tailwindcss/node": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", @@ -5899,6 +5985,12 @@ "node": ">= 0.4" } }, + "node_modules/hls.js": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.16.tgz", + "integrity": "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==", + "license": "Apache-2.0" + }, "node_modules/hookable": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz", @@ -5963,6 +6055,15 @@ "node": ">=16.17.0" } }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -9198,8 +9299,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "optional": true + "license": "0BSD" }, "node_modules/type-fest": { "version": "5.6.0", diff --git a/package.json b/package.json index e72efea..315cf1d 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "postinstall": "nuxt prepare" }, "dependencies": { + "@supabase/supabase-js": "^2.106.0", "@tailwindcss/vite": "^4.3.0", + "hls.js": "^1.6.16", "lucide-vue-next": "^1.0.0", "nuxt": "^4.4.5", "tailwindcss": "^4.3.0",