added scopes

parent 8707193b
......@@ -131,3 +131,4 @@ dmypy.json
data/
.idea
minio/
\ No newline at end of file
......@@ -30,6 +30,23 @@ services:
networks:
- tasktrekernetwork
minio:
image: minio/minio
ports:
- "9000:9000"
- "9090:9090"
env_file:
- .env
command: server --console-address ":9090" /data
volumes:
- ./minio/data:/data
- ./minio/conf:/root/.minio
depends_on:
- db
- core
networks:
- tasktrekernetwork
networks:
tasktrekernetwork:
driver: bridge
This diff is collapsed.
......@@ -18,13 +18,9 @@ python-jose = {extras = ["cryptography"], version = "^3.3.0"}
passlib = {extras = ["bcrypt"], version = "^1.7.4"}
python-multipart = "^0.0.6"
pydantic = {extras = ["email"], version = "^2.5.2"}
miniopy-async = "^1.18"
[tool.aerich]
tortoise_orm = "src.app.db.conf.TORTOISE_ORM"
location = "src/migrations"
src_folder = "./."
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
import io
import fastapi
from miniopy_async import Minio
import settings
class MinioClient(Minio):
def __init__(self, *args, **kwargs):
super().__init__(
*args,
endpoint=settings.minio_url,
access_key=settings.minio_root_user,
secret_key=settings.minio_root_password,
secure=False,
**kwargs,
)
async def upload_from_bytes(self, file: fastapi.UploadFile):
file_data = await file.read()
return await self.put_object(
bucket_name=settings.minio_bucket_name,
object_name=file.filename,
data=io.BytesIO(file_data),
length=len(file_data),
)
......@@ -4,17 +4,17 @@ import fastapi
from .schemas import CategoryBaseSchema
from .ctrl import CategoryController
from ..dependencies import get_user_role
from ..dependencies import get_user_role, get_current_user_scopes
router = fastapi.APIRouter(prefix='/categories', tags=['Category'])
ctrl = CategoryController()
@router.get('')
@router.get("")
async def get_categories(
query: CategoryBaseSchema = fastapi.Depends(),
current_user: str = fastapi.Depends(get_user_role)
current_user: str = fastapi.Depends(get_current_user_scopes)
):
return await ctrl.get_list(**query.model_dump(exclude_none=True))
......
import fastapi as fa
from fastapi.security import SecurityScopes
from fastapi import HTTPException, status
from .user.services import TokenService, oauth2_scheme
from db.repositories import UserRepository
from db.repositories import UserRepository, GroupRepository
from db.models import UserRoleEnum
token_service = TokenService()
user_repo = UserRepository()
group_repo = GroupRepository()
async def get_user_role(token: str = fa.Security(oauth2_scheme)):
......@@ -22,3 +24,29 @@ async def get_user_role(token: str = fa.Security(oauth2_scheme)):
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
async def get_current_user_scopes(security_scopes: SecurityScopes, token: str = fa.Depends(oauth2_scheme)):
if not token:
raise HTTPException(status_code=401, detail="Invalid authentication credentials")
token_data = token_service.decode_token(token)
user = await user_repo.get(id=token_data.sub)
if not user:
raise HTTPException(status_code=401, detail="Invalid authentication credentials")
group_ids = token_data.group
all_scopes = set()
for group_id in group_ids:
group = await group_repo.get(id=group_id)
scopes = await group.scopes.all()
all_scopes.update(scopes)
user_scopes = [scope.name for scope in all_scopes]
if "read_categories" not in user_scopes:
raise HTTPException(status_code=403, detail="Not enough permissions")
return user
from uuid import UUID
from starlette import status
import fastapi
from tortoise.exceptions import ValidationError
from base_ctrl import BaseController
from exceptions import common as common_exc, http as http_exc
from .services import GroupService
class GroupController(BaseController):
group_service = GroupService()
async def get_list(self, **kwargs):
return await self.group_service.get_group_list(**kwargs)
async def get(self, id: UUID):
try:
return await self.group_service.get_group(id)
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
async def create(self, **kwargs):
try:
return await self.group_service.create_group(**kwargs)
except common_exc.CreateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
except ValidationError as e:
raise http_exc.HTTPBadRequestException(detail=f"Validation error: {str(e)}")
async def update(self, id: UUID, **kwargs):
try:
return await self.group_service.update_group(id, **kwargs)
except common_exc.UpdateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
async def delete(self, id: UUID):
try:
await self.group_service.delete_group(id)
return fastapi.responses.Response(status_code=status.HTTP_204_NO_CONTENT)
except common_exc.DeleteException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
from uuid import UUID
import fastapi
from .schemas import GroupBaseSchema, GroupCreateSchema
from .ctrl import GroupController
router = fastapi.APIRouter(prefix='/groups', tags=['Group'])
ctrl = GroupController()
@router.get('')
async def get_groups(
query: GroupBaseSchema = fastapi.Depends(),
):
return await ctrl.get_list(**query.model_dump(exclude_none=True))
@router.get('/{id}')
async def get_group(
id: UUID,
):
return await ctrl.get(id)
@router.post('')
async def create_group(
body: GroupCreateSchema,
):
return await ctrl.create(**body.model_dump(exclude_none=True))
@router.patch('/{id}')
async def update_group(
id: UUID, body: GroupBaseSchema,
):
return await ctrl.update(id, **body.model_dump(exclude_none=True))
@router.delete('/{id}')
async def delete_group(
id: UUID,
):
return await ctrl.delete(id)
from typing import List
from uuid import UUID
from pydantic import BaseModel, ConfigDict
class GroupBaseSchema(BaseModel):
name: str | None = None
class GroupIdSchema(BaseModel):
id: UUID
model_config = ConfigDict(from_attributes=True)
class GroupSchema(GroupBaseSchema, GroupIdSchema):
pass
class GroupCreateSchema(GroupBaseSchema):
name: str
scopes: List[UUID] = []
class GroupGetSchema(GroupBaseSchema):
name: str
scopes: List[UUID]
from uuid import UUID
from db.repositories import GroupRepository
from .schemas import GroupSchema, GroupGetSchema
from db.models import Scopes
class GroupService:
group_repository = GroupRepository()
async def get_group_list(self, **kwargs):
groups = await self.group_repository.get_list(**kwargs)
return [GroupSchema.model_validate(group) for group in groups]
async def get_group(self, id: UUID):
group = await self.group_repository.get(id)
return GroupSchema.model_validate(group)
async def create_group(self, **kwargs):
scopes_uuids = kwargs.get('scopes', [])
group = await self.group_repository.create(
name=kwargs.get('name')
)
scopes = await Scopes.filter(id__in=scopes_uuids)
await group.scopes.add(*scopes)
group = {
'id': group.id,
'name': group.name,
'scopes': await group.scopes.all().values_list('id', flat=True)
}
return GroupGetSchema.model_validate(group)
async def update_group(self, id: UUID, **kwargs):
group = await self.group_repository.update(id, **kwargs)
return GroupSchema.model_validate(group)
async def delete_group(self, id: UUID):
return await self.group_repository.delete(id)
......@@ -5,6 +5,8 @@ from .type.routes import router as type_route
from .task.routes import router as task_route
from .user.routes import router as user_route, auth_router as auth_route
from .category.routes import router as category_route
from .group.routes import router as group_route
from .scopes.routes import router as scope_route
router = APIRouter(prefix='/api')
......@@ -14,3 +16,5 @@ router.include_router(task_route)
router.include_router(user_route)
router.include_router(auth_route)
router.include_router(category_route)
router.include_router(group_route)
router.include_router(scope_route)
from uuid import UUID
from starlette import status
import fastapi
from tortoise.exceptions import ValidationError
from base_ctrl import BaseController
from exceptions import common as common_exc, http as http_exc
from .services import ScopeService
class ScopeController(BaseController):
scope_service = ScopeService()
async def get_list(self, **kwargs):
return await self.scope_service.get_scope_list(**kwargs)
async def get(self, id: UUID):
try:
return await self.scope_service.get_scope(id)
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
async def create(self, **kwargs):
try:
return await self.scope_service.create_scope(**kwargs)
except common_exc.CreateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
except ValidationError as e:
raise http_exc.HTTPBadRequestException(detail=f"Validation error: {str(e)}")
async def update(self, id: UUID, **kwargs):
try:
return await self.scope_service.update_scope(id, **kwargs)
except common_exc.UpdateException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
async def delete(self, id: UUID):
try:
await self.scope_service.delete_scope(id)
return fastapi.responses.Response(status_code=status.HTTP_204_NO_CONTENT)
except common_exc.DeleteException as e:
raise http_exc.HTTPBadRequestException(detail=str(e))
except common_exc.NotFoundException as e:
raise http_exc.HTTPNotFoundException(detail=str(e))
from uuid import UUID
import fastapi
from .schemas import ScopeBaseSchema
from .ctrl import ScopeController
router = fastapi.APIRouter(prefix='/scopes', tags=['Scope'])
ctrl = ScopeController()
@router.get('')
async def get_gscope(
query: ScopeBaseSchema = fastapi.Depends(),
):
return await ctrl.get_list(**query.model_dump(exclude_none=True))
@router.get('/{id}')
async def get_scope(
id: UUID,
):
return await ctrl.get(id)
@router.post('')
async def create_scope(
body: ScopeBaseSchema,
):
return await ctrl.create(**body.model_dump(exclude_none=True))
@router.patch('/{id}')
async def update_scope(
id: UUID, body: ScopeBaseSchema,
):
return await ctrl.update(id, **body.model_dump(exclude_none=True))
@router.delete('/{id}')
async def delete_scope(
id: UUID,
):
return await ctrl.delete(id)
from uuid import UUID
from pydantic import BaseModel, ConfigDict
class ScopeBaseSchema(BaseModel):
name: str | None = None
class ScopeIdSchema(BaseModel):
id: UUID
model_config = ConfigDict(from_attributes=True)
class ScopeSchema(ScopeBaseSchema, ScopeIdSchema):
pass
from uuid import UUID
from db.repositories import ScopeRepository
from .schemas import ScopeSchema
class ScopeService:
scope_repository = ScopeRepository()
async def get_scope_list(self, **kwargs):
groups = await self.scope_repository.get_list(**kwargs)
return [ScopeSchema.model_validate(group) for group in groups]
async def get_scope(self, id: UUID):
group = await self.scope_repository.get(id)
return ScopeSchema.model_validate(group)
async def create_scope(self, **kwargs):
group = await self.scope_repository.create(**kwargs)
return ScopeSchema.model_validate(group)
async def update_scope(self, id: UUID, **kwargs):
group = await self.scope_repository.update(id, **kwargs)
return ScopeSchema.model_validate(group)
async def delete_scope(self, id: UUID):
return await self.scope_repository.delete(id)
from uuid import UUID
import fastapi
from fastapi import Form
from fastapi.security import OAuth2PasswordRequestForm
from .schemas import UserCreateSchema, UserUpdateSchema, UserGetSchema
from .schemas import UserUpdateSchema, UserGetSchema, UserCreateSchema, UserListSchema
from .ctrl import UserController
from .services import auth_service, token_service
from ..dependencies import get_user_role
......@@ -16,8 +17,7 @@ ctrl = UserController()
@router.get('')
async def get_users(
query: UserGetSchema = fastapi.Depends(),
current_user: str = fastapi.Depends(get_user_role)
query: UserListSchema = fastapi.Depends(),
):
return await ctrl.get_list(**query.model_dump(exclude_none=True))
......@@ -32,11 +32,10 @@ async def get_user(
@router.post('')
async def create_user(
body: UserCreateSchema,
current_user: str = fastapi.Depends(get_user_role)
body: UserCreateSchema = fastapi.Depends(),
avatar: fastapi.UploadFile = fastapi.File(...),
):
return await ctrl.create(**body.model_dump(exclude_none=True))
return await ctrl.create(avatar=avatar, **body.model_dump(exclude_none=True))
@router.patch('/{id}')
async def update_user(
......
from typing import List
from uuid import UUID
from dataclasses import dataclass
from dataclasses import dataclass, field
from datetime import datetime
from pydantic import BaseModel, ConfigDict
from pydantic import EmailStr
from db.models import UserRoleEnum
from ..group.schemas import GroupIdSchema
class UserBaseSchema(BaseModel):
username: str | None = None
......@@ -23,21 +24,33 @@ class UserSchema(UserBaseSchema, UserIdSchema):
pass
class UserListSchema(BaseModel):
name: str | None = None
surname: str | None = None
email: EmailStr | None = None
role: UserRoleEnum | None = None
class UserGetSchema(BaseModel):
model_config = ConfigDict(from_attributes=True, use_enum_values=True)
id: UUID | None = None
name: str | None = None
surname: str | None = None
email: EmailStr | None = None
role: UserRoleEnum
role: UserRoleEnum | None = None
avatar: str | None = None
group: List[UUID] | None = None
class UserCreateSchema(UserBaseSchema):
class UserCreateSchema(BaseModel):
model_config = ConfigDict(use_enum_values=True)
username: str
password: str
name: str
surname: str
email: EmailStr
role: UserRoleEnum
group: List[UUID] = []
class UserUpdateSchema(BaseModel):
......@@ -52,6 +65,7 @@ class UserUpdateSchema(BaseModel):
class TokenData:
sub: int
role: str
group: List[UUID] = field(default_factory=list)
exp: datetime | None = None
......
......@@ -6,11 +6,14 @@ import fastapi.security as fs
from passlib.context import CryptContext
from jose import jwt, JWTError
from db.repositories import UserRepository
from db.repositories import UserRepository, GroupRepository
from .schemas import TokenData, LoginRequestModel, UserGetSchema
from .const import (
SECRET_KEY, ACCESS_TOKEN_EXPIRE_MINUTES, REFRESH_TOKEN_EXPIRE_HOURS, ALGORITHM
)
from exceptions import common as common_exc, http as http_exc
from db.models import Group
from ..group.schemas import GroupIdSchema
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
......@@ -23,9 +26,10 @@ class TokenService:
payload = {
'sub': str(data.sub),
'role': data.role,
'group': data.group,
'exp': datetime.utcnow() + expires_delta,
}
print(payload)
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
def create_tokens(self, data: TokenData):
......@@ -50,6 +54,7 @@ class TokenService:
user_id = decoded_token_payload.get('sub')
if user_id is None:
raise credentials_exception
except JWTError:
raise credentials_exception
......@@ -74,7 +79,7 @@ class AuthService:
async def authenticate_user(self, username: str, password: str):
users = await self.user_repo.get_list(username=username)
if users and self.verify_password(password, users[0].password):
if users and await self.verify_password(password, users[0].password):
return users[0]
async def login(self, data: LoginRequestModel):
......@@ -86,12 +91,17 @@ class AuthService:
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
groups = await user.group.all()
group_uuids = [str(group.id) for group in groups]
return token_service.create_tokens(TokenData(sub=user.id, role=user.role))
return token_service.create_tokens(
TokenData(sub=user.id, role=user.role, group=group_uuids)
)
async def get_current_user(self, token: str = fa.Security(oauth2_scheme)):
token_data = token_service.decode_token(token)
return await self.user_repo.get(id=token_data.sub)
user = await self.user_repo.get(id=token_data.sub)
return user
auth_service = AuthService()
......@@ -99,17 +109,49 @@ auth_service = AuthService()
class UserService:
user_repository = UserRepository()
group_repository = GroupRepository()
async def get_user_list(self, **kwargs):
users = await self.user_repository.get_list(**kwargs)
return [UserGetSchema.model_validate(user) for user in users]
return [UserGetSchema.model_validate(
{
'id': user.id,
'name': user.name,
'surname': user.surname,
'email': user.email,
'role': user.role,
'avatar': user.avatar,
'group': await user.group.all().values_list('id', flat=True)
}
) for user in users]
async def get_user(self, id: UUID):
user = await self.user_repository.get(id)
return UserGetSchema.model_validate(user)
async def create_user(self, **kwargs):
user = await self.user_repository.create(**kwargs)
user = await self.user_repository.create(
username=kwargs.get('username'),
password=kwargs.get('password'),
name=kwargs.get('name'),
surname=kwargs.get('surname'),
email=kwargs.get('email'),
role=kwargs.get('role'),
avatar=kwargs.get('avatar')
)
group_uuids = kwargs.get('group', [])
groups = await Group.filter(id__in=group_uuids)
await user.group.add(*groups)
groups = await user.group.all()
user = {
'id': user.id,
'name': user.name,
'surname': user.surname,
'email': user.email,
'role': user.role,
'avatar': user.avatar,
'group': [group.id for group in groups]
}
return UserGetSchema.model_validate(user)
async def update_user(self, id: UUID, **kwargs):
......
import os
import uuid
from datetime import datetime
from pathlib import Path
from starlette.datastructures import UploadFile
from tortoise.fields import TextField
def get_valid_filename(filename: str) -> str:
return str(filename).strip().replace(' ', '_').replace('..', '')
class FileField(TextField):
def __init__(self, *, upload_to: str, **kwargs):
super().__init__(**kwargs)
upload_to = datetime.now().strftime(upload_to)
if not os.path.exists(upload_to):
os.makedirs(upload_to, exist_ok=True)
self.upload_to = Path(upload_to)
def to_db_value(self, value: str | UploadFile | bytes, *args, **kwargs):
if isinstance(value, UploadFile):
return self.save_file(value)
elif isinstance(value, str):
return value
return value
def to_python_value(self, value: str | UploadFile | bytes, *args, **kwargs):
if isinstance(value, str):
return value
elif isinstance(value, UploadFile):
return self.save_file(value)
def save_file(self, value: UploadFile):
file = value.file
filename = get_valid_filename(value.filename)
file_path = self.upload_to / filename
if os.path.isfile(file_path):
file_path = self.upload_to / f'{uuid.uuid4()}-{filename}'
with open(file_path, 'wb') as f:
f.write(file.read())
return str(file_path)
......@@ -2,6 +2,7 @@ from enum import Enum
from tortoise import fields
from .base import BaseModel
from .custom_fields import FileField
class Status(BaseModel):
......@@ -61,6 +62,29 @@ class UserRoleEnum(str, Enum):
system_consumer = "system_consumer"
class Scopes(BaseModel):
name = fields.CharField(max_length=250)
def __str__(self):
return self.name
class Meta:
table = 'scopes'
class Group(BaseModel):
name = fields.CharField(max_length=55, unique=True)
scopes = fields.ManyToManyField(
'models.Scopes', related_name='group_scopes', through='scope_groups'
)
def __str__(self):
return self.name
class Meta:
table = 'groups'
class User(BaseModel):
username = fields.CharField(max_length=100, unique=True)
name = fields.CharField(max_length=100)
......@@ -68,6 +92,11 @@ class User(BaseModel):
email = fields.CharField(max_length=100)
role = fields.CharEnumField(enum_type=UserRoleEnum, default=UserRoleEnum.user)
password = fields.CharField(max_length=255)
avatar = FileField(upload_to='media/avatars', null=True)
group = fields.ManyToManyField(
'models.Group', related_name='user_groups', through='groups_users'
)
def __str__(self):
return self.username
......
......@@ -5,7 +5,7 @@ from tortoise.expressions import Q
from passlib.context import CryptContext
from .models import (
Status, Type, Task, User, Comment, Category
Status, Type, Task, User, Comment, Category, Group, Scopes
)
from exceptions import common as common_exc
......@@ -119,12 +119,12 @@ class UserRepository(BaseRepository):
model = User
@staticmethod
def __get_password_hash(password):
def get_password_hash(password):
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
return pwd_context.hash(password)
async def create(self, **kwargs):
kwargs['password'] = self.__get_password_hash(kwargs['password'])
kwargs['password'] = self.get_password_hash(kwargs['password'])
return await super().create(**kwargs)
......@@ -134,3 +134,11 @@ class CommentRepository(BaseRepository):
class CategoryRepository(BaseRepository):
model = Category
class GroupRepository(BaseRepository):
model = Group
class ScopeRepository(BaseRepository):
model = Scopes
......@@ -4,3 +4,8 @@ import pydantic
database_url = pydantic.PostgresDsn(os.getenv('DATABASE_URL'))
minio_root_user = os.getenv('MINIO_ROOT_USER')
minio_root_password = os.getenv('MINIO_ROOT_PASSWORD')
minio_url = os.getenv('MINIO_URL')
minio_bucket_name = os.getenv('MINIO_BUCKET_NAME')
......@@ -8,6 +8,24 @@ async def upgrade(db: BaseDBAsyncClient) -> str:
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS "categories" (
"id" UUID NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"name" VARCHAR(255) NOT NULL
);
CREATE TABLE IF NOT EXISTS "groups" (
"id" UUID NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"name" VARCHAR(55) NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS "scopes" (
"id" UUID NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"name" VARCHAR(250) NOT NULL
);
CREATE TABLE IF NOT EXISTS "statuses" (
"id" UUID NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
......@@ -20,23 +38,55 @@ CREATE TABLE IF NOT EXISTS "tasks" (
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"title" VARCHAR(255) NOT NULL,
"description" TEXT NOT NULL,
"priority" VARCHAR(6) NOT NULL DEFAULT 'low',
"category_id" UUID REFERENCES "categories" ("id") ON DELETE SET NULL,
"status_id" UUID NOT NULL REFERENCES "statuses" ("id") ON DELETE CASCADE
);
COMMENT ON COLUMN "tasks"."priority" IS 'LOW: low\nMEDIUM: medium\nHIGH: high';
CREATE TABLE IF NOT EXISTS "types" (
"id" UUID NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"name" VARCHAR(255) NOT NULL
);
CREATE TABLE IF NOT EXISTS "users" (
"id" UUID NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"username" VARCHAR(100) NOT NULL UNIQUE,
"name" VARCHAR(100) NOT NULL,
"surname" VARCHAR(100) NOT NULL,
"email" VARCHAR(100) NOT NULL,
"role" VARCHAR(15) NOT NULL DEFAULT 'user',
"password" VARCHAR(255) NOT NULL,
"avatar" TEXT
);
COMMENT ON COLUMN "users"."role" IS 'admin: admin\nuser: user\nsystem_consumer: system_consumer';
CREATE TABLE IF NOT EXISTS "comments" (
"id" UUID NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"text" TEXT NOT NULL,
"task_id" UUID NOT NULL REFERENCES "tasks" ("id") ON DELETE CASCADE,
"user_id" UUID NOT NULL REFERENCES "users" ("id") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "aerich" (
"id" SERIAL NOT NULL PRIMARY KEY,
"version" VARCHAR(255) NOT NULL,
"app" VARCHAR(100) NOT NULL,
"content" JSONB NOT NULL
);
CREATE TABLE IF NOT EXISTS "scope_groups" (
"groups_id" UUID NOT NULL REFERENCES "groups" ("id") ON DELETE CASCADE,
"scopes_id" UUID NOT NULL REFERENCES "scopes" ("id") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "types_tasks" (
"tasks_id" UUID NOT NULL REFERENCES "tasks" ("id") ON DELETE CASCADE,
"type_id" UUID NOT NULL REFERENCES "types" ("id") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "groups_users" (
"users_id" UUID NOT NULL REFERENCES "users" ("id") ON DELETE CASCADE,
"group_id" UUID NOT NULL REFERENCES "groups" ("id") ON DELETE CASCADE
);"""
......
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
CREATE TABLE IF NOT EXISTS "users" (
"id" UUID NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"username" VARCHAR(100) NOT NULL UNIQUE,
"password" VARCHAR(255) NOT NULL
);"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
DROP TABLE IF EXISTS "users";"""
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
CREATE TABLE IF NOT EXISTS "comments" (
"id" UUID NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"text" TEXT NOT NULL,
"task_id" UUID NOT NULL REFERENCES "tasks" ("id") ON DELETE CASCADE,
"user_id" UUID NOT NULL REFERENCES "users" ("id") ON DELETE CASCADE
);"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
DROP TABLE IF EXISTS "comments";"""
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
ALTER TABLE "tasks" ADD "priority" VARCHAR(6) NOT NULL DEFAULT 'low';"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
ALTER TABLE "tasks" DROP COLUMN "priority";"""
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
CREATE TABLE IF NOT EXISTS "categories" (
"id" UUID NOT NULL PRIMARY KEY,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"name" VARCHAR(255) NOT NULL
);"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
DROP TABLE IF EXISTS "categories";"""
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
ALTER TABLE "tasks" ADD "category_id" UUID;
ALTER TABLE "tasks" ADD CONSTRAINT "fk_tasks_categori_3e76a3ba" FOREIGN KEY ("category_id") REFERENCES "categories" ("id") ON DELETE SET NULL;"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
ALTER TABLE "tasks" DROP CONSTRAINT "fk_tasks_categori_3e76a3ba";
ALTER TABLE "tasks" DROP COLUMN "category_id";"""
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
ALTER TABLE "users" ADD "role" VARCHAR(15) NOT NULL DEFAULT 'user';
ALTER TABLE "users" ADD "surname" VARCHAR(100) NOT NULL;
ALTER TABLE "users" ADD "name" VARCHAR(100) NOT NULL;
ALTER TABLE "users" ADD "email" VARCHAR(100) NOT NULL;"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
ALTER TABLE "users" DROP COLUMN "role";
ALTER TABLE "users" DROP COLUMN "surname";
ALTER TABLE "users" DROP COLUMN "name";
ALTER TABLE "users" DROP COLUMN "email";"""
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment