Skip to content

Commit

Permalink
Movies (#28)
Browse files Browse the repository at this point in the history
* Add type to room

* Change room type default

* Add type to room creation modal

* Allow user to select movies
  • Loading branch information
Rabrennie authored Mar 3, 2024
1 parent 3a772a4 commit 52f5b89
Show file tree
Hide file tree
Showing 16 changed files with 378 additions and 47 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ GOOGLE_SECRET=""
# Spotify credentials for album search
SPOTIFY_CLIENT_ID=""
SPOTIFY_CLIENT_SECRET=""

# OMDB API key for movie search
OMDB_API_KEY=""
58 changes: 58 additions & 0 deletions prisma/migrations/20240303163024_add_type_to_room/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Choice" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"albumId" TEXT NOT NULL,
"albumImage" TEXT NOT NULL,
"albumArtist" TEXT NOT NULL,
"albumName" TEXT NOT NULL,
"eliminated" BOOLEAN NOT NULL,
"cssGradient" TEXT,
"createdAt" INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
"roomId" INTEGER NOT NULL,
"userId" INTEGER NOT NULL,
CONSTRAINT "Choice_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "Room" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Choice_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Choice" ("albumArtist", "albumId", "albumImage", "albumName", "createdAt", "cssGradient", "eliminated", "id", "roomId", "userId") SELECT "albumArtist", "albumId", "albumImage", "albumName", "createdAt", "cssGradient", "eliminated", "id", "roomId", "userId" FROM "Choice";
DROP TABLE "Choice";
ALTER TABLE "new_Choice" RENAME TO "Choice";
CREATE UNIQUE INDEX "Choice_roomId_userId_key" ON "Choice"("roomId", "userId");
CREATE TABLE "new_Room" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"linkId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"step" TEXT NOT NULL DEFAULT 'selecting',
"createdAt" INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
"type" TEXT NOT NULL DEFAULT 'albums',
"teamId" INTEGER NOT NULL,
CONSTRAINT "Room_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Room" ("createdAt", "id", "linkId", "name", "step", "teamId") SELECT "createdAt", "id", "linkId", "name", "step", "teamId" FROM "Room";
DROP TABLE "Room";
ALTER TABLE "new_Room" RENAME TO "Room";
CREATE UNIQUE INDEX "Room_linkId_key" ON "Room"("linkId");
CREATE TABLE "new_User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"email" TEXT NOT NULL,
"name" TEXT NOT NULL,
"image" TEXT,
"provider" TEXT NOT NULL,
"providerId" TEXT NOT NULL,
"createdAt" INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);
INSERT INTO "new_User" ("createdAt", "email", "id", "image", "name", "provider", "providerId") SELECT "createdAt", "email", "id", "image", "name", "provider", "providerId" FROM "User";
DROP TABLE "User";
ALTER TABLE "new_User" RENAME TO "User";
CREATE UNIQUE INDEX "User_provider_providerId_key" ON "User"("provider", "providerId");
CREATE TABLE "new_Team" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"createdAt" INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
"invite" TEXT NOT NULL
);
INSERT INTO "new_Team" ("createdAt", "id", "invite", "name") SELECT "createdAt", "id", "invite", "name" FROM "Team";
DROP TABLE "Team";
ALTER TABLE "new_Team" RENAME TO "Team";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Room" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"linkId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"step" TEXT NOT NULL DEFAULT 'selecting',
"createdAt" INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
"type" TEXT NOT NULL DEFAULT 'albums',
"teamId" INTEGER NOT NULL,
CONSTRAINT "Room_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Room" ("createdAt", "id", "linkId", "name", "step", "teamId", "type") SELECT "createdAt", "id", "linkId", "name", "step", "teamId", "type" FROM "Room";
DROP TABLE "Room";
ALTER TABLE "new_Room" RENAME TO "Room";
CREATE UNIQUE INDEX "Room_linkId_key" ON "Room"("linkId");
CREATE TABLE "new_User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"email" TEXT NOT NULL,
"name" TEXT NOT NULL,
"image" TEXT,
"provider" TEXT NOT NULL,
"providerId" TEXT NOT NULL,
"createdAt" INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
);
INSERT INTO "new_User" ("createdAt", "email", "id", "image", "name", "provider", "providerId") SELECT "createdAt", "email", "id", "image", "name", "provider", "providerId" FROM "User";
DROP TABLE "User";
ALTER TABLE "new_User" RENAME TO "User";
CREATE UNIQUE INDEX "User_provider_providerId_key" ON "User"("provider", "providerId");
CREATE TABLE "new_Choice" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"albumId" TEXT NOT NULL,
"albumImage" TEXT NOT NULL,
"albumArtist" TEXT NOT NULL,
"albumName" TEXT NOT NULL,
"eliminated" BOOLEAN NOT NULL,
"cssGradient" TEXT,
"createdAt" INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
"roomId" INTEGER NOT NULL,
"userId" INTEGER NOT NULL,
CONSTRAINT "Choice_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "Room" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT "Choice_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Choice" ("albumArtist", "albumId", "albumImage", "albumName", "createdAt", "cssGradient", "eliminated", "id", "roomId", "userId") SELECT "albumArtist", "albumId", "albumImage", "albumName", "createdAt", "cssGradient", "eliminated", "id", "roomId", "userId" FROM "Choice";
DROP TABLE "Choice";
ALTER TABLE "new_Choice" RENAME TO "Choice";
CREATE UNIQUE INDEX "Choice_roomId_userId_key" ON "Choice"("roomId", "userId");
CREATE TABLE "new_Team" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" TEXT NOT NULL,
"createdAt" INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
"invite" TEXT NOT NULL
);
INSERT INTO "new_Team" ("createdAt", "id", "invite", "name") SELECT "createdAt", "id", "invite", "name" FROM "Team";
DROP TABLE "Team";
ALTER TABLE "new_Team" RENAME TO "Team";
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ model Room {
name String
step String @default("selecting")
createdAt Int @default(dbgenerated("(strftime('%s', 'now'))"))
type String @default("albums")
choices Choice[]
Expand Down
8 changes: 5 additions & 3 deletions src/lib/components/SelectedAlbum/SelectedAlbum.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
export let avatar = '';
export let small = false;
export let winner = false;
export let type: 'albums' | 'movies' = 'albums';
let imageLoaded = false;
Expand All @@ -24,7 +25,8 @@
}
$: height = small ? 'h-[4.5rem]' : 'h-auto sm:h-40';
$: width = small ? 'w-[4.5rem]' : 'w-full sm:w-40';
$: width = 'auto';
$: aspect = type === 'albums' ? 'aspect-square' : 'aspect-[2/3]';
</script>

