Skip to content

Commit da64808

Browse files
committed
added soundtracks to featured film
1 parent b9b4eb9 commit da64808

File tree

15 files changed

+437
-106
lines changed

15 files changed

+437
-106
lines changed

backend/.env

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@
44
# DB_NAME ="defaultdb"
55
# DB_PWD ="AVNS_QD6yQHA6jtcUcc5-MDO"
66

7-
DB_URL = "postgresql://postgres:postgres@localhost:5432/backend"
7+
DB_URL = "postgresql://postgres:postgres@localhost:5432/backend"
8+
SPOTIFY_CLIENT_ID=d2f3e52ef0cc4f81b8059f9275a6a08b
9+
SPOTIFY_CLIENT_SECRET=0d22cb1aaa364608a7e3d68abec68701
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.2.20 on 2025-05-24 13:13
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api', '0002_alter_note_options_note_edited_note_parent_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='userprofile',
15+
name='avatar',
16+
field=models.URLField(default='https://api.dicebear.com/7.x/bottts/svg'),
17+
),
18+
]
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from django.db import migrations
2+
3+
def update_avatars(apps, schema_editor):
4+
UserProfile = apps.get_model('api', 'UserProfile')
5+
for profile in UserProfile.objects.all():
6+
profile.avatar = f'https://api.dicebear.com/7.x/bottts/svg?seed={profile.user.username}'
7+
profile.save()
8+
9+
class Migration(migrations.Migration):
10+
dependencies = [
11+
('api', '0003_alter_userprofile_avatar'),
12+
]
13+
14+
operations = [
15+
migrations.RunPython(update_avatars),
16+
]

backend/api/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# Create your models here.
66
class UserProfile(models.Model):
77
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
8-
avatar = models.URLField(default='https://api.dicebear.com/7.x/avataaars/svg') # Default avatar URL
8+
avatar = models.URLField(default='https://api.dicebear.com/7.x/bottts/svg') # Default avatar URL
99
bio = models.TextField(max_length=500, blank=True)
1010

1111
def __str__(self):
@@ -14,7 +14,7 @@ def __str__(self):
1414
def save(self, *args, **kwargs):
1515
if not self.avatar:
1616
# Generate unique avatar URL using username
17-
self.avatar = f'https://api.dicebear.com/7.x/avataaars/svg?seed={self.user.username}'
17+
self.avatar = f'https://api.dicebear.com/7.x/bottts/svg?seed={self.user.username}'
1818
super().save(*args, **kwargs)
1919

2020
class Note(models.Model):

backend/api/serializers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def get_author_avatar(self, obj):
5050
try:
5151
return obj.author.profile.avatar
5252
except UserProfile.DoesNotExist:
53-
return 'https://api.dicebear.com/7.x/avataaars/svg'
53+
return f'https://api.dicebear.com/7.x/bottts/svg?seed={obj.author.username}'
5454

5555
class NoteReplySerializer(serializers.ModelSerializer):
5656
author_username = serializers.CharField(source='author.username', read_only=True)
@@ -66,4 +66,4 @@ def get_author_avatar(self, obj):
6666
try:
6767
return obj.author.profile.avatar
6868
except UserProfile.DoesNotExist:
69-
return 'https://api.dicebear.com/7.x/avataaars/svg'
69+
return f'https://api.dicebear.com/7.x/bottts/svg?seed={obj.author.username}'

backend/api/urls.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@
77
path("notes/delete/<int:pk>/", views.NoteDelete.as_view(), name="note-delete"),
88
path("users/create/", views.CreateUserView.as_view(), name="create-user"),
99
path("profile/", views.UserProfileView.as_view(), name="user-profile"),
10-
path("search-movie/", views.SearchOMDbView.as_view(), name="search-movies")
10+
path("search-movie/", views.SearchOMDbView.as_view(), name="search-movies"),
11+
path("spotify/token/", views.SpotifyTokenView.as_view(), name="spotify-token"),
12+
path("spotify/search/", views.SpotifySearchView.as_view(), name="spotify-search"),
1113
]

backend/api/views.py

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from rest_framework.response import Response
77
import requests
88
from .models import Note, UserProfile
9+
import os
910

1011
class NoteListCreate(generics.ListCreateAPIView):
1112
serializer_class = NoteSerializer
@@ -60,4 +61,122 @@ def get(self, request):
6061
api_key = '1e75925c' # Consider moving this to environment variables
6162

