creating first admin and update restrictions for ordinary users

This commit is contained in:
2026-03-04 17:06:43 +03:00
parent 08e48aac29
commit ea06c16aac
16 changed files with 128 additions and 58 deletions

View File

@@ -13,16 +13,10 @@ jobs:
run: | run: |
echo "${{ secrets.SSH_PRIVATE_KEY }}" > /tmp/id_fin echo "${{ secrets.SSH_PRIVATE_KEY }}" > /tmp/id_fin
chmod 600 /tmp/id_fin chmod 600 /tmp/id_fin
#ссылка на репо
- name: Create inventory
run: echo "${{ secrets.INVENTORY }}" > inventory.ini
- name: Create secrets.yml
run: echo "${{ secrets.SECRETS }}" > secrets.yml
- name: Create .env file - name: Create .env file
run: echo "${{ secrets.RUNNER_ENV }}" > .env run: echo "${{ secrets.RUNNER_ENV }}" > .env
#env для runners
- name: Checkout only deploy.yml - name: Checkout only deploy.yml
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
@@ -31,6 +25,8 @@ jobs:
path: tmp-repo path: tmp-repo
sparse-checkout: | sparse-checkout: |
ansible/deploy.yml ansible/deploy.yml
ansible/inventory.ini
ansible/secrets.yml
- name: Run Ansible playbook - name: Run Ansible playbook
run: ansible-playbook -i inventory.ini tmp-repo/ansible/deploy.yml -e @secrets.yml -e env_file="$(pwd)/.env" run: ansible-playbook -i inventory.ini tmp-repo/ansible/deploy.yml -e @secrets.yml -e env_file="$(pwd)/.env"
@@ -46,7 +42,7 @@ jobs:
- name: Create .env file - name: Create .env file
run: echo "${{ secrets.WEDDING_SITE_ENV }}" > .env run: echo "${{ secrets.WEDDING_SITE_ENV }}" > .env
#env для проекта
- name: Build image - name: Build image
run: docker build -t back:latest -f docker/dockerfile . run: docker build -t back:latest -f docker/dockerfile .

1
.gitignore vendored
View File

@@ -22,7 +22,6 @@ hint.py
#env #env
*.env *.env
secrets.yml
#db #db
*.db *.db
versions/ versions/

View File

@@ -31,11 +31,6 @@
group: root group: root
mode: '0600' mode: '0600'
- name: Download nginx
shell: wget -O /opt/infra/nginx.yaml "{{ URL for docker-compose nginx }}" #перенести докер в pipeline и выкачать страницы для запуска с созданием loacations в nginx
args:
creates: /opt/infra/nginx.yaml
- name: Download node-docker.yaml - name: Download node-docker.yaml
shell: wget -O /opt/infra/node-docker.yaml "{{ gitea_instance_url }}" shell: wget -O /opt/infra/node-docker.yaml "{{ gitea_instance_url }}"
args: args:
@@ -43,6 +38,3 @@
- name: Start node-docker - name: Start node-docker
shell: docker-compose -f /opt/infra/node-docker.yaml up -d shell: docker-compose -f /opt/infra/node-docker.yaml up -d
- name: Start nginx
shell: docker-compose -f /opt/infra/nginx.yaml up -d

2
ansible/inventory.ini Normal file
View File

@@ -0,0 +1,2 @@
[servers]
myserver ansible_host=38.244.136.102 ansible_user=root

View File

@@ -1,2 +0,0 @@
[servers]
myserver host=... ansible_user=...

1
ansible/secrets.yml Normal file
View File

@@ -0,0 +1 @@
gitea_instance_url: https://git.homyk.space/MH.Dmitrii/wedding-site/src/branch/main/docker/gitea_runners.git

View File

@@ -1 +0,0 @@
gitea_instance_url: ...

18
docker/caddy/caddy.conf Normal file
View File

