diff --git a/backend/package.json b/backend/package.json index 2ade55ad..a978ed2e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,7 +12,7 @@ }, "scripts": { "dev": "prisma generate && tsx watch src/index.ts", - "build": "prisma generate && prisma db push --accept-data-loss && tsc && tsc-alias", + "build": "prisma generate && prisma db push --accept-data-loss && tsc && tsc-alias && tsx prisma/seed.ts", "build:ci": "prisma generate && tsc && tsc-alias", "start": "node dist/index.js", "lint": "eslint src/ prisma/seed.ts", diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index a3342362..f510f31b 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -130,4 +130,8 @@ enum Genre { Music Horror SciFi + Drama + Romance + Crime + Mystery } diff --git a/backend/prisma/seed.ts b/backend/prisma/seed.ts index 5e8fadeb..8fb89cd9 100644 --- a/backend/prisma/seed.ts +++ b/backend/prisma/seed.ts @@ -1,12 +1,23 @@ -import { PrismaClient } from '@prisma/client'; +import { $Enums, PrismaClient } from '@prisma/client'; import { hashPassword } from '../src/utils/password'; +import { seedData } from './seedData'; +import { PopulateDataService } from 'src/services/populateDataService'; const prisma = new PrismaClient(); async function main() { console.log('🌱 Starting seed...'); + // Clean existing data (keep accounts/users) + console.log('🧹 Clearing existing data...'); + await prisma.userReaction.deleteMany(); + await prisma.video.deleteMany(); + await prisma.season.deleteMany(); + await prisma.title.deleteMany(); + await prisma.category.deleteMany(); + console.log('✅ Existing data cleared'); + // Create or get test account const hashedPassword = await hashPassword('password123'); const account = await prisma.account.upsert({ @@ -42,6 +53,152 @@ async function main() { console.log('✅ Test user already exists:', testUser); } + // Populate Category, Title, Season and Video + + const populateDataService = new PopulateDataService(); + + const titles: { + name: string; + type: $Enums.VideoType; + cast: string[]; + genre: $Enums.Genre[]; + synopsis: string | null; + createdAt: Date; + updatedAt: Date; + id: number; + }[] = []; + + for (const t of seedData.title) { + const { maturity, ...rest } = t; + + const createdTitle = await populateDataService.createTitle({ + ...rest, + category: { + connectOrCreate: [ + { + where: { age_restriction: maturity }, + create: { + name: maturity, + age_restriction: maturity, + }, + }, + ], + }, + }); + + titles.push(createdTitle); + } + console.log('✅ Titles created:', titles); + + const seasons: Array< + { + title: { + id: number; + name: string; + type: $Enums.VideoType; + createdAt: Date; + updatedAt: Date; + } | null; + } & { + number: number; + id: number; + createdAt: Date; + updatedAt: Date; + thumbnail: string; + title_id: number | null; + } + > = []; + + for (const s of seedData.season) { + const parentTitle = titles.find((t) => t.name === s.title && t.type === 'SERIES'); + + const createdSeason = await populateDataService.createSeason({ + number: s.number, + thumbnail: s.thumbnail, + title: parentTitle ? { connect: { id: parentTitle.id } } : undefined, + }); + + seasons.push(createdSeason); + } + console.log('✅ Seasons created:', seasons); + + const videos: { + id: number; + name: string | null; + createdAt: Date; + updatedAt: Date; + image: string | null; + duration: number; + url: string; + episode_number: number | null; + movie_title_id: number | null; + season_id: number | null; + }[] = []; + + for (const v of seedData.video) { + // const parentTitle = titles.find((t) => t.name === v.title); + // const parentSeason = + // v.episode_number != null + // ? seasons.find( + // (s) => + // seedData.season.find( + // (orig) => orig.title === v.title && orig.number === v.season_number + // ) && s.number === v.season_number + // ) + // : null; + + // const createVideo = await prisma.video.create({ + // data: { + // duration: v.duration, + // image: v.image, + // url: v.url, + // episode_number: v.episode_number, + // name: + // v.episode_number != null + // ? `${v.title}: S${v.season_number} Ep${v.episode_number}` + // : v.title, + // season: + // v.episode_number != null && parentSeason + // ? { connect: { id: parentSeason.id } } + // : undefined, + // title: + // v.episode_number == null && parentTitle ? { connect: { id: parentTitle.id } } : undefined, + // }, + // }); + + const createVideo = await prisma.video.create({ + data: { + duration: v.duration, + image: v.image, + url: v.url, + episode_number: v.episode_number, + name: + v.episode_number != null + ? `${v.title}: S${v.season_number} Ep${v.episode_number}` + : v.title, + season: + v.episode_number != null + ? { + connect: { + id: seasons.find((s) => { + return s.title?.name === v.title && s.number === v.season_number; + })?.id, + }, + } + : undefined, + title: + v.episode_number == null + ? { + connect: { + id: titles.find((t) => t.name === v.title)?.id, + }, + } + : undefined, + }, + }); + videos.push(createVideo); + } + console.log('✅ Videos created:', videos); console.log('🎉 Seed completed successfully!'); } diff --git a/backend/prisma/seedData.ts b/backend/prisma/seedData.ts new file mode 100644 index 00000000..4df815a8 --- /dev/null +++ b/backend/prisma/seedData.ts @@ -0,0 +1,979 @@ +import { $Enums } from '@prisma/client'; + +export const seedData: { + title: { + name: string; + type: $Enums.VideoType; + maturity: $Enums.AgeRestriction; + cast: string[]; + genre: $Enums.Genre[]; + synopsis: string; + }[]; + season: { + title: string; + thumbnail: string; + number: number; + }[]; + video: { + url: string; + image: string; + duration: number; + episode_number: number | null; + season_number: number | null; + title: string; + }[]; +} = { + title: [ + { + name: 'Mission Impossible Fallout', + type: 'MOVIE', + maturity: 'PG13', + cast: ['Tom Cruise', 'Henry Cavill', 'Rebecca Ferguson'], + genre: ['Action', 'Thriller'], + synopsis: + 'Ethan Hunt and his IMF team race against time to stop a global catastrophe after a mission goes dangerously wrong.', + }, + { + name: 'Jumanji', + type: 'MOVIE', + maturity: 'TVPG', + cast: ['Dwayne Johnson', 'Kevin Hart', 'Jack Black', 'Karen Gillan'], + genre: ['Adventure', 'Fantasy', 'Comedy'], + synopsis: + 'Four teens are transported into a dangerous video-game world where they must survive challenges to escape and return home.', + }, + { + name: 'K-Pop Demon Hunters', + type: 'MOVIE', + maturity: 'PG', + cast: ['Kim Bora', 'Kim Jihye', 'Park Soeun'], + genre: ['Action', 'Fantasy', 'Comedy'], + synopsis: + 'A K-pop girl group secretly battles supernatural demons threatening humanity while balancing fame, friendship, and fierce performances.', + }, + { + name: 'Space Force', + type: 'SERIES', + maturity: 'TVMA', + cast: ['Steve Carell', 'John Malkovich', 'Ben Schwartz'], + genre: ['Comedy'], + synopsis: + 'A general is tasked with leading America’s newest military branch, facing political chaos, scientific challenges, and hilarious misunderstandings.', + }, + { + name: 'Sword of Destiny', + type: 'MOVIE', + maturity: 'PG13', + cast: ['Michelle Yeoh', 'Donnie Yen'], + genre: ['Action', 'Fantasy'], + synopsis: + 'Warriors reunite to protect a legendary sword from rival clans and deadly assassins in a battle for honor and destiny.', + }, + { + name: 'Cristiano Ronaldo', + type: 'MOVIE', + maturity: 'TVY7', + cast: ['Cristiano Ronaldo'], + genre: ['Documentary', 'Sports'], + synopsis: + 'A revealing look into Cristiano Ronaldo’s life, exploring his rise from humble beginnings to becoming one of football’s greatest icons.', + }, + { + name: 'The Road Story: Vietnam', + type: 'MOVIE', + maturity: 'TVY', + cast: ['unknown'], + genre: ['Documentary', 'Travel'], + synopsis: + 'A visual journey through Vietnam, capturing daily life, landscapes, and the stories of people encountered along the road.', + }, + { + name: 'Greenland - Land of Ice', + type: 'MOVIE', + maturity: 'TVY', + cast: ['unknown'], + genre: ['Documentary', 'Nature'], + synopsis: + 'A stunning exploration of Greenland’s glaciers, wildlife, and remote communities living amidst vast frozen landscapes.', + }, + //// Generated data starts here + { + name: 'Streets - New York City', + type: 'MOVIE', + maturity: 'TVY7', + cast: ['unknown'], + genre: ['Documentary'], + synopsis: + 'A cinematic portrait of New York City’s bustling streets, diverse neighborhoods, and the vibrant life that fills its avenues.', + }, + { + name: 'A Day in Rio', + type: 'MOVIE', + maturity: 'TVY7', + cast: ['unknown'], + genre: ['Documentary', 'Travel'], + synopsis: + 'A colorful look at daily life in Rio de Janeiro, highlighting culture, beaches, music, and the spirit of the Brazilian people.', + }, + { + name: 'The Vince Staples Show', + type: 'SERIES', + maturity: 'TVMA', + cast: ['Vince Staples'], + genre: ['Documentary', 'Music'], + synopsis: 'A behind-the-scenes look at Vince Staples’ life, music, and creative process.', + }, + { + name: 'Suits', + type: 'SERIES', + maturity: 'TVMA', + cast: ['Gabriel Macht', 'Patrick J. Adams', 'Meghan Markle'], + genre: ['Comedy', 'Drama'], + synopsis: 'A smart lawyer recruits a college dropout to work at a top New York law firm.', + }, + { + name: 'Extraordinary Attorney Woo', + type: 'SERIES', + maturity: 'TVPG', + cast: ['Park Eun-bin', 'Kang Tae-oh'], + genre: ['Drama', 'Comedy'], + synopsis: + 'An autistic lawyer navigates her career and personal life in a challenging legal world.', + }, + { + name: 'Headspace Guide To Sleep', + type: 'SERIES', + maturity: 'TVY', + cast: ['Narrator'], + genre: ['Documentary'], + synopsis: 'Guided sleep meditation and relaxation techniques to improve your nightly rest.', + }, + { + name: 'Apollo 13', + type: 'MOVIE', + maturity: 'PG', + cast: ['Tom Hanks', 'Kevin Bacon', 'Bill Paxton'], + genre: ['Adventure', 'Drama'], + synopsis: + 'NASA astronauts struggle to survive after an oxygen tank explodes on their mission to the moon.', + }, + { + name: 'Oldboy', + type: 'MOVIE', + maturity: 'R', + cast: ['Choi Min-sik'], + genre: ['Thriller', 'Action'], + synopsis: 'A man is imprisoned for 15 years and seeks revenge on his captors upon release.', + }, + { + name: 'Pacific Rim', + type: 'MOVIE', + maturity: 'PG13', + cast: ['Charlie Hunnam', 'Rinko Kikuchi'], + genre: ['Action', 'Adventure', 'Fantasy'], + synopsis: + 'Humanity fights massive monsters using giant robots to save Earth from destruction.', + }, + { + name: 'Jurassic World: Camp Cretaceous', + type: 'SERIES', + maturity: 'TVPG', + cast: ['Paul-Mikél Williams', 'Sean Giambrone'], + genre: ['Adventure', 'Fantasy'], + synopsis: + 'A group of teens must survive on Isla Nublar when the dinosaurs escape their enclosures.', + }, + { + name: 'Rurouni Kenshin: The Final', + type: 'MOVIE', + maturity: 'PG13', + cast: ['Takeru Satoh', 'Emi Takei'], + genre: ['Action', 'Adventure'], + synopsis: 'The wandering swordsman Kenshin confronts his ultimate enemy in a final battle.', + }, + { + name: 'Ready Player One', + type: 'MOVIE', + maturity: 'PG13', + cast: ['Tye Sheridan', 'Olivia Cooke'], + genre: ['Adventure', 'Fantasy', 'Action'], + synopsis: + 'A young gamer enters a virtual reality world to win a contest and inherit a vast fortune.', + }, + { + name: 'Teenage Mutant Ninja Turtles', + type: 'MOVIE', + maturity: 'PG', + cast: ['Pete Ploszek', 'Johnny Knoxville'], + genre: ['Action', 'Comedy', 'Adventure'], + synopsis: + 'Four mutated turtles fight crime in New York City with martial arts skills and humor.', + }, + { + name: 'Shot Caller', + type: 'MOVIE', + maturity: 'R', + cast: ['Nikolaj Coster-Waldau'], + genre: ['Thriller', 'Action'], + synopsis: + 'A successful businessman is transformed into a hardened criminal after a prison stint.', + }, + { + name: 'Crouching Tiger, Hidden Dragon: Sword Of Destiny', + type: 'MOVIE', + maturity: 'PG13', + cast: ['Donnie Yen', 'Michelle Yeoh'], + genre: ['Action', 'Fantasy'], + synopsis: + 'Legendary warriors must protect a powerful sword from falling into the wrong hands.', + }, + { + name: 'Captains Of The World', + type: 'MOVIE', + maturity: 'TVY7', + cast: ['unknown'], + genre: ['Documentary', 'Sports'], + synopsis: + 'A look at the lives of football captains from around the globe and their leadership journeys.', + }, + { + name: 'Love On The Spectrum', + type: 'SERIES', + maturity: 'TVPG', + cast: ['unknown'], + genre: ['Documentary', 'Comedy'], + synopsis: 'Young adults on the autism spectrum explore dating and relationships.', + }, + { + name: 'Super Mario Bros Movie', + type: 'MOVIE', + maturity: 'PG', + cast: ['Chris Pratt', 'Anya Taylor-Joy'], + genre: ['Adventure', 'Comedy', 'Fantasy'], + synopsis: 'Mario and Luigi journey to save the Mushroom Kingdom from Bowser.', + }, + { + name: 'Pluto', + type: 'MOVIE', + maturity: 'PG', + cast: ['unknown'], + genre: ['Documentary', 'Nature'], + synopsis: + 'Exploring the mysteries and wonders of Pluto in the outer reaches of our solar system.', + }, + { + name: 'Blacklist', + type: 'SERIES', + maturity: 'TVMA', + cast: ['James Spader'], + genre: ['Thriller', 'Action'], + synopsis: 'A former criminal mastermind helps the FBI track dangerous criminals.', + }, + { + name: 'Lady Bird', + type: 'MOVIE', + maturity: 'R', + cast: ['Saoirse Ronan'], + genre: ['Comedy', 'Drama'], + synopsis: 'A young woman navigates high school, family, and self-discovery in Sacramento.', + }, + { + name: 'Kong: King Of The Apes', + type: 'MOVIE', + maturity: 'PG', + cast: ['unknown'], + genre: ['Action', 'Adventure', 'Fantasy'], + synopsis: 'Kong must defend his island from invaders and save his species.', + }, + { + name: 'The Man From Nowhere', + type: 'MOVIE', + maturity: 'R', + cast: ['Won Bin'], + genre: ['Action', 'Thriller'], + synopsis: 'A quiet pawnshop keeper embarks on a violent mission to rescue a kidnapped girl.', + }, + { + name: 'Resident Alien', + type: 'SERIES', + maturity: 'TVPG', + cast: ['Alan Tudyk', 'Sara Tomko'], + genre: ['Comedy'], + synopsis: + 'An alien crash-lands on Earth and tries to blend in while hiding from a dangerous mission.', + }, + { + name: 'Revenger', + type: 'MOVIE', + maturity: 'R', + cast: ['Atsushi Shinohara'], + genre: ['Action', 'Thriller'], + synopsis: 'A lone swordsman seeks revenge against a corrupt empire in feudal Japan.', + }, + { + name: 'Legend Of The Fist', + type: 'MOVIE', + maturity: 'PG13', + cast: ['Donnie Yen'], + genre: ['Action', 'Adventure'], + synopsis: 'A martial artist challenges his destiny in an epic battle to protect his people.', + }, + { + name: 'The Devils Plan', + type: 'MOVIE', + maturity: 'R', + cast: ['unknown'], + genre: ['Thriller'], + synopsis: + 'A criminal mastermind plots an elaborate scheme that tests the limits of morality.', + }, + { + name: 'House Of Ninjas', + type: 'SERIES', + maturity: 'TVMA', + cast: ['unknown'], + genre: ['Action', 'Adventure'], + synopsis: + 'A clan of elite ninjas navigates ancient rivalries and deadly missions in feudal Japan.', + }, + { + name: 'Lover Stalker Killer', + type: 'MOVIE', + maturity: 'R', + cast: ['unknown'], + genre: ['Thriller'], + synopsis: 'A serial criminal targets victims in a twisted game of obsession and danger.', + }, + { + name: 'The Outsider', + type: 'MOVIE', + maturity: 'R', + cast: ['Jason Bateman'], + genre: ['Thriller', 'Drama'], + synopsis: + 'An outsider infiltrates a dangerous criminal network and uncovers secrets he cannot ignore.', + }, + { + name: 'Headspace Unwind Your Mind', + type: 'SERIES', + maturity: 'TVY', + cast: ['Narrator'], + genre: ['Documentary'], + synopsis: 'Guided meditation to help relax and clear your mind at the end of the day.', + }, + { + name: 'The Farewell', + type: 'MOVIE', + maturity: 'PG', + cast: ['Awkwafina'], + genre: ['Comedy', 'Drama'], + synopsis: + 'A family returns to China under the guise of a fake wedding to stealthily say goodbye to their matriarch.', + }, + { + name: 'Extraction 2', + type: 'MOVIE', + maturity: 'R', + cast: ['Chris Hemsworth'], + genre: ['Action', 'Thriller'], + synopsis: + 'A black ops mercenary faces a new high-stakes mission to rescue kidnapped hostages.', + }, + { + name: 'The Speed Cubers', + type: 'MOVIE', + maturity: 'TVY7', + cast: ['Max Park', 'Feliks Zemdegs'], + genre: ['Documentary', 'Sports'], + synopsis: + 'An intimate look into the competitive world of professional Rubik’s Cube speedsolving.', + }, + ], + season: [ + { + title: 'Space Force', + thumbnail: 'SpaceForce', + number: 1, + }, + { + title: 'Space Force', + thumbnail: 'SpaceForce', + number: 2, + }, + //// Generated data starts here + { title: 'The Vince Staples Show', thumbnail: 'TheVinceStaplesShow', number: 1 }, + { title: 'Suits', thumbnail: 'Suits', number: 1 }, + { title: 'Extraordinary Attorney Woo', thumbnail: 'ExtraordinaryAttorneyWoo', number: 1 }, + { title: 'Headspace Guide To Sleep', thumbnail: 'HeadspaceGuideToSleep', number: 1 }, + { + title: 'Jurassic World: Camp Cretaceous', + thumbnail: 'JurassicWorldCampCretaceous', + number: 1, + }, + { title: 'Love On The Spectrum', thumbnail: 'LoveOnTheSpectrum', number: 1 }, + { title: 'Blacklist', thumbnail: 'Blacklist', number: 1 }, + { title: 'Resident Alien', thumbnail: 'ResidentAlien', number: 1 }, + { title: 'House Of Ninjas', thumbnail: 'HouseOfNinjas', number: 1 }, + { title: 'Headspace Unwind Your Mind', thumbnail: 'HeadspaceUnwindYourMind', number: 1 }, + ], + video: [ + { + url: 'https://vimeo.com/287971114', + image: + 'https://i.vimeocdn.com/video/723251206-daf702211c0027abdc707208d3405144724e2aad7e2ba09b8af8caec30e458d6-d_640?region=us', + duration: 30, + episode_number: null, + season_number: null, + title: 'Mission Impossible Fallout', + }, + { + url: 'https://vimeo.com/238721432', + image: + 'https://i.vimeocdn.com/video/667157015-c9ff83b37f7d5a0bd330c499934d032e62a3b601a47e54d4eb0923c51b681838-d_640?region=us', + duration: 151, + episode_number: null, + season_number: null, + title: 'Jumanji', + }, + { + url: 'https://vimeo.com/1112533851', + image: + 'https://i.vimeocdn.com/video/2050795243-daeb5f87da5317cd278ba0b3d65dbda189c32a03cc8a00506cf8436c12160897-d_295x166?region=us', + duration: 28, + episode_number: null, + season_number: null, + title: 'K-Pop Demon Hunters', + }, + { + url: 'https://vimeo.com/679974686', + image: 'SpaceForce', + duration: 151, + episode_number: 1, + season_number: 1, + title: 'Space Force', + }, + { + url: 'https://vimeo.com/679974686', + image: 'SpaceForce', + duration: 151, + episode_number: 2, + season_number: 1, + title: 'Space Force', + }, + { + url: 'https://vimeo.com/679974686', + image: 'SpaceForce', + duration: 151, + episode_number: 3, + season_number: 1, + title: 'Space Force', + }, + { + url: 'https://vimeo.com/679974686', + image: 'SpaceForce', + duration: 151, + episode_number: 1, + season_number: 2, + title: 'Space Force', + }, + { + url: 'https://vimeo.com/679974686', + image: 'SpaceForce', + duration: 151, + episode_number: 2, + season_number: 2, + title: 'Space Force', + }, + { + url: 'https://vimeo.com/679974686', + image: 'SpaceForce', + duration: 151, + episode_number: 3, + season_number: 2, + title: 'Space Force', + }, + { + url: 'https://vimeo.com/239539507', + image: + 'https://i.vimeocdn.com/video/662539335-2190bfe32ae3c13e4e1609e77caf669ef28ea3a4e4d4168997cab476b14b6271-d_295x166?region=us', + duration: 6151, + episode_number: null, + season_number: null, + title: 'Sword of Destiny', + }, + { + url: 'https://vimeo.com/99443394', + image: + 'https://i.vimeocdn.com/video/518636879-b7ee73f5b36bb068bc5568db97dc05ecdb28678d0115b1af6b4ce67feeb3f357-d_640?region=us', + duration: 140, + episode_number: null, + season_number: null, + title: 'Cristiano Ronaldo', + }, + { + url: 'https://vimeo.com/117669654', + image: + 'https://i.vimeocdn.com/video/504549477-eef607506ca9b61086636833daf2cccc9e38ecd48ee525a61840c05cd5e880c5-d_640?region=us', + duration: 217, + episode_number: null, + season_number: null, + title: 'The Road Story: Vietnam', + }, + { + url: 'https://vimeo.com/302312473', + image: + 'https://i.vimeocdn.com/video/741076140-e37c690b054707a36e79fc46cfc1f1fa955a22f5894c9d9eec16c712dbcadd41-d_295x166?region=us', + duration: 565, + episode_number: null, + season_number: null, + title: 'Greenland - Land of Ice', + }, + { + url: 'https://vimeo.com/101895020', + image: + 'https://i.vimeocdn.com/video/484345272-38141eb36a0c94123c533d8755cc2dc75839e9d132f4b880a5910cc0db599903-d_295x166?region=us', + duration: 275, + episode_number: null, + season_number: null, + title: 'Streets - New York City', + }, + { + url: 'https://vimeo.com/48378823', + image: + 'https://i.vimeocdn.com/video/334266098-2e1d8509e26e074fb2b87809abe62c9743904b9b3c455f0cee22f6d21ac647ff-d_295x166?region=us', + duration: 138, + episode_number: null, + season_number: null, + title: 'A Day in Rio', + }, + + /// Generated data starts here + { + url: 'https://vimeo.com/287971114', + image: 'TheVinceStaplesShow', + duration: 150, + episode_number: 1, + season_number: 1, + title: 'The Vince Staples Show', + }, + { + url: 'https://vimeo.com/287971114', + image: 'TheVinceStaplesShow', + duration: 150, + episode_number: 2, + season_number: 1, + title: 'The Vince Staples Show', + }, + { + url: 'https://vimeo.com/287971114', + image: 'TheVinceStaplesShow', + duration: 150, + episode_number: 3, + season_number: 1, + title: 'The Vince Staples Show', + }, + + { + url: 'https://vimeo.com/238721432', + image: 'Suits', + duration: 150, + episode_number: 1, + season_number: 1, + title: 'Suits', + }, + { + url: 'https://vimeo.com/238721432', + image: 'Suits', + duration: 150, + episode_number: 2, + season_number: 1, + title: 'Suits', + }, + { + url: 'https://vimeo.com/238721432', + image: 'Suits', + duration: 150, + episode_number: 3, + season_number: 1, + title: 'Suits', + }, + + { + url: 'https://vimeo.com/1112533851', + image: 'ExtraordinaryAttorneyWoo', + duration: 150, + episode_number: 1, + season_number: 1, + title: 'Extraordinary Attorney Woo', + }, + { + url: 'https://vimeo.com/1112533851', + image: 'ExtraordinaryAttorneyWoo', + duration: 150, + episode_number: 2, + season_number: 1, + title: 'Extraordinary Attorney Woo', + }, + { + url: 'https://vimeo.com/1112533851', + image: 'ExtraordinaryAttorneyWoo', + duration: 150, + episode_number: 3, + season_number: 1, + title: 'Extraordinary Attorney Woo', + }, + + { + url: 'https://vimeo.com/679974686', + image: 'HeadspaceGuideToSleep', + duration: 150, + episode_number: 1, + season_number: 1, + title: 'Headspace Guide To Sleep', + }, + { + url: 'https://vimeo.com/679974686', + image: 'HeadspaceGuideToSleep', + duration: 150, + episode_number: 2, + season_number: 1, + title: 'Headspace Guide To Sleep', + }, + { + url: 'https://vimeo.com/679974686', + image: 'HeadspaceGuideToSleep', + duration: 150, + episode_number: 3, + season_number: 1, + title: 'Headspace Guide To Sleep', + }, + + { + url: 'https://vimeo.com/99443394', + image: 'JurassicWorldCampCretaceous', + duration: 150, + episode_number: 1, + season_number: 1, + title: 'Jurassic World: Camp Cretaceous', + }, + { + url: 'https://vimeo.com/99443394', + image: 'JurassicWorldCampCretaceous', + duration: 150, + episode_number: 2, + season_number: 1, + title: 'Jurassic World: Camp Cretaceous', + }, + { + url: 'https://vimeo.com/99443394', + image: 'JurassicWorldCampCretaceous', + duration: 150, + episode_number: 3, + season_number: 1, + title: 'Jurassic World: Camp Cretaceous', + }, + + { + url: 'https://vimeo.com/101895020', + image: 'LoveOnTheSpectrum', + duration: 150, + episode_number: 1, + season_number: 1, + title: 'Love On The Spectrum', + }, + { + url: 'https://vimeo.com/101895020', + image: 'LoveOnTheSpectrum', + duration: 150, + episode_number: 2, + season_number: 1, + title: 'Love On The Spectrum', + }, + { + url: 'https://vimeo.com/101895020', + image: 'LoveOnTheSpectrum', + duration: 150, + episode_number: 3, + season_number: 1, + title: 'Love On The Spectrum', + }, + + { + url: 'https://vimeo.com/99443394', + image: 'Blacklist', + duration: 150, + episode_number: 1, + season_number: 1, + title: 'Blacklist', + }, + { + url: 'https://vimeo.com/99443394', + image: 'Blacklist', + duration: 150, + episode_number: 2, + season_number: 1, + title: 'Blacklist', + }, + { + url: 'https://vimeo.com/99443394', + image: 'Blacklist', + duration: 150, + episode_number: 3, + season_number: 1, + title: 'Blacklist', + }, + + { + url: 'https://vimeo.com/679974686', + image: 'ResidentAlien', + duration: 150, + episode_number: 1, + season_number: 1, + title: 'Resident Alien', + }, + { + url: 'https://vimeo.com/679974686', + image: 'ResidentAlien', + duration: 150, + episode_number: 2, + season_number: 1, + title: 'Resident Alien', + }, + { + url: 'https://vimeo.com/679974686', + image: 'ResidentAlien', + duration: 150, + episode_number: 3, + season_number: 1, + title: 'Resident Alien', + }, + + { + url: 'https://vimeo.com/1112533851', + image: 'HouseOfNinjas', + duration: 150, + episode_number: 1, + season_number: 1, + title: 'House Of Ninjas', + }, + { + url: 'https://vimeo.com/1112533851', + image: 'HouseOfNinjas', + duration: 150, + episode_number: 2, + season_number: 1, + title: 'House Of Ninjas', + }, + { + url: 'https://vimeo.com/1112533851', + image: 'HouseOfNinjas', + duration: 150, + episode_number: 3, + season_number: 1, + title: 'House Of Ninjas', + }, + + { + url: 'https://vimeo.com/287971114', + image: 'HeadspaceUnwindYourMind', + duration: 150, + episode_number: 1, + season_number: 1, + title: 'Headspace Unwind Your Mind', + }, + { + url: 'https://vimeo.com/287971114', + image: 'HeadspaceUnwindYourMind', + duration: 150, + episode_number: 2, + season_number: 1, + title: 'Headspace Unwind Your Mind', + }, + { + url: 'https://vimeo.com/287971114', + image: 'HeadspaceUnwindYourMind', + duration: 150, + episode_number: 3, + season_number: 1, + title: 'Headspace Unwind Your Mind', + }, + + // Movies (single videos) + { + url: 'https://vimeo.com/238721432', + image: 'Apollo13', + duration: 140, + episode_number: null, + season_number: null, + title: 'Apollo 13', + }, + { + url: 'https://vimeo.com/1112533851', + image: 'Oldboy', + duration: 140, + episode_number: null, + season_number: null, + title: 'Oldboy', + }, + { + url: 'https://vimeo.com/287971114', + image: 'PacificRim', + duration: 150, + episode_number: null, + season_number: null, + title: 'Pacific Rim', + }, + { + url: 'https://vimeo.com/99443394', + image: 'RurouniKenshinTheFinal', + duration: 150, + episode_number: null, + season_number: null, + title: 'Rurouni Kenshin: The Final', + }, + { + url: 'https://vimeo.com/679974686', + image: 'ReadyPlayerOne', + duration: 150, + episode_number: null, + season_number: null, + title: 'Ready Player One', + }, + { + url: 'https://vimeo.com/101895020', + image: 'TeenageMutantNinjaTurtles', + duration: 150, + episode_number: null, + season_number: null, + title: 'Teenage Mutant Ninja Turtles', + }, + { + url: 'https://vimeo.com/238721432', + image: 'ShotCaller', + duration: 150, + episode_number: null, + season_number: null, + title: 'Shot Caller', + }, + { + url: 'https://vimeo.com/99443394', + image: 'CrouchingTigerHiddenDragonSwordOfDestiny', + duration: 150, + episode_number: null, + season_number: null, + title: 'Crouching Tiger, Hidden Dragon: Sword Of Destiny', + }, + { + url: 'https://vimeo.com/287971114', + image: 'CaptainsOfTheWorld', + duration: 150, + episode_number: null, + season_number: null, + title: 'Captains Of The World', + }, + { + url: 'https://vimeo.com/1112533851', + image: 'SuperMarioBrosMovie', + duration: 150, + episode_number: null, + season_number: null, + title: 'Super Mario Bros Movie', + }, + { + url: 'https://vimeo.com/679974686', + image: 'Pluto', + duration: 150, + episode_number: null, + season_number: null, + title: 'Pluto', + }, + { + url: 'https://vimeo.com/101895020', + image: 'LadyBird', + duration: 150, + episode_number: null, + season_number: null, + title: 'Lady Bird', + }, + { + url: 'https://vimeo.com/238721432', + image: 'KongKingOfTheApes', + duration: 150, + episode_number: null, + season_number: null, + title: 'Kong: King Of The Apes', + }, + { + url: 'https://vimeo.com/99443394', + image: 'TheManFromNowhere', + duration: 150, + episode_number: null, + season_number: null, + title: 'The Man From Nowhere', + }, + { + url: 'https://vimeo.com/287971114', + image: 'Revenger', + duration: 150, + episode_number: null, + season_number: null, + title: 'Revenger', + }, + { + url: 'https://vimeo.com/1112533851', + image: 'LegendOfTheFist', + duration: 150, + episode_number: null, + season_number: null, + title: 'Legend Of The Fist', + }, + { + url: 'https://vimeo.com/679974686', + image: 'TheDevilsPlan', + duration: 150, + episode_number: null, + season_number: null, + title: 'The Devils Plan', + }, + { + url: 'https://vimeo.com/101895020', + image: 'LoverStalkerKiller', + duration: 150, + episode_number: null, + season_number: null, + title: 'Lover Stalker Killer', + }, + { + url: 'https://vimeo.com/238721432', + image: 'TheOutsider', + duration: 150, + episode_number: null, + season_number: null, + title: 'The Outsider', + }, + { + url: 'https://vimeo.com/99443394', + image: 'TheFarewell', + duration: 150, + episode_number: null, + season_number: null, + title: 'The Farewell', + }, + { + url: 'https://vimeo.com/287971114', + image: 'Extraction2', + duration: 150, + episode_number: null, + season_number: null, + title: 'Extraction 2', + }, + { + url: 'https://vimeo.com/1112533851', + image: 'TheSpeedCubers', + duration: 150, + episode_number: null, + season_number: null, + title: 'The Speed Cubers', + }, + ], +}; diff --git a/frontend/src/assets/shows/index.tsx b/frontend/src/assets/shows/index.tsx index f791c271..32340cfc 100644 --- a/frontend/src/assets/shows/index.tsx +++ b/frontend/src/assets/shows/index.tsx @@ -17,7 +17,7 @@ import CaptainsOfTheWorld from './show16.png'; import LoveOnTheSpectrum from './show17.png'; import SuperMarioBrosMovie from './show18.png'; import Pluto from './show19.png'; -import BlackList from './show20.png'; +import Blacklist from './show20.png'; import SuperMarioBrosMovieDup from './show21.png'; import LadyBird from './show22.png'; import KongKingOfTheApes from './show23.png'; @@ -54,7 +54,7 @@ export const SHOWS: Record = { LoveOnTheSpectrum, SuperMarioBrosMovie, Pluto, - BlackList, + Blacklist, SuperMarioBrosMovieDup, LadyBird, KongKingOfTheApes, diff --git a/frontend/src/components/MovieCards/MovieCards.module.css b/frontend/src/components/MovieCards/MovieCards.module.css index 302805e3..828e2b3d 100644 --- a/frontend/src/components/MovieCards/MovieCards.module.css +++ b/frontend/src/components/MovieCards/MovieCards.module.css @@ -21,7 +21,7 @@ flex-wrap: nowrap; gap: 16px; align-items: flex-start; - overflow-x: auto; + /* overflow-x: auto; */ } /* Base card look shared by all variants */ diff --git a/frontend/src/components/MovieCards/MovieCards.tsx b/frontend/src/components/MovieCards/MovieCards.tsx index 9f9968e7..5740ab6b 100644 --- a/frontend/src/components/MovieCards/MovieCards.tsx +++ b/frontend/src/components/MovieCards/MovieCards.tsx @@ -90,7 +90,7 @@ const MovieCards: React.FC = ({ 2h 18m ); })()} - {card.title} + {card.title} {/* Hover play overlay */} @@ -117,7 +117,7 @@ const MovieCards: React.FC = ({ aria-label={`Open ${card.title}`} onClick={() => onCardClick?.(card.id)} > - {card.title} + {card.title} {/* Hover play overlay */}
@@ -143,7 +143,7 @@ const MovieCards: React.FC = ({ aria-label={`Open ${card.title}`} onClick={() => onCardClick?.(card.id)} > - {card.title} + {card.title}
@@ -163,7 +163,7 @@ const MovieCards: React.FC = ({ onClick={() => onCardClick?.(card.id)} onKeyDown={(e) => activateOnKey(e, () => onCardClick?.(card.id))} > - {card.title} + {card.title} {/* Bottom-centered time bar */} {(() => { const tc = formatTime(card.duration); @@ -199,7 +199,7 @@ const MovieCards: React.FC = ({ onKeyDown={(e) => activateOnKey(e, () => onCardClick?.(card.id))} >
@@ -215,7 +215,7 @@ const MovieCards: React.FC = ({ aria-label={`Open ${card.title}`} onClick={() => onCardClick?.(card.id)} > - {card.title} + {card.title} {card.progress && (
diff --git a/frontend/src/components/ShowsCarousel/ShowsCarousel.tsx b/frontend/src/components/ShowsCarousel/ShowsCarousel.tsx index d2cd4ae5..dd110135 100644 --- a/frontend/src/components/ShowsCarousel/ShowsCarousel.tsx +++ b/frontend/src/components/ShowsCarousel/ShowsCarousel.tsx @@ -4,7 +4,6 @@ import type { ShowsCarouselProps } from './ShowsCarousel.types'; import { MovieCards } from '../MovieCards'; export const ShowsCarousel: React.FC = ({ title, movieCard, className }) => { - console.log('movieCard', movieCard); const carouselRef = useRef(null); const [steps, setSteps] = useState(1); const [currentStep, setCurrentStep] = useState(0); diff --git a/frontend/src/pages/Home/Home.tsx b/frontend/src/pages/Home/Home.tsx index f044d2ac..c71292fd 100644 --- a/frontend/src/pages/Home/Home.tsx +++ b/frontend/src/pages/Home/Home.tsx @@ -7,6 +7,7 @@ import { ShowDetailsDialog, type Episode, type MovieCardData, + type MovieCardVariant, } from '../../components'; import { useEffect, useRef, useState } from 'react'; @@ -32,20 +33,60 @@ const Home = () => { const [useMockData, setUseMockData] = useState(false); const [isModalOpen, openModal, closeModal] = useStateToggleHandlers(false); + const genres = [...new Set(titles?.flatMap((t) => t.genre))]; + + const categories: [string, MovieCardData[], MovieCardVariant][] = + titles && titles.length > 0 && genres.length > 0 + ? [ + ['Matched to you', mapTitleToMovieCard(titles.slice(0, 15)), 'default'], + ['Top 10', mapTitleToMovieCard(titles.slice(0, 10), true), 'top10'], + ['New on Netflix', mapTitleToMovieCard(titles.slice(0, 8)), 'default'], + ...genres + .map((genre) => { + const filteredTitle = titles.filter((t) => t.genre.includes(genre)); + return filteredTitle.length > 0 + ? ([genre, mapTitleToMovieCard(filteredTitle), 'default'] as [ + string, + MovieCardData[], + MovieCardVariant, + ]) + : null; + }) + .filter((item): item is [string, MovieCardData[], MovieCardVariant] => item !== null), + ] + : []; + + function mapTitleToMovieCard(titles: Title[], ranked: boolean = false) { + return titles.map((title, idx) => { + return { + id: title.id, + thumbnail: + title.type === 'MOVIE' ? (title.video?.image ?? '') : (title.season[0]?.thumbnail ?? ''), + duration: title.type === 'MOVIE' ? formatDuration(title.video?.duration) : undefined, + isNew: undefined, + progress: undefined, + rank: ranked ? idx + 1 : undefined, + seasonInfo: undefined, + title: title.name, + } as unknown as MovieCardData; + }); + } + let movieCardData: MovieCardData[] = []; if (titles && titles.length > 0) { movieCardData = titles.map((title) => { return { id: title.id, - thumbnail: title.type === 'MOVIE' ? title.video.image : title.season[0].thumbnail, - duration: title.type === 'MOVIE' ? formatDuration(title.video.duration) : undefined, + thumbnail: + title.type === 'MOVIE' ? (title.video?.image ?? '') : (title.season[0]?.thumbnail ?? ''), + duration: title.type === 'MOVIE' ? formatDuration(title.video?.duration) : undefined, isNew: undefined, progress: undefined, rank: undefined, seasonInfo: undefined, title: title.name, - } as unknown as MovieCardData; + } as MovieCardData; }); } else if (useMockData) { // Use mockData when API returns no data @@ -186,7 +227,6 @@ const Home = () => { const dialogTitle = title ? title.name : (selectedMockShow?.title ?? ''); const dialogDescription = title ? title.synopsis : (selectedMockShow?.details?.description ?? ''); - return ( <>
@@ -242,7 +282,25 @@ const Home = () => { }} episodes={episodeList} /> - {movieCardData.length > 0 && + + {!useMockData && + titles && + titles.length > 0 && + categories.map(([title, data, variant], id) => ( + handleCardClick(id), + }} + className={styles.carousel} + /> + ))} + + {useMockData && + movieCardData.length > 0 && mockShows.map((category, index) => { // Filter cards based on category IDs, maintaining the order from mockShows const filteredCards = category.ids