CineDev commited on
Commit
ea84df0
·
verified ·
1 Parent(s): b9af093

Upload folder using huggingface_hub

Browse files
.env.example ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ SECRET_KEY=change-this-secret
2
+ DEBUG=True
3
+ PORT=8000
4
+ FRONTEND_URL=http://localhost:3000
5
+ ALLOWED_HOSTS=localhost,127.0.0.1
6
+
7
+ # Supabase Postgres connection string.
8
+ # Example: postgresql://postgres:<password>@db.<project-ref>.supabase.co:5432/postgres
9
+ DATABASE_URL=
10
+ SUPABASE_URL=
11
+ SUPABASE_ANON_KEY=
12
+
13
+ # Optional, only needed for Google sign-in.
14
+ GOOGLE_CLIENT_ID=
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ /node_modules
2
+ /.env
Dockerfile ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Set environment variables
4
+ ENV PYTHONDONTWRITEBYTECODE=1
5
+ ENV PYTHONUNBUFFERED=1
6
+ ENV PORT=7860
7
+
8
+ # Install system dependencies
9
+ RUN apt-get update && apt-get install -y \
10
+ build-essential \
11
+ libpq-dev \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ WORKDIR /app
15
+
16
+ # Copy requirements and install
17
+ COPY requirements.txt /app/
18
+ RUN pip install --no-cache-dir -r requirements.txt
19
+
20
+ # Copy the rest of the application code
21
+ COPY . /app/
22
+
23
+ # Hugging Face Spaces runs as user 1000 (non-root)
24
+ RUN chown -R 1000:1000 /app
25
+
26
+ # Expose the port Hugging Face Spaces expects
27
+ EXPOSE 7860
28
+
29
+ # Switch to the non-root user
30
+ USER 1000
31
+
32
+ # Run migrations and start Daphne ASGI server
33
+ CMD python manage.py migrate --noinput && daphne -b 0.0.0.0 -p 7860 flux_backend.asgi:application
apps/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
apps/accounts/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
apps/accounts/apps.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class AccountsConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "apps.accounts"
apps/accounts/auth_urls.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from django.urls import path
2
+
3
+ from . import views
4
+
5
+
6
+ urlpatterns = [
7
+ path("google", views.google_login),
8
+ path("supabase", views.supabase_login),
9
+ ]
apps/accounts/migrations/0001_initial.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.db import migrations, models
2
+ import django.db.models.deletion
3
+
4
+
5
+ class Migration(migrations.Migration):
6
+ initial = True
7
+
8
+ dependencies = []
9
+
10
+ operations = [
11
+ migrations.CreateModel(
12
+ name="UserAccount",
13
+ fields=[
14
+ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
15
+ ("name", models.CharField(max_length=255)),
16
+ ("username", models.CharField(max_length=255, unique=True)),
17
+ ("password", models.CharField(max_length=255)),
18
+ ("token", models.CharField(blank=True, db_index=True, max_length=80, null=True)),
19
+ ("google_id", models.CharField(blank=True, max_length=255, null=True, unique=True)),
20
+ ("profile_picture", models.URLField(blank=True, null=True)),
21
+ ("created_at", models.DateTimeField(auto_now_add=True)),
22
+ ("updated_at", models.DateTimeField(auto_now=True)),
23
+ ],
24
+ ),
25
+ migrations.CreateModel(
26
+ name="Meeting",
27
+ fields=[
28
+ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
29
+ ("meeting_code", models.CharField(max_length=255)),
30
+ ("date", models.DateTimeField(auto_now_add=True)),
31
+ ("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="meetings", to="accounts.useraccount")),
32
+ ],
33
+ options={
34
+ "ordering": ["-date"],
35
+ },
36
+ ),
37
+ ]
apps/accounts/migrations/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
apps/accounts/models.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.db import models
2
+
3
+
4
+ class UserAccount(models.Model):
5
+ name = models.CharField(max_length=255)
6
+ username = models.CharField(max_length=255, unique=True)
7
+ password = models.CharField(max_length=255)
8
+ token = models.CharField(max_length=80, blank=True, null=True, db_index=True)
9
+ google_id = models.CharField(max_length=255, unique=True, blank=True, null=True)
10
+ profile_picture = models.URLField(blank=True, null=True)
11
+ created_at = models.DateTimeField(auto_now_add=True)
12
+ updated_at = models.DateTimeField(auto_now=True)
13
+
14
+ def __str__(self):
15
+ return self.username
16
+
17
+
18
+ class Meeting(models.Model):
19
+ user = models.ForeignKey(UserAccount, on_delete=models.CASCADE, related_name="meetings")
20
+ meeting_code = models.CharField(max_length=255)
21
+ date = models.DateTimeField(auto_now_add=True)
22
+
23
+ class Meta:
24
+ ordering = ["-date"]
25
+
26
+ def __str__(self):
27
+ return f"{self.user.username} - {self.meeting_code}"
apps/accounts/serializers.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from rest_framework import serializers
2
+
3
+ from .models import Meeting
4
+
5
+
6
+ class MeetingSerializer(serializers.ModelSerializer):
7
+ user_id = serializers.CharField(source="user.username", read_only=True)
8
+ meetingCode = serializers.CharField(source="meeting_code", read_only=True)
9
+
10
+ class Meta:
11
+ model = Meeting
12
+ fields = ["id", "user_id", "meetingCode", "date"]
apps/accounts/urls.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.urls import path
2
+
3
+ from . import views
4
+
5
+
6
+ urlpatterns = [
7
+ path("login", views.login),
8
+ path("register", views.register),
9
+ path("add_to_activity", views.add_to_history),
10
+ path("get_all_activity", views.get_user_history),
11
+ path("delete_meeting/<str:meeting_code>", views.delete_meeting),
12
+ path("delete_all_meetings", views.delete_all_meetings),
13
+ ]
apps/accounts/views.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import secrets
2
+
3
+ import requests
4
+ from django.conf import settings
5
+ from django.contrib.auth.hashers import check_password, make_password
6
+ from google.auth.transport import requests as google_requests
7
+ from google.oauth2 import id_token
8
+ from rest_framework import status
9
+ from rest_framework.decorators import api_view
10
+ from rest_framework.response import Response
11
+
12
+ from .models import Meeting, UserAccount
13
+ from .serializers import MeetingSerializer
14
+
15
+
16
+ def _public_user(user):
17
+ return {
18
+ "name": user.name,
19
+ "username": user.username,
20
+ "profilePicture": user.profile_picture,
21
+ }
22
+
23
+
24
+ def _issue_token(user):
25
+ user.token = secrets.token_hex(20)
26
+ user.save(update_fields=["token", "updated_at"])
27
+ return user.token
28
+
29
+
30
+ def _get_user_by_token(token):
31
+ if not token:
32
+ return None
33
+ return UserAccount.objects.filter(token=token).first()
34
+
35
+
36
+ @api_view(["POST"])
37
+ def register(request):
38
+ name = request.data.get("name")
39
+ username = request.data.get("username")
40
+ password = request.data.get("password")
41
+
42
+ if not name or not username or not password:
43
+ return Response({"message": "Please Provide"}, status=status.HTTP_400_BAD_REQUEST)
44
+
45
+ if UserAccount.objects.filter(username=username).exists():
46
+ return Response({"message": "User already exists"}, status=status.HTTP_302_FOUND)
47
+
48
+ UserAccount.objects.create(
49
+ name=name,
50
+ username=username,
51
+ password=make_password(password),
52
+ )
53
+
54
+ return Response({"message": "User Registered"}, status=status.HTTP_201_CREATED)
55
+
56
+
57
+ @api_view(["POST"])
58
+ def login(request):
59
+ username = request.data.get("username")
60
+ password = request.data.get("password")
61
+
62
+ if not username or not password:
63
+ return Response({"message": "Please Provide"}, status=status.HTTP_400_BAD_REQUEST)
64
+
65
+ user = UserAccount.objects.filter(username=username).first()
66
+ if not user:
67
+ return Response({"message": "User Not Found"}, status=status.HTTP_404_NOT_FOUND)
68
+
69
+ if not check_password(password, user.password):
70
+ return Response({"message": "Invalid Username or password"}, status=status.HTTP_401_UNAUTHORIZED)
71
+
72
+ token = _issue_token(user)
73
+ return Response({"token": token, "user": _public_user(user)}, status=status.HTTP_200_OK)
74
+
75
+
76
+ @api_view(["GET"])
77
+ def get_user_history(request):
78
+ user = _get_user_by_token(request.query_params.get("token"))
79
+ if not user:
80
+ return Response({"message": "User not found"}, status=status.HTTP_404_NOT_FOUND)
81
+
82
+ return Response(MeetingSerializer(user.meetings.all(), many=True).data, status=status.HTTP_200_OK)
83
+
84
+
85
+ @api_view(["POST"])
86
+ def add_to_history(request):
87
+ user = _get_user_by_token(request.data.get("token"))
88
+ if not user:
89
+ return Response({"message": "User not found"}, status=status.HTTP_404_NOT_FOUND)
90
+
91
+ meeting_code = request.data.get("meeting_code")
92
+ if not meeting_code:
93
+ return Response({"message": "meeting_code is required"}, status=status.HTTP_400_BAD_REQUEST)
94
+
95
+ Meeting.objects.create(user=user, meeting_code=meeting_code)
96
+ return Response({"message": "Added code to history"}, status=status.HTTP_201_CREATED)
97
+
98
+
99
+ @api_view(["DELETE"])
100
+ def delete_meeting(request, meeting_code):
101
+ user = _get_user_by_token(request.query_params.get("token"))
102
+ if not user:
103
+ return Response({"message": "User not found"}, status=status.HTTP_404_NOT_FOUND)
104
+
105
+ deleted_count, _ = Meeting.objects.filter(user=user, meeting_code=meeting_code).delete()
106
+ if deleted_count == 0:
107
+ return Response({"message": "Meeting not found"}, status=status.HTTP_404_NOT_FOUND)
108
+
109
+ return Response({"message": "Meeting deleted successfully"}, status=status.HTTP_200_OK)
110
+
111
+
112
+ @api_view(["DELETE"])
113
+ def delete_all_meetings(request):
114
+ user = _get_user_by_token(request.query_params.get("token"))
115
+ if not user:
116
+ return Response({"message": "User not found"}, status=status.HTTP_404_NOT_FOUND)
117
+
118
+ Meeting.objects.filter(user=user).delete()
119
+ return Response({"message": "All meetings deleted successfully"}, status=status.HTTP_200_OK)
120
+
121
+
122
+ @api_view(["POST"])
123
+ def google_login(request):
124
+ credential = request.data.get("token")
125
+ if not credential:
126
+ return Response({"message": "Token is required"}, status=status.HTTP_400_BAD_REQUEST)
127
+
128
+ if not settings.GOOGLE_CLIENT_ID:
129
+ return Response({"message": "Google login is not configured"}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
130
+
131
+ try:
132
+ payload = id_token.verify_oauth2_token(
133
+ credential,
134
+ google_requests.Request(),
135
+ settings.GOOGLE_CLIENT_ID,
136
+ )
137
+ except ValueError:
138
+ return Response({"message": "Invalid Google token. Please try again."}, status=status.HTTP_401_UNAUTHORIZED)
139
+
140
+ email = payload.get("email")
141
+ google_id = payload.get("sub")
142
+ name = payload.get("name") or email
143
+ picture = payload.get("picture")
144
+
145
+ user = UserAccount.objects.filter(username=email).first()
146
+ if not user:
147
+ user = UserAccount.objects.create(
148
+ name=name,
149
+ username=email,
150
+ password=make_password(secrets.token_hex(16)),
151
+ google_id=google_id,
152
+ profile_picture=picture,
153
+ )
154
+ else:
155
+ user.google_id = user.google_id or google_id
156
+ user.profile_picture = picture or user.profile_picture
157
+ user.name = user.name or name
158
+ user.save(update_fields=["google_id", "profile_picture", "name", "updated_at"])
159
+
160
+ token = _issue_token(user)
161
+ return Response({"token": token, "user": _public_user(user)}, status=status.HTTP_200_OK)
162
+
163
+
164
+ @api_view(["POST"])
165
+ def supabase_login(request):
166
+ access_token = request.data.get("access_token")
167
+ if not access_token:
168
+ return Response({"message": "access_token is required"}, status=status.HTTP_400_BAD_REQUEST)
169
+
170
+ if not settings.SUPABASE_URL or not settings.SUPABASE_ANON_KEY:
171
+ return Response({"message": "Supabase auth is not configured"}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
172
+
173
+ try:
174
+ supabase_response = requests.get(
175
+ f"{settings.SUPABASE_URL.rstrip('/')}/auth/v1/user",
176
+ headers={
177
+ "apikey": settings.SUPABASE_ANON_KEY,
178
+ "Authorization": f"Bearer {access_token}",
179
+ },
180
+ timeout=10,
181
+ )
182
+ except requests.RequestException:
183
+ return Response({"message": "Could not verify Supabase session"}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
184
+
185
+ if supabase_response.status_code != 200:
186
+ return Response({"message": "Invalid Supabase session"}, status=status.HTTP_401_UNAUTHORIZED)
187
+
188
+ supabase_user = supabase_response.json()
189
+ email = supabase_user.get("email")
190
+ metadata = supabase_user.get("user_metadata") or {}
191
+
192
+ if not email:
193
+ return Response({"message": "Supabase user email is missing"}, status=status.HTTP_400_BAD_REQUEST)
194
+
195
+ name = metadata.get("full_name") or metadata.get("name") or email
196
+ picture = metadata.get("avatar_url") or metadata.get("picture")
197
+ provider_id = supabase_user.get("id")
198
+
199
+ user = UserAccount.objects.filter(username=email).first()
200
+ if not user:
201
+ user = UserAccount.objects.create(
202
+ name=name,
203
+ username=email,
204
+ password=make_password(secrets.token_hex(16)),
205
+ google_id=provider_id,
206
+ profile_picture=picture,
207
+ )
208
+ else:
209
+ user.name = name or user.name
210
+ user.google_id = user.google_id or provider_id
211
+ user.profile_picture = picture or user.profile_picture
212
+ user.save(update_fields=["name", "google_id", "profile_picture", "updated_at"])
213
+
214
+ token = _issue_token(user)
215
+ return Response({"token": token, "user": _public_user(user)}, status=status.HTTP_200_OK)
apps/calls/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
apps/calls/apps.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class CallsConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "apps.calls"
apps/calls/consumers.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uuid
2
+
3
+ from channels.generic.websocket import AsyncJsonWebsocketConsumer
4
+
5
+
6
+ rooms = {}
7
+
8
+
9
+ class CallConsumer(AsyncJsonWebsocketConsumer):
10
+ async def connect(self):
11
+ self.room_code = self.scope["url_route"]["kwargs"]["room_code"]
12
+ self.room_group_name = f"call_{self.room_code}"
13
+ self.client_id = uuid.uuid4().hex
14
+ self.username = "Guest"
15
+
16
+ await self.channel_layer.group_add(self.room_group_name, self.channel_name)
17
+ await self.accept()
18
+ await self.send_json({"event": "connect", "socketId": self.client_id})
19
+
20
+ async def disconnect(self, close_code):
21
+ room = rooms.get(self.room_code, {})
22
+ was_joined = self.client_id in room
23
+
24
+ if was_joined:
25
+ room.pop(self.client_id, None)
26
+ if not room:
27
+ rooms.pop(self.room_code, None)
28
+
29
+ await self.channel_layer.group_send(
30
+ self.room_group_name,
31
+ {
32
+ "type": "room_event",
33
+ "sender_channel_name": self.channel_name,
34
+ "payload": {
35
+ "event": "user-left",
36
+ "socketId": self.client_id,
37
+ },
38
+ },
39
+ )
40
+
41
+ await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
42
+
43
+ async def receive_json(self, content):
44
+ event = content.get("event")
45
+
46
+ if event == "username":
47
+ self.username = content.get("username") or "Guest"
48
+ room = rooms.get(self.room_code, {})
49
+ if self.client_id in room:
50
+ room[self.client_id]["username"] = self.username
51
+ await self._broadcast({
52
+ "event": "username",
53
+ "socketId": self.client_id,
54
+ "username": self.username,
55
+ })
56
+ return
57
+
58
+ if event == "join-call":
59
+ room = rooms.setdefault(self.room_code, {})
60
+ existing_users = [
61
+ {"socketId": socket_id, "username": participant["username"]}
62
+ for socket_id, participant in room.items()
63
+ if socket_id != self.client_id
64
+ ]
65
+
66
+ room[self.client_id] = {
67
+ "channel_name": self.channel_name,
68
+ "username": self.username,
69
+ }
70
+
71
+ await self.send_json({"event": "room-users", "users": existing_users})
72
+ await self._broadcast({
73
+ "event": "user-joined",
74
+ "user": {
75
+ "socketId": self.client_id,
76
+ "username": self.username,
77
+ },
78
+ })
79
+ return
80
+
81
+ if event == "signal":
82
+ await self._send_to_peer(
83
+ content.get("toId"),
84
+ {
85
+ "event": "signal",
86
+ "fromSocketId": self.client_id,
87
+ "message": content.get("message"),
88
+ },
89
+ )
90
+ return
91
+
92
+ if event == "chat-message":
93
+ await self._broadcast(
94
+ {
95
+ "event": "chat-message",
96
+ "data": content.get("data", ""),
97
+ "sender": content.get("sender") or self.username,
98
+ "socketIdSender": self.client_id,
99
+ },
100
+ include_sender=False,
101
+ )
102
+ return
103
+
104
+ if event in ("screen-share-started", "screen-share-stopped"):
105
+ await self._broadcast(
106
+ {
107
+ "event": event,
108
+ "sharerSocketId": content.get("sharerSocketId") or self.client_id,
109
+ },
110
+ include_sender=False,
111
+ )
112
+
113
+ async def _send_to_peer(self, socket_id, payload):
114
+ participant = rooms.get(self.room_code, {}).get(socket_id)
115
+ if not participant:
116
+ return
117
+
118
+ await self.channel_layer.send(
119
+ participant["channel_name"],
120
+ {
121
+ "type": "direct_event",
122
+ "payload": payload,
123
+ },
124
+ )
125
+
126
+ async def _broadcast(self, payload, include_sender=True):
127
+ await self.channel_layer.group_send(
128
+ self.room_group_name,
129
+ {
130
+ "type": "room_event",
131
+ "sender_channel_name": None if include_sender else self.channel_name,
132
+ "payload": payload,
133
+ },
134
+ )
135
+
136
+ async def room_event(self, event):
137
+ if event.get("sender_channel_name") == self.channel_name:
138
+ return
139
+ await self.send_json(event["payload"])
140
+
141
+ async def direct_event(self, event):
142
+ await self.send_json(event["payload"])
apps/calls/routing.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from django.urls import re_path
2
+
3
+ from .consumers import CallConsumer
4
+
5
+
6
+ websocket_urlpatterns = [
7
+ re_path(r"ws/call/(?P<room_code>[^/]+)/$", CallConsumer.as_asgi()),
8
+ ]
flux_backend/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
flux_backend/asgi.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ from channels.routing import ProtocolTypeRouter, URLRouter
4
+ from django.core.asgi import get_asgi_application
5
+
6
+
7
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "flux_backend.settings")
8
+
9
+ from apps.calls.routing import websocket_urlpatterns
10
+
11
+ application = ProtocolTypeRouter({
12
+ "http": get_asgi_application(),
13
+ "websocket": URLRouter(websocket_urlpatterns),
14
+ })
flux_backend/settings.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pathlib import Path
3
+ from urllib.parse import unquote, urlparse
4
+
5
+ from dotenv import load_dotenv
6
+
7
+
8
+ BASE_DIR = Path(__file__).resolve().parent.parent
9
+ load_dotenv(BASE_DIR / ".env")
10
+
11
+ SECRET_KEY = os.environ.get("SECRET_KEY", "django-insecure-flux-local-key")
12
+ DEBUG = os.environ.get("DEBUG", "True").lower() in ("1", "true", "yes", "on")
13
+ ALLOWED_HOSTS = [
14
+ host.strip()
15
+ for host in os.environ.get("ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
16
+ if host.strip()
17
+ ]
18
+
19
+ if DEBUG and "*" not in ALLOWED_HOSTS:
20
+ ALLOWED_HOSTS.append("*")
21
+
22
+ INSTALLED_APPS = [
23
+ "daphne",
24
+ "django.contrib.auth",
25
+ "django.contrib.contenttypes",
26
+ "django.contrib.sessions",
27
+ "django.contrib.staticfiles",
28
+ "corsheaders",
29
+ "rest_framework",
30
+ "channels",
31
+ "apps.accounts",
32
+ "apps.calls",
33
+ ]
34
+
35
+ MIDDLEWARE = [
36
+ "corsheaders.middleware.CorsMiddleware",
37
+ "django.middleware.security.SecurityMiddleware",
38
+ "django.contrib.sessions.middleware.SessionMiddleware",
39
+ "django.middleware.common.CommonMiddleware",
40
+ ]
41
+
42
+ ROOT_URLCONF = "flux_backend.urls"
43
+ ASGI_APPLICATION = "flux_backend.asgi.application"
44
+ WSGI_APPLICATION = "flux_backend.wsgi.application"
45
+
46
+ DATABASE_URL = os.environ.get("DATABASE_URL", "").strip()
47
+ if DATABASE_URL:
48
+ parsed_db = urlparse(DATABASE_URL)
49
+ DATABASES = {
50
+ "default": {
51
+ "ENGINE": "django.db.backends.postgresql",
52
+ "NAME": parsed_db.path.lstrip("/"),
53
+ "USER": unquote(parsed_db.username or ""),
54
+ "PASSWORD": unquote(parsed_db.password or ""),
55
+ "HOST": parsed_db.hostname,
56
+ "PORT": parsed_db.port or 5432,
57
+ "OPTIONS": {"sslmode": "require"},
58
+ }
59
+ }
60
+ else:
61
+ DATABASES = {
62
+ "default": {
63
+ "ENGINE": "django.db.backends.sqlite3",
64
+ "NAME": BASE_DIR / "db.sqlite3",
65
+ }
66
+ }
67
+
68
+ REST_FRAMEWORK = {
69
+ "DEFAULT_PERMISSION_CLASSES": [
70
+ "rest_framework.permissions.AllowAny",
71
+ ],
72
+ }
73
+
74
+ FRONTEND_URL = os.environ.get("FRONTEND_URL", "http://localhost:3000")
75
+ CORS_ALLOW_ALL_ORIGINS = os.environ.get("CORS_ALLOW_ALL_ORIGINS", "True").lower() in ("1", "true", "yes", "on")
76
+ CORS_ALLOW_CREDENTIALS = True
77
+ CORS_ALLOWED_ORIGINS = [
78
+ origin.strip()
79
+ for origin in os.environ.get("CORS_ALLOWED_ORIGINS", FRONTEND_URL).split(",")
80
+ if origin.strip()
81
+ ]
82
+
83
+ CHANNEL_LAYERS = {
84
+ "default": {
85
+ "BACKEND": "channels.layers.InMemoryChannelLayer",
86
+ },
87
+ }
88
+
89
+ LANGUAGE_CODE = "en-us"
90
+ TIME_ZONE = "UTC"
91
+ USE_I18N = True
92
+ USE_TZ = True
93
+ STATIC_URL = "static/"
94
+ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
95
+
96
+ GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID", "")
97
+ SUPABASE_URL = os.environ.get("SUPABASE_URL", "")
98
+ SUPABASE_ANON_KEY = os.environ.get("SUPABASE_ANON_KEY", "")
flux_backend/urls.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.http import JsonResponse
2
+ from django.urls import include, path
3
+
4
+
5
+ def health(_request):
6
+ return JsonResponse({"status": "ok", "message": "Server is running"})
7
+
8
+
9
+ urlpatterns = [
10
+ path("health", health),
11
+ path("api/v1/users/", include("apps.accounts.urls")),
12
+ path("api/v1/auth/", include("apps.accounts.auth_urls")),
13
+ ]
flux_backend/wsgi.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ from django.core.wsgi import get_wsgi_application
4
+
5
+
6
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "flux_backend.settings")
7
+
8
+ application = get_wsgi_application()
manage.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ import os
3
+ import sys
4
+
5
+
6
+ def main():
7
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "flux_backend.settings")
8
+ from django.core.management import execute_from_command_line
9
+
10
+ execute_from_command_line(sys.argv)
11
+
12
+
13
+ if __name__ == "__main__":
14
+ main()
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ Django>=5.0,<6.0
2
+ djangorestframework>=3.15.0
3
+ django-cors-headers>=4.3.0
4
+ channels>=4.0.0
5
+ daphne>=4.0.0
6
+ psycopg2-binary>=2.9.0
7
+ python-dotenv>=1.0.0
8
+ google-auth>=2.29.0
9
+ requests>=2.31.0