mirror of
https://github.com/StepanovPlaton/torrent_backend.git
synced 2026-04-03 20:30:38 +04:00
Code refactoring. Add support genres and actors to routes
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
from .games import games_router as games_router
|
||||
from .game_genres import game_genres_router as game_genres_router
|
||||
|
||||
from .movies import movies_router as movies_router
|
||||
from .movie_actors import movie_actors_router as movie_actors_router
|
||||
from .movie_genres import movie_genres_router as movie_genres_router
|
||||
|
||||
from .audiobooks import audiobooks_router as audiobooks_router
|
||||
from .audiobook_genres import audiobook_genres_router as audiobook_genres_router
|
||||
|
||||
from .files import files_router as files_router
|
||||
from .startup import startup_router as startup_router
|
||||
from .auth import auth_router as auth_router
|
||||
|
||||
22
routes/audiobook_genres.py
Normal file
22
routes/audiobook_genres.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
|
||||
from database import *
|
||||
|
||||
from file_handler import *
|
||||
from routes.auth import get_user
|
||||
|
||||
audiobook_genres_router = APIRouter(
|
||||
prefix="/genres/audiobooks", tags=["Audiobooks", "Genres"])
|
||||
|
||||
|
||||
@audiobook_genres_router.get("", response_model=list[AudiobookGenre])
|
||||
async def get_audiobook_genres(db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await AudiobookGenresCRUD.get_all(db_session)
|
||||
|
||||
|
||||
@audiobook_genres_router.post("", response_model=AudiobookGenre)
|
||||
async def add_audiobook_genre(genre: AudiobookGenreCreate,
|
||||
user: User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await AudiobookGenresCRUD.add(db_session, genre)
|
||||
@@ -1,41 +1,41 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
|
||||
import database as db
|
||||
from database import *
|
||||
from file_handler import *
|
||||
from routes.auth import get_user
|
||||
|
||||
audiobooks_router = APIRouter(prefix="/audiobooks", tags=["Audiobooks"])
|
||||
|
||||
|
||||
@audiobooks_router.get("", response_model=list[db.Audiobook])
|
||||
async def get_audiobooks(db_session: AsyncSession = Depends(db.get_session)):
|
||||
return await db.get_audiobooks(db_session)
|
||||
@audiobooks_router.get("/{audiobook_id}", response_model=Audiobook)
|
||||
async def get_audiobook(audiobook_id: int, db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await AudiobooksCRUD.get(db_session, audiobook_id)
|
||||
|
||||
|
||||
@audiobooks_router.post("", response_model=db.Audiobook)
|
||||
async def add_audiobook(audiobook: db.AudiobookCreate,
|
||||
user: db.User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(db.get_session)):
|
||||
return await db.add_audiobook(db_session, audiobook, user.id)
|
||||
@audiobooks_router.get("/cards", response_model=list[AudiobookCard])
|
||||
async def get_audiobooks_cards(db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await AudiobooksCRUD.get_all(db_session)
|
||||
|
||||
|
||||
@audiobooks_router.get("/cards", response_model=list[db.AudiobookCard])
|
||||
async def get_audiobooks_cards(db_session: AsyncSession = Depends(db.get_session)):
|
||||
return await db.get_audiobooks(db_session)
|
||||
@audiobooks_router.get("", response_model=list[Audiobook])
|
||||
async def get_audiobooks(db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await AudiobooksCRUD.get_all(db_session)
|
||||
|
||||
|
||||
@audiobooks_router.get("/{audiobook_id}", response_model=db.Audiobook)
|
||||
async def get_audiobook(audiobook_id: int, db_session: AsyncSession = Depends(db.get_session)):
|
||||
return await db.get_audiobook(db_session, audiobook_id)
|
||||
@audiobooks_router.post("", response_model=Audiobook)
|
||||
async def add_audiobook(audiobook: AudiobookCreate,
|
||||
user: User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await AudiobooksCRUD.add(db_session, audiobook, user.id)
|
||||
|
||||
|
||||
@audiobooks_router.put("/{audiobook_id}", response_model=db.Audiobook)
|
||||
@audiobooks_router.put("/{audiobook_id}", response_model=Audiobook)
|
||||
async def edit_audiobook(audiobook_id: int,
|
||||
audiobook: db.AudiobookCreate,
|
||||
user: db.User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(db.get_session)):
|
||||
audiobook_db = await db.get_audiobook(db_session, audiobook_id)
|
||||
audiobook: AudiobookCreate,
|
||||
user: User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(Database.get_session)):
|
||||
audiobook_db = await AudiobooksCRUD.get(db_session, audiobook_id)
|
||||
if (audiobook_db is None):
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Audiobook with id={audiobook_id} not found")
|
||||
@@ -43,14 +43,14 @@ async def edit_audiobook(audiobook_id: int,
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"Audiobook can only be edited "
|
||||
"by the owner (creator)")
|
||||
return await db.edit_audiobook(db_session, audiobook_id, audiobook)
|
||||
return await AudiobooksCRUD.change(db_session, audiobook_id, audiobook)
|
||||
|
||||
|
||||
@audiobooks_router.delete("/{audiobook_id}", response_model=db.Audiobook)
|
||||
@audiobooks_router.delete("/{audiobook_id}", response_model=Audiobook)
|
||||
async def delete_audiobook(audiobook_id: int,
|
||||
user: db.User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(db.get_session)):
|
||||
audiobook_db = await db.get_audiobook(db_session, audiobook_id)
|
||||
user: User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(Database.get_session)):
|
||||
audiobook_db = await AudiobooksCRUD.get(db_session, audiobook_id)
|
||||
if (audiobook_db is None):
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Audiobook with id={audiobook_id} not found")
|
||||
@@ -58,4 +58,4 @@ async def delete_audiobook(audiobook_id: int,
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"Audiobook can only be deleted "
|
||||
"by the owner (creator)")
|
||||
return await db.delete_audiobook(db_session, audiobook_id)
|
||||
return await AudiobooksCRUD.delete(db_session, audiobook_id)
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
from typing import Annotated, Any
|
||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||
from passlib.context import CryptContext
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from fastapi import APIRouter, Depends, status, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from pydantic import BaseModel
|
||||
from jose import JWTError, jwt
|
||||
from jose import jwt
|
||||
|
||||
import database as db
|
||||
from database import *
|
||||
from env import Env
|
||||
|
||||
SECRET_KEY = Env.get_strict("JWT_SECRET_KEY", str)
|
||||
@@ -36,7 +35,7 @@ def get_hash(password): return crypt.hash(password)
|
||||
|
||||
|
||||
async def get_user(token: str = Depends(oauth2_scheme),
|
||||
db_session: AsyncSession = Depends(db.get_session)):
|
||||
db_session: AsyncSession = Depends(Database.get_session)):
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
@@ -48,13 +47,13 @@ async def get_user(token: str = Depends(oauth2_scheme),
|
||||
except Exception as e:
|
||||
print(e)
|
||||
raise credentials_exception
|
||||
user = await db.get_user(db_session, token_data.username)
|
||||
user = await UsersCRUD.get(db_session, token_data.username)
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
return user
|
||||
|
||||
|
||||
def create_token(user: db.User):
|
||||
def create_token(user: User):
|
||||
access_token_expires = \
|
||||
timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
expire = datetime.now(timezone.utc) + access_token_expires
|
||||
@@ -70,33 +69,33 @@ def create_token(user: db.User):
|
||||
|
||||
@auth_router.post("/registration")
|
||||
async def registration_user(
|
||||
user_data: db.UserCreate,
|
||||
db_session: AsyncSession = Depends(db.get_session)
|
||||
user_data: UserCreate,
|
||||
db_session: AsyncSession = Depends(Database.get_session)
|
||||
) -> Token:
|
||||
if (not await db.check_email(db_session, user_data.email)):
|
||||
if (not await UsersCRUD.check_email(db_session, user_data.email)):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="This email is occupied by another user",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
elif (await db.get_user(db_session, user_data.name) is not None):
|
||||
elif (await UsersCRUD.get(db_session, user_data.name) is not None):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="User with the same name already exists",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
else:
|
||||
user = await db.add_user(db_session, user_data,
|
||||
get_hash(user_data.password))
|
||||
user = await UsersCRUD.add(db_session, user_data,
|
||||
get_hash(user_data.password))
|
||||
return create_token(user)
|
||||
|
||||
|
||||
@auth_router.post("")
|
||||
async def login_user(
|
||||
auth_data: OAuth2PasswordRequestForm = Depends(),
|
||||
db_session: AsyncSession = Depends(db.get_session)
|
||||
db_session: AsyncSession = Depends(Database.get_session)
|
||||
) -> Token:
|
||||
user = await db.get_user(db_session, auth_data.username)
|
||||
user = await UsersCRUD.get(db_session, auth_data.username)
|
||||
if (user is None):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@@ -112,6 +111,6 @@ async def login_user(
|
||||
return create_token(user)
|
||||
|
||||
|
||||
@auth_router.get("/me", response_model=db.UserOpenData)
|
||||
async def read_me(user: db.User = Depends(get_user)):
|
||||
@auth_router.get("/me", response_model=UserOpenData)
|
||||
async def read_me(user: User = Depends(get_user)):
|
||||
return user
|
||||
|
||||
22
routes/game_genres.py
Normal file
22
routes/game_genres.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
|
||||
from database import *
|
||||
|
||||
from file_handler import *
|
||||
from routes.auth import get_user
|
||||
|
||||
game_genres_router = APIRouter(
|
||||
prefix="/genres/games", tags=["Games", "Genres"])
|
||||
|
||||
|
||||
@game_genres_router.get("", response_model=list[GameGenre])
|
||||
async def get_game_genres(db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await GameGenresCRUD.get_all(db_session)
|
||||
|
||||
|
||||
@game_genres_router.post("", response_model=GameGenre)
|
||||
async def add_game_genre(genre: GameGenreCreate,
|
||||
user: User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await GameGenresCRUD.add(db_session, genre)
|
||||
@@ -1,41 +1,42 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
|
||||
import database as db
|
||||
from database import *
|
||||
|
||||
from file_handler import *
|
||||
from routes.auth import get_user
|
||||
|
||||
games_router = APIRouter(prefix="/games", tags=["Games"])
|
||||
|
||||
|
||||
@games_router.get("", response_model=list[db.Game])
|
||||
async def get_games(db_session: AsyncSession = Depends(db.get_session)):
|
||||
return await db.get_games(db_session)
|
||||
@games_router.get("/{game_id}", response_model=Game)
|
||||
async def get_game(game_id: int, db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await GamesCRUD.get(db_session, game_id)
|
||||
|
||||
|
||||
@games_router.post("", response_model=db.Game)
|
||||
async def add_game(game: db.GameCreate,
|
||||
user: db.User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(db.get_session)):
|
||||
return await db.add_game(db_session, game, user.id)
|
||||
@games_router.get("/cards", response_model=list[GameCard])
|
||||
async def get_games_cards(db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await GamesCRUD.get_all(db_session)
|
||||
|
||||
|
||||
@games_router.get("/cards", response_model=list[db.GameCard])
|
||||
async def get_games_cards(db_session: AsyncSession = Depends(db.get_session)):
|
||||
return await db.get_games(db_session)
|
||||
@games_router.get("", response_model=list[Game])
|
||||
async def get_games(db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await GamesCRUD.get_all(db_session)
|
||||
|
||||
|
||||
@games_router.get("/{game_id}", response_model=db.Game)
|
||||
async def get_game(game_id: int, db_session: AsyncSession = Depends(db.get_session)):
|
||||
return await db.get_game(db_session, game_id)
|
||||
@games_router.post("", response_model=Game)
|
||||
async def add_game(game: GameCreate,
|
||||
user: User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await GamesCRUD.add(db_session, game, user.id)
|
||||
|
||||
|
||||
@games_router.put("/{game_id}", response_model=db.Game)
|
||||
@games_router.put("/{game_id}", response_model=Game)
|
||||
async def edit_game(game_id: int,
|
||||
game: db.GameCreate,
|
||||
user: db.User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(db.get_session)):
|
||||
game_db = await db.get_game(db_session, game_id)
|
||||
game: GameCreate,
|
||||
user: User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(Database.get_session)):
|
||||
game_db = await GamesCRUD.get(db_session, game_id)
|
||||
if (game_db is None):
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Game with id={game_id} not found")
|
||||
@@ -43,14 +44,14 @@ async def edit_game(game_id: int,
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"Game can only be edited "
|
||||
"by the owner (creator)")
|
||||
return await db.edit_game(db_session, game_id, game)
|
||||
return await GamesCRUD.change(db_session, game_id, game)
|
||||
|
||||
|
||||
@games_router.delete("/{game_id}", response_model=db.Game)
|
||||
@games_router.delete("/{game_id}", response_model=Game)
|
||||
async def delete_game(game_id: int,
|
||||
user: db.User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(db.get_session)):
|
||||
game_db = await db.get_game(db_session, game_id)
|
||||
user: User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(Database.get_session)):
|
||||
game_db = await GamesCRUD.get(db_session, game_id)
|
||||
if (game_db is None):
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Game with id={game_id} not found")
|
||||
@@ -58,4 +59,4 @@ async def delete_game(game_id: int,
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"Game can only be deleted "
|
||||
"by the owner (creator)")
|
||||
return await db.delete_game(db_session, game_id)
|
||||
return await GamesCRUD.delete(db_session, game_id)
|
||||
|
||||
22
routes/movie_actors.py
Normal file
22
routes/movie_actors.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
|
||||
from database import *
|
||||
|
||||
from file_handler import *
|
||||
from routes.auth import get_user
|
||||
|
||||
movie_actors_router = APIRouter(
|
||||
prefix="/actors", tags=["Movies", "Actors"])
|
||||
|
||||
|
||||
@movie_actors_router.get("", response_model=list[MovieActor])
|
||||
async def get_movie_actors(db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await MovieActorsCRUD.get_all(db_session)
|
||||
|
||||
|
||||
@movie_actors_router.post("", response_model=MovieActor)
|
||||
async def add_movie_actor(actor: MovieActorCreate,
|
||||
user: User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await MovieActorsCRUD.add(db_session, actor)
|
||||
22
routes/movie_genres.py
Normal file
22
routes/movie_genres.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
|
||||
from database import *
|
||||
|
||||
from file_handler import *
|
||||
from routes.auth import get_user
|
||||
|
||||
movie_genres_router = APIRouter(
|
||||
prefix="/genres/movies", tags=["Movies", "Genres"])
|
||||
|
||||
|
||||
@movie_genres_router.get("", response_model=list[MovieGenre])
|
||||
async def get_movie_genres(db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await MovieGenresCRUD.get_all(db_session)
|
||||
|
||||
|
||||
@movie_genres_router.post("", response_model=MovieGenre)
|
||||
async def add_movie_genre(genre: MovieGenreCreate,
|
||||
user: User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await MovieGenresCRUD.add(db_session, genre)
|
||||
@@ -1,41 +1,41 @@
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
|
||||
import database as db
|
||||
from database import *
|
||||
from file_handler import *
|
||||
from routes.auth import get_user
|
||||
|
||||
movies_router = APIRouter(prefix="/movies", tags=["Movies"])
|
||||
|
||||
|
||||
@movies_router.get("", response_model=list[db.Movie])
|
||||
async def get_movies(db_session: AsyncSession = Depends(db.get_session)):
|
||||
return await db.get_movies(db_session)
|
||||
@movies_router.get("/{movie_id}", response_model=Movie)
|
||||
async def get_movie(movie_id: int, db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await MoviesCRUD.get(db_session, movie_id)
|
||||
|
||||
|
||||
@movies_router.post("", response_model=db.Movie)
|
||||
async def add_movie(movie: db.MovieCreate,
|
||||
user: db.User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(db.get_session)):
|
||||
return await db.add_movie(db_session, movie, user.id)
|
||||
@movies_router.get("/cards", response_model=list[MovieCard])
|
||||
async def get_movies_cards(db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await MoviesCRUD.get_all(db_session)
|
||||
|
||||
|
||||
@movies_router.get("/cards", response_model=list[db.MovieCard])
|
||||
async def get_movies_cards(db_session: AsyncSession = Depends(db.get_session)):
|
||||
return await db.get_movies(db_session)
|
||||
@movies_router.get("", response_model=list[Movie])
|
||||
async def get_movies(db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await MoviesCRUD.get_all(db_session)
|
||||
|
||||
|
||||
@movies_router.get("/{movie_id}", response_model=db.Movie)
|
||||
async def get_movie(movie_id: int, db_session: AsyncSession = Depends(db.get_session)):
|
||||
return await db.get_movie(db_session, movie_id)
|
||||
@movies_router.post("", response_model=Movie)
|
||||
async def add_movie(movie: MovieCreate,
|
||||
user: User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(Database.get_session)):
|
||||
return await MoviesCRUD.add(db_session, movie, user.id)
|
||||
|
||||
|
||||
@movies_router.put("/{movie_id}", response_model=db.Movie)
|
||||
@movies_router.put("/{movie_id}", response_model=Movie)
|
||||
async def edit_movie(movie_id: int,
|
||||
movie: db.MovieCreate,
|
||||
user: db.User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(db.get_session)):
|
||||
movie_db = await db.get_movie(db_session, movie_id)
|
||||
movie: MovieCreate,
|
||||
user: User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(Database.get_session)):
|
||||
movie_db = await MoviesCRUD.get(db_session, movie_id)
|
||||
if (movie_db is None):
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Movie with id={movie_id} not found")
|
||||
@@ -43,14 +43,14 @@ async def edit_movie(movie_id: int,
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"Movie can only be edited "
|
||||
"by the owner (creator)")
|
||||
return await db.edit_movie(db_session, movie_id, movie)
|
||||
return await MoviesCRUD.change(db_session, movie_id, movie)
|
||||
|
||||
|
||||
@movies_router.delete("/{movie_id}", response_model=db.Movie)
|
||||
@movies_router.delete("/{movie_id}", response_model=Movie)
|
||||
async def delete_movie(movie_id: int,
|
||||
user: db.User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(db.get_session)):
|
||||
movie_db = await db.get_movie(db_session, movie_id)
|
||||
user: User = Depends(get_user),
|
||||
db_session: AsyncSession = Depends(Database.get_session)):
|
||||
movie_db = await MoviesCRUD.get(db_session, movie_id)
|
||||
if (movie_db is None):
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Movie with id={movie_id} not found")
|
||||
@@ -58,4 +58,4 @@ async def delete_movie(movie_id: int,
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED,
|
||||
detail=f"Movie can only be deleted "
|
||||
"by the owner (creator)")
|
||||
return await db.delete_movie(db_session, movie_id)
|
||||
return await MoviesCRUD.delete(db_session, movie_id)
|
||||
|
||||
Reference in New Issue
Block a user