Skip to content

Commit 219abb9

Browse files
fix: Spotify Creation of Playlist bug (#39)
* Fix Spotify Creation of Playlist bug (#38) * =added prisma * feat: update Spotify API import style and add Prisma dependencies - Changed single quotes to double quotes in `spotifyApi.ts`. - Added Prisma dependencies in `package-lock.json` and `package.json`. - Created initial migration for `users` table in PostgreSQL. - Updated Prisma schema to reflect new `User` model and changed database URL environment variable. - Modified `dev` script in `package.json` to include Node.js inspector. * fix: update build script to include Prisma generation * fix: Handle Spotify API authentication on the server * fix: Update playlist creation with descriptive names * fix: Ensure displayName is non-nullable in user upsert and make playlist type optional * fix: Add postinstall script to ensure Prisma generation after installation * fix: Update postinstall script to disable Prisma engine during generation * revert old code * Apply suggestions from code review --------- Co-authored-by: Dunsin <[email protected]> * fix: update .env.sample to streamline database configuration * fix: simplify dev script in package.json and format pnpm-workspace.yaml --------- Co-authored-by: Divine Amunega <[email protected]>
1 parent a3f0b0a commit 219abb9

File tree

18 files changed

+21877
-143
lines changed

18 files changed

+21877
-143
lines changed

.env.sample

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
# Get the Gemini API Key => https://aistudio.google.com/app/apikey
22
NEXT_PUBLIC_API_KEY=<Gemini API Key>
33

4-
# Get the postgres data => https://vercel.com/docs/storage/vercel-postgres/quickstart
5-
POSTGRES_DATABASE=
6-
POSTGRES_HOST=
7-
POSTGRES_PASSWORD=
8-
POSTGRES_PRISMA_URL=
4+
# Get the postgres data => https://vercel.com/marketplace/prisma
5+
DATABASE_URL=
6+
PRISMA_DATABASE_URL=
97
POSTGRES_URL=
10-
POSTGRES_URL_NON_POOLING=
11-
POSTGRES_URL_NO_SSL=
12-
POSTGRES_USER=
138

149
REDIRECT_URL=http://localhost:3000
1510
SECRET_KEY=<Any random string can be used here>

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ yarn-error.log*
3535
*.tsbuildinfo
3636
next-env.d.ts
3737
.vercel
38+
39+
/app/generated/prisma

app/api/auth/route.ts

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import axios, { AxiosError } from 'axios';
1+
import axios, { AxiosError } from "axios";
22

3-
import { NextResponse } from 'next/server';
4-
import { getUser } from '@/app/lib/spotify';
5-
import { sql } from '@vercel/postgres';
3+
import { NextResponse } from "next/server";
4+
import { getUser } from "@/app/lib/spotify";
5+
import { sql } from "@vercel/postgres";
6+
import prisma from "@/app/lib/prisma";
67

78
const redirect_uri = process.env.REDIRECT_URL;
89
const client_id = process.env.SPOTIFY_CLIENT_ID;
@@ -11,44 +12,51 @@ const client_secret = process.env.SPOTIFY_CLIENT_SECRET;
1112
export async function POST(req: Request) {
1213
const res = await req.json();
1314
const code = res.code;
14-
1515
if (!code) {
16-
return Response.json({ error: 'Invalid request' }, { status: 400 });
16+
return Response.json({ error: "Invalid request" }, { status: 400 });
1717
}
1818

1919
try {
2020
const response = await axios.post(
21-
'https://accounts.spotify.com/api/token',
21+
"https://accounts.spotify.com/api/token",
2222
{
23-
grant_type: 'authorization_code',
23+
grant_type: "authorization_code",
2424
code,
2525
redirect_uri,
2626
},
2727
{
2828
headers: {
2929
Authorization:
30-
'Basic ' +
31-
Buffer.from(client_id + ':' + client_secret).toString('base64'),
32-
'content-Type': 'application/x-www-form-urlencoded',
30+
"Basic " +
31+
Buffer.from(client_id + ":" + client_secret).toString("base64"),
32+
"content-Type": "application/x-www-form-urlencoded",
3333
},
34-
},
34+
}
3535
);
3636

37+
3738
const { access_token, refresh_token, expires_in } = response.data;
3839
const user = await getUser(access_token);
3940

40-
if (user)
41-
sql`
42-
INSERT INTO users (display_name, user_id, profile_image_url)
43-
VALUES (${user.display_name}, ${user.user_id}, ${user.profile_image_url})
44-
ON CONFLICT (user_id) DO NOTHING; -- Avoid duplicates
45-
`;
41+
if (user) {
42+
const prismaUser = await prisma.user.upsert({
43+
where: { userId: user.user_id },
44+
create: {
45+
displayName: user.display_name!,
46+
userId: user.user_id,
47+
profileImageUrl: user.profile_image_url,
48+
},
49+
update: {},
50+
});
51+
}
52+
4653

4754
return NextResponse.json(
4855
{ expires_in, refresh_token, access_token, user },
49-
{ status: 200 },
56+
{ status: 200 }
5057
);
5158
} catch (error) {
59+
console.log(error);
5260
error = error as AxiosError;
5361
return NextResponse.json({ error }, { status: 400 });
5462
}

app/api/users/[userId]/history/route.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
1-
import { addUserHistory, getUserHistory } from '@/app/lib/db';
1+
import { addUserHistory, getUserHistory } from "@/app/lib/db";
22

3-
import { NextResponse } from 'next/server';
3+
import { NextResponse } from "next/server";
44

55
export async function PUT(
66
req: Request,
7-
{ params }: { params: { userId: string } },
7+
{ params }: { params: { userId: string } }
88
) {
99
const res = await req.json();
1010
const { artists } = res;
1111

1212
const { userId } = params;
1313

1414
if (!artists || !userId) {
15-
return Response.json({ error: 'Invalid request' }, { status: 400 });
15+
return Response.json({ error: "Invalid request" }, { status: 400 });
1616
}
1717

1818
try {
1919
const response = await addUserHistory(userId, artists);
2020

21-
if (response.message === 'success') {
21+
if (response.message === "success") {
2222
return NextResponse.json({ status: 200 });
2323
} else {
2424
return NextResponse.json(
2525
{ message: `Could not update user history` },
26-
{ status: 400 },
26+
{ status: 400 }
2727
);
2828
}
2929
} catch (error) {
@@ -33,19 +33,16 @@ export async function PUT(
3333

3434
export async function GET(
3535
req: Request,
36-
{ params }: { params: { userId: string } },
36+
{ params }: { params: { userId: string } }
3737
) {
3838
const { userId } = params;
3939

4040
if (!userId) {
41-
return Response.json({ error: 'Invalid request' }, { status: 400 });
41+
return Response.json({ error: "Invalid request" }, { status: 400 });
4242
}
4343
try {
4444
const response = await getUserHistory(userId);
45-
return NextResponse.json(
46-
{ message: response?.rows[0]?.history },
47-
{ status: 200 },
48-
);
45+
return NextResponse.json({ message: response }, { status: 200 });
4946
} catch (error) {
5047
return NextResponse.json({ error }, { status: 400 });
5148
}

app/components/DiscoverTracks/SubmitButtion.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,11 @@ const SubmitButtion = () => {
8787
const tracks = await getAllTracks(albums as string[], 1);
8888

8989
setLoadingMessage('Creating The PlayList');
90+
const playlistName = `Similar to ${artists.join(', ')}`;
9091
const playlistInfo = await Promise.resolve(
9192
createPlayList(
92-
finalList.slice(0, -1).join(', ') + ' and ' + finalList.slice(-1),
93-
'new',
93+
playlistName,
94+
'Created by HearItFresh',
9495
),
9596
);
9697

app/components/History/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import HistoryCard from './HistoryCard';
66
import axios from 'axios';
77
import { useAuth } from '@/app/context/authContext';
88
import { useHistory } from '@/app/context/HistoryContext';
9+
import { getUser } from '@/app/lib/spotify';
910

1011
const History = () => {
1112
const { user, logOut } = useAuth();

app/components/TopTracks/SubmitButton.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,9 @@ const SubmitButton = () => {
4242
const tracks = await getAllTracks(albums, maxTracksPerAlbum);
4343

4444
setLoadingMessage('Creating The PlayList');
45+
const playlistName = `Top Tracks from ${artists.join(', ')}`;
4546
const playlistInfo = await Promise.resolve(
46-
createPlayList(
47-
artists.slice(0, -1).join(', ') + ' and ' + artists.slice(-1),
48-
'old',
49-
),
47+
createPlayList(playlistName, 'Created by HearItFresh'),
5048
);
5149

5250
if ('isError' in playlistInfo) {

app/lib/db/index.ts

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
'use server';
1+
"use server";
22

3-
import { sql } from '@vercel/postgres';
3+
import { sql } from "@vercel/postgres";
4+
import prisma from "../prisma";
45

56
export interface HistoryEntry {
67
text: string;
@@ -9,7 +10,7 @@ export interface HistoryEntry {
910

1011
export async function addUserHistory(
1112
userId: string,
12-
artists: string,
13+
artists: string
1314
): Promise<{ message: string; history: HistoryEntry[] }> {
1415
try {
1516
const lastUsed = new Date();
@@ -18,79 +19,79 @@ export async function addUserHistory(
1819

1920
const result = await getUserHistory(userId);
2021

21-
const currentHistory: HistoryEntry[] = result?.rows[0]?.history || [];
22+
let currentHistory: HistoryEntry[] = result ? result : [];
2223

2324
const artistExists = currentHistory.find(
24-
(entry: HistoryEntry) => entry.text === artists,
25+
(entry: HistoryEntry) => entry.text === artists
2526
);
2627

2728
if (artistExists) {
2829
const updatedHistory = currentHistory.map((entry: HistoryEntry) =>
29-
entry.text === artists ? { ...entry, lastUsed: lastUsedString } : entry,
30+
entry.text === artists ? { ...entry, lastUsed: lastUsedString } : entry
3031
);
3132

32-
await sql`
33-
UPDATE users
34-
SET history = ${JSON.stringify(updatedHistory)}::jsonb
35-
WHERE user_id = ${userId};
36-
`;
37-
console.log('Updated existing history entry');
33+
await prisma.user.update({
34+
where: { userId },
35+
data: {
36+
history: updatedHistory,
37+
},
38+
});
39+
3840
} else {
39-
await sql`
40-
UPDATE users
41-
SET history = history || ${JSON.stringify([newObject])}::jsonb
42-
WHERE user_id = ${userId};
43-
`;
44-
console.log('New history entry added');
41+
currentHistory = [...currentHistory, newObject];
42+
await prisma.user.update({
43+
where: { userId },
44+
data: { history: currentHistory },
45+
});
4546
}
4647

4748
// Fetch the updated history from the database
4849
const updatedResult = await getUserHistory(userId);
49-
const updatedHistory: HistoryEntry[] =
50-
updatedResult?.rows[0]?.history || [];
50+
const updatedHistory: HistoryEntry[] = updatedResult ? updatedResult : [];
5151

52-
return { message: 'success', history: updatedHistory };
52+
return { message: "success", history: updatedHistory };
5353
} catch (error) {
54-
console.error('Error updating history:', error);
55-
return { message: 'error', history: [] };
54+
console.error("Error updating history:", error);
55+
return { message: "error", history: [] };
5656
}
5757
}
5858

5959
export async function removeUserHistory(
6060
userId: string,
61-
artistToRemove: string,
61+
artistToRemove: string
6262
): Promise<string> {
6363
try {
64-
const result = await getUserHistory(userId);
65-
const currentHistory: HistoryEntry[] = result?.rows[0]?.history || [];
64+
const history = await getUserHistory(userId);
65+
const currentHistory: HistoryEntry[] = history ? history : [];
6666

6767
const updatedHistory = currentHistory.filter(
68-
(entry: HistoryEntry) => entry.text !== artistToRemove,
68+
(entry: HistoryEntry) => entry.text !== artistToRemove
6969
);
7070

71-
await sql`
72-
UPDATE users
73-
SET history = ${JSON.stringify(updatedHistory)}::jsonb
74-
WHERE user_id = ${userId};
75-
`;
71+
await prisma.user.update({
72+
where: { userId },
73+
data: { history: updatedHistory },
74+
});
7675

77-
console.log('History entry removed');
78-
return 'success';
76+
console.log("History entry removed");
77+
return "success";
7978
} catch (error) {
80-
console.error('Error removing history:', error);
81-
return 'error';
79+
console.error("Error removing history:", error);
80+
return "error";
8281
}
8382
}
8483

8584
export async function getUserHistory(
86-
userId: string,
87-
): Promise<{ rows: { history: HistoryEntry[] }[] }> {
85+
userId: string
86+
): Promise<HistoryEntry[] | null | undefined> {
8887
try {
89-
return await sql`
90-
SELECT history FROM users WHERE user_id = ${userId};
91-
`;
88+
89+
const history = (await prisma.user.findUnique({ where: { userId } }))
90+
?.history;
91+
92+
return history as unknown as HistoryEntry[];
9293
} catch (error) {
93-
console.error('Error fetching user history:', error);
94+
console.error("Error fetching user history:", error);
9495
throw error;
9596
}
9697
}

app/lib/prisma.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { PrismaClient } from "@/app/generated/prisma";
2+
import { withAccelerate } from "@prisma/extension-accelerate";
3+
4+
const prisma = new PrismaClient().$extends(withAccelerate());
5+
6+
export default prisma;

0 commit comments

Comments
 (0)