6263
response = requests.get(f'http://www.omdbapi.com/?t={query}&apikey={api_key}')
63-
return Response(response.json())
64+
return Response(response.json())
65+
66+
class SpotifyTokenView(APIView):
67+
permission_classes = [permissions.IsAuthenticated]
68+
69+
def get(self, request):
70+
try:
71+
# Get Spotify credentials from environment variables
72+
client_id = os.getenv('SPOTIFY_CLIENT_ID')
73+
client_secret = os.getenv('SPOTIFY_CLIENT_SECRET')
74+
75+
print("Spotify credentials check:", {
76+
'client_id_exists': bool(client_id),
77+
'client_secret_exists': bool(client_secret)
78+
})
79+
80+
if not client_id or not client_secret:
81+
return Response(
82+
{'error': 'Spotify credentials not configured'},
83+
status=status.HTTP_503_SERVICE_UNAVAILABLE
84+
)
85+
86+
# Get access token from Spotify
87+
auth_response = requests.post(
88+
'https://accounts.spotify.com/api/token',
89+
data={
90+
'grant_type': 'client_credentials'
91+
},
92+
auth=(client_id, client_secret)
93+
)
94+
95+
print("Spotify auth response:", {
96+
'status_code': auth_response.status_code,
97+
'content': auth_response.text
98+
})
99+
100+
if auth_response.status_code != 200:
101+
error_details = auth_response.json() if auth_response.text else 'No error details available'
102+
return Response(
103+
{
104+
'error': 'Failed to authenticate with Spotify',
105+
'details': error_details
106+
},
107+
status=status.HTTP_503_SERVICE_UNAVAILABLE
108+
)
109+
110+
return Response(auth_response.json())
111+
except Exception as e:
112+
print("Spotify token error:", str(e))
113+
return Response(
114+
{'error': str(e)},
115+
status=status.HTTP_500_INTERNAL_SERVER_ERROR
116+
)
117+
118+
class SpotifySearchView(APIView):
119+
permission_classes = [permissions.IsAuthenticated]
120+
121+
def get(self, request):
122+
try:
123+
query = request.GET.get('q', '')
124+
if not query:
125+
return Response(
126+
{'error': 'Search query is required'},
127+
status=status.HTTP_400_BAD_REQUEST
128+
)
129+
130+
# Get Spotify credentials and token
131+
client_id = os.getenv('SPOTIFY_CLIENT_ID')
132+
client_secret = os.getenv('SPOTIFY_CLIENT_SECRET')
133+
134+
if not client_id or not client_secret:
135+
return Response(
136+
{'error': 'Spotify credentials not configured'},
137+
status=status.HTTP_503_SERVICE_UNAVAILABLE
138+
)
139+
140+
# Get access token
141+
auth_response = requests.post(
142+
'https://accounts.spotify.com/api/token',
143+
data={
144+
'grant_type': 'client_credentials'
145+
},
146+
auth=(client_id, client_secret)
147+
)
148+
149+
if auth_response.status_code != 200:
150+
return Response(
151+
{'error': 'Failed to authenticate with Spotify'},
152+
status=status.HTTP_503_SERVICE_UNAVAILABLE
153+
)
154+
155+
access_token = auth_response.json()['access_token']
156+
157+
# Search Spotify
158+
search_response = requests.get(
159+
'https://api.spotify.com/v1/search',
160+
params={
161+
'q': query,
162+
'type': 'playlist',
163+
'limit': 5
164+
},
165+
headers={
166+
'Authorization': f'Bearer {access_token}'
167+
}
168+
)
169+
170+
if search_response.status_code != 200:
171+
return Response(
172+
{'error': 'Failed to search Spotify'},
173+
status=status.HTTP_503_SERVICE_UNAVAILABLE
174+
)
175+
176+
return Response(search_response.json())
177+
except Exception as e:
178+
print("Spotify search error:", str(e))
179+
return Response(
180+
{'error': str(e)},
181+
status=status.HTTP_500_INTERNAL_SERVER_ERROR
182+
)

backend/backend/settings.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,21 +109,21 @@
109109
# }
110110

111111
# local db - uncomment when running locally
112-
DATABASES = {
113-
"default": {
114-
"ENGINE": "django.db.backends.sqlite3",
115-
"NAME": "mydatabase",
116-
}
117-
}
118-
119-
# prod - db -- uncomment when pushing code
120112
# DATABASES = {
121-
# 'default': dj_database_url.config(
122-
# default=os.environ.get("DB_URL"),
123-
# conn_max_age=600
124-
# )
113+
# "default": {
114+
# "ENGINE": "django.db.backends.sqlite3",
115+
# "NAME": "mydatabase",
116+
# }
125117
# }
126118

119+
# prod - db -- uncomment when pushing code
120+
DATABASES = {
121+
'default': dj_database_url.config(
122+
default=os.environ.get("DB_URL"),
123+
conn_max_age=600
124+
)
125+
}
126+
127127
# Password validation
128128
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
129129

backend/mydatabase

8 KB
Binary file not shown.

frontend/src/components/Note.jsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,17 @@ function Note({ note, onDelete, onEdit, onReply, currentUser }) {
128128
/>
129129
<span className="reply-author">{reply.author_username}</span>
130130
</div>
131-
<span className="reply-time">{reply.time_ago}</span>
131+
<div className="reply-actions">
132+
{currentUser && currentUser.username === reply.author_username && (
133+
<button
134+
className="action-button delete-button small"
135+
onClick={() => onDelete(reply.id)}
136+
>
137+
Delete
138+
</button>
139+
)}
140+
<span className="reply-time">{reply.time_ago}</span>
141+
</div>
132142
</div>
133143
<p className="reply-content">{reply.content}</p>
134144
{reply.edited && <span className="edited-tag small">(edited)</span>}

0 commit comments

Comments
 (0)