Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd033f458d | |||
| 1b58ec8e27 | |||
| 85b5168445 | |||
| e8763fe635 | |||
| 69e58e6dfb | |||
| d8914627a3 | |||
| b256e20491 | |||
| a8df860752 | |||
| 4e88b52dc3 | |||
| 9772c9f554 | |||
| e81d473a69 | |||
| aca9a1aa4a | |||
| 57129c2d37 | |||
| e09f7cddf3 | |||
| a68dd05794 | |||
| 62e22c56c4 | |||
| 7aa6a12044 | |||
| 39a0759b0b | |||
| e82f2825ae | |||
| 74d29ae8ef | |||
| 614c5f20aa |
@@ -0,0 +1,19 @@
|
|||||||
|
name: Gitea Actions Demo
|
||||||
|
run-name: ${{ gitea.actor }} ist testing out Gitea Actions
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Explore-Gitea-Actions:
|
||||||
|
runs-on: inky
|
||||||
|
steps:
|
||||||
|
- run: echo "The job was automatically triggered by a ${{ gitea.event_name }} event."
|
||||||
|
- run: echo "This job is now running on a ${{ runner.os }} server hosted by Gitea!"
|
||||||
|
- run: echo "The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- run: echo "The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||||
|
- run: echo "The workflow is now ready to test your code on the runner."
|
||||||
|
- name: List files in the directory
|
||||||
|
run: |
|
||||||
|
ls ${{ gitea.workspace }}
|
||||||
|
- run: echo "The job's status is ${{ job.status }}."
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
### STAGE 1: Build ###
|
### STAGE 1: Build ###
|
||||||
FROM node:22.15-alpine AS build
|
FROM docker.io/node:22-alpine AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
@@ -7,8 +7,9 @@ COPY . .
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
### STAGE 2: Run ###
|
### STAGE 2: Run ###
|
||||||
FROM nginx:1.17.1-alpine
|
FROM docker.io/library/nginx:stable-alpine
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
COPY --from=build /app/dist/kontor-angular/browser /usr/share/nginx/html
|
COPY --from=build /app/dist/kontor-angular/browser /usr/share/nginx/html
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
server {
|
server {
|
||||||
|
listen 8800;
|
||||||
# Root-Verzeichnis für den Server setzen (wir kopieren unsere Anwendung hierher)
|
# Root-Verzeichnis für den Server setzen (wir kopieren unsere Anwendung hierher)
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,32 @@
|
|||||||
|
"""
|
||||||
|
add router for different parts (like comics, tysc, media)
|
||||||
|
"""
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from src.apis.version1 import comic, mediaactor, mediafile, mediaactorfile, tysc, admin, user
|
from src.apis.version1 import (
|
||||||
|
comic,
|
||||||
|
mediaactor,
|
||||||
|
mediafile,
|
||||||
|
mediaactorfile,
|
||||||
|
sport,
|
||||||
|
player,
|
||||||
|
team,
|
||||||
|
fieldposition,
|
||||||
|
vendor,
|
||||||
|
admin,
|
||||||
|
user,
|
||||||
|
)
|
||||||
|
|
||||||
api_router = APIRouter(prefix="/api")
|
api_router = APIRouter(prefix="/api")
|
||||||
api_router.include_router(comic.router, prefix="/comics", tags=["comics"])
|
api_router.include_router(comic.router, prefix="/comics", tags=["comics"])
|
||||||
api_router.include_router(mediafile.router, prefix="/media", tags=["media"])
|
api_router.include_router(mediafile.router, prefix="/media", tags=["media"])
|
||||||
api_router.include_router(mediaactor.router, prefix="/media", tags=["media"])
|
api_router.include_router(mediaactor.router, prefix="/media", tags=["media"])
|
||||||
api_router.include_router(mediaactorfile.router, prefix="/media", tags=["media"])
|
api_router.include_router(mediaactorfile.router, prefix="/media", tags=["media"])
|
||||||
api_router.include_router(tysc.router, prefix="/tysc", tags=["tysc"])
|
api_router.include_router(sport.router, prefix="/tysc", tags=["tysc"])
|
||||||
|
api_router.include_router(player.router, prefix="/tysc", tags=["tysc"])
|
||||||
|
api_router.include_router(team.router, prefix="/tysc", tags=["tysc"])
|
||||||
|
api_router.include_router(fieldposition.router, prefix="/tysc", tags=["tysc"])
|
||||||
|
api_router.include_router(vendor.router, prefix="/tysc", tags=["tysc"])
|
||||||
api_router.include_router(admin.router, prefix="/login", tags=["login"])
|
api_router.include_router(admin.router, prefix="/login", tags=["login"])
|
||||||
api_router.include_router(user.router, prefix="/user", tags=["user"])
|
api_router.include_router(user.router, prefix="/user", tags=["user"])
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ from fastapi import APIRouter, Body, HTTPException, status, Depends, Response
|
|||||||
from fastapi.security import OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
|
|
||||||
from src.core.config import settings
|
from src.core.config import settings
|
||||||
from src.core.security import create_access_token, authenticate_user, get_current_active_user
|
from src.core.log_conf import logger
|
||||||
|
from src.core.security import create_access_token, authenticate_user_by_email, authenticate_user_by_username, get_current_active_user
|
||||||
from src.db.models.admin import Profile
|
from src.db.models.admin import Profile
|
||||||
from src.schema.admin import Token, ProfileModel
|
from src.schema.admin import Token, ProfileModel
|
||||||
from src.webapps.auth.forms import LoginForm
|
from src.webapps.auth.forms import LoginForm
|
||||||
@@ -15,7 +16,8 @@ router = APIRouter()
|
|||||||
|
|
||||||
@router.post("/token")
|
@router.post("/token")
|
||||||
def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> Token:
|
def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> Token:
|
||||||
user = authenticate_user(form_data.username, form_data.password)
|
user = authenticate_user_by_username(form_data.username, form_data.password)
|
||||||
|
logger.info(f"Request /token: login with {form_data.username}")
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
@@ -31,7 +33,7 @@ def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depen
|
|||||||
|
|
||||||
# @router.post("/token-cookie", response_model=Token)
|
# @router.post("/token-cookie", response_model=Token)
|
||||||
def login_for_token_cookie(response: Response, form_data: LoginForm = Depends()):
|
def login_for_token_cookie(response: Response, form_data: LoginForm = Depends()):
|
||||||
user = authenticate_user(form_data.username, form_data.password) # type: ignore
|
user = authenticate_user_by_email(form_data.username, form_data.password) # type: ignore
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from src.db.models.tysc import FieldPosition
|
||||||
|
from src.db.session import SessionDep
|
||||||
|
from src.schema.tysc.fieldposition import FieldPositionResponse, to_response
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.get("/positions")
|
||||||
|
def get_all_teams(db: SessionDep) -> List[FieldPositionResponse]:
|
||||||
|
results: list[FieldPositionResponse] = []
|
||||||
|
sports = db.query(FieldPosition).all()
|
||||||
|
for sport in sports:
|
||||||
|
response = to_response(sport)
|
||||||
|
results.append(response)
|
||||||
|
return results
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer, SecurityScopes
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from typing import Annotated
|
||||||
from src.core.config import settings
|
from src.core.config import settings
|
||||||
from src.core.log_conf import logger
|
from src.core.log_conf import logger
|
||||||
from src.core.security import authenticate_user, create_access_token
|
from src.core.security import authenticate_user_by_email, authenticate_user_by_username, create_access_token
|
||||||
from src.schema.admin import Token
|
from src.schema.admin import Token
|
||||||
|
|
||||||
login_router = APIRouter()
|
login_router = APIRouter()
|
||||||
@@ -25,7 +26,7 @@ class LoginRequest(BaseModel):
|
|||||||
)
|
)
|
||||||
def login(request: LoginRequest) -> Token:
|
def login(request: LoginRequest) -> Token:
|
||||||
logger.info(f"login with {request.email}")
|
logger.info(f"login with {request.email}")
|
||||||
user = authenticate_user(request.email, request.password)
|
user = authenticate_user_by_email(request.email, request.password)
|
||||||
scopes = ["admin", "read"]
|
scopes = ["admin", "read"]
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -39,3 +40,17 @@ def login(request: LoginRequest) -> Token:
|
|||||||
expires_delta=access_token_expires,
|
expires_delta=access_token_expires,
|
||||||
)
|
)
|
||||||
return Token(access_token=access_token, token_type="bearer")
|
return Token(access_token=access_token, token_type="bearer")
|
||||||
|
|
||||||
|
@login_router.post("/token", tags=["login"], summary="Login for access token")
|
||||||
|
async def login_for_access_token(
|
||||||
|
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
|
||||||
|
) -> Token:
|
||||||
|
user = authenticate_user_by_username(form_data.username, form_data.password)
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=400, detail="Incorrect username or password")
|
||||||
|
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||||
|
access_token = create_access_token(
|
||||||
|
data={"sub": user.user_name, "scope": " ".join(form_data.scopes)},
|
||||||
|
expires_delta=access_token_expires,
|
||||||
|
)
|
||||||
|
return Token(access_token=access_token, token_type="bearer")
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from src.db.models.tysc import Player
|
||||||
|
from src.db.session import SessionDep
|
||||||
|
from src.schema.tysc.player import PlayerResponse, to_response
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.get("/players")
|
||||||
|
def get_all_players(db: SessionDep) -> List[PlayerResponse]:
|
||||||
|
results: List[PlayerResponse] = []
|
||||||
|
players = db.query(Player).all()
|
||||||
|
for player in players:
|
||||||
|
response = to_response(player)
|
||||||
|
results.append(response)
|
||||||
|
return results
|
||||||
@@ -2,7 +2,7 @@ from typing import List
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from src.db.session import SessionDep
|
from src.db.session import SessionDep
|
||||||
from src.schema.tysc.sport import SportResponse
|
from src.schema.tysc.sport import SportResponse, to_response
|
||||||
from src.db.models.tysc import Sport
|
from src.db.models.tysc import Sport
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
@@ -12,5 +12,6 @@ def get_all_sports(db: SessionDep) -> List[SportResponse]:
|
|||||||
results: list[SportResponse] = []
|
results: list[SportResponse] = []
|
||||||
sports = db.query(Sport).all()
|
sports = db.query(Sport).all()
|
||||||
for sport in sports:
|
for sport in sports:
|
||||||
results.append(SportResponse(id=sport.id, name=sport.name))
|
response = to_response(sport)
|
||||||
|
results.append(response)
|
||||||
return results
|
return results
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from src.db.models.tysc import Team
|
||||||
|
from src.db.session import SessionDep
|
||||||
|
from src.schema.tysc.team import TeamResponse, to_response
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.get("/teams")
|
||||||
|
def get_all_teams(db: SessionDep) -> List[TeamResponse]:
|
||||||
|
results: list[TeamResponse] = []
|
||||||
|
sports = db.query(Team).all()
|
||||||
|
for sport in sports:
|
||||||
|
response = to_response(sport)
|
||||||
|
results.append(response)
|
||||||
|
return results
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from src.db.models.tysc import Vendor
|
||||||
|
from src.db.session import SessionDep
|
||||||
|
from src.schema.tysc.vendor import VendorResponse, to_response
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.get("/vendors")
|
||||||
|
def get_all_vendors(db: SessionDep) -> List[VendorResponse]:
|
||||||
|
"""
|
||||||
|
retrieve all vendors as json response.
|
||||||
|
"""
|
||||||
|
results: list[VendorResponse] = []
|
||||||
|
vendors = db.query(Vendor).all()
|
||||||
|
for vendor in vendors:
|
||||||
|
response = to_response(vendor)
|
||||||
|
results.append(response)
|
||||||
|
return results
|
||||||
@@ -13,48 +13,47 @@ from pydantic import ValidationError
|
|||||||
from src.core.config import settings
|
from src.core.config import settings
|
||||||
from src.core.log_conf import logger
|
from src.core.log_conf import logger
|
||||||
from src.db.models.admin import Profile
|
from src.db.models.admin import Profile
|
||||||
from src.db.repository.admin import get_profile, is_database_empty
|
from src.db.repository.admin import get_profile_by_username, get_profile_by_email, is_database_empty
|
||||||
from src.db.session import SessionLocal
|
from src.db.session import SessionLocal
|
||||||
from src.schema.admin import ProfileModel, TokenData
|
from src.schema.admin import ProfileModel, TokenData
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(
|
oauth2_scheme = OAuth2PasswordBearer(
|
||||||
tokenUrl="/api/login/token",
|
tokenUrl="/token",
|
||||||
scopes={"me": "read", "admin": "read"},
|
scopes={"me": "read", "admin": "read", "ROLE_ADMIN": "admin", "ROLE_MEDIA": "media", "ROLE_USER": "user"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class OAuth2PasswordBearerWithCookie(OAuth2):
|
# class OAuth2PasswordBearerWithCookie(OAuth2):
|
||||||
def __init__(
|
# def __init__(
|
||||||
self,
|
# self,
|
||||||
tokenUrl: str,
|
# tokenUrl: str,
|
||||||
scheme_name: Optional[str] = None,
|
# scheme_name: Optional[str] = None,
|
||||||
scopes: Optional[Dict[str, str]] = None,
|
# scopes: Optional[Dict[str, str]] = None,
|
||||||
auto_error: bool = True,
|
# auto_error: bool = True,
|
||||||
):
|
# ):
|
||||||
if not scopes:
|
# if not scopes:
|
||||||
scopes = {}
|
# scopes = {}
|
||||||
flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes}) # type: ignore
|
# flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes}) # type: ignore
|
||||||
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
|
# super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
|
||||||
|
|
||||||
async def __call__(self, request: Request) -> Optional[str]:
|
# async def __call__(self, request: Request) -> Optional[str]:
|
||||||
authorization: str = request.cookies.get("access_token") # changed to accept access token from httpOnly Cookie
|
# authorization: str = request.cookies.get("access_token") # changed to accept access token from httpOnly Cookie
|
||||||
|
|
||||||
scheme, param = get_authorization_scheme_param(authorization)
|
# scheme, param = get_authorization_scheme_param(authorization)
|
||||||
if not authorization or scheme.lower() != "bearer":
|
# if not authorization or scheme.lower() != "bearer":
|
||||||
if self.auto_error:
|
# if self.auto_error:
|
||||||
raise HTTPException(
|
# raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
# status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
detail="Not authenticated",
|
# detail="Not authenticated",
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
# headers={"WWW-Authenticate": "Bearer"},
|
||||||
)
|
# )
|
||||||
else:
|
# else:
|
||||||
return None
|
# return None
|
||||||
return param
|
# return param
|
||||||
|
|
||||||
|
def authenticate_user_by_email(email: str, password: str) -> Optional[Profile]:
|
||||||
def authenticate_user(username: str, password: str) -> Optional[Profile]:
|
|
||||||
with SessionLocal() as db:
|
with SessionLocal() as db:
|
||||||
user = get_profile(username=username, db=db)
|
user = get_profile_by_email(email=email, db=db)
|
||||||
logger.debug(user)
|
logger.debug(user)
|
||||||
if not user:
|
if not user:
|
||||||
if is_database_empty(db):
|
if is_database_empty(db):
|
||||||
@@ -65,7 +64,26 @@ def authenticate_user(username: str, password: str) -> Optional[Profile]:
|
|||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
if bcrypt.checkpw(password.encode(), user.password.encode()):
|
if bcrypt.checkpw(password.encode(), user.password.encode()):
|
||||||
print("User successful authenticated")
|
logger.info("User successful authenticated")
|
||||||
|
else:
|
||||||
|
logger.info("Authentication failed!")
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def authenticate_user_by_username(username: str, password: str) -> Optional[Profile]:
|
||||||
|
with SessionLocal() as db:
|
||||||
|
user = get_profile_by_username(username=username, db=db)
|
||||||
|
logger.debug(user)
|
||||||
|
if not user:
|
||||||
|
if is_database_empty(db):
|
||||||
|
logger.info("database is empty, use temporary access")
|
||||||
|
user = Profile()
|
||||||
|
user.email = "init_user@thpeetz.de"
|
||||||
|
return user
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
if bcrypt.checkpw(password.encode(), user.password.encode()):
|
||||||
|
logger.info("User successful authenticated")
|
||||||
else:
|
else:
|
||||||
logger.info("Authentication failed!")
|
logger.info("Authentication failed!")
|
||||||
return user
|
return user
|
||||||
@@ -103,17 +121,19 @@ async def get_current_user(
|
|||||||
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
||||||
)
|
)
|
||||||
username: str = payload.get("sub") # type: ignore
|
username: str = payload.get("sub") # type: ignore
|
||||||
logger.info("username/email extracted is ", username)
|
logger.info("username/email extracted is %s", username)
|
||||||
if username is None:
|
if username is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
scope: str = payload.get("scope", "")
|
scope: str = payload.get("scope", "")
|
||||||
token_scopes: List[str] = scope.split(" ")
|
token_scopes: List[str] = scope.split(" ")
|
||||||
token_data = TokenData(scopes=token_scopes, username=username)
|
token_data = TokenData(scopes=token_scopes, username=username)
|
||||||
except (JWTError, ValidationError):
|
except (JWTError, ValidationError):
|
||||||
|
logger.info("Exception raised", exc_info=True)
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
with SessionLocal() as db:
|
with SessionLocal() as db:
|
||||||
user = get_profile(username=token_data.username, db=db) # type: ignore
|
user = get_profile_by_username(username=token_data.username, db=db)
|
||||||
if user is None:
|
if user is None:
|
||||||
|
logger.info("user not found")
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
for scope in security_scopes.scopes:
|
for scope in security_scopes.scopes:
|
||||||
if scope not in token_scopes:
|
if scope not in token_scopes:
|
||||||
@@ -128,7 +148,7 @@ async def get_current_user(
|
|||||||
async def get_current_active_user(
|
async def get_current_active_user(
|
||||||
current_user: Annotated[Profile, Security(get_current_user, scopes=["me"])],
|
current_user: Annotated[Profile, Security(get_current_user, scopes=["me"])],
|
||||||
) -> ProfileModel:
|
) -> ProfileModel:
|
||||||
if not current_user.enabled: # type: ignore
|
if not current_user.enabled:
|
||||||
raise HTTPException(status_code=400, detail="Inactive user")
|
raise HTTPException(status_code=400, detail="Inactive user")
|
||||||
user_model = ProfileModel(
|
user_model = ProfileModel(
|
||||||
username=current_user.user_name,
|
username=current_user.user_name,
|
||||||
@@ -150,13 +170,13 @@ def get_current_user_from_token(token: str = Depends(oauth2_scheme)):
|
|||||||
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
||||||
)
|
)
|
||||||
username: str = payload.get("sub") # type: ignore
|
username: str = payload.get("sub") # type: ignore
|
||||||
logger.info("username/email extracted is ", username)
|
logger.info("username/email extracted is %s", username)
|
||||||
if username is None:
|
if username is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
except JWTError:
|
except JWTError:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
with SessionLocal() as db:
|
with SessionLocal() as db:
|
||||||
user = get_profile(username=username, db=db)
|
user = get_profile_by_email(email=username, db=db)
|
||||||
if user is None:
|
if user is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
return user
|
return user
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
from typing import AnyStr, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from src.db.models.admin import Profile
|
from src.db.models.admin import Profile
|
||||||
|
|
||||||
|
|
||||||
def get_profile(username: AnyStr, db: Session) -> Optional[Profile]:
|
def get_profile_by_username(username: str, db: Session) -> Optional[Profile]:
|
||||||
profile = db.query(Profile).filter(Profile.email == username).first()
|
profile = db.query(Profile).filter(Profile.user_name == username).first()
|
||||||
|
return profile
|
||||||
|
|
||||||
|
def get_profile_by_email(email: str, db: Session) -> Optional[Profile]:
|
||||||
|
profile = db.query(Profile).filter(Profile.email == email).first()
|
||||||
return profile
|
return profile
|
||||||
|
|
||||||
def is_database_empty(db: Session) -> bool:
|
def is_database_empty(db: Session) -> bool:
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
|
"""
|
||||||
|
Schema definitions for Comics.
|
||||||
|
"""
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from pydantic import BaseModel, AnyUrl
|
from pydantic import BaseModel, AnyUrl
|
||||||
from src.core.log_conf import logger
|
|
||||||
|
|
||||||
|
|
||||||
class ComicResponse(BaseModel):
|
class ComicResponse(BaseModel):
|
||||||
|
"""
|
||||||
|
Pydantic model for returning Comic objects.
|
||||||
|
"""
|
||||||
id: str
|
id: str
|
||||||
title: str
|
title: str
|
||||||
completed: bool
|
completed: bool
|
||||||
|
|
||||||
|
|
||||||
class ComicSchema(BaseModel):
|
class ComicSchema(BaseModel):
|
||||||
|
"""
|
||||||
|
Pydantic model for uploading Comic object.
|
||||||
|
"""
|
||||||
id: str
|
id: str
|
||||||
title: str
|
title: str
|
||||||
weblink: Optional[AnyUrl]
|
weblink: Optional[AnyUrl]
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from src.db.models.tysc import FieldPosition, Team
|
||||||
|
|
||||||
|
|
||||||
|
class FieldPositionResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
created_date: datetime
|
||||||
|
last_modified_date: datetime
|
||||||
|
version: int
|
||||||
|
name: str
|
||||||
|
short_name: str
|
||||||
|
sport_id: str
|
||||||
|
|
||||||
|
|
||||||
|
def to_response(fieldposition: FieldPosition) -> FieldPositionResponse:
|
||||||
|
response: FieldPositionResponse = FieldPositionResponse(
|
||||||
|
id=fieldposition.id,
|
||||||
|
created_date=fieldposition.created_date,
|
||||||
|
last_modified_date=fieldposition.last_modified_date,
|
||||||
|
version=fieldposition.version,
|
||||||
|
name=fieldposition.name,
|
||||||
|
short_name=fieldposition.short_name,
|
||||||
|
sport_id=fieldposition.sport_id
|
||||||
|
)
|
||||||
|
return response
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from src.db.models.tysc import Player
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
created_date: datetime
|
||||||
|
last_modified_date: datetime
|
||||||
|
version: int
|
||||||
|
first_name: str
|
||||||
|
last_name: str
|
||||||
|
|
||||||
|
def to_response(player: Player) -> PlayerResponse:
|
||||||
|
response: PlayerResponse = PlayerResponse(
|
||||||
|
id=player.id,
|
||||||
|
created_date=player.created_date,
|
||||||
|
last_modified_date=player.last_modified_date,
|
||||||
|
version=player.version,
|
||||||
|
first_name=player.first_name,
|
||||||
|
last_name=player.last_name
|
||||||
|
)
|
||||||
|
return response
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from src.db.models.tysc import Rooster
|
||||||
|
|
||||||
|
|
||||||
|
class RoosterResponse(BaseModel):
|
||||||
|
"""
|
||||||
|
Pydantic model for returning Rooster objects.
|
||||||
|
"""
|
||||||
|
id: str
|
||||||
|
created_date: datetime
|
||||||
|
last_modified_date: datetime
|
||||||
|
version: int
|
||||||
|
year: int
|
||||||
|
team_id: str
|
||||||
|
player_id: str
|
||||||
|
position_id: str
|
||||||
|
|
||||||
|
|
||||||
|
def to_reponse(rooster: Rooster) -> RoosterResponse:
|
||||||
|
"""
|
||||||
|
convert database object to response object (Pydantic).
|
||||||
|
"""
|
||||||
|
response: RoosterResponse = RoosterResponse(
|
||||||
|
id=rooster.id,
|
||||||
|
created_date=rooster.created_date,
|
||||||
|
last_modified_date=rooster.last_modified_date,
|
||||||
|
version=rooster.version,
|
||||||
|
year=rooster.year,
|
||||||
|
team_id=rooster.team_id,
|
||||||
|
player_id=rooster.player_id,
|
||||||
|
position_id=rooster.position_id
|
||||||
|
)
|
||||||
|
return response
|
||||||
@@ -1,8 +1,24 @@
|
|||||||
|
|
||||||
from typing import AnyStr
|
from datetime import datetime
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from src.db.models.tysc import Sport
|
||||||
|
|
||||||
|
|
||||||
class SportResponse(BaseModel):
|
class SportResponse(BaseModel):
|
||||||
id: AnyStr
|
id: str
|
||||||
|
created_date: datetime
|
||||||
|
last_modified_date: datetime
|
||||||
|
version: int
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
def to_response(sport: Sport) -> SportResponse:
|
||||||
|
response: SportResponse = SportResponse(
|
||||||
|
id=sport.id,
|
||||||
|
created_date=sport.created_date,
|
||||||
|
last_modified_date=sport.last_modified_date,
|
||||||
|
version=sport.version,
|
||||||
|
name=sport.name
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from src.db.models.tysc import Team
|
||||||
|
|
||||||
|
|
||||||
|
class TeamResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
created_date: datetime
|
||||||
|
last_modified_date: datetime
|
||||||
|
version: int
|
||||||
|
name: str
|
||||||
|
short_name: str
|
||||||
|
sport_id: str
|
||||||
|
|
||||||
|
|
||||||
|
def to_response(team: Team) -> TeamResponse:
|
||||||
|
response: TeamResponse = TeamResponse(
|
||||||
|
id=team.id,
|
||||||
|
created_date=team.created_date,
|
||||||
|
last_modified_date=team.last_modified_date,
|
||||||
|
version=team.version,
|
||||||
|
name=team.name,
|
||||||
|
short_name=team.short_name,
|
||||||
|
sport_id=team.sport_id
|
||||||
|
)
|
||||||
|
return response
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"""
|
||||||
|
class and function for json response objects for Vendor.
|
||||||
|
"""
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from src.db.models.tysc import Vendor
|
||||||
|
|
||||||
|
|
||||||
|
class VendorResponse(BaseModel):
|
||||||
|
"""
|
||||||
|
Pydantic model for Vendor reponse object.
|
||||||
|
"""
|
||||||
|
id: str
|
||||||
|
created_date: datetime
|
||||||
|
last_modified_date: datetime
|
||||||
|
version: int
|
||||||
|
name: str
|
||||||
|
|
||||||
|
def to_response(vendor: Vendor) -> VendorResponse:
|
||||||
|
"""
|
||||||
|
convert database object Vendor to response object VendorResponse.
|
||||||
|
"""
|
||||||
|
reponse: VendorResponse = VendorResponse(
|
||||||
|
id=vendor.id,
|
||||||
|
created_date=vendor.created_date,
|
||||||
|
last_modified_date=vendor.last_modified_date,
|
||||||
|
version=vendor.version,
|
||||||
|
name=vendor.name
|
||||||
|
)
|
||||||
|
return reponse
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#
|
||||||
|
# https://help.github.com/articles/dealing-with-line-endings/
|
||||||
|
#
|
||||||
|
# Linux start script should use lf
|
||||||
|
/gradlew text eol=lf
|
||||||
|
|
||||||
|
# These are Windows script files and should use crlf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
|
||||||
|
# Binary files should be left untouched
|
||||||
|
*.jar binary
|
||||||
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Ignore Gradle project-specific cache directory
|
||||||
|
.gradle
|
||||||
|
|
||||||
|
# Ignore Gradle build output directory
|
||||||
|
build
|
||||||
|
|
||||||
|
# Ignore Kotlin plugin data
|
||||||
|
.kotlin
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* This file was generated by the Gradle 'init' task.
|
||||||
|
*
|
||||||
|
* This is a general purpose Gradle build.
|
||||||
|
* Learn more about Gradle by exploring our Samples at https://docs.gradle.org/9.4.1/samples
|
||||||
|
*/
|
||||||
|
plugins {
|
||||||
|
id("base")
|
||||||
|
id("de.infolektuell.typst") version "0.8.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
typst.version = "v0.14.2"
|
||||||
|
|
||||||
|
typst.sourceSets {
|
||||||
|
// Sources in src/main
|
||||||
|
val main by registering {
|
||||||
|
// The files to compile (without .typ extension) in src/main/typst
|
||||||
|
documents = listOf("kontor") // src/main/typst/document.typst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# This file was generated by the Gradle 'init' task.
|
||||||
|
# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties
|
||||||
|
|
||||||
|
org.gradle.configuration-cache=true
|
||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# This file was generated by the Gradle 'init' task.
|
||||||
|
# https://docs.gradle.org/current/userguide/version_catalogs.html#sec::toml-dependencies-format
|
||||||
BIN
Binary file not shown.
@@ -0,0 +1,7 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
+248
@@ -0,0 +1,248 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
Vendored
+93
@@ -0,0 +1,93 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* This file was generated by the Gradle 'init' task.
|
||||||
|
*
|
||||||
|
* The settings file is used to specify which projects to include in your build.
|
||||||
|
* For more detailed information on multi-project builds, please refer to https://docs.gradle.org/9.4.1/userguide/multi_project_builds.html in the Gradle documentation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
rootProject.name = "kontor-doc"
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
= Anforderungen der Domäne
|
||||||
|
|
||||||
|
== Systemfunktionen
|
||||||
|
|
||||||
|
=== Anwendungsfälle
|
||||||
|
|
||||||
|
=== Akteure
|
||||||
|
|
||||||
|
=== Zielgruppen
|
||||||
|
|
||||||
|
== Anforderungen
|
||||||
|
|
||||||
|
=== Anforderungen an externe Schnittstellen
|
||||||
|
|
||||||
|
=== Funktionale Anforderungen
|
||||||
|
|
||||||
|
=== Qualitätsanforderungen
|
||||||
|
|
||||||
|
=== Randbedingungen
|
||||||
|
|
||||||
|
=== Weitere Anforderungen
|
||||||
|
|
||||||
|
=== Wartungs- und Supportinformationen
|
||||||
|
|
||||||
|
== Verifikation
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
//#set page("a4")
|
||||||
|
#import "@preview/basic-report:0.4.0": *
|
||||||
|
#import "@preview/in-dexter:0.7.2": *
|
||||||
|
#import "@preview/pintorita:0.1.4"
|
||||||
|
|
||||||
|
#set text(fill: white)
|
||||||
|
#show raw.where(lang: "pintora"): it => pintorita.render(it.text)
|
||||||
|
|
||||||
|
#show: it => basic-report(
|
||||||
|
doc-category: "Entwicklungs- und Projekthandbuch",
|
||||||
|
doc-title: "Projekt kontor",
|
||||||
|
author: "Thomas Peetz",
|
||||||
|
//affiliation: "MouseTec, Entenhausen",
|
||||||
|
//logo: image("assets/aerospace-engineering.png", width: 2cm),
|
||||||
|
// <a href="https://www.flaticon.com/free-icons/aerospace" title="aerospace icons">Aerospace icons created by gravisio - Flaticon</a>
|
||||||
|
language: "de",
|
||||||
|
compact-mode: false,
|
||||||
|
it
|
||||||
|
)
|
||||||
|
|
||||||
|
= Allgemeines
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
table(
|
||||||
|
columns: 4,
|
||||||
|
[Version], [Datum], [Autor], [Änderungsgrund / Bemerkungen],
|
||||||
|
[1.0.0], [16.05.2022], [Thomas Peetz], [Ersterstellung],
|
||||||
|
),
|
||||||
|
caption: [Dokumenthistorie],
|
||||||
|
)
|
||||||
|
|
||||||
|
== Zweck des Dokumentes
|
||||||
|
|
||||||
|
Das Entwicklungshandbuch beschreibt die Werkzeuge und die Vorgehensweise bei der Entwicklung
|
||||||
|
im Projekt kontor-spring und der Erstellung der Dokumentation.
|
||||||
|
|
||||||
|
== Verwendete Tools
|
||||||
|
|
||||||
|
=== Gitea
|
||||||
|
|
||||||
|
Für die Verwaltung des Sourcecode kommt Gitea #index[Gitea]@gitea zum Einsatz.
|
||||||
|
Mit Gitea werden auch die Projektaufgaben verwaltet.
|
||||||
|
|
||||||
|
Das Projekt und das dazugehörige Git Repository sind unter der Adresse @gitea-kontor zu finden.
|
||||||
|
|
||||||
|
= Erstellung der Dokumentation
|
||||||
|
|
||||||
|
Die Dokumentation des Projektes wird mit Asciidoctor #index[Asciidoctor]@asciidoctor geschrieben.
|
||||||
|
Die Dokumente erhalten ihre Namen nach dem jeweiligen Hauptdokument.
|
||||||
|
|
||||||
|
== Quellcode Verwaltung
|
||||||
|
|
||||||
|
Die Asciidoctor-Dateien haben die Endung `.adoc`.
|
||||||
|
|
||||||
|
== Buildsystem
|
||||||
|
|
||||||
|
Zur Erstellung der PDF-Dateien aus den Asciidoctor-Dateien wird das Buildsystem Gradle #index[Gradle]@gradle verwendet.
|
||||||
|
Die Dateien für die Dokumente liegen im Verzeichnis `src/docs/asciidoc`.
|
||||||
|
|
||||||
|
Der Gradle Build wird über die Datei `build.gradle` definiert.
|
||||||
|
|
||||||
|
#pagebreak()
|
||||||
|
= Einführung
|
||||||
|
|
||||||
|
== Zweck
|
||||||
|
|
||||||
|
== Stakeholder des Systems
|
||||||
|
|
||||||
|
== Systemumfang
|
||||||
|
|
||||||
|
=== Zielsetzung des Systems
|
||||||
|
|
||||||
|
#pagebreak()
|
||||||
|
#include "system.typ"
|
||||||
|
|
||||||
|
#include "domain.typ"
|
||||||
|
|
||||||
|
#include "projekt.typ"
|
||||||
|
|
||||||
|
= Verschiedenes
|
||||||
|
|
||||||
|
== Erreichbarkeiten
|
||||||
|
|
||||||
|
= Referenzen
|
||||||
|
|
||||||
|
#bibliography("reference.yaml", title: none, full: true)
|
||||||
|
|
||||||
|
= Glossar
|
||||||
|
|
||||||
|
= Index
|
||||||
|
#make-index(title: none, outlined: true, use-page-counter: true)
|
||||||
|
|
||||||
|
= Verzeichnisse
|
||||||
|
|
||||||
|
== Abbildungsverzeichnis
|
||||||
|
#outline(title: none, target: figure.where(kind: image))
|
||||||
|
|
||||||
|
== Tabellenverzeichnis
|
||||||
|
#outline(title: none, target: figure.where(kind: table))
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
= Projektbeschreibung
|
||||||
|
|
||||||
|
== Ausgangslage
|
||||||
|
|
||||||
|
//==== Rechtliche Vorgaben und Rahmenbedingungen
|
||||||
|
//=== Rahmenbedingungen
|
||||||
|
|
||||||
|
//==== Vorhandene Regelungen
|
||||||
|
|
||||||
|
== Projektziele
|
||||||
|
|
||||||
|
== Projektabgrenzung
|
||||||
|
|
||||||
|
//=== Voraussichtliche Kosten
|
||||||
|
|
||||||
|
//=== Projektrisiken
|
||||||
|
|
||||||
|
//==== Produktivität
|
||||||
|
|
||||||
|
//==== Finanzielle Risiken
|
||||||
|
|
||||||
|
//==== Akzeptanz
|
||||||
|
|
||||||
|
= Projektorganisation
|
||||||
|
|
||||||
|
== Projekt-Aufbauorganisation
|
||||||
|
|
||||||
|
== Rollendefinition
|
||||||
|
|
||||||
|
//==== Projektauftraggeber
|
||||||
|
|
||||||
|
//==== Projektausschuss
|
||||||
|
|
||||||
|
//==== Beratung / Qualitätssicherung
|
||||||
|
|
||||||
|
=== Projekteiter
|
||||||
|
|
||||||
|
=== Projektteam
|
||||||
|
|
||||||
|
=== Liste der Stakeholder
|
||||||
|
|
||||||
|
== Projektablauforganisation
|
||||||
|
|
||||||
|
=== Projekt-Phasen
|
||||||
|
|
||||||
|
== Erstellung der Projektdokumentation
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
asciidoctor:
|
||||||
|
type: web
|
||||||
|
url: http://asciidoctor.org
|
||||||
|
gitea:
|
||||||
|
type: web
|
||||||
|
url: http://www.gitea.org
|
||||||
|
gradle:
|
||||||
|
type: web
|
||||||
|
url: http://www.gradle.org
|
||||||
|
jenkins:
|
||||||
|
type: web
|
||||||
|
url: http://jenkins-ci.org
|
||||||
|
gitea-kontor:
|
||||||
|
type: web
|
||||||
|
url: https://gitea.thpeetz.de/kontor/kontor-spring
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
= Systemübersicht
|
||||||
|
|
||||||
|
== Systemkontext
|
||||||
|
|
||||||
|
== Systemarchitektur
|
||||||
|
|
||||||
|
== Systemschnittstellen
|
||||||
|
|
||||||
|
=== Realisierte Schnittstellen
|
||||||
|
|
||||||
|
=== Verwendete Schnittstellen
|
||||||
|
|
||||||
|
== Logisches Datenmodell
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
kind: image,
|
||||||
|
```pintora
|
||||||
|
erDiagram
|
||||||
|
USER {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string email
|
||||||
|
boolean enabled
|
||||||
|
string firstName
|
||||||
|
string lastName
|
||||||
|
string password
|
||||||
|
string token
|
||||||
|
boolean tokenExpired
|
||||||
|
string userName UNIQUE
|
||||||
|
}
|
||||||
|
USER ||--o{ AUTHORIZATION_MATRIX : "matrix"
|
||||||
|
ROLE {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string name
|
||||||
|
}
|
||||||
|
ROLE ||--o{ AUTHORIZATION_MATRIX : "matrix"
|
||||||
|
AUTHORIZATION_MATRIX {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string user_id FK
|
||||||
|
string role_id FK
|
||||||
|
}
|
||||||
|
```,
|
||||||
|
caption: [Benutzer ER-Diagramm]
|
||||||
|
)
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
kind: image,
|
||||||
|
```pintora
|
||||||
|
erDiagram
|
||||||
|
comic {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
boolean completed
|
||||||
|
boolean currentOrder
|
||||||
|
string title
|
||||||
|
string publisher_id FK
|
||||||
|
}
|
||||||
|
comic ||--o{ comic_work : "1"
|
||||||
|
comic ||--o{ issue : "1"
|
||||||
|
comic ||--o{ volume : "1"
|
||||||
|
comic ||--o{ story_arc : "1"
|
||||||
|
comic ||--o{ trade_paperback : "1"
|
||||||
|
volume {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string name
|
||||||
|
string comic_id FK
|
||||||
|
}
|
||||||
|
volume ||--o{ issue : "1"
|
||||||
|
issue {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
boolean in_stock
|
||||||
|
boolean is_read
|
||||||
|
string issue_number
|
||||||
|
string comic_id FK
|
||||||
|
string volume_id FK
|
||||||
|
}
|
||||||
|
publisher {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string name
|
||||||
|
}
|
||||||
|
publisher ||--o{ comic : "1"
|
||||||
|
artist {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string name
|
||||||
|
}
|
||||||
|
artist ||--o{ comic_work : "1"
|
||||||
|
story_arc {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string name
|
||||||
|
string comic_id FK
|
||||||
|
}
|
||||||
|
trade_paperback {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
int issueStart
|
||||||
|
int issueEnd
|
||||||
|
string name
|
||||||
|
string comic_id FK
|
||||||
|
}
|
||||||
|
worktype {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string name
|
||||||
|
}
|
||||||
|
worktype ||--o{ comic-work : "1"
|
||||||
|
comic_work {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string artist_id FK
|
||||||
|
string comic_id FK
|
||||||
|
string worktype_id FK
|
||||||
|
}
|
||||||
|
```,
|
||||||
|
caption: [Comics ER-Diagramm]
|
||||||
|
)
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
kind: image,
|
||||||
|
```pintora
|
||||||
|
erDiagram
|
||||||
|
sport {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string name
|
||||||
|
}
|
||||||
|
team {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string name
|
||||||
|
string short_name
|
||||||
|
string sport_id FK
|
||||||
|
}
|
||||||
|
field_position {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string name
|
||||||
|
string short_name
|
||||||
|
string sport_id FK
|
||||||
|
}
|
||||||
|
rooster {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
int year
|
||||||
|
string player_id FK
|
||||||
|
string position_id FK
|
||||||
|
string team_id FK
|
||||||
|
}
|
||||||
|
player {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string first_name
|
||||||
|
string last_name
|
||||||
|
}
|
||||||
|
vendor {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string name
|
||||||
|
}
|
||||||
|
card_set {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
boolean insert_set
|
||||||
|
string name
|
||||||
|
boolean parallel_set
|
||||||
|
string vendor_id FK
|
||||||
|
}
|
||||||
|
card {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
int cardNumber
|
||||||
|
int year
|
||||||
|
string card_set FK
|
||||||
|
string rooster_id FK
|
||||||
|
string vendor_id FK
|
||||||
|
}
|
||||||
|
sport ||--o{ team : "1"
|
||||||
|
sport ||--o{ field_position : "1"
|
||||||
|
field_position ||--o{ rooster : "1"
|
||||||
|
player ||--o{ rooster : "1"
|
||||||
|
team ||--o{ rooster : "1"
|
||||||
|
vendor ||--o{ card : "1"
|
||||||
|
card_set ||--o{ card : "1"
|
||||||
|
rooster ||--o{ card : "1"
|
||||||
|
```,
|
||||||
|
caption: [TYSC ER-Diagramm]
|
||||||
|
)
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
kind: image,
|
||||||
|
```pintora
|
||||||
|
erDiagram
|
||||||
|
book {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string isbn UNIQUE
|
||||||
|
string title
|
||||||
|
int year
|
||||||
|
string publisher_id FK
|
||||||
|
}
|
||||||
|
bookshelf_publisher {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string name UNIQUE
|
||||||
|
}
|
||||||
|
author {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string first_name
|
||||||
|
string last_name
|
||||||
|
}
|
||||||
|
article {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string title
|
||||||
|
}
|
||||||
|
article_author {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string article_id FK
|
||||||
|
string author_id FK
|
||||||
|
}
|
||||||
|
book_author {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string book_id FK
|
||||||
|
string author_id FK
|
||||||
|
}
|
||||||
|
bookshelf_publisher ||--o{ book : "1"
|
||||||
|
article ||--o{ article_author : "1"
|
||||||
|
author ||--o{ article_author : "1"
|
||||||
|
book ||--o{ book_author : "1"
|
||||||
|
author ||--o{ book_author : "1"
|
||||||
|
```,
|
||||||
|
caption: [Bookshelf ER-Diagramm]
|
||||||
|
)
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
kind: image,
|
||||||
|
```pintora
|
||||||
|
erDiagram
|
||||||
|
mail {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string subject
|
||||||
|
string content
|
||||||
|
datetime received_date
|
||||||
|
datetime sent_date
|
||||||
|
}
|
||||||
|
mail_account {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string host
|
||||||
|
string password
|
||||||
|
int port
|
||||||
|
string protocol
|
||||||
|
boolean start_tls
|
||||||
|
string user_name
|
||||||
|
}
|
||||||
|
mail_address {
|
||||||
|
string id PK
|
||||||
|
datetime created_date
|
||||||
|
datetime last_modified_date
|
||||||
|
int version
|
||||||
|
string internet_address UNIQUE
|
||||||
|
string personal
|
||||||
|
string user_id FK
|
||||||
|
}
|
||||||
|
user ||--o{ mail_address : "1"
|
||||||
|
```,
|
||||||
|
caption: [Mail ER-Diagramm]
|
||||||
|
)
|
||||||
|
|
||||||
|
=== Einschränkungen
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
|
|
||||||
clean:
|
|
||||||
find . -name '*.py[co]' -delete
|
|
||||||
|
|
||||||
test:
|
|
||||||
python -m pytest \
|
|
||||||
-v \
|
|
||||||
--cov=kontor \
|
|
||||||
--cov-report=term \
|
|
||||||
--cov-report=html:coverage-report \
|
|
||||||
tests/
|
|
||||||
|
|
||||||
docker: clean
|
|
||||||
docker build -t kontor:latest .
|
|
||||||
|
|
||||||
@@ -1,369 +0,0 @@
|
|||||||
"""
|
|
||||||
add actors
|
|
||||||
"""
|
|
||||||
import logging.config
|
|
||||||
import requests
|
|
||||||
import re
|
|
||||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
|
|
||||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
|
||||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
def get_logger(level: int) -> logging.Logger:
|
|
||||||
logging.config.dictConfig({
|
|
||||||
'version': 1,
|
|
||||||
'disable_existing_loggers': False,
|
|
||||||
'formatters': {
|
|
||||||
'simple': {
|
|
||||||
'format': '[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s',
|
|
||||||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'handlers': {
|
|
||||||
'console': {
|
|
||||||
'class': logging.StreamHandler,
|
|
||||||
'level': logging.DEBUG,
|
|
||||||
'formatter': 'simple',
|
|
||||||
'stream': 'ext://sys.stdout'
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'loggers': {
|
|
||||||
'urllib3.connectionpool': {
|
|
||||||
'level': 'WARNING',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
'root': {
|
|
||||||
'level': 'DEBUG',
|
|
||||||
'handlers': ['console'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
logger = logging.getLogger(__file__)
|
|
||||||
if level is not None:
|
|
||||||
match level:
|
|
||||||
case 0:
|
|
||||||
logger.setLevel(logging.WARNING)
|
|
||||||
case 1:
|
|
||||||
logger.setLevel(logging.INFO)
|
|
||||||
case 2:
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
case _:
|
|
||||||
logger.setLevel(logging.CRITICAL)
|
|
||||||
return logger
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
log = get_logger(args.verbose)
|
|
||||||
log.warning('kontor.add_actors started')
|
|
||||||
log.debug('get all actors')
|
|
||||||
response = requests.get("http://127.0.0.1:8800/api/media/actors")
|
|
||||||
data = response.json()
|
|
||||||
actors = {}
|
|
||||||
for item in data:
|
|
||||||
actor = {}
|
|
||||||
actor['id'] = item['id']
|
|
||||||
actor['name'] = item['name']
|
|
||||||
actor['url'] = item['url']
|
|
||||||
actors[item['url']] = actor
|
|
||||||
log.debug(f'all actors: {actors}')
|
|
||||||
|
|
||||||
new_actor_list = [
|
|
||||||
{ 'name': 'Herschel Savage', 'url': 'https://ge.xhamster.com/pornstars/herschel-savage'},
|
|
||||||
{ 'name': 'Janey Robbins', 'url': 'https://ge.xhamster.com/pornstars/janey-robbins'},
|
|
||||||
{ 'name': 'Kimberly Carson', 'url': 'https://ge.xhamster.com/pornstars/kimberly-carson'},
|
|
||||||
{ 'name': 'Paul Thomas', 'url': 'https://ge.xhamster.com/pornstars/paul-thomas'},
|
|
||||||
{ 'name': 'Shauna Grant', 'url': 'https://ge.xhamster.com/pornstars/shauna-grant'},
|
|
||||||
{ 'name': 'Tara Aire', 'url': 'https://ge.xhamster.com/pornstars/tara-aire'},
|
|
||||||
{ 'name': 'Kari Milla', 'url': 'https://ge.xhamster.com/pornstars/kari-milla'},
|
|
||||||
{ 'name': 'Drago Gvozdik', 'url': 'https://ge.xhamster.com/pornstars/drago-gvozdik'},
|
|
||||||
{ 'name': 'Cheyne Collins', 'url': 'https://ge.xhamster.com/pornstars/cheyne-collins'},
|
|
||||||
{ 'name': 'Christian XXX', 'url': 'https://ge.xhamster.com/pornstars/christian-xxx'},
|
|
||||||
{ 'name': 'Derrick Pierce', 'url': 'https://ge.xhamster.com/pornstars/derrick-pierce'},
|
|
||||||
{ 'name': 'Holly Halston', 'url': 'https://ge.xhamster.com/pornstars/holly-halston'},
|
|
||||||
{ 'name': 'Holly West', 'url': 'https://ge.xhamster.com/pornstars/holly-west'},
|
|
||||||
{ 'name': 'Jarod Diamond', 'url': 'https://ge.xhamster.com/pornstars/jarod-diamond'},
|
|
||||||
{ 'name': 'Kristina Cross', 'url': 'https://ge.xhamster.com/pornstars/kristina-cross'},
|
|
||||||
{ 'name': 'Lee Stone', 'url': 'https://ge.xhamster.com/pornstars/lee-stone'},
|
|
||||||
{ 'name': 'Monica Mayhem', 'url': 'https://ge.xhamster.com/pornstars/monica-mayhem'},
|
|
||||||
{ 'name': 'Sienna West', 'url': 'https://ge.xhamster.com/pornstars/sienna-west'},
|
|
||||||
{ 'name': 'Aria Carson', 'url': 'https://ge.xhamster.com/pornstars/aria-carson'},
|
|
||||||
{ 'name': 'Britney Amber', 'url': 'https://ge.xhamster.com/pornstars/britney-amber'},
|
|
||||||
{ 'name': 'Kit Mercer', 'url': 'https://ge.xhamster.com/pornstars/kit-mercer'},
|
|
||||||
{ 'name': 'Riley Reyes', 'url': 'https://ge.xhamster.com/pornstars/riley-reyes'},
|
|
||||||
{ 'name': 'Ryan Keely', 'url': 'https://ge.xhamster.com/pornstars/ryan-keely'},
|
|
||||||
{ 'name': 'Alexandra Diamond', 'url': 'https://ge.xhamster.com/pornstars/alexandra-diamond'},
|
|
||||||
{ 'name': 'Amanda Twice', 'url': 'https://ge.xhamster.com/pornstars/amanda-twice'},
|
|
||||||
{ 'name': 'Choky Ice', 'url': 'https://ge.xhamster.com/pornstars/choky-ice'},
|
|
||||||
{ 'name': 'Cindy Cox', 'url': 'https://ge.xhamster.com/pornstars/cindy-cox'},
|
|
||||||
{ 'name': 'Kelly White', 'url': 'https://ge.xhamster.com/pornstars/kelly-white'},
|
|
||||||
{ 'name': 'Mike Foster', 'url': 'https://ge.xhamster.com/pornstars/mike-foster'},
|
|
||||||
{ 'name': 'Susie Sorrento', 'url': 'https://ge.xhamster.com/pornstars/susie-sorrento'},
|
|
||||||
{ 'name': 'Lara Sanchez', 'url': 'https://ge.xhamster.com/pornstars/lara-sanchez'},
|
|
||||||
{ 'name': 'Alicia Williams', 'url': 'https://ge.xhamster.com/pornstars/alicia-williams'},
|
|
||||||
{ 'name': 'Dirty Tina', 'url': 'https://ge.xhamster.com/pornstars/dirty-tina'},
|
|
||||||
{ 'name': 'Andrew Andretti', 'url': 'https://ge.xhamster.com/pornstars/andrew-andretti'},
|
|
||||||
{ 'name': 'Dick Nasty', 'url': 'https://ge.xhamster.com/pornstars/dick-nasty'},
|
|
||||||
{ 'name': 'Vicky Vette', 'url': 'https://ge.xhamster.com/pornstars/vicky-vette'},
|
|
||||||
{ 'name': 'Alex Gonz', 'url': 'https://ge.xhamster.com/pornstars/alex-gonz'},
|
|
||||||
{ 'name': 'Audrey Hollander', 'url': 'https://ge.xhamster.com/pornstars/audrey-hollander'},
|
|
||||||
{ 'name': 'Desiree Diamond', 'url': 'https://ge.xhamster.com/pornstars/desiree-diamond'},
|
|
||||||
{ 'name': 'Lexi Bardot', 'url': 'https://ge.xhamster.com/pornstars/lexi-bardot'},
|
|
||||||
{ 'name': 'Adam Ocelot', 'url': 'https://ge.xhamster.com/pornstars/adam-ocelot'},
|
|
||||||
{ 'name': 'Scarlett Hampton', 'url': 'https://ge.xhamster.com/pornstars/scarlett-hampton'},
|
|
||||||
{ 'name': 'Laetitia', 'url': 'https://ge.xhamster.com/pornstars/laetitia'},
|
|
||||||
{ 'name': 'Raffaela Anderson', 'url': 'https://ge.xhamster.com/pornstars/raffaela-anderson'},
|
|
||||||
{ 'name': 'Michael Swayze', 'url': 'https://ge.xhamster.com/pornstars/michael-swayze'},
|
|
||||||
{ 'name': 'Winter Jade', 'url': 'https://ge.xhamster.com/pornstars/winter-jade'},
|
|
||||||
{ 'name': 'Celine Noiret', 'url': 'https://ge.xhamster.com/pornstars/celine-noiret'},
|
|
||||||
{ 'name': 'Aurora Snow', 'url': 'https://ge.xhamster.com/pornstars/aurora-snow'},
|
|
||||||
{ 'name': 'Bree Brooks', 'url': 'https://ge.xhamster.com/pornstars/bree-brooks'},
|
|
||||||
{ 'name': 'Flick Shagwell', 'url': 'https://ge.xhamster.com/pornstars/flick-shagwell'},
|
|
||||||
{ 'name': 'Noname Jane', 'url': 'https://ge.xhamster.com/pornstars/noname-jane'},
|
|
||||||
{ 'name': 'Alex Magni', 'url': 'https://ge.xhamster.com/pornstars/alex-magni'},
|
|
||||||
{ 'name': 'Maeva Dream', 'url': 'https://ge.xhamster.com/pornstars/maeva-dream'},
|
|
||||||
{ 'name': 'Squirty Alice', 'url': 'https://ge.xhamster.com/pornstars/squirty-alice'},
|
|
||||||
{ 'name': 'Mandy Rhea', 'url': 'https://ge.xhamster.com/pornstars/mandy-rhea'},
|
|
||||||
{ 'name': 'Alessia Donati', 'url': 'https://ge.xhamster.com/pornstars/alessia-donati'},
|
|
||||||
{ 'name': 'Pierre DJ', 'url': 'https://ge.xhamster.com/pornstars/pierre-dj'},
|
|
||||||
{ 'name': 'Veronica Belli', 'url': 'https://ge.xhamster.com/pornstars/veronica-belli'},
|
|
||||||
{ 'name': 'Christiana Cinn', 'url': 'https://ge.xhamster.com/pornstars/christiana-cinn'},
|
|
||||||
{ 'name': 'Jasmine Jae', 'url': 'https://ge.xhamster.com/pornstars/jasmine-jae'},
|
|
||||||
{ 'name': 'Jay Smooth', 'url': 'https://ge.xhamster.com/pornstars/jay-smooth'},
|
|
||||||
{ 'name': 'Natalie Knight', 'url': 'https://ge.xhamster.com/pornstars/natalie-knight'},
|
|
||||||
{ 'name': 'Joshua Lewis', 'url': 'https://ge.xhamster.com/pornstars/joshua-lewis'},
|
|
||||||
{ 'name': 'Lauren Phillips', 'url': 'https://ge.xhamster.com/pornstars/lauren-phillips'},
|
|
||||||
{ 'name': 'Matt Cash', 'url': 'https://ge.xhamster.com/pornstars/matt-cash'},
|
|
||||||
{ 'name': 'Nickey Huntsman', 'url': 'https://ge.xhamster.com/pornstars/nickey-huntsman'},
|
|
||||||
{ 'name': 'Jade Sin', 'url': 'https://ge.xhamster.com/pornstars/jade-sin'},
|
|
||||||
{ 'name': 'Wein Lewis', 'url': 'https://ge.xhamster.com/pornstars/wein-lewis'},
|
|
||||||
{ 'name': 'Megan Murkovski', 'url': 'https://ge.xhamster.com/pornstars/megan-murkovski'},
|
|
||||||
{ 'name': 'Tara Wild', 'url': 'https://ge.xhamster.com/pornstars/tara-wild'},
|
|
||||||
{ 'name': 'Lana Roy', 'url': 'https://ge.xhamster.com/pornstars/lana-roy'},
|
|
||||||
{ 'name': 'Nick Moreno', 'url': 'https://ge.xhamster.com/pornstars/nick-moreno'},
|
|
||||||
{ 'name': 'Brad Armstrong', 'url': 'https://ge.xhamster.com/pornstars/brad-armstrong'},
|
|
||||||
{ 'name': 'Kaylani Lei', 'url': 'https://ge.xhamster.com/pornstars/kaylani-lei'},
|
|
||||||
{ 'name': 'Deborah Wells', 'url': 'https://ge.xhamster.com/pornstars/deborah-wells'},
|
|
||||||
{ 'name': 'Judith Ramirez', 'url': 'https://ge.xhamster.com/pornstars/judith-ramirez'},
|
|
||||||
{ 'name': 'Richard Langin', 'url': 'https://ge.xhamster.com/pornstars/richard-langin'},
|
|
||||||
{ 'name': 'Simona Valli', 'url': 'https://ge.xhamster.com/pornstars/simona-valli'},
|
|
||||||
{ 'name': 'Mandy Dee', 'url': 'https://ge.xhamster.com/pornstars/mandy-dee'},
|
|
||||||
{ 'name': 'Allison Wyte', 'url': 'https://ge.xhamster.com/pornstars/allison-wyte'},
|
|
||||||
{ 'name': 'Ben English', 'url': 'https://ge.xhamster.com/pornstars/ben-english'},
|
|
||||||
{ 'name': 'Chuck Martino', 'url': 'https://ge.xhamster.com/pornstars/chuck-martino'},
|
|
||||||
{ 'name': 'Erika Kole', 'url': 'https://ge.xhamster.com/pornstars/erika-kole'},
|
|
||||||
{ 'name': 'Katie Morgan', 'url': 'https://ge.xhamster.com/pornstars/katie-morgan'},
|
|
||||||
{ 'name': 'Misty Parks', 'url': 'https://ge.xhamster.com/pornstars/misty-parks'},
|
|
||||||
{ 'name': 'Mr. Pete', 'url': 'https://ge.xhamster.com/pornstars/mr-pete'},
|
|
||||||
{ 'name': 'Candie Luciani', 'url': 'https://ge.xhamster.com/pornstars/candie-luciani'},
|
|
||||||
{ 'name': 'Clara Mia', 'url': 'https://ge.xhamster.com/pornstars/clara-mia'},
|
|
||||||
{ 'name': 'Tommy Cabrio', 'url': 'https://ge.xhamster.com/pornstars/tommy-cabrio'},
|
|
||||||
{ 'name': 'Nathan Bronson', 'url': 'https://ge.xhamster.com/pornstars/nathan-bronson'},
|
|
||||||
{ 'name': 'Octavia Red', 'url': 'https://ge.xhamster.com/pornstars/octavia-red'},
|
|
||||||
{ 'name': 'Big George', 'url': 'https://ge.xhamster.com/pornstars/big-george'},
|
|
||||||
{ 'name': 'Cassy Young', 'url': 'https://ge.xhamster.com/pornstars/cassy-young'},
|
|
||||||
{ 'name': 'Egon Kowalski', 'url': 'https://ge.xhamster.com/pornstars/egon-kowalski'},
|
|
||||||
{ 'name': 'Mark Aurel', 'url': 'https://ge.xhamster.com/pornstars/mark-aurel'},
|
|
||||||
{ 'name': 'Samy Fox', 'url': 'https://ge.xhamster.com/pornstars/samy-fox'},
|
|
||||||
{ 'name': 'Valeria Jones', 'url': 'https://ge.xhamster.com/pornstars/valeria-jones'},
|
|
||||||
{ 'name': 'Dieter Von Stein', 'url': 'https://ge.xhamster.com/pornstars/dieter-von-stein'},
|
|
||||||
{ 'name': 'Donna Bell', 'url': 'https://ge.xhamster.com/pornstars/donna-bell'},
|
|
||||||
{ 'name': 'Molly Jane', 'url': 'https://ge.xhamster.com/pornstars/molly-jane'},
|
|
||||||
{ 'name': 'Mark Zane', 'url': 'https://ge.xhamster.com/pornstars/mark-zane'},
|
|
||||||
{ 'name': 'Titus Steel', 'url': 'https://ge.xhamster.com/pornstars/titus-steel'},
|
|
||||||
{ 'name': 'Mackenzie Mace', 'url': 'https://ge.xhamster.com/pornstars/mackenzie-mace'},
|
|
||||||
{ 'name': 'Christina Lang', 'url': 'https://ge.xhamster.com/pornstars/christina-lang'},
|
|
||||||
{ 'name': 'Jens Modena', 'url': 'https://ge.xhamster.com/pornstars/jens-modena'},
|
|
||||||
{ 'name': 'Karma Rosenberg', 'url': 'https://ge.xhamster.com/pornstars/karma-rosenberg'},
|
|
||||||
{ 'name': 'Dani Jensen', 'url': 'https://ge.xhamster.com/pornstars/dani-jensen'},
|
|
||||||
{ 'name': 'Crystal Ray', 'url': 'https://ge.xhamster.com/pornstars/crystal-ray'},
|
|
||||||
{ 'name': 'Andrea Szabo', 'url': 'https://ge.xhamster.com/pornstars/andrea-szabo'},
|
|
||||||
{ 'name': 'Desiree Barclay', 'url': 'https://ge.xhamster.com/pornstars/desiree-barclay'},
|
|
||||||
{ 'name': 'Jada Stevens', 'url': 'https://ge.xhamster.com/pornstars/jada-stevens'},
|
|
||||||
{ 'name': 'Syren Demer', 'url': 'https://ge.xhamster.com/pornstars/syren-demer'},
|
|
||||||
{ 'name': 'Ara Mix', 'url': 'https://ge.xhamster.com/pornstars/ara-mix'},
|
|
||||||
{ 'name': 'Redneck John', 'url': 'https://ge.xhamster.com/pornstars/redneck-john'},
|
|
||||||
{ 'name': 'Allysin Chaynes', 'url': 'https://ge.xhamster.com/pornstars/allysin-chaynes'},
|
|
||||||
{ 'name': 'Aspen Brock', 'url': 'https://ge.xhamster.com/pornstars/aspen-brock'},
|
|
||||||
{ 'name': 'Kaylynn', 'url': 'https://ge.xhamster.com/pornstars/kaylynn'},
|
|
||||||
{ 'name': 'Monica Cameron', 'url': 'https://ge.xhamster.com/pornstars/monica-cameron'},
|
|
||||||
{ 'name': 'Sheila Rossi', 'url': 'https://ge.xhamster.com/pornstars/sheila-rossi'},
|
|
||||||
{ 'name': 'Alex Ginger', 'url': 'https://ge.xhamster.com/pornstars/alex-ginger'},
|
|
||||||
{ 'name': 'Ally Style', 'url': 'https://ge.xhamster.com/pornstars/ally-style'},
|
|
||||||
{ 'name': 'Paola Mike', 'url': 'https://ge.xhamster.com/pornstars/paola-mike'},
|
|
||||||
{ 'name': 'Jeanette Littledove', 'url': 'https://ge.xhamster.com/pornstars/jeanette-littledove'},
|
|
||||||
{ 'name': 'Melissa Melendez', 'url': 'https://ge.xhamster.com/pornstars/melissa-melendez'},
|
|
||||||
{ 'name': 'Peter North', 'url': 'https://ge.xhamster.com/pornstars/peter-north'},
|
|
||||||
{ 'name': 'Siobhan Hunter', 'url': 'https://ge.xhamster.com/pornstars/siobhan-hunter'},
|
|
||||||
{ 'name': 'Tami White', 'url': 'https://ge.xhamster.com/pornstars/tami-white'},
|
|
||||||
{ 'name': 'Tracey Adams', 'url': 'https://ge.xhamster.com/pornstars/tracey-adams'},
|
|
||||||
{ 'name': 'Ashley Haze', 'url': 'https://ge.xhamster.com/pornstars/ashley-haze'},
|
|
||||||
{ 'name': 'Cailey Taylor', 'url': 'https://ge.xhamster.com/pornstars/cailey-taylor'},
|
|
||||||
{ 'name': 'Eve Laurence', 'url': 'https://ge.xhamster.com/pornstars/eve-laurence'},
|
|
||||||
{ 'name': 'Naudia Nyce', 'url': 'https://ge.xhamster.com/pornstars/naudia-nyce'},
|
|
||||||
{ 'name': 'Candy Apples', 'url': 'https://ge.xhamster.com/pornstars/candy-apples'},
|
|
||||||
{ 'name': 'Kate Rich', 'url': 'https://ge.xhamster.com/pornstars/kate-rich'},
|
|
||||||
{ 'name': 'Aniko Kaposi', 'url': 'https://ge.xhamster.com/pornstars/aniko-kaposi'},
|
|
||||||
{ 'name': 'Beatrice Poggi', 'url': 'https://ge.xhamster.com/pornstars/beatrice-poggi'},
|
|
||||||
{ 'name': 'Erika Bella', 'url': 'https://ge.xhamster.com/pornstars/erika-bella'},
|
|
||||||
{ 'name': 'Nikita Gross', 'url': 'https://ge.xhamster.com/pornstars/nikita-gross'},
|
|
||||||
{ 'name': 'Ursula Moore', 'url': 'https://ge.xhamster.com/pornstars/ursula-moore'},
|
|
||||||
{ 'name': 'Caty Kiss', 'url': 'https://ge.xhamster.com/pornstars/caty-kiss'},
|
|
||||||
{ 'name': 'Light Fairy', 'url': 'https://ge.xhamster.com/pornstars/light-fairy'},
|
|
||||||
{ 'name': 'Flame', 'url': 'https://ge.xhamster.com/pornstars/flame'},
|
|
||||||
{ 'name': 'Tiffany Tatum', 'url': 'https://ge.xhamster.com/pornstars/tiffany-tatum'},
|
|
||||||
{ 'name': 'Alyson Sykes', 'url': 'https://ge.xhamster.com/pornstars/alyson-sykes'},
|
|
||||||
{ 'name': 'Jenifer Stone', 'url': 'https://ge.xhamster.com/pornstars/jenifer-stone'},
|
|
||||||
{ 'name': 'Lucy Love', 'url': 'https://ge.xhamster.com/pornstars/lucy-love'},
|
|
||||||
{ 'name': 'Thomas Stone', 'url': 'https://ge.xhamster.com/pornstars/thomas-stone'},
|
|
||||||
{ 'name': 'Nikki Babe', 'url': 'https://ge.xhamster.com/pornstars/nikki-babe'},
|
|
||||||
{ 'name': 'Tyra Misoux', 'url': 'https://ge.xhamster.com/pornstars/tyra-misoux'},
|
|
||||||
{ 'name': 'Kenzie Reeves', 'url': 'https://ge.xhamster.com/pornstars/kenzie-reeves'},
|
|
||||||
{ 'name': 'Anissa Kate', 'url': 'https://ge.xhamster.com/pornstars/anissa-kate'},
|
|
||||||
{ 'name': 'Anna Polina', 'url': 'https://ge.xhamster.com/pornstars/anna-polina'},
|
|
||||||
{ 'name': 'Kimber Delice', 'url': 'https://ge.xhamster.com/pornstars/kimber-delice'},
|
|
||||||
{ 'name': 'Lucy Heart', 'url': 'https://ge.xhamster.com/pornstars/lucy-heart'},
|
|
||||||
{ 'name': 'John Strong', 'url': 'https://ge.xhamster.com/pornstars/john-strong'},
|
|
||||||
{ 'name': 'Markus Dupree', 'url': 'https://ge.xhamster.com/pornstars/markus-dupree'},
|
|
||||||
{ 'name': 'Mick Blue', 'url': 'https://ge.xhamster.com/pornstars/mick-blue'},
|
|
||||||
{ 'name': 'Natasha Nice', 'url': 'https://ge.xhamster.com/pornstars/natasha-nice'},
|
|
||||||
{ 'name': 'Lena Reif', 'url': 'https://ge.xhamster.com/pornstars/lena-reif'},
|
|
||||||
{ 'name': 'Sonia Paganini', 'url': 'https://ge.xhamster.com/pornstars/sonia-paganini'},
|
|
||||||
{ 'name': 'Demi Hawks', 'url': 'https://ge.xhamster.com/pornstars/demi-hawks'},
|
|
||||||
{ 'name': 'Juan El Caballo Loco', 'url': 'https://ge.xhamster.com/pornstars/juan-el-caballo-loco'},
|
|
||||||
{ 'name': 'Mike Mancini', 'url': 'https://ge.xhamster.com/pornstars/mike-mancini'},
|
|
||||||
{ 'name': 'Millie Morgan', 'url': 'https://ge.xhamster.com/pornstars/millie-morgan'},
|
|
||||||
{ 'name': 'Richard Allan', 'url': 'https://ge.xhamster.com/pornstars/richard-allan'},
|
|
||||||
{ 'name': 'Damon Dice', 'url': 'https://ge.xhamster.com/pornstars/damon-dice'},
|
|
||||||
{ 'name': 'Sera Ryder', 'url': 'https://ge.xhamster.com/pornstars/sera-ryder'},
|
|
||||||
{ 'name': 'Zoltan Kaabai', 'url': 'https://ge.xhamster.com/pornstars/zoltan-kabai'},
|
|
||||||
{ 'name': 'Cathy Heaven', 'url': 'https://ge.xhamster.com/pornstars/cathy-heaven'},
|
|
||||||
{ 'name': 'Coco Lovelock', 'url': 'https://ge.xhamster.com/pornstars/coco-lovelock'},
|
|
||||||
{ 'name': 'Percy Sires', 'url': 'https://ge.xhamster.com/pornstars/percy-sires'},
|
|
||||||
{ 'name': 'Meridian', 'url': 'https://ge.xhamster.com/pornstars/meridian'},
|
|
||||||
{ 'name': 'Pascal St. James', 'url': 'https://ge.xhamster.com/pornstars/pascal-st-james'},
|
|
||||||
{ 'name': 'Red Fox', 'url': 'https://ge.xhamster.com/pornstars/red-fox'},
|
|
||||||
{ 'name': 'Tony Art', 'url': 'https://ge.xhamster.com/pornstars/tony-art'},
|
|
||||||
{ 'name': 'Addison Lee', 'url': 'https://ge.xhamster.com/pornstars/addison-lee'},
|
|
||||||
{ 'name': 'Daria Glover', 'url': 'https://ge.xhamster.com/pornstars/daria-glover'},
|
|
||||||
{ 'name': 'Mandy Bright', 'url': 'https://ge.xhamster.com/pornstars/mandy-bright'},
|
|
||||||
{ 'name': 'Antonia Sainz', 'url': 'https://ge.xhamster.com/pornstars/antonia-sainz'},
|
|
||||||
{ 'name': 'Nicole Love', 'url': 'https://ge.xhamster.com/pornstars/nicole-love'},
|
|
||||||
{ 'name': 'Sarah Kay', 'url': 'https://ge.xhamster.com/pornstars/sarah-kay'},
|
|
||||||
{ 'name': 'Judith Kostner', 'url': 'https://ge.xhamster.com/pornstars/judith-kostner'},
|
|
||||||
{ 'name': 'Maria Bellucci', 'url': 'https://ge.xhamster.com/pornstars/maria-bellucci'},
|
|
||||||
{ 'name': 'Melissa Monet', 'url': 'https://ge.xhamster.com/pornstars/melissa-monet'},
|
|
||||||
{ 'name': 'Stephanie Cane', 'url': 'https://ge.xhamster.com/pornstars/stephanie-cane'},
|
|
||||||
{ 'name': 'Will Steiger', 'url': 'https://ge.xhamster.com/pornstars/will-steiger'},
|
|
||||||
{ 'name': 'Katty West', 'url': 'https://ge.xhamster.com/pornstars/katty-west'},
|
|
||||||
{ 'name': 'Jean Pallett', 'url': 'https://ge.xhamster.com/pornstars/jean-pallett'},
|
|
||||||
{ 'name': 'Conny Dachs', 'url': 'https://ge.xhamster.com/pornstars/conny-dachs'},
|
|
||||||
{ 'name': 'Juicy Leyla', 'url': 'https://ge.xhamster.com/pornstars/juicy-leyla'},
|
|
||||||
{ 'name': 'Mandy Mystery', 'url': 'https://ge.xhamster.com/pornstars/mandy-mystery'},
|
|
||||||
{ 'name': 'Jack Vegas', 'url': 'https://ge.xhamster.com/pornstars/jack-vegas'},
|
|
||||||
{ 'name': 'Jessica Ryan', 'url': 'https://ge.xhamster.com/pornstars/jessica-ryan'},
|
|
||||||
{ 'name': 'Nathan Bronson', 'url': 'https://ge.xhamster.com/pornstars/nathan-bronson'},
|
|
||||||
{ 'name': 'Lexi Lore', 'url': 'https://ge.xhamster.com/pornstars/lexi-lore'},
|
|
||||||
{ 'name': 'Molly Little', 'url': 'https://ge.xhamster.com/pornstars/molly-little'},
|
|
||||||
{ 'name': 'Dolly Leigh', 'url': 'https://ge.xhamster.com/pornstars/dolly-leigh'},
|
|
||||||
{ 'name': 'Marcus London', 'url': 'https://ge.xhamster.com/pornstars/marcus-london'},
|
|
||||||
{ 'name': 'Kendra Sunderland', 'url': 'https://ge.xhamster.com/pornstars/kendra-sunderland'},
|
|
||||||
{ 'name': 'Manuel Ferrara', 'url': 'https://ge.xhamster.com/pornstars/manuel-ferrara'},
|
|
||||||
{ 'name': 'Kelly Trump', 'url': 'https://ge.xhamster.com/pornstars/kelly-trump'},
|
|
||||||
{ 'name': 'Angelica Heaven', 'url': 'https://ge.xhamster.com/pornstars/angelica-heaven'},
|
|
||||||
{ 'name': 'Luna Lynx', 'url': 'https://ge.xhamster.com/pornstars/luna-lynx'},
|
|
||||||
{ 'name': 'Princess Lili', 'url': 'https://ge.xhamster.com/pornstars/princess-lili'},
|
|
||||||
{ 'name': 'Alexa Wild', 'url': 'https://ge.xhamster.com/pornstars/alexa-wild'},
|
|
||||||
{ 'name': 'Jessyka Swan', 'url': 'https://ge.xhamster.com/pornstars/jessyka-swan'},
|
|
||||||
{ 'name': 'Nikky Thorne', 'url': 'https://ge.xhamster.com/pornstars/nikky-thorne'},
|
|
||||||
{ 'name': 'Tristan Summers', 'url': 'https://ge.xhamster.com/pornstars/tristan-summers'},
|
|
||||||
{ 'name': 'Rhaya Shyne', 'url': 'https://ge.xhamster.com/pornstars/rhaya-shyne'},
|
|
||||||
{ 'name': 'Desiree West', 'url': 'https://ge.xhamster.com/pornstars/desiree-west'},
|
|
||||||
{ 'name': 'Joan Devlon', 'url': 'https://ge.xhamster.com/pornstars/joan-devlon'},
|
|
||||||
{ 'name': 'Jodi Thorpe', 'url': 'https://ge.xhamster.com/pornstars/jodi-thorpe'},
|
|
||||||
{ 'name': 'Laurien Dominique', 'url': 'https://ge.xhamster.com/pornstars/laurien-dominique'},
|
|
||||||
{ 'name': 'Paul Scharf', 'url': 'https://ge.xhamster.com/pornstars/paul-scharf'},
|
|
||||||
{ 'name': 'Ray Wells', 'url': 'https://ge.xhamster.com/pornstars/ray-wells'},
|
|
||||||
{ 'name': 'Sandy Carey', 'url': 'https://ge.xhamster.com/pornstars/sandy-carey'},
|
|
||||||
{ 'name': 'Spender Travis', 'url': 'https://ge.xhamster.com/pornstars/spender-travis'},
|
|
||||||
{ 'name': 'Starlyn Simone', 'url': 'https://ge.xhamster.com/pornstars/starlyn-simone'},
|
|
||||||
{ 'name': 'Uschi Digard', 'url': 'https://ge.xhamster.com/pornstars/uschi-digard'},
|
|
||||||
{ 'name': 'Katarina Muti', 'url': 'https://ge.xhamster.com/pornstars/katarina-muti'},
|
|
||||||
{ 'name': 'Julia Power', 'url': 'https://ge.xhamster.com/pornstars/julia-power'},
|
|
||||||
{ 'name': 'Salma De Nora', 'url': 'https://ge.xhamster.com/pornstars/salma-de-nora'},
|
|
||||||
{ 'name': 'Valeria Jones', 'url': 'https://ge.xhamster.com/pornstars/valeria-jones'},
|
|
||||||
{ 'name': 'Brandy Canyon', 'url': 'https://ge.xhamster.com/pornstars/brandy-canyon'},
|
|
||||||
{ 'name': 'Marie Berger', 'url': 'https://ge.xhamster.com/pornstars/marie-berger'},
|
|
||||||
{ 'name': 'Luna Rishi', 'url': 'https://ge.xhamster.com/pornstars/luna-rishi'},
|
|
||||||
{ 'name': 'Colleen Brennan', 'url': 'https://ge.xhamster.com/pornstars/colleen-brennan'},
|
|
||||||
{ 'name': 'Roxanne Brewer', 'url': 'https://ge.xhamster.com/pornstars/roxanne-brewer'},
|
|
||||||
{ 'name': 'Elle Denay', 'url': 'https://ge.xhamster.com/pornstars/elle-denay'},
|
|
||||||
{ 'name': 'Juan Largo', 'url': 'https://ge.xhamster.com/pornstars/juan-largo'},
|
|
||||||
{ 'name': 'Codey Steele', 'url': 'https://ge.xhamster.com/pornstars/codey-steele'},
|
|
||||||
{ 'name': 'Laney Grey', 'url': 'https://ge.xhamster.com/pornstars/laney-grey'},
|
|
||||||
{ 'name': 'Stirling Cooper', 'url': 'https://ge.xhamster.com/pornstars/stirling-cooper'},
|
|
||||||
{ 'name': 'Lana Smalls', 'url': 'https://ge.xhamster.com/pornstars/lana-smalls'},
|
|
||||||
{ 'name': 'Alex Sanders', 'url':'https://ge.xhamster.com/pornstars/alex-sanders'},
|
|
||||||
{ 'name': 'Felecia Danay', 'url':'https://ge.xhamster.com/pornstars/felecia-danay'},
|
|
||||||
{ 'name': 'Lita Chase', 'url':'https://ge.xhamster.com/pornstars/lita-chase'},
|
|
||||||
{ 'name': 'Mark Ashley', 'url':'https://ge.xhamster.com/pornstars/mark-ashley'},
|
|
||||||
{ 'name': 'Phyllisha Anne', 'url':'https://ge.xhamster.com/pornstars/phyllisha-anne'},
|
|
||||||
{ 'name': 'Ryan Conner', 'url':'https://ge.xhamster.com/pornstars/ryan-conner'},
|
|
||||||
{ 'name': 'Tanya Danielle', 'url':'https://ge.xhamster.com/pornstars/tanya-danielle'},
|
|
||||||
{ 'name': 'Jessica Moore', 'url':'https://ge.xhamster.com/pornstars/jessica-moore'},
|
|
||||||
{ 'name': 'Mike Angelo', 'url':'https://ge.xhamster.com/pornstars/mike-angelo'},
|
|
||||||
{ 'name': 'Morgan Moon', 'url':'https://ge.xhamster.com/pornstars/morgan-moon'},
|
|
||||||
{ 'name': 'Tyler Steel', 'url':'https://ge.xhamster.com/pornstars/tyler-steel'},
|
|
||||||
{ 'name': 'Abella Danger', 'url':'https://ge.xhamster.com/pornstars/abella-danger'},
|
|
||||||
{ 'name': 'Alex Jett', 'url':'https://ge.xhamster.com/pornstars/alex-jett'},
|
|
||||||
{ 'name': 'Alyson Queen', 'url':'https://ge.xhamster.com/pornstars/alyson-queen'},
|
|
||||||
{ 'name': 'Antynia Rouge', 'url':'https://ge.xhamster.com/pornstars/antynia-rouge'},
|
|
||||||
{ 'name': 'Bea Dumas', 'url':'https://ge.xhamster.com/pornstars/bea-dumas'},
|
|
||||||
{ 'name': 'Callie Black', 'url':'https://ge.xhamster.com/pornstars/callie-black'},
|
|
||||||
{ 'name': 'Caroline Cage', 'url':'https://ge.xhamster.com/pornstars/caroline-cage'},
|
|
||||||
{ 'name': 'Cindy Dollar', 'url':'https://ge.xhamster.com/pornstars/cindy-dollar'},
|
|
||||||
{ 'name': 'Crystal Frost', 'url':'https://ge.xhamster.com/pornstars/crystal-frost'},
|
|
||||||
{ 'name': 'Fanny Steel', 'url':'https://ge.xhamster.com/pornstars/fanny-steel'},
|
|
||||||
{ 'name': 'Gia Derza', 'url':'https://ge.xhamster.com/pornstars/gia-derza'},
|
|
||||||
{ 'name': 'Horst Baron', 'url':'https://ge.xhamster.com/pornstars/horst-baron'},
|
|
||||||
{ 'name': 'Jasmine Rouge', 'url':'https://ge.xhamster.com/pornstars/jasmine-rouge'},
|
|
||||||
{ 'name': 'Jean-Pierre Armand', 'url':'https://ge.xhamster.com/pornstars/jean-pierre-armand'},
|
|
||||||
{ 'name': 'Jessa Rhodes', 'url':'https://ge.xhamster.com/pornstars/jessa-rhodes'},
|
|
||||||
{ 'name': 'Leonie Saint', 'url':'https://ge.xhamster.com/pornstars/leonie-saint'},
|
|
||||||
{ 'name': 'Linda Ray', 'url':'https://ge.xhamster.com/pornstars/linda-ray'},
|
|
||||||
{ 'name': 'Luca Ferrero', 'url':'https://ge.xhamster.com/pornstars/luca-ferrero'},
|
|
||||||
{ 'name': 'Paris Pink', 'url':'https://ge.xhamster.com/pornstars/paris-pink'},
|
|
||||||
{ 'name': 'Pavlina Stejskalova', 'url':'https://ge.xhamster.com/pornstars/pavlina-stejskalova'},
|
|
||||||
{ 'name': 'Phoenix Marie', 'url':'https://ge.xhamster.com/pornstars/phoenix-marie'},
|
|
||||||
{ 'name': 'Ricky Spanish', 'url':'https://ge.xhamster.com/pornstars/ricky-spanish'},
|
|
||||||
{ 'name': 'Rumika Powers', 'url':'https://ge.xhamster.com/pornstars/rumika-powers'},
|
|
||||||
{ 'name': 'Sara Blonde', 'url':'https://ge.xhamster.com/pornstars/sara-blonde'},
|
|
||||||
{ 'name': 'Sean Lawless', 'url':'https://ge.xhamster.com/pornstars/sean-lawless'},
|
|
||||||
{ 'name': 'Seth Gamble', 'url':'https://ge.xhamster.com/pornstars/seth-gamble'},
|
|
||||||
{ 'name': 'Siri Dahl', 'url':'https://ge.xhamster.com/pornstars/siri-dahl'},
|
|
||||||
{ 'name': 'Stephie Staar', 'url':'https://ge.xhamster.com/pornstars/stephie-staar'},
|
|
||||||
{ 'name': 'Steve Holmes', 'url':'https://ge.xhamster.com/pornstars/steve-holmes'},
|
|
||||||
{ 'name': 'Suzette Dale', 'url':'https://ge.xhamster.com/pornstars/suzette-dale'},
|
|
||||||
{ 'name': 'Uncle George', 'url':'https://ge.xhamster.com/pornstars/uncle-george'},
|
|
||||||
{ 'name': 'Winnie', 'url':'https://ge.xhamster.com/pornstars/winnie'},
|
|
||||||
{ 'name': 'Zenza Raggi', 'url':'https://ge.xhamster.com/pornstars/zenza-raggi'},
|
|
||||||
{ 'name': 'Zorah White', 'url':'https://ge.xhamster.com/pornstars/zorah-white'},
|
|
||||||
{ 'name': 'Marilyn Jess', 'url':'https://ge.xhamster.com/pornstars/marilyn-jess'},
|
|
||||||
{ 'name': 'Alexis Capri', 'url':'https://ge.xhamster.com/pornstars/alexis-capri'},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
for new_actor in new_actor_list:
|
|
||||||
if new_actor['url'] in actors:
|
|
||||||
log.warning(f"Actor {new_actor['url']} already persisted")
|
|
||||||
continue
|
|
||||||
actor_response = requests.post(f"http://127.0.0.1:8800/api/media/actors", json=new_actor)
|
|
||||||
log.warning(f"add status: {actor_response.status_code}")
|
|
||||||
if actor_response.status_code == 201:
|
|
||||||
log.warning(f"add Actor {new_actor['url']} to existing actor list")
|
|
||||||
actors[new_actor['url']] = new_actor
|
|
||||||
actor_data = actor_response.json()
|
|
||||||
log.warning(f"Actor {actor_data} persisted")
|
|
||||||
log.warning('kontor.add_actors finished')
|
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
import logging
|
||||||
|
import logging.config
|
||||||
|
from logging import Logger
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from platformdirs import PlatformDirs
|
||||||
|
import requests
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
MAPPING: Dict[str, str] = {
|
||||||
|
"sport": "api/tysc/sports",
|
||||||
|
"player": "api/tysc/players",
|
||||||
|
"team": "api/tysc/teams",
|
||||||
|
"field_position": "api/tysc/positions",
|
||||||
|
"rooster": "api/tysc/roosters",
|
||||||
|
"vendor": "api/tysc/vendors",
|
||||||
|
"card_set": "api/tysc/cardsets",
|
||||||
|
"card": "api/tysc/cards",
|
||||||
|
"artist": "api/comics/artists",
|
||||||
|
"publisher": "api/comics/publishers",
|
||||||
|
"worktype": "api/comics/worktypes",
|
||||||
|
"comic": "api/comics/comics",
|
||||||
|
"volume": "api/comics/volumes",
|
||||||
|
"story_arc": "api/comics/storyarcs",
|
||||||
|
"issue": "api/comics/issues",
|
||||||
|
"issue_work": "api/comics/issueworks",
|
||||||
|
"article": "",
|
||||||
|
"bookshelf_publisher": "",
|
||||||
|
"book": "",
|
||||||
|
"author": "",
|
||||||
|
"article_author": "",
|
||||||
|
"book_author": "",
|
||||||
|
"media_article": "",
|
||||||
|
"media_video": "api/media/videos",
|
||||||
|
"media_file": "api/media/files",
|
||||||
|
"media_actor": "api/media/actors",
|
||||||
|
"media_actor_file": "api/media/actorfiles",
|
||||||
|
"profile": "",
|
||||||
|
"permission": "",
|
||||||
|
"assignment": "",
|
||||||
|
"token": "",
|
||||||
|
"mail_account": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Login:
|
||||||
|
"""
|
||||||
|
Dataclass to store login information.
|
||||||
|
"""
|
||||||
|
|
||||||
|
email: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Server:
|
||||||
|
"""
|
||||||
|
Dataclass to represent a Kontor-API instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
url: str
|
||||||
|
token: str
|
||||||
|
token_type: str
|
||||||
|
timeout: int
|
||||||
|
|
||||||
|
def login(self, login: Login, log: Logger):
|
||||||
|
"""
|
||||||
|
get token from server by calling login endpoint.
|
||||||
|
"""
|
||||||
|
if not self.token:
|
||||||
|
log.info("Call login first")
|
||||||
|
login_url = f"{self.url}/login"
|
||||||
|
login_data = {}
|
||||||
|
login_data["email"] = login.email
|
||||||
|
login_data["password"] = login.password
|
||||||
|
response = requests.post(login_url, json=login_data, timeout=self.timeout)
|
||||||
|
status = response.status_code
|
||||||
|
log.info(f"Status: {status}")
|
||||||
|
if status != 200:
|
||||||
|
log.fatal("authentication failed")
|
||||||
|
return
|
||||||
|
data = response.json()
|
||||||
|
log.debug(f"got data: {data}")
|
||||||
|
token = data["access_token"]
|
||||||
|
token_type = data["token_type"]
|
||||||
|
self.token = str(token)
|
||||||
|
self.token_type = str(token_type)
|
||||||
|
|
||||||
|
def request(self, log: Logger, table: str, param: Optional[str] = None):
|
||||||
|
if not param:
|
||||||
|
url: str = f"{self.url}/{MAPPING[table]}"
|
||||||
|
else:
|
||||||
|
url: str = f"{self.url}/{MAPPING[table]}?{param}"
|
||||||
|
headers: Dict[str, str] = {"Authorization": f"Bearer {self.token}"}
|
||||||
|
response = requests.get(url, headers=headers, timeout=self.timeout)
|
||||||
|
log.info(f"Status: {response.status_code}")
|
||||||
|
data = response.json()
|
||||||
|
return data
|
||||||
|
|
||||||
|
def update(self, log: Logger, table: str, item_id: UUID, file_info: dict):
|
||||||
|
url: str = f"{self.url}/{MAPPING[table]}/{item_id}"
|
||||||
|
headers: Dict[str, str] = {"Authorization": f"Bearer {self.token}"}
|
||||||
|
update = requests.put(
|
||||||
|
url, headers=headers, json=file_info, timeout=self.timeout
|
||||||
|
)
|
||||||
|
log.info(f"Status: {update.status_code}")
|
||||||
|
return update
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ApiConfig:
|
||||||
|
"""
|
||||||
|
Dataclass to define required contents of configuration file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
login: Login
|
||||||
|
server: List[Server]
|
||||||
|
|
||||||
|
|
||||||
|
def get_logger(level, config: str):
|
||||||
|
"""
|
||||||
|
get Logger according to given log level by verbosity.
|
||||||
|
"""
|
||||||
|
dirs = PlatformDirs(config)
|
||||||
|
logging_config = Path(dirs.user_config_dir, "logging-config.yaml")
|
||||||
|
log_config = None
|
||||||
|
with open(logging_config, "rt", encoding="utf-8") as f:
|
||||||
|
log_config = yaml.safe_load(f.read())
|
||||||
|
logging.config.dictConfig(log_config)
|
||||||
|
logger = logging.getLogger("development")
|
||||||
|
if level is not None:
|
||||||
|
match level:
|
||||||
|
case 0:
|
||||||
|
logger.setLevel(logging.CRITICAL)
|
||||||
|
case 1:
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
case 2:
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
case _:
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
return logger
|
||||||
|
|
||||||
|
|
||||||
|
def get_api_config(log: Logger, config: str) -> ApiConfig:
|
||||||
|
dirs = PlatformDirs(config)
|
||||||
|
api_config = Path(dirs.user_config_dir, "api.yaml")
|
||||||
|
with open(api_config, "rt") as f:
|
||||||
|
api_data = yaml.safe_load(f.read())
|
||||||
|
servers = [Server(**server) for server in api_data["server"]]
|
||||||
|
login = Login(**(api_data["login"]))
|
||||||
|
apiConfig = ApiConfig(server=servers, login=login)
|
||||||
|
log.debug(apiConfig)
|
||||||
|
if not api_data:
|
||||||
|
log.fatal("API configuration is missing")
|
||||||
|
return apiConfig
|
||||||
|
for server in apiConfig.server:
|
||||||
|
server.login(apiConfig.login, log)
|
||||||
|
with open(api_config, "w") as f:
|
||||||
|
yaml.dump(api_data, f)
|
||||||
|
return apiConfig
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
"""
|
|
||||||
copy data from SQLite to MariaDB
|
|
||||||
"""
|
|
||||||
import sqlite3
|
|
||||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
|
||||||
|
|
||||||
from config import get_logger, get_database_cursors, get_meta_data, get_scripts
|
|
||||||
|
|
||||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
|
||||||
parser.add_argument('--recreate-db', action='store_true')
|
|
||||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def copy_data(mariadb_conn, sqlite_conn, table_scripts):
|
|
||||||
mariadb_cursor = mariadb_conn.cursor()
|
|
||||||
sqlite_cursor = sqlite_conn.cursor()
|
|
||||||
# logger.info(table_scripts)
|
|
||||||
for table_id in scripts:
|
|
||||||
select_statement = scripts[table_id]['select_sqlite']
|
|
||||||
# logger.info(select_statement)
|
|
||||||
insert_statement = scripts[table_id]['insert_mariadb']
|
|
||||||
mariadb_cursor.execute("SET FOREIGN_KEY_CHECKS = 0")
|
|
||||||
mariadb_cursor.execute(scripts[table_id]['truncate_mariadb'])
|
|
||||||
try:
|
|
||||||
sqlite_cursor.execute(select_statement)
|
|
||||||
rows = sqlite_cursor.fetchall()
|
|
||||||
for row in rows:
|
|
||||||
try:
|
|
||||||
mariadb_cursor.execute(insert_statement, row)
|
|
||||||
except sqlite3.Error as error:
|
|
||||||
logger.info('insert failed with %s\n%s\n%s', error, insert_statement, row)
|
|
||||||
mariadb_conn.commit()
|
|
||||||
mariadb_cursor.execute(scripts[table_id]['count'])
|
|
||||||
(number_of_rows,) = mariadb_cursor.fetchone()
|
|
||||||
row = sqlite_cursor.execute(scripts[table_id]['count']).fetchone()
|
|
||||||
logger.info('%s contains %d : %d entries', scripts[table_id]['name'], number_of_rows, row[0])
|
|
||||||
except sqlite3.Error as error:
|
|
||||||
logger.info('select failed with %s', error)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
logger = get_logger(args.verbose)
|
|
||||||
logger.info('kontor.copy_to_sqlite started')
|
|
||||||
s_conn, m_conn = get_database_cursors(logger)
|
|
||||||
meta_data_tables = get_meta_data(m_conn)
|
|
||||||
# logger.info(meta_data_tables)
|
|
||||||
scripts = get_scripts(meta_data_tables, logger)
|
|
||||||
copy_data(m_conn, s_conn, scripts)
|
|
||||||
s_conn.close()
|
|
||||||
m_conn.close()
|
|
||||||
logger.info('kontor.copy_to_sqlite finished')
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
"""
|
|
||||||
copy data from MariaDB to SQLite
|
|
||||||
"""
|
|
||||||
import sqlite3
|
|
||||||
import mariadb
|
|
||||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
|
||||||
from config import get_database_cursors, create_tables, get_logger, get_meta_data, get_scripts
|
|
||||||
|
|
||||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
|
||||||
parser.add_argument('--recreate-db', action='store_true')
|
|
||||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def copy_data(mariadb_conn, sqlite_conn, table_scripts):
|
|
||||||
mariadb_cursor = mariadb_conn.cursor()
|
|
||||||
sqlite_cursor = sqlite_conn.cursor()
|
|
||||||
# logger.info(table_scripts)
|
|
||||||
for table_id in table_scripts:
|
|
||||||
select_statement = scripts[table_id]['select_mariadb']
|
|
||||||
# logger.info(select_statement)
|
|
||||||
insert_statement = scripts[table_id]['insert_sqlite']
|
|
||||||
try:
|
|
||||||
mariadb_cursor.execute(select_statement)
|
|
||||||
rows = mariadb_cursor.fetchall()
|
|
||||||
for row in rows:
|
|
||||||
try:
|
|
||||||
sqlite_cursor.execute(insert_statement, row)
|
|
||||||
except sqlite3.Error as error:
|
|
||||||
logger.info('insert failed with %s\n%s\n%s', error, insert_statement, row)
|
|
||||||
sqlite_conn.commit()
|
|
||||||
mariadb_cursor.execute(scripts[table_id]['count'])
|
|
||||||
(number_of_rows,) = mariadb_cursor.fetchone()
|
|
||||||
row = sqlite_cursor.execute(scripts[table_id]['count']).fetchone()
|
|
||||||
logger.info('%s contains %d : %d entries', scripts[table_id]['name'], number_of_rows, row[0])
|
|
||||||
except mariadb.Error as error:
|
|
||||||
logger.info('select failed with %s', error)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
logger = get_logger(args.verbose)
|
|
||||||
logger.info('kontor.copy_to_sqlite started')
|
|
||||||
s_conn, m_conn = get_database_cursors(logger)
|
|
||||||
meta_data_tables = get_meta_data(m_conn)
|
|
||||||
# logger.info(meta_data_tables)
|
|
||||||
scripts = get_scripts(meta_data_tables, logger)
|
|
||||||
create_tables(s_conn, logger, args.recreate_db, scripts)
|
|
||||||
copy_data(m_conn, s_conn, scripts)
|
|
||||||
s_conn.close()
|
|
||||||
m_conn.close()
|
|
||||||
logger.info('kontor.copy_to_sqlite finished')
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
"""
|
|
||||||
Prints the database kontor structure
|
|
||||||
"""
|
|
||||||
import mariadb
|
|
||||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
|
||||||
from config import get_database_cursors, get_logger
|
|
||||||
|
|
||||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
|
||||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def show_tables(cur, log):
|
|
||||||
"""
|
|
||||||
Retrieves the list of tables from the database
|
|
||||||
:param cur:
|
|
||||||
:param log:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
log.info('get list of tables')
|
|
||||||
table_list = []
|
|
||||||
cur.execute("SHOW TABLES")
|
|
||||||
for (tablename,) in cur.fetchall():
|
|
||||||
table_list.append(tablename)
|
|
||||||
return table_list
|
|
||||||
|
|
||||||
|
|
||||||
def get_field_info(cur):
|
|
||||||
"""
|
|
||||||
Retrieves the field info associated with a cursor
|
|
||||||
:param cur:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
field_info = mariadb.fieldinfo()
|
|
||||||
field_info_text_list = []
|
|
||||||
for column in cur.description:
|
|
||||||
column_name = column[0]
|
|
||||||
column_type = field_info.type(column)
|
|
||||||
column_flags = field_info.flag(column)
|
|
||||||
field_info_text_list.append(f"{column_name}: {column_type} {column_flags}")
|
|
||||||
return field_info_text_list
|
|
||||||
|
|
||||||
|
|
||||||
def get_table_field_info(cur, tablename):
|
|
||||||
"""
|
|
||||||
Retrieves the field info associated with a table
|
|
||||||
:param cur:
|
|
||||||
:param tablename:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
cur.execute(f"SELECT * FROM {tablename} LIMIT 1")
|
|
||||||
field_info = get_field_info(cur)
|
|
||||||
return field_info
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
logger = get_logger(args.verbose)
|
|
||||||
logger.info("kontor.db_structure started")
|
|
||||||
_, mariadb_conn = get_database_cursors(logger)
|
|
||||||
tables = show_tables(mariadb_conn.cursor(), logger)
|
|
||||||
for table in tables:
|
|
||||||
field_info_text = get_table_field_info(mariadb_conn.cursor(), table)
|
|
||||||
print(f"Columns in table {table}:")
|
|
||||||
print("\n".join(field_info_text))
|
|
||||||
print("\n")
|
|
||||||
mariadb_conn.close()
|
|
||||||
logger.info("kontor.db_structure finished")
|
|
||||||
+17
-22
@@ -2,7 +2,6 @@
|
|||||||
download files with URLs from DB
|
download files with URLs from DB
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -10,16 +9,17 @@ from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict
|
from typing import Dict
|
||||||
|
from logging import Logger
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from config import get_api_config, get_logger
|
from api import Server, get_api_config, get_logger
|
||||||
|
|
||||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument("--verbose", "-v", action="count", default=0)
|
parser.add_argument("--verbose", "-v", action="count", default=0)
|
||||||
parser.add_argument("--config", "-c", default="kontor-docker")
|
parser.add_argument("--config", "-c", default="kontor-api")
|
||||||
parser.add_argument("--dir", "-d", default="/data/media")
|
parser.add_argument("--dir", "-d", default="/data/media")
|
||||||
parser.add_argument("--limit", "-l", type=int, help="maximum number of links to check")
|
parser.add_argument("--limit", "-l", type=int, help="maximum number of links to check")
|
||||||
parser.add_argument("--tool", "-t", default="yt-dlp")
|
parser.add_argument("--tool", "-t", default="yt-dlp")
|
||||||
@@ -104,12 +104,8 @@ def is_file_downloaded(media_file: dict, dir: Path) -> FileStatus:
|
|||||||
return FileStatus.UNKNOWN
|
return FileStatus.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
def update_status(item_id: UUID, file_info: dict, api_data: Dict[str, Any]):
|
def update_status(item_id: UUID, file_info: dict, server: Server, log: Logger):
|
||||||
host = api_data["host"]
|
update = server.update(log, "media_file", item_id, file_info)
|
||||||
token = api_data["token"]
|
|
||||||
url: str = f"http://{host}:{port}/api/media/files/{item_id}"
|
|
||||||
headers: Dict[str, str] = {"Authorization": f"Bearer {token}"}
|
|
||||||
update = requests.put(url, headers=headers, json=file_info)
|
|
||||||
log.info(f"update status: {update.status_code}")
|
log.info(f"update status: {update.status_code}")
|
||||||
log.info(f"update result: {update.json()}")
|
log.info(f"update result: {update.json()}")
|
||||||
|
|
||||||
@@ -132,15 +128,14 @@ def rename_file(file_info: dict):
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
log = get_logger(args.verbose, args.config)
|
log = get_logger(args.verbose, args.config)
|
||||||
log.info("kontor.download started")
|
log.info("kontor.download started")
|
||||||
api_data = get_api_config(log, args.config)
|
apiConfig = get_api_config(log, args.config)
|
||||||
host = api_data["host"]
|
server: Server = apiConfig.server[0]
|
||||||
port = api_data["port"]
|
data = server.request(log=log, table="media_file", param="download=true")
|
||||||
token = api_data["token"]
|
# url: str = f"http://{host}:{port}/api/media/files?download=true"
|
||||||
url: str = f"http://{host}:{port}/api/media/files?download=true"
|
# headers: Dict[str, str] = {"Authorization": f"Bearer {token}"}
|
||||||
headers: Dict[str, str] = {"Authorization": f"Bearer {token}"}
|
# response = requests.get(url, headers=headers)
|
||||||
response = requests.get(url, headers=headers)
|
# log.info(f"Status: {response.status_code}")
|
||||||
log.info(f"Status: {response.status_code}")
|
# data = response.json()
|
||||||
data = response.json()
|
|
||||||
entries_count = len(data)
|
entries_count = len(data)
|
||||||
log.info(f"data: {entries_count}")
|
log.info(f"data: {entries_count}")
|
||||||
mediafile_index = 1
|
mediafile_index = 1
|
||||||
@@ -158,15 +153,15 @@ if __name__ == "__main__":
|
|||||||
match download_status:
|
match download_status:
|
||||||
case FileStatus.DOWNLOADED:
|
case FileStatus.DOWNLOADED:
|
||||||
rename_file(item)
|
rename_file(item)
|
||||||
update_status(file_id, item, api_data)
|
update_status(file_id, item, server=server, log=log)
|
||||||
case FileStatus.RENAMED:
|
case FileStatus.RENAMED:
|
||||||
log.info("update status")
|
log.info("update status")
|
||||||
update_status(file_id, item, api_data)
|
update_status(file_id, item, server=server, log=log)
|
||||||
case FileStatus.UNKNOWN:
|
case FileStatus.UNKNOWN:
|
||||||
download_file(link, item, args.dir)
|
download_file(link, item, args.dir)
|
||||||
rename_file(item)
|
rename_file(item)
|
||||||
log.info(f"{item}")
|
log.info(f"{item}")
|
||||||
update_status(file_id, item, api_data)
|
update_status(file_id, item, server=server, log=log)
|
||||||
log.warning(f"processed {mediafile_index}/{entries_count}")
|
log.warning(f"processed {mediafile_index}/{entries_count}")
|
||||||
if args.limit and args.limit <= mediafile_index:
|
if args.limit and args.limit <= mediafile_index:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
"""
|
|
||||||
copy data from JSON to MariaDB
|
|
||||||
"""
|
|
||||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
|
||||||
from pathlib import Path
|
|
||||||
from config import get_logger, get_database_cursors
|
|
||||||
import mariadb
|
|
||||||
import json
|
|
||||||
|
|
||||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
|
||||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
|
||||||
parser.add_argument('--config', '-c', default='kontor-docker')
|
|
||||||
parser.add_argument('--file', '-f', default='~/.sync/media/data.json')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
def copy_data(mariadb_conn, data_file: Path, log):
|
|
||||||
mariadb_cursor = mariadb_conn.cursor()
|
|
||||||
import_file = Path(data_file)
|
|
||||||
if not import_file.exists():
|
|
||||||
log.info(f"File {data_file} does not exist. Do nothing.")
|
|
||||||
return
|
|
||||||
log.info("read json file")
|
|
||||||
with open(data_file, 'r') as json_file:
|
|
||||||
json_load = json.load(json_file)
|
|
||||||
for table in json_load:
|
|
||||||
log.info(f"{table}: {len(json_load[table])}")
|
|
||||||
# result[table] = import_table(table, json_load[table])
|
|
||||||
truncate_statement = 'TRUNCATE {}'.format(table)
|
|
||||||
#log.info(f"truncate: {truncate_statement}")
|
|
||||||
mariadb_cursor.execute("SET FOREIGN_KEY_CHECKS = 0")
|
|
||||||
mariadb_cursor.execute(truncate_statement)
|
|
||||||
items = json_load[table]
|
|
||||||
for item in items:
|
|
||||||
#log.info(f"item: {item}")
|
|
||||||
values = []
|
|
||||||
columns = []
|
|
||||||
for (key, value) in item.items():
|
|
||||||
columns.append(key)
|
|
||||||
values.append(value)
|
|
||||||
row = tuple(values)
|
|
||||||
log.info(f"values: {row}")
|
|
||||||
insert_statement = 'INSERT INTO {}({}) VALUES({})'.format(table, ', '.join(columns), ', '.join(['?']*len(columns)))
|
|
||||||
#log.info(f"statement: {insert_statement}")
|
|
||||||
mariadb_cursor.execute(insert_statement, row)
|
|
||||||
try:
|
|
||||||
mariadb_conn.commit()
|
|
||||||
except mariadb.Error as error:
|
|
||||||
log.info('insert failed with %s', error)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
logger = get_logger(args.verbose, args.config)
|
|
||||||
logger.info('kontor.json_to_mariadb started')
|
|
||||||
_, m_conn = get_database_cursors(logger, args.config)
|
|
||||||
copy_data(m_conn, args.file, logger)
|
|
||||||
m_conn.close()
|
|
||||||
logger.info('kontor.json_to_mariadb finished')
|
|
||||||
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
"""
|
|
||||||
copy data from JSON to Postgres
|
|
||||||
"""
|
|
||||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
from config import get_logger, get_database_cursors
|
|
||||||
import json
|
|
||||||
from psycopg2.sql import SQL
|
|
||||||
|
|
||||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
|
||||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
|
||||||
parser.add_argument('--config', '-c', default='kontor-docker')
|
|
||||||
parser.add_argument('--file', '-f', default='~/.sync/media/data.json')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
def copy_data(postgres_conn, data_file: Path, log):
|
|
||||||
postgres_cursor = postgres_conn.cursor()
|
|
||||||
import_file = Path(data_file)
|
|
||||||
if not import_file.exists():
|
|
||||||
log.info(f"File {data_file} does not exist. Do nothing.")
|
|
||||||
return
|
|
||||||
log.info("read json file")
|
|
||||||
with open(data_file, 'r') as json_file:
|
|
||||||
json_load = json.load(json_file)
|
|
||||||
postgres_cursor.execute("SET session_replication_role='replica'")
|
|
||||||
for table in json_load:
|
|
||||||
log.info(f"{table}: {len(json_load[table])}")
|
|
||||||
# result[table] = import_table(table, json_load[table])
|
|
||||||
truncate_statement = 'TRUNCATE {} CASCADE'.format(table)
|
|
||||||
#log.info(f"truncate: {truncate_statement}")
|
|
||||||
try:
|
|
||||||
postgres_cursor.execute(truncate_statement)
|
|
||||||
except:
|
|
||||||
log.info(f"statement: {insert_statement} FAILED")
|
|
||||||
items = json_load[table]
|
|
||||||
for item in items:
|
|
||||||
#log.info(f"item: {item}")
|
|
||||||
values = []
|
|
||||||
columns = []
|
|
||||||
for (key, value) in item.items():
|
|
||||||
columns.append(key)
|
|
||||||
values.append(value)
|
|
||||||
row = tuple(values)
|
|
||||||
#log.info(f"values: {row}")
|
|
||||||
insert_statement = 'INSERT INTO {}({}) VALUES({})'.format(table, ', '.join(columns), ', '.join(['%s']*len(columns)))
|
|
||||||
#log.info(f"statement: {insert_statement}")
|
|
||||||
try:
|
|
||||||
postgres_cursor.execute(SQL(insert_statement), row)
|
|
||||||
postgres_conn.commit()
|
|
||||||
except:
|
|
||||||
log.info(f'insert failed with {insert_statement}')
|
|
||||||
postgres_cursor.execute("SET session_replication_role='origin'")
|
|
||||||
|
|
||||||
|
|
||||||
def load_json(data_file, log) -> dict:
|
|
||||||
import_file = Path(data_file)
|
|
||||||
if not import_file.exists():
|
|
||||||
log.info(f"File {data_file} does not exist. Do nothing.")
|
|
||||||
return
|
|
||||||
log.info("read json file")
|
|
||||||
with open(data_file, 'r') as json_file:
|
|
||||||
json_load = json.load(json_file)
|
|
||||||
return json_load
|
|
||||||
|
|
||||||
|
|
||||||
def insert_data(postgres_conn, data: dict, log):
|
|
||||||
postgres_cursor = postgres_conn.cursor()
|
|
||||||
log.info("insert data")
|
|
||||||
table_list = []
|
|
||||||
table_list = ['worktype', 'artist', 'publisher', 'volume', 'comic', 'issue', 'story_arc', 'trade_paperback', 'comic_work']
|
|
||||||
table_list.extend(['sport', 'team', 'field_position', 'vendor', 'player', 'rooster', 'card_set', 'card'])
|
|
||||||
#table_list.extend(['card'])
|
|
||||||
table_list.extend(['media_file', 'media_video', 'media_actor', 'media_article', 'media_actor_file'])
|
|
||||||
#table_list.extend(['media_actor_file'])
|
|
||||||
table_list.extend(['profile', 'permission', 'token', 'assignment'])
|
|
||||||
table_list.extend(['mail', 'mail_account', 'module_data', 'meta_data_table', 'meta_data_column'])
|
|
||||||
table_list.extend(['meta_data_table', 'meta_data_column'])
|
|
||||||
table_list.extend(['book', 'author', 'article', 'bookshelf_publisher', 'book_author', 'article_author'])
|
|
||||||
#if len(table_list) != 37:
|
|
||||||
# log.info(f"number of tables incorrect: {len(table_list)}")
|
|
||||||
# return
|
|
||||||
for table in table_list:
|
|
||||||
log.info(f"{table}: {len(data[table])}")
|
|
||||||
truncate_statement = 'DELETE FROM {}'.format(table)
|
|
||||||
log.info(f"truncate: {truncate_statement}")
|
|
||||||
try:
|
|
||||||
postgres_cursor.execute(truncate_statement)
|
|
||||||
postgres_conn.commit()
|
|
||||||
except:
|
|
||||||
log.info(f"statement: {truncate_statement} FAILED")
|
|
||||||
items = data[table]
|
|
||||||
for item in items:
|
|
||||||
# log.info(f"item: {item}")
|
|
||||||
values = []
|
|
||||||
columns = []
|
|
||||||
for (key, value) in item.items():
|
|
||||||
columns.append(key)
|
|
||||||
values.append(value)
|
|
||||||
row = tuple(values)
|
|
||||||
# log.info(f"values: {row}")
|
|
||||||
insert_statement = 'INSERT INTO {}({}) VALUES({})'.format(table, ', '.join(columns),
|
|
||||||
', '.join(['%s'] * len(columns)))
|
|
||||||
# log.info(f"statement: {insert_statement}")
|
|
||||||
try:
|
|
||||||
postgres_cursor.execute(SQL(insert_statement), row)
|
|
||||||
postgres_conn.commit()
|
|
||||||
except:
|
|
||||||
log.info(f'insert failed with {insert_statement}')
|
|
||||||
|
|
||||||
|
|
||||||
def parse_table_order(data: dict, log):
|
|
||||||
log.info("parse_table_order")
|
|
||||||
table_refs: Dict[str, List[str]] = {}
|
|
||||||
for table in data:
|
|
||||||
log.info(f"{table}: {len(data[table])}")
|
|
||||||
items = data[table]
|
|
||||||
table_refs[table] = []
|
|
||||||
if len(items) == 0:
|
|
||||||
continue
|
|
||||||
item = items[0]
|
|
||||||
for key, _ in item.items():
|
|
||||||
if key.endswith('_id'):
|
|
||||||
ref = key[0:-3]
|
|
||||||
log.info(f"table {table} has reference to {ref}")
|
|
||||||
if table in table_refs:
|
|
||||||
table_refs[table].append(ref)
|
|
||||||
else:
|
|
||||||
table_refs[table] = [ref]
|
|
||||||
log.info(f"parsed refs: {table_refs}")
|
|
||||||
table_order = []
|
|
||||||
for table in table_refs:
|
|
||||||
if len(table_refs[table]) == 0:
|
|
||||||
log.info(f"insert {table} at beginning")
|
|
||||||
table_order.insert(0, table)
|
|
||||||
else:
|
|
||||||
log.info(f"insert {table} at end")
|
|
||||||
table_order.append(table)
|
|
||||||
log.info(f"table_list: {len(table_order)}: {table_order}")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
logger = get_logger(args.verbose, args.config)
|
|
||||||
logger.info('kontor.json_to_postgres started')
|
|
||||||
_, _, p_conn = get_database_cursors(logger, args.config)
|
|
||||||
data = load_json(args.file, logger)
|
|
||||||
#parse_table_order(data, logger)
|
|
||||||
insert_data(p_conn, data, logger)
|
|
||||||
#copy_data(p_conn, args.file, logger)
|
|
||||||
p_conn.close()
|
|
||||||
logger.info('kontor.json_to_postgres finished')
|
|
||||||
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import logging.config
|
|
||||||
import yaml
|
|
||||||
from platformdirs import PlatformDirs
|
|
||||||
from sqlalchemy import create_engine, select
|
|
||||||
from sqlalchemy.orm import sessionmaker
|
|
||||||
|
|
||||||
from schema import Comic, Publisher, Base
|
|
||||||
|
|
||||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
|
||||||
parser.add_argument('--recreate-db', action='store_true')
|
|
||||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
|
||||||
parser.add_argument('--file', '-f', default='~/data.json')
|
|
||||||
parser.add_argument('--config', '-c', default='kontor-docker')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def get_logger(level: int, config: str):
|
|
||||||
dirs = PlatformDirs(config)
|
|
||||||
logging_config = Path(dirs.user_config_dir, 'logging-config.yaml')
|
|
||||||
with open(logging_config, 'rt') as f:
|
|
||||||
configDict = yaml.safe_load(f.read())
|
|
||||||
logging.config.dictConfig(configDict)
|
|
||||||
logger = logging.getLogger('development')
|
|
||||||
if level is not None:
|
|
||||||
match level:
|
|
||||||
case 0:
|
|
||||||
logger.setLevel(logging.INFO)
|
|
||||||
case 1:
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
case _:
|
|
||||||
logger.setLevel(logging.CRITICAL)
|
|
||||||
return logger
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
log = get_logger(args.verbose, args.config)
|
|
||||||
log.info('kontor started')
|
|
||||||
dirs = PlatformDirs(args.config)
|
|
||||||
database_config = Path(dirs.user_config_dir, 'database-config.yaml')
|
|
||||||
with open(database_config, 'rt') as f:
|
|
||||||
db_config = yaml.safe_load(f.read())
|
|
||||||
print(db_config)
|
|
||||||
connect_string = ('mariadb+mariadbconnector://{}:{}@{}:{}/{}'.format(
|
|
||||||
db_config['mariadb']['user'],
|
|
||||||
db_config['mariadb']['password'],
|
|
||||||
db_config['mariadb']['host'],
|
|
||||||
db_config['mariadb']['port'],
|
|
||||||
db_config['mariadb']['database']
|
|
||||||
))
|
|
||||||
engine = create_engine(connect_string)
|
|
||||||
Base.metadata.create_all(bind=engine, checkfirst=True)
|
|
||||||
__session__ = sessionmaker(engine)
|
|
||||||
with __session__() as session:
|
|
||||||
comics = session.scalars(select(Comic)).all()
|
|
||||||
for comic in comics:
|
|
||||||
print(comic)
|
|
||||||
publishers = session.scalars(select(Publisher)).all()
|
|
||||||
for publisher in publishers:
|
|
||||||
print(publisher)
|
|
||||||
print(len(publisher.comics))
|
|
||||||
log.info('kontor finished')
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
"""
|
|
||||||
read file with URLs and store in DB
|
|
||||||
"""
|
|
||||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
from proton import Message, Event
|
|
||||||
from proton.handlers import MessagingHandler
|
|
||||||
from proton.reactor import Container
|
|
||||||
|
|
||||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
|
||||||
parser.add_argument('-f', '--links', help='file with links')
|
|
||||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def read_links_file(links_file):
|
|
||||||
with open(links_file, 'r') as input_file:
|
|
||||||
lines = input_file.readlines()
|
|
||||||
return lines
|
|
||||||
|
|
||||||
|
|
||||||
class AddLinkMessage(MessagingHandler):
|
|
||||||
def __init__(self, server, url, log):
|
|
||||||
super(AddLinkMessage, self).__init__()
|
|
||||||
log.info("create AddLinkMessage")
|
|
||||||
self.server = server
|
|
||||||
self.address = "KontorMediaFile::add_link_file"
|
|
||||||
self.url = url
|
|
||||||
self.log = log
|
|
||||||
|
|
||||||
def on_start(self, event: Event):
|
|
||||||
self.log.info("Connection...")
|
|
||||||
conn = event.container.connect(self.server, user="artemis", password="artemis")
|
|
||||||
event.container.create_sender(conn, self.address)
|
|
||||||
|
|
||||||
def on_connection_opened(self, event: Event) -> None:
|
|
||||||
self.log.debug("connection open")
|
|
||||||
|
|
||||||
def on_connection_error(self, event: Event) -> None:
|
|
||||||
self.log.info(f"error: {event}")
|
|
||||||
|
|
||||||
def on_disconnected(self, event: Event) -> None:
|
|
||||||
self.log.debug(f"disconnected: {repr(event)}")
|
|
||||||
|
|
||||||
|
|
||||||
def on_sendable(self, event: Event):
|
|
||||||
self.log.info("send message")
|
|
||||||
message = Message(body=self.url, address=self.address, content_type="application/json", durable=True)
|
|
||||||
delivery = event.sender.send(message)
|
|
||||||
self.log.info(f"Delivery {delivery} sent")
|
|
||||||
event.connection.close()
|
|
||||||
|
|
||||||
def on_accepted(self, event: Event) -> None:
|
|
||||||
self.log.info(f"accepted Delivery: {event.delivery.remote_state}")
|
|
||||||
|
|
||||||
|
|
||||||
def on_rejected(self, event: Event) -> None:
|
|
||||||
self.log.info(f"rejected Delivery: {event.delivery}")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s')
|
|
||||||
logging.info('kontor.read_list started')
|
|
||||||
#conn = stomp.Connection([('127.0.0.1', '61616')])
|
|
||||||
#conn.connect('artemis', 'artemis', wait=True)
|
|
||||||
if args.links:
|
|
||||||
logging.info("read links from file")
|
|
||||||
links = read_links_file(args.links)
|
|
||||||
for link in links:
|
|
||||||
data_dict = {'url': link.strip()}
|
|
||||||
data = json.dumps(data_dict)
|
|
||||||
logging.info("send link message")
|
|
||||||
handler = AddLinkMessage("amqp://127.0.0.1:5672", data, logging)
|
|
||||||
container = Container(handler)
|
|
||||||
container.container_id = "process_add_links"
|
|
||||||
container.run()
|
|
||||||
# conn.send(body=data, destination='KontorMediaFile::add_link_file', headers={'content-type': 'application/json'})
|
|
||||||
#conn.disconnect()
|
|
||||||
logging.info('kontor.read_list finished')
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
sqlalchemy
|
|
||||||
pathlib
|
|
||||||
platformdirs
|
|
||||||
pyyaml
|
|
||||||
beautifulsoup4
|
|
||||||
sqlmodel
|
|
||||||
requests
|
|
||||||
fastapi[standard]
|
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from api import get_logger, get_api_config
|
||||||
|
|
||||||
|
|
||||||
|
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||||
|
parser.add_argument("--verbose", "-v", action="count", default=0)
|
||||||
|
parser.add_argument("--config", "-c", default="kontor-api")
|
||||||
|
parser.add_argument("--dry-run", "-m", action="store_true")
|
||||||
|
parser.add_argument("--cleanup", "-d", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__== "__main__":
|
||||||
|
logger = get_logger(args.verbose, "kontor")
|
||||||
|
logger.info("kontor.sync started")
|
||||||
|
apiConfig = get_api_config(logger, args.config)
|
||||||
|
for server in apiConfig.server:
|
||||||
|
data = server.request(logger, "media_file")
|
||||||
|
logger.info(len(data))
|
||||||
|
logger.info("kontor.sync finished")
|
||||||
@@ -30,6 +30,7 @@ plugins {
|
|||||||
alias(libs.plugins.asciidoctorPdf)
|
alias(libs.plugins.asciidoctorPdf)
|
||||||
alias(libs.plugins.asciidoctorConvert)
|
alias(libs.plugins.asciidoctorConvert)
|
||||||
alias(libs.plugins.asciidoctorGems)
|
alias(libs.plugins.asciidoctorGems)
|
||||||
|
id "de.infolektuell.typst" version "0.8.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:22.18.0-bookworm-slim AS build
|
FROM docker.io/node:20-alpine AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV PATH=/app/node_modules/.bin:$PATH
|
ENV PATH=/app/node_modules/.bin:$PATH
|
||||||
COPY package.json /app/package.json
|
COPY package.json /app/package.json
|
||||||
@@ -7,7 +7,8 @@ RUN npm install -g @vue/cli
|
|||||||
COPY . /app
|
COPY . /app
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM nginx:1.29.0-alpine
|
FROM docker.io/library/nginx:stable-alpine
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
COPY --from=build /app/dist /usr/share/nginx/html
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
server {
|
||||||
|
listen 8700;
|
||||||
|
# Root-Verzeichnis für den Server setzen (wir kopieren unsere Anwendung hierher)
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
|
# Definieren der Standard-Indexdatei (Angular erstellt die Datei index.html für uns und sie befindet sich im oben genannten Verzeichnis)
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Cache-Header für Medien-ASsets
|
||||||
|
location ~* \.(?:cur|jpe?g|gif|htc|ico|png|xml|otf|ttf|eot|woff|woff2|svg)$ {
|
||||||
|
access_log off;
|
||||||
|
add_header Pragma "must-revalidate, public";
|
||||||
|
add_header Cache-Control "must-revalidate, public";
|
||||||
|
expires max;
|
||||||
|
|
||||||
|
tcp_nodelay off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache-Header für HTML, CSS und JS-Dateien
|
||||||
|
location ~* \.(?:css|js|html)$ {
|
||||||
|
access_log off;
|
||||||
|
add_header Pragma "must-revalidate, public";
|
||||||
|
add_header Cache-Control "must-revalidate, public";
|
||||||
|
expires 2d;
|
||||||
|
|
||||||
|
tcp_nodelay off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Konfiguration für den /-Pfad
|
||||||
|
location / {
|
||||||
|
# Zunächst versuchen wir die angeforderte URI auzuliefern
|
||||||
|
# Klappt das nicht, versuchen wir es mit einem abschließenden Slash
|
||||||
|
# Klappt auch das nicht, liefern wir die index.html aus.
|
||||||
|
# Das ist nötig, damit Angular-Routen korrekt augeflöst und ausgeliefert werden
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -5,12 +5,12 @@ const API_URL = "http://localhost:8200/";
|
|||||||
class AuthService {
|
class AuthService {
|
||||||
login(user) {
|
login(user) {
|
||||||
return axios
|
return axios
|
||||||
.post(API_URL + "login", {
|
.post(API_URL + "api/login/token", {
|
||||||
email: user.username,
|
username: user.username,
|
||||||
password: user.password,
|
password: user.password,
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.data.accessToken) {
|
if (response.data.access_token) {
|
||||||
localStorage.setItem("user", JSON.stringify(response.data));
|
localStorage.setItem("user", JSON.stringify(response.data));
|
||||||
}
|
}
|
||||||
return response.data;
|
return response.data;
|
||||||
|
|||||||
@@ -20,4 +20,6 @@ echo " => Baue Image localhost/kontor-javalin:0.3.0"
|
|||||||
buildah build -t kontor-javalin:0.3.0 kontor-javalin
|
buildah build -t kontor-javalin:0.3.0 kontor-javalin
|
||||||
echo " => Baue Image localhost/kontor-vue:0.3.0"
|
echo " => Baue Image localhost/kontor-vue:0.3.0"
|
||||||
buildah build -t kontor-vue:0.3.0 kontor-vue
|
buildah build -t kontor-vue:0.3.0 kontor-vue
|
||||||
|
echo " => Baue Image localhost/kontor-angular:0.3.0"
|
||||||
|
buildah build -t kontor-angular:0.3.0 kontor-angular
|
||||||
|
|
||||||
|
|||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# script/service: Setup user systemd scripts
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
echo "==> Setting up systemd user services"
|
||||||
|
cp service/container/*.service $HOME/.config/systemd/user/
|
||||||
|
|
||||||
+21
-2
@@ -8,7 +8,7 @@ cd "$(dirname "$0")/.."
|
|||||||
|
|
||||||
script/bootstrap
|
script/bootstrap
|
||||||
|
|
||||||
script/cibuild
|
#script/cibuild
|
||||||
|
|
||||||
|
|
||||||
echo "==> Setting up pod kontor"
|
echo "==> Setting up pod kontor"
|
||||||
@@ -29,7 +29,8 @@ else
|
|||||||
-p 8400:8400 \
|
-p 8400:8400 \
|
||||||
-p 8500:8500 \
|
-p 8500:8500 \
|
||||||
-p 8600:8600 \
|
-p 8600:8600 \
|
||||||
-p 8700:80 \
|
-p 8700:8700 \
|
||||||
|
-p 8800:8800 \
|
||||||
-p 8900:8080 \
|
-p 8900:8080 \
|
||||||
-p 61616:61616 \
|
-p 61616:61616 \
|
||||||
-p 8161:8161 \
|
-p 8161:8161 \
|
||||||
@@ -235,3 +236,21 @@ else
|
|||||||
localhost/kontor-vue:0.3.0
|
localhost/kontor-vue:0.3.0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "==> Setting up container kontor-angular"
|
||||||
|
if podman image exists localhost/kontor-angular:0.3.0; then
|
||||||
|
echo " => Image localhost/kontor-angular:0.3.0 available"
|
||||||
|
fi
|
||||||
|
if podman container exists kontor-angular; then
|
||||||
|
if podman ps -q --filter "name=kontor-angular"; then
|
||||||
|
echo " => kontor-angular is running"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
podman run -d \
|
||||||
|
--replace \
|
||||||
|
--pod kontor \
|
||||||
|
--name kontor-angular \
|
||||||
|
--label "io.containers.autoupdate=local" \
|
||||||
|
--label "PODMAN_SYSTEMD_UNIT=container-kontor-angular.service" \
|
||||||
|
localhost/kontor-angular:0.3.0
|
||||||
|
fi
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -9,7 +9,9 @@ cd "$(dirname "$0")/.."
|
|||||||
echo "==> Stopping and removing pod kontor"
|
echo "==> Stopping and removing pod kontor"
|
||||||
if podman pod exists kontor; then
|
if podman pod exists kontor; then
|
||||||
podman pod stop kontor
|
podman pod stop kontor
|
||||||
podman pod rm kontor
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
script/cibuild
|
||||||
|
|
||||||
script/setup
|
script/setup
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
# container-activemq.service
|
# kontor-activemq.service
|
||||||
# autogenerated by Podman 4.9.3
|
# autogenerated by Podman 4.9.3
|
||||||
# Fri Feb 6 17:23:12 CET 2026
|
# Sun Apr 19 16:28:14 CEST 2026
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Podman container-activemq.service
|
Description=Podman kontor-activemq.service
|
||||||
Documentation=man:podman-generate-systemd(1)
|
Documentation=man:podman-generate-systemd(1)
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
After=network-online.target
|
|
||||||
RequiresMountsFor=%t/containers
|
RequiresMountsFor=%t/containers
|
||||||
BindsTo=pod-kontor.service
|
BindsTo=pod-kontor.service
|
||||||
After=pod-kontor.service
|
After=pod-kontor.service
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
# container-adminer.service
|
# kontor-adminer.service
|
||||||
# autogenerated by Podman 4.9.3
|
# autogenerated by Podman 4.9.3
|
||||||
# Fri Feb 6 17:23:12 CET 2026
|
# Sun Apr 19 16:28:14 CEST 2026
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Podman container-adminer.service
|
Description=Podman kontor-adminer.service
|
||||||
Documentation=man:podman-generate-systemd(1)
|
Documentation=man:podman-generate-systemd(1)
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
After=network-online.target
|
|
||||||
RequiresMountsFor=%t/containers
|
RequiresMountsFor=%t/containers
|
||||||
BindsTo=pod-kontor.service
|
BindsTo=pod-kontor.service
|
||||||
After=pod-kontor.service
|
After=pod-kontor.service
|
||||||
@@ -23,7 +22,8 @@ ExecStart=/usr/bin/podman run \
|
|||||||
--sdnotify=conmon \
|
--sdnotify=conmon \
|
||||||
-d \
|
-d \
|
||||||
--replace \
|
--replace \
|
||||||
--name adminer docker.io/library/adminer
|
--name kontor-adminer \
|
||||||
|
docker.io/library/adminer
|
||||||
ExecStop=/usr/bin/podman stop \
|
ExecStop=/usr/bin/podman stop \
|
||||||
--ignore -t 10 \
|
--ignore -t 10 \
|
||||||
--cidfile=%t/%n.ctr-id
|
--cidfile=%t/%n.ctr-id
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# kontor-angular.service
|
||||||
|
# autogenerated by Podman 4.9.3
|
||||||
|
# Sun Apr 19 16:28:14 CEST 2026
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Podman kontor-angular.service
|
||||||
|
Documentation=man:podman-generate-systemd(1)
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
RequiresMountsFor=%t/containers
|
||||||
|
BindsTo=pod-kontor.service
|
||||||
|
After=pod-kontor.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||||
|
Restart=on-failure
|
||||||
|
TimeoutStopSec=70
|
||||||
|
ExecStart=/usr/bin/podman run \
|
||||||
|
--cidfile=%t/%n.ctr-id \
|
||||||
|
--cgroups=no-conmon \
|
||||||
|
--rm \
|
||||||
|
--pod-id-file %t/pod-kontor.pod-id \
|
||||||
|
--sdnotify=conmon \
|
||||||
|
-d \
|
||||||
|
--replace \
|
||||||
|
--name kontor-angular \
|
||||||
|
--label io.containers.autoupdate=local \
|
||||||
|
--label PODMAN_SYSTEMD_UNIT=kontor-angular.service \
|
||||||
|
localhost/kontor-angular:0.3.0
|
||||||
|
ExecStop=/usr/bin/podman stop \
|
||||||
|
--ignore -t 10 \
|
||||||
|
--cidfile=%t/%n.ctr-id
|
||||||
|
ExecStopPost=/usr/bin/podman rm \
|
||||||
|
-f \
|
||||||
|
--ignore -t 10 \
|
||||||
|
--cidfile=%t/%n.ctr-id
|
||||||
|
Type=notify
|
||||||
|
NotifyAccess=all
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
# container-kontor-api.service
|
# kontor-api.service
|
||||||
# autogenerated by Podman 4.9.3
|
# autogenerated by Podman 4.9.3
|
||||||
# Fri Feb 6 17:23:12 CET 2026
|
# Sun Apr 19 16:28:14 CEST 2026
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Podman container-kontor-api.service
|
Description=Podmanco kontor-api.service
|
||||||
Documentation=man:podman-generate-systemd(1)
|
Documentation=man:podman-generate-systemd(1)
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
After=network-online.target
|
|
||||||
RequiresMountsFor=%t/containers
|
RequiresMountsFor=%t/containers
|
||||||
BindsTo=pod-kontor.service
|
BindsTo=pod-kontor.service
|
||||||
After=pod-kontor.service
|
After=pod-kontor.service
|
||||||
@@ -25,10 +24,12 @@ ExecStart=/usr/bin/podman run \
|
|||||||
--replace \
|
--replace \
|
||||||
--name kontor-api \
|
--name kontor-api \
|
||||||
--label io.containers.autoupdate=local \
|
--label io.containers.autoupdate=local \
|
||||||
--label PODMAN_SYSTEMD_UNIT=container-kontor-api.service "--health-cmd=curl -f http://kontor-api:8200/health || exit 1" \
|
--label PODMAN_SYSTEMD_UNIT=kontor-api.service \
|
||||||
|
--health-cmd="curl -f http://kontor-api:8200/health || exit 1" \
|
||||||
--health-interval=1s \
|
--health-interval=1s \
|
||||||
--health-timeout=5s \
|
--health-timeout=5s \
|
||||||
--health-retries=10 localhost/kontor-api:0.3.0
|
--health-retries=10 \
|
||||||
|
localhost/kontor-api:0.3.0
|
||||||
ExecStop=/usr/bin/podman stop \
|
ExecStop=/usr/bin/podman stop \
|
||||||
--ignore -t 10 \
|
--ignore -t 10 \
|
||||||
--cidfile=%t/%n.ctr-id
|
--cidfile=%t/%n.ctr-id
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# container-kontor-echo.service
|
# kontor-echo.service
|
||||||
# autogenerated by Podman 4.9.3
|
# autogenerated by Podman 4.9.3
|
||||||
# Fri Feb 6 17:23:12 CET 2026
|
# Sun Apr 19 16:28:14 CEST 2026
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Podman container-kontor-echo.service
|
Description=Podman kontor-echo.service
|
||||||
Documentation=man:podman-generate-systemd(1)
|
Documentation=man:podman-generate-systemd(1)
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
After=network-online.target
|
After=network-online.target
|
||||||
@@ -25,10 +25,12 @@ ExecStart=/usr/bin/podman run \
|
|||||||
--replace \
|
--replace \
|
||||||
--name kontor-echo \
|
--name kontor-echo \
|
||||||
--label io.containers.autoupdate=local \
|
--label io.containers.autoupdate=local \
|
||||||
--label PODMAN_SYSTEMD_UNIT=container-kontor-echo.service "--health-cmd=curl -f http://kontor-echo:8400/health || exit 1" \
|
--label PODMAN_SYSTEMD_UNIT=kontor-echo.service \
|
||||||
|
--health-cmd="curl -f http://kontor-echo:8400/health || exit 1" \
|
||||||
--health-interval=1s \
|
--health-interval=1s \
|
||||||
--health-timeout=5s \
|
--health-timeout=5s \
|
||||||
--health-retries=10 localhost/kontor-echo:0.3.0
|
--health-retries=10 \
|
||||||
|
localhost/kontor-echo:0.3.0
|
||||||
ExecStop=/usr/bin/podman stop \
|
ExecStop=/usr/bin/podman stop \
|
||||||
--ignore -t 10 \
|
--ignore -t 10 \
|
||||||
--cidfile=%t/%n.ctr-id
|
--cidfile=%t/%n.ctr-id
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# container-kontor-fiber.service
|
# kontor-fiber.service
|
||||||
# autogenerated by Podman 4.9.3
|
# autogenerated by Podman 4.9.3
|
||||||
# Fri Feb 6 17:23:12 CET 2026
|
# Sun Apr 19 16:28:14 CEST 2026
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Podman container-kontor-fiber.service
|
Description=Podman kontor-fiber.service
|
||||||
Documentation=man:podman-generate-systemd(1)
|
Documentation=man:podman-generate-systemd(1)
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
After=network-online.target
|
After=network-online.target
|
||||||
@@ -25,10 +25,12 @@ ExecStart=/usr/bin/podman run \
|
|||||||
--replace \
|
--replace \
|
||||||
--name kontor-fiber \
|
--name kontor-fiber \
|
||||||
--label io.containers.autoupdate=local \
|
--label io.containers.autoupdate=local \
|
||||||
--label PODMAN_SYSTEMD_UNIT=container-kontor-fiber.service "--health-cmd=curl -f http://kontor-echo:8500/health || exit 1" \
|
--label PODMAN_SYSTEMD_UNIT=kontor-fiber.service \
|
||||||
|
--health-cmd="curl -f http://kontor-echo:8500/health || exit 1" \
|
||||||
--health-interval=1s \
|
--health-interval=1s \
|
||||||
--health-timeout=5s \
|
--health-timeout=5s \
|
||||||
--health-retries=10 localhost/kontor-fiber:0.3.0
|
--health-retries=10 \
|
||||||
|
localhost/kontor-fiber:0.3.0
|
||||||
ExecStop=/usr/bin/podman stop \
|
ExecStop=/usr/bin/podman stop \
|
||||||
--ignore -t 10 \
|
--ignore -t 10 \
|
||||||
--cidfile=%t/%n.ctr-id
|
--cidfile=%t/%n.ctr-id
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# container-kontor-javalin.service
|
# kontor-javalin.service
|
||||||
# autogenerated by Podman 4.9.3
|
# autogenerated by Podman 4.9.3
|
||||||
# Fri Feb 6 17:23:12 CET 2026
|
# Sun Apr 19 16:28:14 CEST 2026
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Podman container-kontor-javalin.service
|
Description=Podman kontor-javalin.service
|
||||||
Documentation=man:podman-generate-systemd(1)
|
Documentation=man:podman-generate-systemd(1)
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
After=network-online.target
|
After=network-online.target
|
||||||
@@ -25,10 +25,12 @@ ExecStart=/usr/bin/podman run \
|
|||||||
--replace \
|
--replace \
|
||||||
--name kontor-javalin \
|
--name kontor-javalin \
|
||||||
--label io.containers.autoupdate=local \
|
--label io.containers.autoupdate=local \
|
||||||
--label PODMAN_SYSTEMD_UNIT=container-kontor-javalin.service "--health-cmd=curl -f http://kontor-echo:8600/health || exit 1" \
|
--label PODMAN_SYSTEMD_UNIT=kontor-javalin.service \
|
||||||
|
--health-cmd="curl -f http://kontor-echo:8600/health || exit 1" \
|
||||||
--health-interval=1s \
|
--health-interval=1s \
|
||||||
--health-timeout=5s \
|
--health-timeout=5s \
|
||||||
--health-retries=10 localhost/kontor-javalin:0.3.0
|
--health-retries=10 \
|
||||||
|
localhost/kontor-javalin:0.3.0
|
||||||
ExecStop=/usr/bin/podman stop \
|
ExecStop=/usr/bin/podman stop \
|
||||||
--ignore -t 10 \
|
--ignore -t 10 \
|
||||||
--cidfile=%t/%n.ctr-id
|
--cidfile=%t/%n.ctr-id
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# container-postgres.service
|
# kontor-postgres.service
|
||||||
# autogenerated by Podman 4.9.3
|
# autogenerated by Podman 4.9.3
|
||||||
# Fri Feb 6 17:23:12 CET 2026
|
# Sun Apr 19 16:28:14 CEST 2026
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Podman container-postgres.service
|
Description=Podman kontor-postgres.service
|
||||||
Documentation=man:podman-generate-systemd(1)
|
Documentation=man:podman-generate-systemd(1)
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
After=network-online.target
|
After=network-online.target
|
||||||
@@ -27,10 +27,12 @@ ExecStart=/usr/bin/podman run \
|
|||||||
-e POSTGRES_PASSWORD=kontor \
|
-e POSTGRES_PASSWORD=kontor \
|
||||||
-e POSTGRES_DB=kontor \
|
-e POSTGRES_DB=kontor \
|
||||||
-e POSTGRES_USER=kontor \
|
-e POSTGRES_USER=kontor \
|
||||||
-v kontor-db:/var/lib/postgresql/data "--health-cmd=pg_isready -U kontor || exit 1" \
|
-v kontor-db:/var/lib/postgresql/data \
|
||||||
|
--health-cmd="pg_isready -U kontor || exit 1" \
|
||||||
--health-interval=1s \
|
--health-interval=1s \
|
||||||
--health-timeout=5s \
|
--health-timeout=5s \
|
||||||
--health-retries=10 docker.io/library/postgres:17
|
--health-retries=10 \
|
||||||
|
docker.io/library/postgres:17
|
||||||
ExecStop=/usr/bin/podman stop \
|
ExecStop=/usr/bin/podman stop \
|
||||||
--ignore -t 10 \
|
--ignore -t 10 \
|
||||||
--cidfile=%t/%n.ctr-id
|
--cidfile=%t/%n.ctr-id
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# container-kontor-quarkus.service
|
# kontor-quarkus.service
|
||||||
# autogenerated by Podman 4.9.3
|
# autogenerated by Podman 4.9.3
|
||||||
# Fri Feb 6 17:23:12 CET 2026
|
# Sun Apr 19 16:28:14 CEST 2026
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Podman container-kontor-quarkus.service
|
Description=Podman kontor-quarkus.service
|
||||||
Documentation=man:podman-generate-systemd(1)
|
Documentation=man:podman-generate-systemd(1)
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
After=network-online.target
|
After=network-online.target
|
||||||
@@ -25,10 +25,12 @@ ExecStart=/usr/bin/podman run \
|
|||||||
--replace \
|
--replace \
|
||||||
--name kontor-quarkus \
|
--name kontor-quarkus \
|
||||||
--label io.containers.autoupdate=local \
|
--label io.containers.autoupdate=local \
|
||||||
--label PODMAN_SYSTEMD_UNIT=container-kontor-quarkus.service "--health-cmd=curl -f http://kontor-quarkus:8300/q/health || exit 1" \
|
--label PODMAN_SYSTEMD_UNIT=kontor-quarkus.service \
|
||||||
|
--health-cmd="curl -f http://kontor-quarkus:8300/q/health || exit 1" \
|
||||||
--health-interval=1s \
|
--health-interval=1s \
|
||||||
--health-timeout=5s \
|
--health-timeout=5s \
|
||||||
--health-retries=10 localhost/kontor-quarkus:0.3.0
|
--health-retries=10 \
|
||||||
|
localhost/kontor-quarkus:0.3.0
|
||||||
ExecStop=/usr/bin/podman stop \
|
ExecStop=/usr/bin/podman stop \
|
||||||
--ignore -t 10 \
|
--ignore -t 10 \
|
||||||
--cidfile=%t/%n.ctr-id
|
--cidfile=%t/%n.ctr-id
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# container-kontor-spring.service
|
# kontor-spring.service
|
||||||
# autogenerated by Podman 4.9.3
|
# autogenerated by Podman 4.9.3
|
||||||
# Fri Feb 6 17:23:12 CET 2026
|
# Sun Apr 19 16:28:14 CEST 2026
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Podman container-kontor-spring.service
|
Description=Podman kontor-spring.service
|
||||||
Documentation=man:podman-generate-systemd(1)
|
Documentation=man:podman-generate-systemd(1)
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
After=network-online.target
|
After=network-online.target
|
||||||
@@ -25,7 +25,8 @@ ExecStart=/usr/bin/podman run \
|
|||||||
--replace \
|
--replace \
|
||||||
--name kontor-spring \
|
--name kontor-spring \
|
||||||
--label io.containers.autoupdate=local \
|
--label io.containers.autoupdate=local \
|
||||||
--label PODMAN_SYSTEMD_UNIT=container-kontor-spring.service localhost/kontor-spring:0.3.0
|
--label PODMAN_SYSTEMD_UNIT=kontor-spring.service \
|
||||||
|
localhost/kontor-spring:0.3.0
|
||||||
ExecStop=/usr/bin/podman stop \
|
ExecStop=/usr/bin/podman stop \
|
||||||
--ignore -t 10 \
|
--ignore -t 10 \
|
||||||
--cidfile=%t/%n.ctr-id
|
--cidfile=%t/%n.ctr-id
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# kontor-vue.service
|
||||||
|
# autogenerated by Podman 4.9.3
|
||||||
|
# Sun Apr 19 16:28:14 CEST 2026
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Podman kontor-vue.service
|
||||||
|
Documentation=man:podman-generate-systemd(1)
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
RequiresMountsFor=%t/containers
|
||||||
|
BindsTo=pod-kontor.service
|
||||||
|
After=pod-kontor.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||||
|
Restart=on-failure
|
||||||
|
TimeoutStopSec=70
|
||||||
|
ExecStart=/usr/bin/podman run \
|
||||||
|
--cidfile=%t/%n.ctr-id \
|
||||||
|
--cgroups=no-conmon \
|
||||||
|
--rm \
|
||||||
|
--pod-id-file %t/pod-kontor.pod-id \
|
||||||
|
--sdnotify=conmon \
|
||||||
|
-d \
|
||||||
|
--replace \
|
||||||
|
--name kontor-vue \
|
||||||
|
--label io.containers.autoupdate=local \
|
||||||
|
--label PODMAN_SYSTEMD_UNIT=kontor-vue.service \
|
||||||
|
localhost/kontor-vue:0.3.0
|
||||||
|
ExecStop=/usr/bin/podman stop \
|
||||||
|
--ignore -t 10 \
|
||||||
|
--cidfile=%t/%n.ctr-id
|
||||||
|
ExecStopPost=/usr/bin/podman rm \
|
||||||
|
-f \
|
||||||
|
--ignore -t 10 \
|
||||||
|
--cidfile=%t/%n.ctr-id
|
||||||
|
Type=notify
|
||||||
|
NotifyAccess=all
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# pod-kontor.service
|
# pod-kontor.service
|
||||||
# autogenerated by Podman 4.9.3
|
# autogenerated by Podman 4.9.3
|
||||||
# Fri Feb 6 17:23:12 CET 2026
|
# Sun Apr 19 16:28:14 CEST 2026
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Podman pod-kontor.service
|
Description=Podman pod-kontor.service
|
||||||
@@ -8,8 +8,8 @@ Documentation=man:podman-generate-systemd(1)
|
|||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
After=network-online.target
|
After=network-online.target
|
||||||
RequiresMountsFor=/run/user/1000/containers
|
RequiresMountsFor=/run/user/1000/containers
|
||||||
Wants=container-activemq.service container-adminer.service container-kontor-api.service container-kontor-echo.service container-kontor-fiber.service container-kontor-javalin.service container-kontor-quarkus.service container-kontor-spring.service container-postgres.service
|
Wants=kontor-activemq.service kontor-adminer.service kontor-angular.service kontor-api.service kontor-echo.service kontor-fiber.service kontor-javalin.service kontor-quarkus.service kontor-spring.service kontor-vue.service kontor-postgres.service
|
||||||
Before=container-activemq.service container-adminer.service container-kontor-api.service container-kontor-echo.service container-kontor-fiber.service container-kontor-javalin.service container-kontor-quarkus.service container-kontor-spring.service container-postgres.service
|
Before=kontor-activemq.service kontor-adminer.service kontor-angular.service kontor-api.service kontor-echo.service kontor-fiber.service kontor-javalin.service kontor-quarkus.service kontor-spring.service kontor-vue.service kontor-postgres.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Environment=PODMAN_SYSTEMD_UNIT=%n
|
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||||
@@ -28,6 +28,8 @@ ExecStartPre=/usr/bin/podman pod create \
|
|||||||
-p 8400:8400 \
|
-p 8400:8400 \
|
||||||
-p 8500:8500 \
|
-p 8500:8500 \
|
||||||
-p 8600:8600 \
|
-p 8600:8600 \
|
||||||
|
-p 8700:8700 \
|
||||||
|
-p 8800:8800 \
|
||||||
-p 8900:8080 \
|
-p 8900:8080 \
|
||||||
-p 61616:61616 \
|
-p 61616:61616 \
|
||||||
-p 8161:8161 \
|
-p 8161:8161 \
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=kontor-api via UV
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=tpeetz
|
||||||
|
Group=tpeetz
|
||||||
|
Environment="DB_SERVER=localhost"
|
||||||
|
WorkingDirectory=/home/tpeetz/projects/kontor/kontor-api
|
||||||
|
ExecStart=/home/tpeetz/projects/kontor/kontor-api/.venv/bin/uvicorn src.main:kontor --log-level info --host 127.0.0.1 --port 8200
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
Reference in New Issue
Block a user