<div
Expand All @@ -41,14 +43,14 @@
{/if}
{#if cssGradient && !imageLoaded}
<div
class="z-10 bg-slate-900 bg-cover {width} {height} album-image blur-xl scale-125 bg-no-repeat shrink-0 aspect-square"
class="z-10 bg-slate-900 bg-cover {width} {height} album-image blur-xl scale-125 bg-no-repeat shrink-0 {aspect}"
style={`background-image: ${cssGradient}; background-position: 0 0 ,0 33.33333333333333%,0 66.66666666666666%,0 100%; background-size: 100% 25%;`}
/>
{/if}
{#if imageLoaded}
<a href={url} target="_blank" rel="noreferrer" class="z-10">
<div
class="z-10 bg-cover {height} {width} album-image bg-no-repeat shrink-0 aspect-square"
class="z-10 bg-cover {height} {width} album-image bg-no-repeat shrink-0 {aspect}"
style={`background-image: url(${image});`}
/>
</a>
Expand Down
37 changes: 37 additions & 0 deletions src/lib/server/omdb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { env } from '$env/dynamic/private';

interface OmdbMovie {
Title: string;
Year: string;
imdbID: string;
Type: string;
Poster: string;
}

interface OmdbSearchResponse {
Search: OmdbMovie[];
totalResults: string;
Response: string;
}

export const getToken = async () => {
return env.OMDB_API_KEY;
};

export const search = async (query: string, token: string): Promise<OmdbSearchResponse> => {
const params = new URLSearchParams({ s: query, type: 'movie', apikey: token });
const res = await fetch(`https://www.omdbapi.com/?${params}`, {
method: 'GET'
});

return await res.json();
};

export const singleMovie = async (movieId: string, token: string): Promise<OmdbMovie> => {
const params = new URLSearchParams({ i: movieId, apikey: token });
const res = await fetch(`https://www.omdbapi.com/?${params}`, {
method: 'GET'
});

return await res.json();
};
20 changes: 20 additions & 0 deletions src/routes/api/movie/+server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as OMDb from '$lib/server/omdb';

import type { RequestHandler } from './$types';

export const GET = (async ({ url }) => {
const id = String(url.searchParams.get('id'));

if (!id) {
return new Response('No id', { status: 400 });
}

const token = await OMDb.getToken();
const result = await OMDb.singleMovie(id, token);

return new Response(JSON.stringify(result), {
headers: {
'Content-Type': 'application/json'
}
});
}) satisfies RequestHandler;
38 changes: 30 additions & 8 deletions src/routes/api/search/+server.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
import { getToken, search } from '$lib/server/spotify';
import * as Spotify from '$lib/server/spotify';
import * as OMDb from '$lib/server/omdb';

import type { RequestHandler } from './$types';

export const GET = (async ({ url }) => {
const query = String(url.searchParams.get('q'));
const type = String(url.searchParams.get('type'));

if (!query) {
return new Response('No query', { status: 400 });
} else if (!type) {
return new Response('No type', { status: 400 });
}

if (type === 'albums') {
const token = await Spotify.getToken();
const results = await Spotify.search(query, token);

return new Response(JSON.stringify(results.albums.items), {
headers: {
'Content-Type': 'application/json'
}
});
} else if (type === 'movies') {
const token = await OMDb.getToken();
const results = await OMDb.search(query, token);

const token = await getToken();
const results = await search(query, token);
return new Response(JSON.stringify(results.Search), {
headers: {
'Content-Type': 'application/json'
}
});
}

return new Response(JSON.stringify(results.albums.items), {
headers: {
'Content-Type': 'application/json'
}
});
return new Response('Invalid type', { status: 400 });
}) satisfies RequestHandler;
66 changes: 50 additions & 16 deletions src/routes/room/[room]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,26 @@ import { RoomState, type Choice as RoomChoice } from '../../../types/Room';
import { RequireAuth } from '@rabrennie/sveltekit-auth/helpers';
import type { Choice, User } from '@prisma/client';
import crypto from 'crypto';
import { getToken, singleAlbum } from '$lib/server/spotify';
import * as Spotify from '$lib/server/spotify';
import * as OMDb from '$lib/server/omdb';
import { getPlaiceholder } from 'plaiceholder';

const getLinkId = (event: RequestEvent) => {
const { room: linkId } = zk.parseRouteParams(event, { room: z.string().min(1) });
return linkId;
};

const mapChoice = (choice: Choice): RoomChoice => ({
const mapChoice = (choice: Choice, type: 'albums' | 'movies'): RoomChoice => ({
userId: choice.userId,
eliminated: choice.eliminated,
choice: {
artist: choice.albumArtist,
title: choice.albumName,
imageUrl: choice.albumImage,
url: `https://open.spotify.com/album/${choice.albumId}`,
url:
type === 'albums'
? `https://open.spotify.com/album/${choice.albumId}`
: `https://www.imdb.com/title/${choice.albumId}`,
cssGradient: choice.cssGradient ?? ''
}
});
Expand All @@ -43,9 +47,12 @@ export const load = (async (event) => {
id: dbRoom.id,
teamId: dbRoom.team.id,
name: dbRoom.name,
type: dbRoom.type,
state: dbRoom.step as RoomState,
users: dbRoom.team.users.map((u) => ({ id: u.id, name: u.name, image: u.image })),
choices: Object.fromEntries(dbRoom.choices.map((c) => [c.userId, mapChoice(c)]))
choices: Object.fromEntries(
dbRoom.choices.map((c) => [c.userId, mapChoice(c, dbRoom.type)])
)
};

return {
Expand Down Expand Up @@ -77,18 +84,45 @@ export const actions = {
return result.response;
}

const token = await getToken();
const album = await singleAlbum(result.data.id, token);
const src = album?.images?.at(-1)?.url;
let albumId: string | undefined;
let albumArtist: string | undefined;
let albumImage: string | undefined;
let albumName: string | undefined;
let cssGradient: string | null = null;

if (dbRoom.type === 'albums') {
const token = await Spotify.getToken();
const album = await Spotify.singleAlbum(result.data.id, token);
const src = album?.images?.at(-1)?.url;

if (!album || !src) {
return fail(400, { error: 'Album does not exist' });
}

albumId = album.id;
albumArtist = album.artists[0].name;
albumImage = src;
albumName = album.name;
} else if (dbRoom.type === 'movies') {
const token = await OMDb.getToken();
const movie = await OMDb.singleMovie(result.data.id, token);

if (!album || !src) {
return fail(400, { error: 'Album does not exist' });
if (!movie || movie.Poster === 'N/A') {
return fail(400, { error: 'Movie does not exist' });
}

albumId = movie.imdbID;
albumArtist = movie.Year;
albumImage = movie.Poster;
albumName = movie.Title;
}

let cssGradient: string | null = null;
if (!albumId || !albumArtist || !albumImage || !albumName) {
return fail(400, { error: 'Missing data' });
}

try {
const buffer = await fetch(src).then(async (res) =>
const buffer = await fetch(albumImage).then(async (res) =>
Buffer.from(await res.arrayBuffer())
);

Expand All @@ -107,10 +141,10 @@ export const actions = {
};

const albumData = {
albumId: album.id,
albumArtist: album.artists[0].name,
albumImage: album.images[0].url,
albumName: album.name,
albumId,
albumArtist,
albumImage,
albumName,
eliminated: false,
cssGradient
};
Expand All @@ -125,7 +159,7 @@ export const actions = {
});

roomsState.broadcast(dbRoom.linkId, 'room:choices:update', {
choices: [mapChoice(dbChoice)]
choices: [mapChoice(dbChoice, dbRoom.type)]
});
}),
nextStep: RequireAuth(async (event) => {
Expand Down
Loading

0 comments on commit 52f5b89

Please sign in to comment.