@@ -0,0 +1,18 @@
example.com {
encode gzip
# --- API ---
handle_path /api/* {
reverse_proxy backend:8000
}
# --- Frontend ---
root * /var/www/site
file_server
log {
output stdout
format console
}
}

19
docker/caddy/caddy.yaml Normal file
View File

@@ -0,0 +1,19 @@
services:
caddy:
image: caddy:<version>
restart: unless-stopped
cap_add:
- NET_ADMIN
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- $PWD/conf:/etc/caddy
- $PWD/site:/srv
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:

View File

@@ -1,8 +0,0 @@
web:
image: nginx
volumes:
- ./templates:/etc/nginx/templates
- ./confs:/etc/nginx/conf.d
ports:
- "80:80"
- "443:443"

View File

@@ -2,9 +2,9 @@ VENV=source ./.venv/bin/activate;
ALEMBIC=alembic -c ./server/backend/database/alembic/alembic.ini ALEMBIC=alembic -c ./server/backend/database/alembic/alembic.ini
.PHONY: run run_debug migrate_head migrate_down migrate_history migrate_current migrate .PHONY: run run_debug migrate_head migrate_down migrate_history migrate_current migrate
run: run:
$(VENV) python run.py $(VENV) python run.py --user_name admin
run_debug: run_debug:
$(VENV) python run.py --mode debug $(VENV) python run.py --mode debug --user_name admin
migrate_head: migrate_head:
$(VENV) $(ALEMBIC) upgrade head $(VENV) $(ALEMBIC) upgrade head
migrate_down: migrate_down:

28
run.py
View File

@@ -1,4 +1,7 @@
from server.backend.schema.pydantic import settings from server.backend.schema.pydantic import settings
from server.backend.database.db import create_user, list_users
from server.backend.schema.pydantic import UserCreate
import asyncio
import uvicorn import uvicorn
def start(log_level:str): def start(log_level:str):
if __name__ == "__main__": if __name__ == "__main__":
@@ -11,18 +14,33 @@ def start(log_level:str):
access_log=True access_log=True
) )
import argparse import argparse
parser = argparse.ArgumentParser(description="logging") parser = argparse.ArgumentParser(description="logging and admin creation")
parser.add_argument( parser.add_argument(
"--mode", "--mode",
choices=["debug","info"], choices=["debug","info"],
default="info", default="info",
help="Режим логирования (по умолчанию: info)" help="Режим логирования (по умолчанию: info)"
) )
parser.add_argument(
"--user_name",
type=str,
required=True,
help="Создание первого пользователя)"
)
args = parser.parse_args() args = parser.parse_args()
async def arguments(args):
admin_user = {
"code": "123456",
"name": args.user_name,
"surname": args.user_name,
"admin": True
}
users = await list_users()
label = any(u.admin for u in users)
if not label:
await create_user(UserCreate(**admin_user))
match args.mode: match args.mode:
case "debug": case "debug" | "info":
print("Режим:", args.mode)
start(args.mode)
case "info":
print("Режим:", args.mode) print("Режим:", args.mode)
start(args.mode) start(args.mode)
asyncio.run(arguments(args))

View File

@@ -7,7 +7,6 @@ from server.backend.schema.pydantic import settings
def signJWT(user_info: dict) -> str: def signJWT(user_info: dict) -> str:
payload = { payload = {
"user_id":user_info.id, "user_id":user_info.id,
"admin":user_info.admin,
"expires": time.time() + settings.ACCESS_TOKEN_EXPIRE_SECONDS "expires": time.time() + settings.ACCESS_TOKEN_EXPIRE_SECONDS
} }
token = pyjwt.encode(payload, settings.SECRET_KEY, algorithm=settings.ALGORITHM) token = pyjwt.encode(payload, settings.SECRET_KEY, algorithm=settings.ALGORITHM)

View File

@@ -38,15 +38,19 @@ class User(Base):
async def create_user(user_info): async def create_user(user_info):
async with AsyncSessionLocal() as session: async with AsyncSessionLocal() as session:
result = await session.execute(select(User).where(User.code==user_info.code))
user = result.scalar_one_or_none()
if user == None:
user_data = user_info.dict(exclude_unset=True) user_data = user_info.dict(exclude_unset=True)
new_user = User(**user_data) new_user = User(**user_data)
session.add(new_user) session.add(new_user)
await session.commit() await session.commit()
await session.refresh(new_user) await session.refresh(new_user)
return user
async def update_user(user_info): async def update_user(user_info):
async with AsyncSessionLocal() as session: async with AsyncSessionLocal() as session:
result = await session.execute(select(User).where(User.code==user_info.code)) result = await session.execute(select(User).where(User.id==user_info.id))
user = result.scalar_one_or_none() user = result.scalar_one_or_none()
if user: if user:
update_data = user_info.dict(exclude_unset=True) update_data = user_info.dict(exclude_unset=True)
@@ -64,13 +68,29 @@ async def list_users():
return users return users
else: else:
return None return None
async def list_user(id):
async with AsyncSessionLocal() as session:
result = await session.execute(select(User).where(User.id == id))
user = result.scalar_one_or_none()
if user:
return user
else:
return None
async def list_user_by_code(code):
async with AsyncSessionLocal() as session:
result = await session.execute(select(User).where(User.code == code))
user = result.scalar_one_or_none()
if user:
return user
else:
return None
async def login_user(code): async def login_user(code):
async with AsyncSessionLocal() as session: async with AsyncSessionLocal() as session:
result = await session.execute(select(User).where(User.code == code.code)) result = await session.execute(select(User).where(User.code == code.code))
user = result.scalar_one_or_none() user = result.scalar_one_or_none()
if user: if user:
user.last_login=datetime.now(timezone.utc) user.last_login=datetime.now(timezone.utc)
user.activated=True
await session.commit() await session.commit()
return user return user
else: else:

View File

@@ -19,8 +19,26 @@ async def check_roles(user=Depends(get_current_user)):
@api.post("/update", response_model=pydantic.UserUpdate) @api.post("/update", response_model=pydantic.UserUpdate)
async def update_user(data: pydantic.UserUpdate, user=Depends(get_current_user)): async def update_user(data: pydantic.UserUpdate, user=Depends(get_current_user)):
data = await db.update_user(data) user_check = await db.list_user(user["user_id"])
return data if not user_check:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
if not user_check.admin:
if data.code != user_check.code:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Ordinary users cannot change their code"
)
if user_check.admin:
if data.code != user_check.code:
existing_user = await db.list_user_by_code(data.code)
if existing_user and existing_user.id != user_check.id:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Code already exists for another user"
)
updated_data = data.copy(update={"id": user_check.id})
updated_data = await db.update_user(updated_data)
return updated_data
@api.post("/create", response_model=pydantic.UserAccess) @api.post("/create", response_model=pydantic.UserAccess)
async def create_user(user_info: pydantic.UserCreate,user=Depends(check_roles)): async def create_user(user_info: pydantic.UserCreate,user=Depends(check_roles)):

View File

@@ -23,9 +23,6 @@ class UserOut(BaseModel):
name: NameStr = Field(..., description="Name of the guest") name: NameStr = Field(..., description="Name of the guest")
surname: NameStr = Field(..., description="Surname of the guest") surname: NameStr = Field(..., description="Surname of the guest")
class UserCreate(UserAccess):
pass
class UserUpdate(UserAccess): class UserUpdate(UserAccess):
name: NameStr = Field(..., description="Name of the guest") name: NameStr = Field(..., description="Name of the guest")
surname: NameStr = Field(..., description="Surname of the guest") surname: NameStr = Field(..., description="Surname of the guest")
@@ -35,6 +32,8 @@ class UserUpdate(UserAccess):
alco: bool = Field(False, description="if the guest will drink alco or not") alco: bool = Field(False, description="if the guest will drink alco or not")
types_of_alco: str = Field("", description="types of alco") types_of_alco: str = Field("", description="types of alco")
class UserCreate(UserUpdate):
admin:bool = Field(False, description="Admin privilegies")
class Settings(BaseSettings): class Settings(BaseSettings):
DIR:str DIR:str
PORT:int PORT:int