update project
This commit is contained in:
parent
1f6250eea0
commit
f215f458bc
|
|
@ -86,7 +86,6 @@ path_separator = os
|
|||
# database URL. This is consumed by the user-maintained env.py script only.
|
||||
# other means of configuring database URLs may be customized within the env.py
|
||||
# file.
|
||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,17 @@
|
|||
import asyncio
|
||||
from logging.config import fileConfig
|
||||
|
||||
from sqlalchemy import engine_from_config
|
||||
from sqlalchemy import pool
|
||||
from sqlalchemy.ext.asyncio import async_engine_from_config, AsyncConnection
|
||||
|
||||
from alembic import context
|
||||
|
||||
from core.config import settings
|
||||
from core.config import settings
|
||||
from db.base import Base
|
||||
|
||||
import domains.users.models
|
||||
import domains.groups.models
|
||||
|
||||
from alembic import context
|
||||
|
||||
|
|
@ -14,11 +24,7 @@ config = context.config
|
|||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
target_metadata = None
|
||||
target_metadata = Base.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
|
|
@ -38,7 +44,7 @@ def run_migrations_offline() -> None:
|
|||
script output.
|
||||
|
||||
"""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
url = settings.DATABASE_URL
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
|
|
@ -50,26 +56,39 @@ def run_migrations_offline() -> None:
|
|||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
"""Run migrations in 'online' mode.
|
||||
from sqlalchemy import Connection
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
def do_run_migrations(connection: Connection) -> None:
|
||||
context.configure(connection=connection, target_metadata=target_metadata)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
async def run_async_migrations() -> None:
|
||||
"""In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section, {}),
|
||||
|
||||
configuration = config.get_section(config.config_ini_section, {})
|
||||
configuration["sqlalchemy.url"] = settings.DATABASE_URL
|
||||
|
||||
connectable = async_engine_from_config(
|
||||
configuration,
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(
|
||||
connection=connection, target_metadata=target_metadata
|
||||
)
|
||||
async with connectable.connect() as connection:
|
||||
await connection.run_sync(do_run_migrations)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
await connectable.dispose()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
"""Run migrations in 'online' mode."""
|
||||
asyncio.run(run_async_migrations())
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
|
|
|
|||
77
Back/alembic/versions/4413cbcf58c3_create_db.py
Normal file
77
Back/alembic/versions/4413cbcf58c3_create_db.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
"""create db
|
||||
|
||||
Revision ID: 4413cbcf58c3
|
||||
Revises:
|
||||
Create Date: 2026-03-06 12:26:21.920942
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '4413cbcf58c3'
|
||||
down_revision: Union[str, Sequence[str], None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('groups',
|
||||
sa.Column('name', sa.String(length=100), nullable=False),
|
||||
sa.Column('type', sa.Enum('GROUP', 'DIRECT', name='group_type'), nullable=False),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=False),
|
||||
sa.Column('id', sa.UUID(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_groups_is_active'), 'groups', ['is_active'], unique=False)
|
||||
op.create_index(op.f('ix_groups_name'), 'groups', ['name'], unique=False)
|
||||
op.create_table('users',
|
||||
sa.Column('username', sa.String(length=50), nullable=False),
|
||||
sa.Column('secret_hash', sa.String(length=255), nullable=False),
|
||||
sa.Column('role', sa.Enum('ADMIN', 'GROUP_MANAGER', 'MEMBER', name='user_role'), nullable=False),
|
||||
sa.Column('is_active', sa.Boolean(), nullable=False),
|
||||
sa.Column('id', sa.UUID(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_users_is_active'), 'users', ['is_active'], unique=False)
|
||||
op.create_index(op.f('ix_users_role'), 'users', ['role'], unique=False)
|
||||
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
|
||||
op.create_table('group_members',
|
||||
sa.Column('user_id', sa.UUID(), nullable=False),
|
||||
sa.Column('group_id', sa.UUID(), nullable=False),
|
||||
sa.Column('role', sa.Enum('MANAGER', 'MEMBER', name='group_role'), nullable=False),
|
||||
sa.Column('id', sa.UUID(), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
|
||||
sa.ForeignKeyConstraint(['group_id'], ['groups.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_group_members_group_id'), 'group_members', ['group_id'], unique=False)
|
||||
op.create_index(op.f('ix_group_members_user_id'), 'group_members', ['user_id'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_group_members_user_id'), table_name='group_members')
|
||||
op.drop_index(op.f('ix_group_members_group_id'), table_name='group_members')
|
||||
op.drop_table('group_members')
|
||||
op.drop_index(op.f('ix_users_username'), table_name='users')
|
||||
op.drop_index(op.f('ix_users_role'), table_name='users')
|
||||
op.drop_index(op.f('ix_users_is_active'), table_name='users')
|
||||
op.drop_table('users')
|
||||
op.drop_index(op.f('ix_groups_name'), table_name='groups')
|
||||
op.drop_index(op.f('ix_groups_is_active'), table_name='groups')
|
||||
op.drop_table('groups')
|
||||
# ### end Alembic commands ###
|
||||
32
Back/alembic/versions/b1f09d977759_change_groups_models.py
Normal file
32
Back/alembic/versions/b1f09d977759_change_groups_models.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
"""change groups models
|
||||
|
||||
Revision ID: b1f09d977759
|
||||
Revises: 4413cbcf58c3
|
||||
Create Date: 2026-03-06 16:21:31.915163
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'b1f09d977759'
|
||||
down_revision: Union[str, Sequence[str], None] = '4413cbcf58c3'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('group_members', 'role')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('group_members', sa.Column('role', postgresql.ENUM('MANAGER', 'MEMBER', name='group_role'), autoincrement=False, nullable=False))
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -9,7 +9,7 @@ class Settings(BaseSettings):
|
|||
SECRET_KEY: str
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
||||
ALGORITHM: str = "HS256"
|
||||
SECRET_PASS_LENGTH: int = 16
|
||||
SECRET_PASS_LENGTH: int
|
||||
|
||||
DATABASE_URL: str
|
||||
REDIS_URL: str
|
||||
|
|
@ -21,6 +21,7 @@ class Settings(BaseSettings):
|
|||
class Config:
|
||||
env_file = ".env"
|
||||
case_sensitive = True
|
||||
extra = "ignore"
|
||||
|
||||
|
||||
@lru_cache
|
||||
|
|
|
|||
|
|
@ -1,18 +1,25 @@
|
|||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from db.session import get_db
|
||||
from core.jwt import decode_token
|
||||
from domains.users.repo import get_user_by_id
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
|
||||
|
||||
# Bearer authentication scheme
|
||||
security = HTTPBearer()
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
token: str = Depends(oauth2_scheme),
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Validate JWT token and return the authenticated user
|
||||
"""
|
||||
|
||||
token = credentials.credentials
|
||||
|
||||
payload = decode_token(token)
|
||||
|
||||
|
|
@ -24,6 +31,12 @@ async def get_current_user(
|
|||
|
||||
user_id = payload.get("sub")
|
||||
|
||||
if not user_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid token payload",
|
||||
)
|
||||
|
||||
user = await get_user_by_id(db, user_id)
|
||||
|
||||
if not user:
|
||||
|
|
@ -32,10 +45,21 @@ async def get_current_user(
|
|||
detail="User not found",
|
||||
)
|
||||
|
||||
if not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="User is inactive",
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
async def get_current_admin(user=Depends(get_current_user)):
|
||||
async def get_current_admin(
|
||||
user=Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
Ensure the authenticated user is an admin
|
||||
"""
|
||||
|
||||
if user.role != "admin":
|
||||
raise HTTPException(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ services:
|
|||
container_name: neda_api
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- "./:/app"
|
||||
env_file:
|
||||
- .env
|
||||
depends_on:
|
||||
|
|
@ -28,7 +30,7 @@ services:
|
|||
restart: always
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||
test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}" ]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
|
@ -41,7 +43,7 @@ services:
|
|||
restart: always
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
test: [ "CMD", "redis-cli", "ping" ]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
|
|
|||
|
|
@ -7,13 +7,18 @@ from core.deps import get_current_admin
|
|||
from domains.admin.schemas import (
|
||||
AdminCreateUser,
|
||||
AdminCreateUserResult,
|
||||
AdminResetSecretResult
|
||||
AdminResetSecretResult,
|
||||
AdminUserResponse
|
||||
)
|
||||
|
||||
from domains.groups.schemas import GroupResponse
|
||||
from domains.groups.repo import get_all_groups
|
||||
|
||||
from domains.admin.service import (
|
||||
admin_create_user,
|
||||
admin_reset_user_secret
|
||||
)
|
||||
from domains.users.repo import get_all_users
|
||||
|
||||
|
||||
router = APIRouter(
|
||||
|
|
@ -69,3 +74,18 @@ async def reset_secret(
|
|||
)
|
||||
|
||||
return {"secret": new_secret}
|
||||
|
||||
@router.get("/users", response_model=list[AdminUserResponse])
|
||||
async def list_users(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin=Depends(get_current_admin)
|
||||
):
|
||||
return await get_all_users(db)
|
||||
|
||||
|
||||
@router.get("/groups", response_model=list[GroupResponse])
|
||||
async def list_groups(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin=Depends(get_current_admin)
|
||||
):
|
||||
return await get_all_groups(db)
|
||||
|
|
@ -14,7 +14,8 @@ from core.config import settings
|
|||
|
||||
|
||||
def generate_user_secret():
|
||||
return secrets.token_urlsafe(settings.SECRET_PASS_LENGTH)
|
||||
# return secrets.token_urlsafe(settings.SECRET_PASS_LENGTH)
|
||||
return "1234"
|
||||
|
||||
async def admin_create_user(
|
||||
db: AsyncSession,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from db.session import get_db
|
||||
from core.deps import get_current_admin, get_current_user
|
||||
from core.deps import get_current_admin
|
||||
|
||||
from domains.groups.schemas import (
|
||||
GroupCreate,
|
||||
|
|
@ -13,7 +13,6 @@ from domains.groups.schemas import (
|
|||
from domains.groups.service import (
|
||||
create_new_group,
|
||||
add_member_to_group,
|
||||
list_user_groups
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -30,13 +29,12 @@ router = APIRouter(
|
|||
async def create_group(
|
||||
payload: GroupCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin = Depends(get_current_admin)
|
||||
admin=Depends(get_current_admin)
|
||||
):
|
||||
|
||||
group = await create_new_group(
|
||||
db,
|
||||
payload.name,
|
||||
payload.description
|
||||
payload.name
|
||||
)
|
||||
|
||||
return group
|
||||
|
|
@ -47,26 +45,19 @@ async def add_member(
|
|||
group_id: str,
|
||||
payload: AddMemberRequest,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin = Depends(get_current_admin)
|
||||
admin=Depends(get_current_admin)
|
||||
):
|
||||
|
||||
membership = await add_member_to_group(
|
||||
db,
|
||||
group_id,
|
||||
payload.user_id,
|
||||
payload.role
|
||||
)
|
||||
try:
|
||||
membership = await add_member_to_group(
|
||||
db,
|
||||
group_id,
|
||||
payload.user_id,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(e)
|
||||
)
|
||||
|
||||
return membership
|
||||
|
||||
|
||||
@router.get("/me", response_model=list[GroupResponse])
|
||||
async def my_groups(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
user = Depends(get_current_user)
|
||||
):
|
||||
|
||||
return await list_user_groups(
|
||||
db,
|
||||
user.id
|
||||
)
|
||||
|
|
@ -10,9 +10,6 @@ class GroupType(str, Enum):
|
|||
GROUP = "group"
|
||||
DIRECT = "direct"
|
||||
|
||||
class GroupRole(str, Enum):
|
||||
MANAGER = "manager"
|
||||
MEMBER = "member"
|
||||
|
||||
|
||||
class Group(Base):
|
||||
|
|
@ -49,9 +46,3 @@ class GroupMember(Base):
|
|||
ForeignKey("groups.id", ondelete="CASCADE"),
|
||||
index=True
|
||||
)
|
||||
|
||||
role: Mapped[GroupRole] = mapped_column(
|
||||
SQLEnum(GroupRole, name="group_role"),
|
||||
default=GroupRole.MEMBER,
|
||||
nullable=False
|
||||
)
|
||||
|
|
@ -29,3 +29,8 @@ async def get_user_groups(db: AsyncSession, user_id):
|
|||
.where(GroupMember.user_id == user_id)
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
async def get_all_groups(db: AsyncSession):
|
||||
result = await db.execute(select(Group))
|
||||
return result.scalars().all()
|
||||
|
|
@ -1,17 +1,14 @@
|
|||
import uuid
|
||||
from pydantic import BaseModel
|
||||
|
||||
from domains.groups.models import GroupRole
|
||||
|
||||
|
||||
class GroupCreate(BaseModel):
|
||||
name: str
|
||||
description: str | None = None
|
||||
|
||||
class GroupResponse(BaseModel):
|
||||
id: uuid.UUID
|
||||
name: str
|
||||
description: str | None
|
||||
is_active: bool
|
||||
|
||||
class Config:
|
||||
|
|
@ -19,12 +16,10 @@ class GroupResponse(BaseModel):
|
|||
|
||||
class AddMemberRequest(BaseModel):
|
||||
user_id: uuid.UUID
|
||||
role: GroupRole = GroupRole.MEMBER
|
||||
|
||||
class GroupMemberResponse(BaseModel):
|
||||
user_id: uuid.UUID
|
||||
group_id: uuid.UUID
|
||||
role: GroupRole
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from domains.users.repo import get_user_by_id
|
||||
from domains.groups.models import Group, GroupMember
|
||||
from domains.groups.repo import (
|
||||
create_group,
|
||||
|
|
@ -12,12 +13,10 @@ from domains.groups.repo import (
|
|||
async def create_new_group(
|
||||
db: AsyncSession,
|
||||
name: str,
|
||||
description: str | None
|
||||
):
|
||||
|
||||
group = Group(
|
||||
name=name,
|
||||
description=description
|
||||
)
|
||||
|
||||
return await create_group(db, group)
|
||||
|
|
@ -26,13 +25,22 @@ async def add_member_to_group(
|
|||
db: AsyncSession,
|
||||
group_id,
|
||||
user_id,
|
||||
role
|
||||
):
|
||||
# 1. Check if group exists
|
||||
group = await get_group_by_id(db, group_id)
|
||||
if not group:
|
||||
raise ValueError("Group not found")
|
||||
|
||||
# 2. Check if user exists
|
||||
user = await get_user_by_id(db, user_id)
|
||||
if not user:
|
||||
raise ValueError("User not found")
|
||||
|
||||
# TODO: Check if already a member to avoid duplicate constraint if any (optional)
|
||||
|
||||
membership = GroupMember(
|
||||
group_id=group_id,
|
||||
user_id=user_id,
|
||||
role=role
|
||||
)
|
||||
return await add_group_member(db, membership)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,12 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from db.session import get_db
|
||||
from core.deps import get_current_admin
|
||||
from core.deps import get_current_admin, get_current_user
|
||||
|
||||
from domains.users.schemas import (
|
||||
UserCreate,
|
||||
UserCreateResult
|
||||
)
|
||||
from domains.groups.schemas import GroupResponse
|
||||
from domains.groups.service import list_user_groups
|
||||
|
||||
from domains.users.service import (
|
||||
create_user_by_admin,
|
||||
reset_user_secret
|
||||
)
|
||||
from domains.users.repo import get_user_by_id
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/users",
|
||||
|
|
@ -21,38 +14,9 @@ router = APIRouter(
|
|||
)
|
||||
|
||||
|
||||
@router.post("/", response_model=UserCreateResult)
|
||||
async def create_user(
|
||||
payload: UserCreate,
|
||||
@router.get("/me/groups", response_model=list[GroupResponse])
|
||||
async def my_groups(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin = Depends(get_current_admin)
|
||||
user=Depends(get_current_user)
|
||||
):
|
||||
|
||||
user, secret = await create_user_by_admin(
|
||||
db,
|
||||
payload.username,
|
||||
payload.role
|
||||
)
|
||||
|
||||
return {
|
||||
"user": user,
|
||||
"secret": secret
|
||||
}
|
||||
|
||||
@router.post("/{user_id}/reset-secret")
|
||||
async def reset_secret(
|
||||
user_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
admin = Depends(get_current_admin)
|
||||
):
|
||||
|
||||
user = await get_user_by_id(db, user_id)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="User not found"
|
||||
)
|
||||
|
||||
new_secret = await reset_user_secret(db, user)
|
||||
|
||||
return {"secret": new_secret}
|
||||
return await list_user_groups(db, user.id)
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from domains.users.models import User
|
||||
|
||||
|
||||
|
|
@ -10,16 +9,18 @@ async def get_user_by_id(db: AsyncSession, user_id):
|
|||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def get_user_by_username(db: AsyncSession, username):
|
||||
result = await db.execute(
|
||||
select(User).where(User.username == username)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
|
||||
async def create_user(db: AsyncSession, user: User):
|
||||
db.add(user)
|
||||
await db.commit()
|
||||
await db.refresh(user)
|
||||
return user
|
||||
|
||||
async def get_all_users(db: AsyncSession):
|
||||
result = await db.execute(select(User))
|
||||
return result.scalars().all()
|
||||
|
|
@ -7,7 +7,8 @@ from core.config import settings
|
|||
|
||||
|
||||
def generate_user_secret():
|
||||
return secrets.token_urlsafe(settings.SECRET_PASS_LENGTH)
|
||||
# return secrets.token_urlsafe(settings.SECRET_PASS_LENGTH)
|
||||
return "1234"
|
||||
|
||||
async def create_user_by_admin(
|
||||
db: AsyncSession,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ redis
|
|||
|
||||
python-jose[cryptography]
|
||||
passlib[bcrypt]
|
||||
bcrypt==4.0.1
|
||||
|
||||
pydantic-settings
|
||||
python-dotenv
|
||||
|
|
|
|||
36
Back/scripts/create_admin.py
Normal file
36
Back/scripts/create_admin.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import asyncio
|
||||
import secrets
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from db.session import AsyncSessionLocal
|
||||
from core.security import hash_password
|
||||
|
||||
from domains.users.models import User, UserRole
|
||||
|
||||
|
||||
async def create_admin():
|
||||
|
||||
username = input("Admin username: ")
|
||||
|
||||
secret = "1234"
|
||||
|
||||
async with AsyncSessionLocal() as db:
|
||||
|
||||
user = User(
|
||||
username=username,
|
||||
role=UserRole.ADMIN,
|
||||
secret_hash=hash_password(secret),
|
||||
)
|
||||
|
||||
db.add(user)
|
||||
await db.commit()
|
||||
|
||||
print("\nAdmin created successfully\n")
|
||||
print("Username:", username)
|
||||
print("Secret:", secret)
|
||||
print("\nSave this secret!\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(create_admin())
|
||||
Loading…
Reference in New Issue
Block a user