Add new files and models
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- Dockerfile +12 -0
- app/__init__.py +1 -0
- app/__pycache__/__init__.cpython-312.pyc +0 -0
- app/__pycache__/main.cpython-312.pyc +0 -0
- app/crud/__init__.py +0 -0
- app/crud/__pycache__/__init__.cpython-312.pyc +0 -0
- app/crud/__pycache__/song.cpython-312.pyc +0 -0
- app/crud/__pycache__/user.cpython-312.pyc +0 -0
- app/crud/user.py +25 -0
- app/db/__init__.py +1 -0
- app/db/__pycache__/__init__.cpython-312.pyc +0 -0
- app/db/__pycache__/supabase_service.cpython-312.pyc +0 -0
- app/db/supabase_service.py +13 -0
- app/main.py +26 -0
- app/models/__init__.py +1 -0
- app/models/__pycache__/__init__.cpython-312.pyc +0 -0
- app/models/__pycache__/concert.cpython-312.pyc +0 -0
- app/models/__pycache__/enums.cpython-312.pyc +0 -0
- app/models/__pycache__/group.cpython-312.pyc +0 -0
- app/models/__pycache__/music.cpython-312.pyc +0 -0
- app/models/__pycache__/song.cpython-312.pyc +0 -0
- app/models/__pycache__/user.cpython-312.pyc +0 -0
- app/models/concert.py +15 -0
- app/models/enums.py +13 -0
- app/models/group.py +16 -0
- app/models/music.py +30 -0
- app/models/user.py +28 -0
- app/requirements.txt +0 -0
- app/routers/__init__.py +1 -0
- app/routers/__pycache__/__init__.cpython-312.pyc +0 -0
- app/routers/__pycache__/album.cpython-312.pyc +0 -0
- app/routers/__pycache__/authentication.cpython-312.pyc +0 -0
- app/routers/__pycache__/concert.cpython-312.pyc +0 -0
- app/routers/__pycache__/group_admin.cpython-312.pyc +0 -0
- app/routers/__pycache__/playlist.cpython-312.pyc +0 -0
- app/routers/__pycache__/song.cpython-312.pyc +0 -0
- app/routers/__pycache__/users.cpython-312.pyc +0 -0
- app/routers/album.py +106 -0
- app/routers/authentication.py +114 -0
- app/routers/concert.py +69 -0
- app/routers/group_admin.py +107 -0
- app/routers/playlist.py +104 -0
- app/routers/song.py +129 -0
- app/routers/users.py +134 -0
- app/tests/__init__.py +1 -0
- app/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- app/tests/__pycache__/test_auth.cpython-312-pytest-7.4.3.pyc +0 -0
- app/tests/test_auth.py +2 -0
- app/utils/__init__.py +0 -0
- app/utils/__pycache__/__init__.cpython-312.pyc +0 -0
Dockerfile
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.11.7-bookworm
|
2 |
+
|
3 |
+
WORKDIR /app
|
4 |
+
|
5 |
+
COPY ./app/ .
|
6 |
+
|
7 |
+
RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
|
8 |
+
|
9 |
+
ENV SUPABASE_URL='https://lakewjtjnwmihctodrow.supabase.co'
|
10 |
+
ENV SUPABASE_SECRET_KEY='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imxha2V3anRqbndtaWhjdG9kcm93Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2OTg0MTgwMTMsImV4cCI6MjAxMzk5NDAxM30.6UI7vAzZXZFoSXPaJ2v8LQYpHmzAAuBVykWXV8lMLEA'
|
11 |
+
EXPOSE 5000
|
12 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
app/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# This file is intentionally left blank
|
app/__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (140 Bytes). View file
|
|
app/__pycache__/main.cpython-312.pyc
ADDED
Binary file (1.41 kB). View file
|
|
app/crud/__init__.py
ADDED
File without changes
|
app/crud/__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (145 Bytes). View file
|
|
app/crud/__pycache__/song.cpython-312.pyc
ADDED
Binary file (1.05 kB). View file
|
|
app/crud/__pycache__/user.cpython-312.pyc
ADDED
Binary file (1.13 kB). View file
|
|
app/crud/user.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from supabase import Client
|
2 |
+
from utils.auth import get_user_response
|
3 |
+
from models.user import User
|
4 |
+
|
5 |
+
|
6 |
+
async def update_user(
|
7 |
+
supabase: Client, email: str = None, password: str = None, data: dict = None
|
8 |
+
):
|
9 |
+
info = {}
|
10 |
+
if email:
|
11 |
+
info["email"] = email
|
12 |
+
if password:
|
13 |
+
info["password"] = password
|
14 |
+
if data:
|
15 |
+
info["data"] = data
|
16 |
+
supabase.auth.update_user(info)
|
17 |
+
return True
|
18 |
+
|
19 |
+
|
20 |
+
async def get_user(supabase: Client, token: str):
|
21 |
+
user_response = get_user_response(token, supabase)
|
22 |
+
if user_response:
|
23 |
+
return User(email=user_response.user.email, **user_response.user.user_metadata)
|
24 |
+
else:
|
25 |
+
return None
|
app/db/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# This file is intentionally left blank
|
app/db/__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (143 Bytes). View file
|
|
app/db/__pycache__/supabase_service.cpython-312.pyc
ADDED
Binary file (598 Bytes). View file
|
|
app/db/supabase_service.py
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from supabase import create_client
|
2 |
+
from dotenv import load_dotenv
|
3 |
+
import os
|
4 |
+
|
5 |
+
load_dotenv()
|
6 |
+
|
7 |
+
SUPABASE_URL = os.getenv("SUPABASE_URL")
|
8 |
+
SUPABASE_SECRET_KEY = os.getenv("SUPABASE_SECRET_KEY")
|
9 |
+
SUPABASE = create_client(SUPABASE_URL, SUPABASE_SECRET_KEY)
|
10 |
+
|
11 |
+
|
12 |
+
def get_supabase():
|
13 |
+
return SUPABASE
|
app/main.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi.responses import RedirectResponse
|
2 |
+
from fastapi import FastAPI
|
3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
4 |
+
from routers import authentication, users, group_admin, song, album, concert, playlist
|
5 |
+
|
6 |
+
app = FastAPI()
|
7 |
+
|
8 |
+
app.include_router(authentication.router)
|
9 |
+
app.include_router(users.router)
|
10 |
+
app.include_router(group_admin.router)
|
11 |
+
app.include_router(song.router)
|
12 |
+
app.include_router(album.router)
|
13 |
+
app.include_router(concert.router)
|
14 |
+
app.include_router(playlist.router)
|
15 |
+
app.add_middleware(
|
16 |
+
CORSMiddleware,
|
17 |
+
allow_origins=["*"],
|
18 |
+
allow_credentials=True,
|
19 |
+
allow_methods=["*"],
|
20 |
+
allow_headers=["*"],
|
21 |
+
)
|
22 |
+
|
23 |
+
|
24 |
+
@app.get("/")
|
25 |
+
def read_root():
|
26 |
+
return RedirectResponse(url="/docs")
|
app/models/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# This file is intentionally left blank
|
app/models/__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (147 Bytes). View file
|
|
app/models/__pycache__/concert.cpython-312.pyc
ADDED
Binary file (814 Bytes). View file
|
|
app/models/__pycache__/enums.cpython-312.pyc
ADDED
Binary file (641 Bytes). View file
|
|
app/models/__pycache__/group.cpython-312.pyc
ADDED
Binary file (937 Bytes). View file
|
|
app/models/__pycache__/music.cpython-312.pyc
ADDED
Binary file (1.35 kB). View file
|
|
app/models/__pycache__/song.cpython-312.pyc
ADDED
Binary file (583 Bytes). View file
|
|
app/models/__pycache__/user.cpython-312.pyc
ADDED
Binary file (1.62 kB). View file
|
|
app/models/concert.py
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel
|
2 |
+
|
3 |
+
|
4 |
+
class ConcertInfo(BaseModel):
|
5 |
+
name: str
|
6 |
+
location: str
|
7 |
+
description: str | None = None
|
8 |
+
start_at: str
|
9 |
+
number_of_tickets: int = 0
|
10 |
+
|
11 |
+
|
12 |
+
class Concert(ConcertInfo):
|
13 |
+
id: int
|
14 |
+
created_at: str
|
15 |
+
group_id: int
|
app/models/enums.py
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from enum import Enum
|
2 |
+
|
3 |
+
|
4 |
+
class Role(str, Enum):
|
5 |
+
user = "user"
|
6 |
+
admin = "admin"
|
7 |
+
group_admin = "group_admin"
|
8 |
+
|
9 |
+
|
10 |
+
class Gender(Enum):
|
11 |
+
male = 0
|
12 |
+
female = 1
|
13 |
+
other = 2
|
app/models/group.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel
|
2 |
+
|
3 |
+
|
4 |
+
class GroupBase(BaseModel):
|
5 |
+
id: int
|
6 |
+
created_at: str
|
7 |
+
|
8 |
+
|
9 |
+
class GroupInfo(BaseModel):
|
10 |
+
name: str
|
11 |
+
description: str | None = None
|
12 |
+
|
13 |
+
|
14 |
+
class Group(GroupBase, GroupInfo):
|
15 |
+
admin_id: str
|
16 |
+
members_id: list[str] | None = None
|
app/models/music.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel
|
2 |
+
|
3 |
+
|
4 |
+
class Song(BaseModel):
|
5 |
+
id: int
|
6 |
+
name: str
|
7 |
+
created_at: str
|
8 |
+
user_id: str
|
9 |
+
name_in_storage: str
|
10 |
+
description: str | None
|
11 |
+
lyric: str | None
|
12 |
+
|
13 |
+
|
14 |
+
class BaseAlbum(BaseModel):
|
15 |
+
name: str
|
16 |
+
description: str | None
|
17 |
+
|
18 |
+
|
19 |
+
class Album(BaseAlbum):
|
20 |
+
id: int
|
21 |
+
created_at: str
|
22 |
+
user_id: str
|
23 |
+
|
24 |
+
class BasePlaylist(BaseModel):
|
25 |
+
name: str
|
26 |
+
|
27 |
+
class Playlist(BasePlaylist):
|
28 |
+
id: int
|
29 |
+
created_at: str
|
30 |
+
user_id: str
|
app/models/user.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Optional
|
2 |
+
from pydantic import BaseModel, Field
|
3 |
+
from datetime import date
|
4 |
+
from .enums import Gender, Role
|
5 |
+
|
6 |
+
|
7 |
+
class UserBase(BaseModel):
|
8 |
+
email: str = Field(description="Email of user", example="[email protected]")
|
9 |
+
|
10 |
+
|
11 |
+
class UserLogin(UserBase):
|
12 |
+
password: str
|
13 |
+
|
14 |
+
|
15 |
+
class UserInfo(BaseModel):
|
16 |
+
name: str
|
17 |
+
birthdate: date | None = Field(example="2000-01-01")
|
18 |
+
phone_number: str | None = Field(example="0382929292")
|
19 |
+
gender: Gender
|
20 |
+
role: Role = Role.user
|
21 |
+
|
22 |
+
|
23 |
+
class UserSignup(UserLogin, UserInfo):
|
24 |
+
pass
|
25 |
+
|
26 |
+
|
27 |
+
class User(UserBase, UserInfo):
|
28 |
+
pass
|
app/requirements.txt
ADDED
Binary file (1.5 kB). View file
|
|
app/routers/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# This file is intentionally left blank
|
app/routers/__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (148 Bytes). View file
|
|
app/routers/__pycache__/album.cpython-312.pyc
ADDED
Binary file (4.96 kB). View file
|
|
app/routers/__pycache__/authentication.cpython-312.pyc
ADDED
Binary file (4.85 kB). View file
|
|
app/routers/__pycache__/concert.cpython-312.pyc
ADDED
Binary file (3.35 kB). View file
|
|
app/routers/__pycache__/group_admin.cpython-312.pyc
ADDED
Binary file (4.87 kB). View file
|
|
app/routers/__pycache__/playlist.cpython-312.pyc
ADDED
Binary file (4.81 kB). View file
|
|
app/routers/__pycache__/song.cpython-312.pyc
ADDED
Binary file (5.72 kB). View file
|
|
app/routers/__pycache__/users.cpython-312.pyc
ADDED
Binary file (6.08 kB). View file
|
|
app/routers/album.py
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import (
|
2 |
+
APIRouter,
|
3 |
+
Depends,
|
4 |
+
HTTPException,
|
5 |
+
status,
|
6 |
+
Security,
|
7 |
+
)
|
8 |
+
from models.music import Album, BaseAlbum
|
9 |
+
from db.supabase_service import get_supabase
|
10 |
+
from typing import Annotated, List, Tuple
|
11 |
+
from supabase import Client
|
12 |
+
from utils.auth import get_id
|
13 |
+
from fastapi.encoders import jsonable_encoder
|
14 |
+
from utils.exceptions import BAD_REQUEST
|
15 |
+
|
16 |
+
router = APIRouter(tags=["Album"], prefix="/album")
|
17 |
+
|
18 |
+
|
19 |
+
@router.post("", description="Create album")
|
20 |
+
async def create_album(
|
21 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
22 |
+
user_id: Annotated[str, Security(get_id)],
|
23 |
+
album: BaseAlbum,
|
24 |
+
):
|
25 |
+
try:
|
26 |
+
supabase.table("albums").insert(
|
27 |
+
{"name": album.name, "description": album.description, "user_id": user_id}
|
28 |
+
).execute()
|
29 |
+
return {"detail": "Album created"}
|
30 |
+
except:
|
31 |
+
raise BAD_REQUEST
|
32 |
+
|
33 |
+
|
34 |
+
# delete album
|
35 |
+
@router.delete("", description="Delete album")
|
36 |
+
async def delete_album(
|
37 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
38 |
+
user_id: Annotated[str, Security(get_id)],
|
39 |
+
album_id: int,
|
40 |
+
):
|
41 |
+
try:
|
42 |
+
supabase.table("albums").delete().eq("id", album_id).execute()
|
43 |
+
return {"detail": "Album deleted"}
|
44 |
+
except:
|
45 |
+
raise BAD_REQUEST
|
46 |
+
|
47 |
+
|
48 |
+
# get list of all album
|
49 |
+
@router.get("/all", description="Get list of all album", response_model=List[Album])
|
50 |
+
async def get_all_album(
|
51 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
52 |
+
user_id: Annotated[str, Security(get_id)],
|
53 |
+
) -> List[Album]:
|
54 |
+
try:
|
55 |
+
res = supabase.table("albums").select("*").execute().dict()["data"]
|
56 |
+
return res
|
57 |
+
except:
|
58 |
+
raise BAD_REQUEST
|
59 |
+
|
60 |
+
|
61 |
+
# modify album
|
62 |
+
@router.patch("", description="Modify album")
|
63 |
+
async def modify_album(
|
64 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
65 |
+
user_id: Annotated[str, Security(get_id)],
|
66 |
+
album: Album,
|
67 |
+
songs: List[int],
|
68 |
+
):
|
69 |
+
try:
|
70 |
+
if album.user_id != user_id:
|
71 |
+
raise HTTPException(
|
72 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
73 |
+
detail="You are not the owner of this album",
|
74 |
+
)
|
75 |
+
supabase.table("albums").update(
|
76 |
+
jsonable_encoder(album, exclude={"id", "user_id", "created_at"})
|
77 |
+
).eq("id", album.id).execute()
|
78 |
+
supabase.table("songs").update({"album_id": None}).eq(
|
79 |
+
"album_id", album.id
|
80 |
+
).execute()
|
81 |
+
supabase.table("songs").update({"album_id": album.id}).in_(
|
82 |
+
"id", songs
|
83 |
+
).execute()
|
84 |
+
return {"detail": "Album modified"}
|
85 |
+
except:
|
86 |
+
raise BAD_REQUEST
|
87 |
+
|
88 |
+
|
89 |
+
# get album and its songs
|
90 |
+
@router.get("", description="Get album and its songs")
|
91 |
+
async def get_album(
|
92 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
93 |
+
user_id: Annotated[str, Security(get_id)],
|
94 |
+
album_id: int,
|
95 |
+
):
|
96 |
+
try:
|
97 |
+
res = (
|
98 |
+
supabase.table("albums")
|
99 |
+
.select("*, songs(*)")
|
100 |
+
.eq("id", album_id)
|
101 |
+
.execute()
|
102 |
+
.dict()["data"][0]
|
103 |
+
)
|
104 |
+
return res
|
105 |
+
except:
|
106 |
+
raise BAD_REQUEST
|
app/routers/authentication.py
ADDED
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Depends, HTTPException, status, Security, Body, Query
|
2 |
+
from fastapi.security import OAuth2PasswordRequestForm
|
3 |
+
from models.user import UserSignup
|
4 |
+
from db.supabase_service import get_supabase
|
5 |
+
from typing import Annotated
|
6 |
+
from supabase import Client
|
7 |
+
from utils.auth import get_id
|
8 |
+
from fastapi.encoders import jsonable_encoder
|
9 |
+
from models.enums import Role
|
10 |
+
from gotrue.errors import AuthApiError
|
11 |
+
from crud.user import update_user
|
12 |
+
from utils.exceptions import BAD_REQUEST, CONFLICT, NOT_FOUND
|
13 |
+
|
14 |
+
router = APIRouter(tags=["Authentication"])
|
15 |
+
|
16 |
+
|
17 |
+
@router.post("/login", description="Login user using email and password")
|
18 |
+
async def login(
|
19 |
+
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
|
20 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
21 |
+
):
|
22 |
+
email = form_data.username
|
23 |
+
password = form_data.password
|
24 |
+
try:
|
25 |
+
user = supabase.auth.sign_in_with_password(
|
26 |
+
{"email": email, "password": password}
|
27 |
+
)
|
28 |
+
return {
|
29 |
+
"access_token": user.session.access_token,
|
30 |
+
"token_type": "bearer",
|
31 |
+
}
|
32 |
+
except:
|
33 |
+
raise HTTPException(
|
34 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
35 |
+
detail="Incorrect email or password",
|
36 |
+
)
|
37 |
+
|
38 |
+
|
39 |
+
@router.post(
|
40 |
+
"/signup", description="Sign up new user", status_code=status.HTTP_201_CREATED
|
41 |
+
)
|
42 |
+
async def signup(
|
43 |
+
user: Annotated[UserSignup, Body()],
|
44 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
45 |
+
):
|
46 |
+
email = user.email
|
47 |
+
password = user.password
|
48 |
+
if len(password) < 6:
|
49 |
+
raise HTTPException(
|
50 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
51 |
+
detail="Password should be at least 6 characters",
|
52 |
+
)
|
53 |
+
try:
|
54 |
+
user.role = Role.user
|
55 |
+
res = supabase.auth.sign_up(
|
56 |
+
{
|
57 |
+
"email": email,
|
58 |
+
"password": password,
|
59 |
+
"options": {
|
60 |
+
"data": jsonable_encoder(user, exclude=("password", "email"))
|
61 |
+
},
|
62 |
+
}
|
63 |
+
)
|
64 |
+
return {"detail": "User created"}
|
65 |
+
except AuthApiError:
|
66 |
+
raise CONFLICT
|
67 |
+
except:
|
68 |
+
raise BAD_REQUEST
|
69 |
+
|
70 |
+
|
71 |
+
@router.post("/reset_password", description="Send reset password email")
|
72 |
+
async def reset_password(
|
73 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
74 |
+
email: str,
|
75 |
+
):
|
76 |
+
try:
|
77 |
+
if (
|
78 |
+
len(supabase.rpc("get_user_id_by_email", {"email": email}).execute().data)
|
79 |
+
> 0
|
80 |
+
):
|
81 |
+
supabase.auth.reset_password_email(email)
|
82 |
+
return {"detail": "Reset password email sent"}
|
83 |
+
else:
|
84 |
+
raise NOT_FOUND
|
85 |
+
except:
|
86 |
+
raise NOT_FOUND
|
87 |
+
|
88 |
+
|
89 |
+
@router.post("/reset_password_confirm", description="Reset password")
|
90 |
+
async def reset_password_confirm(
|
91 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
92 |
+
email: Annotated[str, Query(description="Email", title="Email")],
|
93 |
+
new_password: Annotated[
|
94 |
+
str, Query(min_length=6, description="New password", title="New password")
|
95 |
+
],
|
96 |
+
token: Annotated[
|
97 |
+
str, Query(description="Reset password token", title="Reset password token")
|
98 |
+
],
|
99 |
+
):
|
100 |
+
try:
|
101 |
+
supabase.auth.verify_otp(
|
102 |
+
{
|
103 |
+
"email": email,
|
104 |
+
"token": token,
|
105 |
+
"type": "email",
|
106 |
+
}
|
107 |
+
)
|
108 |
+
await update_user(supabase, password=new_password)
|
109 |
+
return {"detail": "Your password has been reset"}
|
110 |
+
except:
|
111 |
+
raise HTTPException(
|
112 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
113 |
+
detail="Incorrect token",
|
114 |
+
)
|
app/routers/concert.py
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Depends, Security
|
2 |
+
from typing import Annotated
|
3 |
+
from fastapi.encoders import jsonable_encoder
|
4 |
+
from supabase import Client
|
5 |
+
from db.supabase_service import get_supabase
|
6 |
+
from typing import Annotated
|
7 |
+
from supabase import Client
|
8 |
+
from utils.auth import get_id
|
9 |
+
from utils.exceptions import BAD_REQUEST
|
10 |
+
from models.concert import ConcertInfo
|
11 |
+
|
12 |
+
router = APIRouter(tags=["Concert"], prefix="/concert")
|
13 |
+
|
14 |
+
|
15 |
+
# create concert
|
16 |
+
@router.post("", description="Create concert")
|
17 |
+
async def create_concert(
|
18 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
19 |
+
user_id: Annotated[str, Security(get_id)],
|
20 |
+
concert_info: ConcertInfo,
|
21 |
+
):
|
22 |
+
try:
|
23 |
+
supabase.table("concerts").insert(jsonable_encoder(concert_info)).execute()
|
24 |
+
return {"detail": "Concert created"}
|
25 |
+
except:
|
26 |
+
raise BAD_REQUEST
|
27 |
+
|
28 |
+
|
29 |
+
# get list of all concert
|
30 |
+
@router.get("/all", description="Get list of all concert")
|
31 |
+
async def get_all_concert(
|
32 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
33 |
+
user_id: Annotated[str, Security(get_id)],
|
34 |
+
):
|
35 |
+
try:
|
36 |
+
res = supabase.table("concerts").select("*").execute().dict()["data"]
|
37 |
+
return res
|
38 |
+
except:
|
39 |
+
raise BAD_REQUEST
|
40 |
+
|
41 |
+
|
42 |
+
# modify concert
|
43 |
+
@router.put("", description="Modify concert")
|
44 |
+
async def modify_concert(
|
45 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
46 |
+
user_id: Annotated[str, Security(get_id)],
|
47 |
+
concert_info: ConcertInfo,
|
48 |
+
):
|
49 |
+
try:
|
50 |
+
supabase.table("concerts").update(jsonable_encoder(concert_info)).eq(
|
51 |
+
"id", concert_info.id
|
52 |
+
).execute()
|
53 |
+
return {"detail": "Concert modified"}
|
54 |
+
except:
|
55 |
+
raise BAD_REQUEST
|
56 |
+
|
57 |
+
|
58 |
+
# delete concert
|
59 |
+
@router.delete("", description="Delete concert")
|
60 |
+
async def delete_concert(
|
61 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
62 |
+
user_id: Annotated[str, Security(get_id)],
|
63 |
+
concert_id: int,
|
64 |
+
):
|
65 |
+
try:
|
66 |
+
supabase.table("concerts").delete().eq("id", concert_id).execute()
|
67 |
+
return {"detail": "Concert deleted"}
|
68 |
+
except:
|
69 |
+
raise BAD_REQUEST
|
app/routers/group_admin.py
ADDED
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Depends, HTTPException, status, Security
|
2 |
+
from db.supabase_service import get_supabase
|
3 |
+
from typing import Annotated
|
4 |
+
from supabase import Client
|
5 |
+
from utils.auth import get_role, get_id, oauth2_scheme
|
6 |
+
from models.enums import Role
|
7 |
+
from models.group import GroupInfo
|
8 |
+
from utils.exceptions import UNAUTHORIZED
|
9 |
+
from typing import List
|
10 |
+
|
11 |
+
router = APIRouter(tags=["Group"], prefix="/group")
|
12 |
+
|
13 |
+
|
14 |
+
@router.post("", description="Create group")
|
15 |
+
async def create_group(
|
16 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
17 |
+
role: Annotated[str, Security(get_role)],
|
18 |
+
id: Annotated[str, Security(get_id)],
|
19 |
+
group_info: GroupInfo,
|
20 |
+
):
|
21 |
+
if role != Role.group_admin:
|
22 |
+
raise UNAUTHORIZED
|
23 |
+
supabase.table("groups").insert(
|
24 |
+
{
|
25 |
+
"name": group_info.name,
|
26 |
+
"description": group_info.description,
|
27 |
+
"admin_id": id,
|
28 |
+
}
|
29 |
+
).execute()
|
30 |
+
return {"detail": "Group created"}
|
31 |
+
|
32 |
+
|
33 |
+
@router.post("/user", description="Modify group")
|
34 |
+
async def add_user(
|
35 |
+
token: Annotated[str, Depends(oauth2_scheme)],
|
36 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
37 |
+
role: Annotated[str, Security(get_role)],
|
38 |
+
id: Annotated[str, Security(get_id)],
|
39 |
+
email: str,
|
40 |
+
group_id: int,
|
41 |
+
):
|
42 |
+
if role != Role.group_admin:
|
43 |
+
raise UNAUTHORIZED
|
44 |
+
user_id = supabase.rpc("get_user_id_by_email", {"email": email}).execute().data
|
45 |
+
if len(user_id) == 0:
|
46 |
+
raise HTTPException(
|
47 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
48 |
+
detail="User does not exist",
|
49 |
+
)
|
50 |
+
user_id = user_id[0]["id"]
|
51 |
+
members_id: List = (
|
52 |
+
supabase.table("groups")
|
53 |
+
.select("members_id")
|
54 |
+
.match({"admin_id": id, "id": group_id})
|
55 |
+
.execute()
|
56 |
+
.data[0]["members_id"]
|
57 |
+
)
|
58 |
+
if not members_id:
|
59 |
+
members_id = []
|
60 |
+
if user_id in members_id:
|
61 |
+
raise HTTPException(
|
62 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
63 |
+
detail="User already in group",
|
64 |
+
)
|
65 |
+
members_id.append(user_id)
|
66 |
+
supabase.table("groups").update({"members_id": members_id}).match(
|
67 |
+
{"admin_id": id, "id": group_id}
|
68 |
+
).execute()
|
69 |
+
return {"detail": "User added to group"}
|
70 |
+
|
71 |
+
|
72 |
+
@router.delete("/user", description="Delete user from group")
|
73 |
+
async def delete_user(
|
74 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
75 |
+
role: Annotated[str, Security(get_role)],
|
76 |
+
id: Annotated[str, Security(get_id)],
|
77 |
+
group_id: int,
|
78 |
+
email: str,
|
79 |
+
):
|
80 |
+
if role != Role.group_admin:
|
81 |
+
raise UNAUTHORIZED
|
82 |
+
user_id = supabase.rpc("get_user_id_by_email", {"email": email}).execute().data
|
83 |
+
if len(user_id) == 0:
|
84 |
+
raise HTTPException(
|
85 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
86 |
+
detail="User does not exist",
|
87 |
+
)
|
88 |
+
user_id = user_id[0]["id"]
|
89 |
+
members_id: List = (
|
90 |
+
supabase.table("groups")
|
91 |
+
.select("members_id")
|
92 |
+
.match({"admin_id": id, "id": group_id})
|
93 |
+
.execute()
|
94 |
+
.data[0]["members_id"]
|
95 |
+
)
|
96 |
+
if not members_id:
|
97 |
+
members_id = []
|
98 |
+
if user_id not in members_id:
|
99 |
+
raise HTTPException(
|
100 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
101 |
+
detail="User not in group",
|
102 |
+
)
|
103 |
+
members_id.remove(user_id)
|
104 |
+
supabase.table("groups").update({"members_id": members_id}).match(
|
105 |
+
{"admin_id": id, "id": group_id}
|
106 |
+
).execute()
|
107 |
+
return {"detail": "User deleted from group"}
|
app/routers/playlist.py
ADDED
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import (
|
2 |
+
APIRouter,
|
3 |
+
Depends,
|
4 |
+
HTTPException,
|
5 |
+
status,
|
6 |
+
Security,
|
7 |
+
)
|
8 |
+
from models.music import Playlist, BasePlaylist
|
9 |
+
from db.supabase_service import get_supabase
|
10 |
+
from typing import Annotated, List
|
11 |
+
from supabase import Client
|
12 |
+
from utils.auth import get_id
|
13 |
+
from fastapi.encoders import jsonable_encoder
|
14 |
+
from utils.exceptions import BAD_REQUEST
|
15 |
+
|
16 |
+
router = APIRouter(tags=["Playlist"], prefix="/playlist")
|
17 |
+
|
18 |
+
|
19 |
+
@router.post("", description="Create playlist")
|
20 |
+
async def create_playlist(
|
21 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
22 |
+
user_id: Annotated[str, Security(get_id)],
|
23 |
+
playlist: BasePlaylist,
|
24 |
+
):
|
25 |
+
try:
|
26 |
+
supabase.table("playlist").insert(
|
27 |
+
{"name": playlist.name, "song_id" : [], "user_id": user_id}
|
28 |
+
).execute()
|
29 |
+
return {"detail": "Playlist created"}
|
30 |
+
except:
|
31 |
+
raise BAD_REQUEST
|
32 |
+
|
33 |
+
|
34 |
+
@router.delete("", description="Delete playlist")
|
35 |
+
async def delete_playlist(
|
36 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
37 |
+
user_id: Annotated[str, Security(get_id)],
|
38 |
+
playlist_id: int,
|
39 |
+
):
|
40 |
+
try:
|
41 |
+
supabase.table("playlist").delete().eq("id", playlist_id).execute()
|
42 |
+
return {"detail": "Album deleted"}
|
43 |
+
except:
|
44 |
+
raise BAD_REQUEST
|
45 |
+
|
46 |
+
# get list of all your playlist
|
47 |
+
@router.get("/all", description="Get list of your playlist", response_model=List[Playlist])
|
48 |
+
async def get_all_album(
|
49 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
50 |
+
user_id: Annotated[str, Security(get_id)],
|
51 |
+
) -> List[Playlist]:
|
52 |
+
try:
|
53 |
+
|
54 |
+
res = supabase.table("playlist").select("*").match({"user_id" : user_id}).execute().data
|
55 |
+
return res
|
56 |
+
except:
|
57 |
+
raise BAD_REQUEST
|
58 |
+
|
59 |
+
# modify playlist
|
60 |
+
@router.patch("", description="Modify playlist")
|
61 |
+
async def modify_playlist(
|
62 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
63 |
+
user_id: Annotated[str, Security(get_id)],
|
64 |
+
playlist: Playlist,
|
65 |
+
songs: List[int],
|
66 |
+
):
|
67 |
+
|
68 |
+
if playlist.user_id != user_id:
|
69 |
+
raise HTTPException(
|
70 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
71 |
+
detail="You are not the owner of this playlist",
|
72 |
+
)
|
73 |
+
try:
|
74 |
+
supabase.table("playlist").update(
|
75 |
+
jsonable_encoder(playlist, exclude={"id", "user_id", "created_at"})
|
76 |
+
).eq("id", playlist.id).execute()
|
77 |
+
|
78 |
+
supabase.table("playlist").update({ "song_id": songs }).eq("id", playlist.id).execute()
|
79 |
+
|
80 |
+
return {"detail": "Playlist modified"}
|
81 |
+
except:
|
82 |
+
raise BAD_REQUEST
|
83 |
+
|
84 |
+
|
85 |
+
|
86 |
+
|
87 |
+
# get playlist and its songs
|
88 |
+
@router.get("/{playlist_id}", description="Get playlist and its songs")
|
89 |
+
async def get_playlist(
|
90 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
91 |
+
user_id: Annotated[str, Security(get_id)],
|
92 |
+
playlist_id: int,
|
93 |
+
):
|
94 |
+
try:
|
95 |
+
res = (
|
96 |
+
supabase.table("playlist")
|
97 |
+
.select("*")
|
98 |
+
.eq("id", playlist_id)
|
99 |
+
.execute()
|
100 |
+
.dict()["data"]
|
101 |
+
)
|
102 |
+
return res
|
103 |
+
except:
|
104 |
+
raise BAD_REQUEST
|
app/routers/song.py
ADDED
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import (
|
2 |
+
APIRouter,
|
3 |
+
Depends,
|
4 |
+
HTTPException,
|
5 |
+
UploadFile,
|
6 |
+
status,
|
7 |
+
Security,
|
8 |
+
)
|
9 |
+
from models.music import Song
|
10 |
+
from db.supabase_service import get_supabase
|
11 |
+
from typing import Annotated, List, Tuple
|
12 |
+
from supabase import Client
|
13 |
+
from utils.auth import get_id
|
14 |
+
from fastapi.encoders import jsonable_encoder
|
15 |
+
from utils.exceptions import BAD_REQUEST
|
16 |
+
|
17 |
+
router = APIRouter(tags=["Song"], prefix="/song")
|
18 |
+
|
19 |
+
|
20 |
+
# upload mp3 file to supabase storage
|
21 |
+
@router.post("", description="Upload music file")
|
22 |
+
async def upload_music(
|
23 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
24 |
+
id: Annotated[str, Security(get_id)],
|
25 |
+
music: UploadFile,
|
26 |
+
name: str | None = None,
|
27 |
+
):
|
28 |
+
if music.content_type != "audio/mpeg":
|
29 |
+
raise HTTPException(
|
30 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
31 |
+
detail="Only mp3 file is accepted",
|
32 |
+
)
|
33 |
+
try:
|
34 |
+
if not name:
|
35 |
+
name = music.filename
|
36 |
+
f = await music.read()
|
37 |
+
supabase.storage.from_("songs").upload(
|
38 |
+
path=music.filename, file=f, file_options={"content-type": "audio/mpeg"}
|
39 |
+
)
|
40 |
+
supabase.table("songs").insert(
|
41 |
+
{"name": name, "name_in_storage": music.filename, "user_id": id}
|
42 |
+
).execute()
|
43 |
+
return {"detail": "Music uploaded"}
|
44 |
+
except:
|
45 |
+
raise BAD_REQUEST
|
46 |
+
|
47 |
+
|
48 |
+
# get url of music file from supabase storage
|
49 |
+
@router.get("/link", description="Get url of music file")
|
50 |
+
async def get_link_to_music(
|
51 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
52 |
+
id: Annotated[str, Security(get_id)],
|
53 |
+
name_in_storage: str,
|
54 |
+
):
|
55 |
+
try:
|
56 |
+
res = supabase.storage.from_("songs").create_signed_url(
|
57 |
+
path=name_in_storage, expires_in=3600
|
58 |
+
)
|
59 |
+
return res
|
60 |
+
except:
|
61 |
+
raise BAD_REQUEST
|
62 |
+
|
63 |
+
|
64 |
+
# get list of all song from search
|
65 |
+
@router.get(
|
66 |
+
"/search", description="Get list of all music", response_model=List[Song]
|
67 |
+
)
|
68 |
+
async def get_music(
|
69 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
70 |
+
id: Annotated[str, Security(get_id)],
|
71 |
+
query: str | None = None,
|
72 |
+
) -> List[Song]:
|
73 |
+
try:
|
74 |
+
if query:
|
75 |
+
res = (
|
76 |
+
supabase.table("songs")
|
77 |
+
.select("*")
|
78 |
+
.ilike("name", f"%{query}%")
|
79 |
+
.execute()
|
80 |
+
.dict()["data"]
|
81 |
+
)
|
82 |
+
else:
|
83 |
+
res = supabase.table("songs").select("*").execute().dict()["data"]
|
84 |
+
return res
|
85 |
+
except:
|
86 |
+
raise BAD_REQUEST
|
87 |
+
|
88 |
+
|
89 |
+
# delete song
|
90 |
+
@router.delete("", description="Delete music")
|
91 |
+
async def delete_music(
|
92 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
93 |
+
user_id: Annotated[str, Security(get_id)],
|
94 |
+
song: Song,
|
95 |
+
):
|
96 |
+
try:
|
97 |
+
if song.user_id != user_id:
|
98 |
+
raise HTTPException(
|
99 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
100 |
+
detail="You are not the owner of this music",
|
101 |
+
)
|
102 |
+
supabase.storage.from_("songs").remove([song.name_in_storage])
|
103 |
+
supabase.table("songs").delete().eq("id", song.id).execute()
|
104 |
+
return {"detail": "Music deleted"}
|
105 |
+
except:
|
106 |
+
raise
|
107 |
+
|
108 |
+
|
109 |
+
# change music info
|
110 |
+
@router.patch("/info", description="Change music info")
|
111 |
+
async def change_music_info(
|
112 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
113 |
+
user_id: Annotated[str, Security(get_id)],
|
114 |
+
song: Song,
|
115 |
+
):
|
116 |
+
try:
|
117 |
+
if song.user_id != user_id:
|
118 |
+
raise HTTPException(
|
119 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
120 |
+
detail="You are not the owner of this music",
|
121 |
+
)
|
122 |
+
supabase.table("songs").update(
|
123 |
+
jsonable_encoder(
|
124 |
+
song, exclude={"id", "user_id", "created_at", "name_in_storage"}
|
125 |
+
)
|
126 |
+
).eq("id", song.id).execute()
|
127 |
+
return {"detail": "Music info updated"}
|
128 |
+
except:
|
129 |
+
raise BAD_REQUEST
|
app/routers/users.py
ADDED
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Depends, HTTPException, status, Security, Body, Query
|
2 |
+
from db.supabase_service import get_supabase
|
3 |
+
from typing import Annotated
|
4 |
+
from supabase import Client
|
5 |
+
from utils.auth import get_role, get_id, oauth2_scheme
|
6 |
+
from models.enums import Role
|
7 |
+
from models.user import UserInfo
|
8 |
+
from models.group import GroupInfo
|
9 |
+
from fastapi.encoders import jsonable_encoder
|
10 |
+
from crud.user import get_user, update_user
|
11 |
+
from utils.exceptions import BAD_REQUEST
|
12 |
+
|
13 |
+
router = APIRouter(tags=["User"], prefix="/user")
|
14 |
+
|
15 |
+
|
16 |
+
@router.get("/info", description="Get information of logged in user")
|
17 |
+
async def get_user_info(
|
18 |
+
token: Annotated[str, Depends(oauth2_scheme)],
|
19 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
20 |
+
):
|
21 |
+
return await get_user(supabase, token)
|
22 |
+
|
23 |
+
|
24 |
+
@router.post(
|
25 |
+
"/signup_group_admin",
|
26 |
+
description="Sign up to be a group admin",
|
27 |
+
status_code=status.HTTP_201_CREATED,
|
28 |
+
)
|
29 |
+
async def signup_as_group_admin(
|
30 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
31 |
+
role: Annotated[str, Security(get_role)],
|
32 |
+
id: Annotated[str, Security(get_id)],
|
33 |
+
group_info: GroupInfo,
|
34 |
+
):
|
35 |
+
if role != Role.user:
|
36 |
+
raise HTTPException(
|
37 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
38 |
+
detail="You are already a group admin or an admin",
|
39 |
+
)
|
40 |
+
try:
|
41 |
+
supabase.auth.update_user({"data": {"role": Role.group_admin}})
|
42 |
+
supabase.table("groups").insert(
|
43 |
+
{
|
44 |
+
"name": group_info.name,
|
45 |
+
"description": group_info.description,
|
46 |
+
"admin_id": id,
|
47 |
+
}
|
48 |
+
).execute()
|
49 |
+
return {"detail": "You are now a group admin"}
|
50 |
+
except:
|
51 |
+
raise BAD_REQUEST
|
52 |
+
|
53 |
+
|
54 |
+
@router.put("/password", description="Change password of logged in user")
|
55 |
+
async def change_password(
|
56 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
57 |
+
id: Annotated[str, Security(get_id)],
|
58 |
+
new_password: Annotated[
|
59 |
+
str, Query(min_length=6, description="New password", title="New password")
|
60 |
+
],
|
61 |
+
):
|
62 |
+
try:
|
63 |
+
await update_user(supabase, password=new_password)
|
64 |
+
return {"detail": "Password updated"}
|
65 |
+
except:
|
66 |
+
raise
|
67 |
+
|
68 |
+
|
69 |
+
@router.patch("/info", description="Update user info")
|
70 |
+
async def update_user_info(
|
71 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
72 |
+
id: Annotated[str, Security(get_id)],
|
73 |
+
new_user_info: Annotated[UserInfo, Body()],
|
74 |
+
):
|
75 |
+
try:
|
76 |
+
if new_user_info.role:
|
77 |
+
del new_user_info.role
|
78 |
+
await update_user(supabase, data=jsonable_encoder(new_user_info))
|
79 |
+
return {"detail": "User info updated"}
|
80 |
+
except:
|
81 |
+
raise BAD_REQUEST
|
82 |
+
|
83 |
+
|
84 |
+
# buy ticket
|
85 |
+
@router.post("/buy_ticket", description="Buy ticket")
|
86 |
+
async def buy_ticket(
|
87 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
88 |
+
user_id: Annotated[str, Security(get_id)],
|
89 |
+
concert_id: int,
|
90 |
+
):
|
91 |
+
try:
|
92 |
+
number_of_tickets = (
|
93 |
+
supabase.table("concerts")
|
94 |
+
.select("number_of_tickets")
|
95 |
+
.eq("id", concert_id)
|
96 |
+
.execute()
|
97 |
+
.dict()["data"][0]["number_of_tickets"]
|
98 |
+
)
|
99 |
+
if number_of_tickets == 0:
|
100 |
+
raise HTTPException(
|
101 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
102 |
+
detail="There are no tickets left",
|
103 |
+
)
|
104 |
+
return {"detail": "Please navigate to the payment page"}
|
105 |
+
except:
|
106 |
+
raise BAD_REQUEST
|
107 |
+
|
108 |
+
|
109 |
+
# Confirm payment
|
110 |
+
@router.post("/confirm_payment", description="Confirm payment")
|
111 |
+
async def confirm_payment(
|
112 |
+
supabase: Annotated[Client, Depends(get_supabase)],
|
113 |
+
user_id: Annotated[str, Security(get_id)],
|
114 |
+
concert_id: int,
|
115 |
+
):
|
116 |
+
try:
|
117 |
+
number_of_tickets = (
|
118 |
+
supabase.table("concerts")
|
119 |
+
.select("number_of_tickets")
|
120 |
+
.eq("id", concert_id)
|
121 |
+
.execute()
|
122 |
+
.dict()["data"][0]["number_of_tickets"]
|
123 |
+
)
|
124 |
+
if number_of_tickets == 0:
|
125 |
+
raise HTTPException(
|
126 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
127 |
+
detail="There are no tickets left",
|
128 |
+
)
|
129 |
+
supabase.table("concerts").update(
|
130 |
+
{"number_of_tickets": number_of_tickets - 1}
|
131 |
+
).eq("id", concert_id).execute()
|
132 |
+
return {"detail": "Payment successful"}
|
133 |
+
except:
|
134 |
+
raise BAD_REQUEST
|
app/tests/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# This file is intentionally left blank.
|
app/tests/__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (146 Bytes). View file
|
|
app/tests/__pycache__/test_auth.cpython-312-pytest-7.4.3.pyc
ADDED
Binary file (335 Bytes). View file
|
|
app/tests/test_auth.py
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
import pytest
|
2 |
+
from fastapi.testclient import TestClient
|
app/utils/__init__.py
ADDED
File without changes
|
app/utils/__pycache__/__init__.cpython-312.pyc
ADDED
Binary file (146 Bytes). View file
|
|