Compare commits
166 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cfaaa4d87 | |||
| 5850b1cfb0 | |||
| 8833d2ca6a | |||
| 1290a45fb5 | |||
| 3afbbf900d | |||
| f71419d37b | |||
| f5f673fd4e | |||
| 8ec0784490 | |||
| 13faff8e01 | |||
| 0a4f6bb0f9 | |||
| 3b4e5634b1 | |||
| 8c98e6de26 | |||
| 525d82cbda | |||
| e26aaa1420 | |||
| ec7f05d8fc | |||
| 727e95e732 | |||
| 75d3bf6ffb | |||
| 94782fc468 | |||
| 101ece44d8 | |||
| e880594a5b | |||
| c222d4cd7a | |||
| cb0fa3f728 | |||
| 4709e431b7 | |||
| bfccca72a1 | |||
| 8a3eebaab5 | |||
| 6716103d0c | |||
| ee78af1abe | |||
| 1fc726ee4b | |||
| 882f48de0b | |||
| 097a3efd4a | |||
| 934ef826c9 | |||
| bb049441b4 | |||
| 2bdbef8f8a | |||
| 20f29d88fa | |||
| 1997957069 | |||
| 116bb77e4c | |||
| a6eeea6c1f | |||
| 4a61d6a727 | |||
| 98e3d91edd | |||
| a169f6a6c1 | |||
| b14a267b5b | |||
| a43e2c806c | |||
| 10834df92b | |||
| 3d97c3360a | |||
| 76d91dd506 | |||
| 84aff2b7d7 | |||
| 2f8c10a692 | |||
| c5f74e007a | |||
| 243f693f9d | |||
| 976b4f3f47 | |||
| 1b18dae311 | |||
| 38e77b25b1 | |||
| bbc08863f0 | |||
| d58f18d206 | |||
| fd5bc54eee | |||
| cbc57d22f8 | |||
| a5cdf8867a | |||
| 45971518ee | |||
| 7db244f599 | |||
| 41e2b11da2 | |||
| 0d1b2e416e | |||
| d8eecb4dab | |||
| 3bb3b22cea | |||
| 8ffd421c1b | |||
| 6d54c7f315 | |||
| f5fb743503 | |||
| ca9022d289 | |||
| ed0e108599 | |||
| 7dc18b10cb | |||
| 171bc1676a | |||
| 1a5cd6ffe8 | |||
| 71ecfaff1f | |||
| f33aaadce7 | |||
| 591171b223 | |||
| da453d642d | |||
| e733fa21e6 | |||
| c61e49720e | |||
| d01489b1fa | |||
| 93c7498a83 | |||
| fe89cc6e0f | |||
| 1e9ca7c1a4 | |||
| 400aff6524 | |||
| 845f085a76 | |||
| 88c623edb7 | |||
| 0cc73c09aa | |||
| e8660faa06 | |||
| 4e884fdbe5 | |||
| 4c330a1138 | |||
| 65288a53a1 | |||
| 60fba0d3e9 | |||
| ada723dc48 | |||
| f07c7b74ee | |||
| f3c59c11ba | |||
| 3c4d3ad326 | |||
| 917e287784 | |||
| 611d6b9e61 | |||
| 58263cb854 | |||
| 33dcbc4413 | |||
| f3cf1a17f3 | |||
| 3466c10a88 | |||
| 4cea554116 | |||
| 332e09b283 | |||
| d3c720f995 | |||
| 453ad2af7b | |||
| 54bc17ee7d | |||
| 89b7b87b8c | |||
| 3f0a37ff19 | |||
| 954dab289a | |||
| 5affa16505 | |||
| d2b1cb999a | |||
| f1d36acff7 | |||
| 7e8a6b3c91 | |||
| b424e95e05 | |||
| 28746adfbb | |||
| 0d2f27f771 | |||
| d6410e2584 | |||
| ce1514f20a | |||
| c8ea7c4188 | |||
| 245ee2378e | |||
| afb3ac88c8 | |||
| 3a0a0055a6 | |||
| 283577df9b | |||
| 276302570f | |||
| f74c07af9a | |||
| d0eae1980a | |||
| 820ae3d374 | |||
| bf14c9a020 | |||
| a3652cc9b8 | |||
| 8f2e99195a | |||
| 2ae11e24ef | |||
| a1e8474149 | |||
| 9b329c0ac4 | |||
| 8120c92b32 | |||
| 5c261529fc | |||
| 6923b3e5ee | |||
| fc4110b11d | |||
| e078f43cc6 | |||
| 24e4c0b58e | |||
| 8d31a92692 | |||
| c619745a4a | |||
| c769115331 | |||
| 1a7da0ab9f | |||
| 78632e0e12 | |||
| 3aed8af868 | |||
| c6d1e4d7e7 | |||
| d98dc79cf8 | |||
| 9dcc09c586 | |||
| 57e7b9e999 | |||
| d97145b629 | |||
| 8ba23b6365 | |||
| 66d61e2c1f | |||
| 83f645d4c6 | |||
| 7d84e341d6 | |||
| c3ec0977c0 | |||
| d51bc47ffe | |||
| faa1d73eea | |||
| fe407d6d89 | |||
| 0759c99c1d | |||
| 77bb86a9fa | |||
| 4d7d7391c3 | |||
| c5aa3b32ef | |||
| 4ff999a74e | |||
| bdf2b2ba43 | |||
| 04dfe483e4 | |||
| 4a279738e3 | |||
| 3f65ec55fc |
@@ -13,9 +13,4 @@ kontor-schema/kontor_schema.egg-info
|
|||||||
kontor-gui/.pdm-python
|
kontor-gui/.pdm-python
|
||||||
kontor-gui/dist
|
kontor-gui/dist
|
||||||
fastapi/.coverage
|
fastapi/.coverage
|
||||||
kontor-api/.coverage
|
|
||||||
db-password.txt
|
db-password.txt
|
||||||
kontor-api/tests/test_main.py
|
|
||||||
kontor-api/tests/test_db.db
|
|
||||||
kontor-api/test_db.db
|
|
||||||
couchdb-password.txt
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
kontor_api := kontor-api
|
kontor_api := kontor-api
|
||||||
kontor_spring := kontor-spring
|
kontor_spring := kontor-spring
|
||||||
kontor_servicemix := kontor-servicemix
|
|
||||||
|
|
||||||
.PHONY: all $(kontor_spring) $(kontor_api)
|
.PHONY: all $(kontor_spring) $(kontor_api)
|
||||||
all: $(kontor_spring) $(kontor_api) $(kontor_servicemix)
|
all: $(kontor_spring) $(kontor_api)
|
||||||
|
|
||||||
$(kontor_spring) $(kontor_api) $(kontor_servicemix):
|
$(kontor_spring) $(kontor_api):
|
||||||
$(MAKE) --directory=$@ $(TARGET)
|
$(MAKE) --directory=$@ $(TARGET)
|
||||||
|
|
||||||
|
|||||||
+16
-73
@@ -1,101 +1,44 @@
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
mariadb:
|
||||||
image: postgres
|
image: mariadb
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_DB=kontor
|
MYSQL_ROOT_PASSWORD: kontor
|
||||||
- POSTGRES_USER=kontor
|
MYSQL_USER: kontor
|
||||||
#- POSTGRES_PASSWORD_FILE=/run/secrets/db-password
|
MYSQL_PASSWORD: kontor
|
||||||
- POSTGRES_PASSWORD=kontor
|
MYSQL_DATABASE: kontor
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U kontor"]
|
|
||||||
interval: 1s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 3316:3306
|
||||||
networks:
|
networks:
|
||||||
- database
|
- database
|
||||||
volumes:
|
volumes:
|
||||||
- postgres-data:/var/lib/postgresql/data:rw
|
- mariadb-storage:/var/lib/mysql:rw
|
||||||
secrets:
|
|
||||||
- db-password
|
|
||||||
adminer:
|
|
||||||
image: adminer
|
|
||||||
ports:
|
|
||||||
- 8090:8080
|
|
||||||
networks:
|
|
||||||
- database
|
|
||||||
- frontend
|
|
||||||
couchdb:
|
|
||||||
image: couchdb
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
- COUCHDB_USER=admin
|
|
||||||
- COUCHDB_PASSWORD=admin
|
|
||||||
ports:
|
|
||||||
- 5984:5984
|
|
||||||
networks:
|
|
||||||
- database
|
|
||||||
- frontend
|
|
||||||
volumes:
|
|
||||||
- couchdb-data:/opt/couchdb/data
|
|
||||||
secrets:
|
|
||||||
- couchdb-password
|
|
||||||
activemq:
|
|
||||||
image: apache/activemq-artemis:latest-alpine
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- 61616:61616
|
|
||||||
- 8161:8161
|
|
||||||
- 5672:5672
|
|
||||||
networks:
|
|
||||||
- integration
|
|
||||||
- frontend
|
|
||||||
volumes:
|
|
||||||
- activemq-data:/var/lib/artemis-instance
|
|
||||||
kontor:
|
kontor:
|
||||||
image: kontor:0.2.0-SNAPSHOT
|
image: kontor
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- database
|
- database
|
||||||
- integration
|
|
||||||
- frontend
|
- frontend
|
||||||
ports:
|
ports:
|
||||||
- 8000:8000
|
- 8000:8000
|
||||||
volumes:
|
|
||||||
- images-data:/data/images
|
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
- mariadb
|
||||||
condition: service_healthy
|
|
||||||
kontor-api:
|
kontor-api:
|
||||||
image: kontor-api:0.2.0-SNAPSHOT
|
image: kontor-api
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- database
|
- database
|
||||||
- integration
|
|
||||||
- frontend
|
- frontend
|
||||||
ports:
|
ports:
|
||||||
- 8800:8800
|
- 8800:8800
|
||||||
volumes:
|
|
||||||
- images-data:/data/images
|
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
- mariadb
|
||||||
condition: service_healthy
|
|
||||||
networks:
|
networks:
|
||||||
database:
|
database:
|
||||||
integration:
|
|
||||||
name: integration
|
|
||||||
frontend:
|
frontend:
|
||||||
volumes:
|
|
||||||
postgres-data:
|
volumes:
|
||||||
couchdb-data:
|
mariadb-storage:
|
||||||
activemq-data:
|
|
||||||
images-data:
|
|
||||||
secrets:
|
|
||||||
db-password:
|
|
||||||
file: db-password.txt
|
|
||||||
couchdb-password:
|
|
||||||
file: couchdb-password.txt
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1,4 +1 @@
|
|||||||
.env
|
.env
|
||||||
.coverage
|
|
||||||
app.log
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
## ------------------------------- Builder Stage ------------------------------ ##
|
## ------------------------------- Builder Stage ------------------------------ ##
|
||||||
FROM python:3.13-bookworm AS builder
|
FROM python:3.13-bookworm AS builder
|
||||||
|
|
||||||
RUN apt-get update && apt-get install --no-install-recommends -y build-essential && \
|
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||||
|
build-essential libmariadb-dev && \
|
||||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Download the latest installer, install it and then remove it
|
# Download the latest installer, install it and then remove it
|
||||||
@@ -41,6 +42,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
COPY /src src
|
COPY /src src
|
||||||
COPY --from=builder /app/.venv .venv
|
COPY --from=builder /app/.venv .venv
|
||||||
|
COPY --from=builder /usr/lib/x86_64-linux-gnu/libmariadb.so.3 /usr/lib/x86_64-linux-gnu
|
||||||
|
|
||||||
# Set up environment variables for production
|
# Set up environment variables for production
|
||||||
ENV PATH="/app/.venv/bin:$PATH"
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
|
|||||||
+3
-3
@@ -4,11 +4,11 @@ clean:
|
|||||||
find . -name '*.py[co]' -delete
|
find . -name '*.py[co]' -delete
|
||||||
|
|
||||||
test:
|
test:
|
||||||
DB_SERVER=localhost uv run pytest -v --cov --cov-report=term --cov-report=html:coverage-report
|
DB_HOST=localhost uv run pytest -v --cov --cov-report=term --cov-report=html:coverage-report
|
||||||
|
|
||||||
docker: clean
|
docker: clean
|
||||||
docker build --target=production -t kontor-api:0.2.0-SNAPSHOT .
|
docker build --target=production -t kontor-api -t kontor-api:0.1.0-SNAPSHOT .
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
DB_SERVER=127.0.0.1 uv run fastapi dev src/main.py --port 8008
|
MARIADB_SERVER=localhost uv run fastapi dev src/main.py --port 8008
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ dependencies = [
|
|||||||
"beautifulsoup4>=4.13.4",
|
"beautifulsoup4>=4.13.4",
|
||||||
"fastapi[standard]>=0.115.12",
|
"fastapi[standard]>=0.115.12",
|
||||||
"httpx==0.24.1",
|
"httpx==0.24.1",
|
||||||
|
"mariadb>=1.1.12",
|
||||||
"sqlalchemy>=2.0.40",
|
"sqlalchemy>=2.0.40",
|
||||||
"platformdirs>=4.3.7",
|
"platformdirs>=4.3.7",
|
||||||
"pathlib>=1.0.1",
|
"pathlib>=1.0.1",
|
||||||
@@ -21,11 +22,4 @@ dependencies = [
|
|||||||
"python-jose>=3.4.0",
|
"python-jose>=3.4.0",
|
||||||
"python-multipart>=0.0.20",
|
"python-multipart>=0.0.20",
|
||||||
"natsort>=8.4.0",
|
"natsort>=8.4.0",
|
||||||
"psycopg2-binary>=2.9.10",
|
|
||||||
"pytest-cov>=6.1.1",
|
|
||||||
"databases[sqlite]>=0.9.0",
|
|
||||||
"pydantic[email]>=2.11.3",
|
|
||||||
"jinja2>=3.1.6",
|
|
||||||
"asyncpg>=0.30.0",
|
|
||||||
"bcrypt>=4.3.0",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from src.apis.version1 import comic, media, tysc, admin
|
from src.apis.version1 import comic, media, tysc
|
||||||
|
|
||||||
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(media.router, prefix="/media", tags=["media"])
|
api_router.include_router(media.router, prefix="/media", tags=["media"])
|
||||||
api_router.include_router(tysc.router, prefix="/tysc", tags=["tysc"])
|
api_router.include_router(tysc.router, prefix="/tysc", tags=["tysc"])
|
||||||
api_router.include_router(admin.router, prefix="/login", tags=["login"])
|
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
from typing import Dict
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import HTTPException
|
|
||||||
from fastapi import Request
|
|
||||||
from fastapi import status
|
|
||||||
from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
|
|
||||||
from fastapi.security import OAuth2
|
|
||||||
from fastapi.security.utils import get_authorization_scheme_param
|
|
||||||
|
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -15,34 +6,3 @@ from sqlalchemy.orm import Session
|
|||||||
from src.db.session import get_db
|
from src.db.session import get_db
|
||||||
|
|
||||||
SessionDep = Annotated[Session, Depends(get_db)]
|
SessionDep = Annotated[Session, Depends(get_db)]
|
||||||
|
|
||||||
|
|
||||||
class OAuth2PasswordBearerWithCookie(OAuth2):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
tokenUrl: str,
|
|
||||||
scheme_name: Optional[str] = None,
|
|
||||||
scopes: Optional[Dict[str, str]] = None,
|
|
||||||
auto_error: bool = True,
|
|
||||||
):
|
|
||||||
if not scopes:
|
|
||||||
scopes = {}
|
|
||||||
flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes})
|
|
||||||
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
|
|
||||||
|
|
||||||
async def __call__(self, request: Request) -> Optional[str]:
|
|
||||||
authorization: str = request.cookies.get(
|
|
||||||
"access_token"
|
|
||||||
) # changed to accept access token from httpOnly Cookie
|
|
||||||
|
|
||||||
scheme, param = get_authorization_scheme_param(authorization)
|
|
||||||
if not authorization or scheme.lower() != "bearer":
|
|
||||||
if self.auto_error:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Not authenticated",
|
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
return param
|
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
import logging
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
import bcrypt
|
|
||||||
from fastapi import APIRouter, HTTPException, status, Response, Depends
|
|
||||||
from fastapi.security import OAuth2PasswordRequestForm
|
|
||||||
from jose import jwt, JWTError
|
|
||||||
from src.apis.utils import SessionDep, OAuth2PasswordBearerWithCookie
|
|
||||||
from src.core.config import settings
|
|
||||||
from src.core.security import create_access_token
|
|
||||||
from src.db.models.admin import Profile
|
|
||||||
from src.db.repository.admin import get_profile
|
|
||||||
from src.schema.admin import Token
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
|
|
||||||
def authenticate_user(username: str, password: str, db: SessionDep) -> Profile | None:
|
|
||||||
user = get_profile(username=username, db=db)
|
|
||||||
print(user)
|
|
||||||
if not user:
|
|
||||||
return None
|
|
||||||
if bcrypt.checkpw(password.encode(), user.password.encode()):
|
|
||||||
print("User successful authenticated")
|
|
||||||
else:
|
|
||||||
logging.info("Authentication failed!")
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/token", response_model=Token)
|
|
||||||
def login_for_access_token(response: Response, db: SessionDep, form_data: OAuth2PasswordRequestForm = Depends()):
|
|
||||||
user = authenticate_user(form_data.username, form_data.password, db)
|
|
||||||
if not user:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Incorrect username or password",
|
|
||||||
)
|
|
||||||
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
|
||||||
access_token = create_access_token(
|
|
||||||
data={"sub": user.email}, expires_delta=access_token_expires
|
|
||||||
)
|
|
||||||
response.set_cookie(
|
|
||||||
key="access_token", value=f"Bearer {access_token}", httponly=True
|
|
||||||
)
|
|
||||||
return {"access_token": access_token, "token_type": "bearer"}
|
|
||||||
|
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearerWithCookie(tokenUrl="/api/login/token")
|
|
||||||
|
|
||||||
|
|
||||||
def get_current_user_from_token(db: SessionDep, token: str = Depends(oauth2_scheme)):
|
|
||||||
credentials_exception = HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Could not validate credentials",
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
payload = jwt.decode(
|
|
||||||
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
|
||||||
)
|
|
||||||
username: str = payload.get("sub")
|
|
||||||
print("username/email extracted is ", username)
|
|
||||||
if username is None:
|
|
||||||
raise credentials_exception
|
|
||||||
except JWTError:
|
|
||||||
raise credentials_exception
|
|
||||||
user = get_profile(username=username, db=db)
|
|
||||||
if user is None:
|
|
||||||
raise credentials_exception
|
|
||||||
return user
|
|
||||||
@@ -1,28 +1,31 @@
|
|||||||
from typing import List, AnyStr
|
from uuid import UUID
|
||||||
|
from typing import List
|
||||||
from fastapi import APIRouter, HTTPException, status
|
from fastapi import APIRouter, HTTPException, status
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
from src.apis.utils import SessionDep
|
from src.apis.utils import SessionDep
|
||||||
from src.db.repository.comics.artist import get_artist_details
|
|
||||||
from src.db.repository.comics.comic import list_comics, get_issue_details
|
|
||||||
from src.schema.comics.comic import ComicResponse, ComicDetailsResponse, get_comic_details, get_short_info
|
from src.schema.comics.comic import ComicResponse, ComicDetailsResponse, get_comic_details, get_short_info
|
||||||
from src.schema.comics.artist import ArtistCreation, ArtistDetailResponse, ArtistResponse
|
from src.schema.comics.artist import ArtistCreation, ArtistDetailResponse, ArtistResponse, get_artist_details
|
||||||
from src.db.models.comic import Comic, Artist, Issue
|
from src.db.models.comic import Comic, Artist
|
||||||
from src.schema.comics.issue import IssueDetailsResponse
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter(
|
||||||
|
prefix="/comic",
|
||||||
|
tags=["comics"],
|
||||||
|
responses={404: {"description": "Not found"}},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/comics")
|
@router.get("/comics")
|
||||||
def get_all_comics(db: SessionDep) -> List[ComicResponse]:
|
def get_all_comics(db: SessionDep) -> List[ComicResponse]:
|
||||||
results: List[ComicResponse] = []
|
results: List[ComicResponse] = []
|
||||||
comics = list_comics(db)
|
comics = db.scalars(select(Comic)).all()
|
||||||
for comic in comics:
|
for comic in comics:
|
||||||
response = get_short_info(comic)
|
response = get_short_info(comic)
|
||||||
results.append(response)
|
results.append(response)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@router.get("/comics/{comic_id}", response_model=ComicDetailsResponse)
|
@router.get("/comics/{comic_id}", response_model=ComicDetailsResponse)
|
||||||
def get_comic(comic_id: AnyStr, db: SessionDep) -> ComicDetailsResponse:
|
def get_comic(comic_id: UUID, db: SessionDep) -> ComicDetailsResponse:
|
||||||
comic = db.get(Comic, comic_id)
|
comic = db.get(Comic, comic_id)
|
||||||
if comic is None:
|
if comic is None:
|
||||||
raise HTTPException(status_code=404, detail="Comic could not be found")
|
raise HTTPException(status_code=404, detail="Comic could not be found")
|
||||||
@@ -38,7 +41,7 @@ def get_all_artists(db: SessionDep) -> List[ArtistResponse]:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
@router.get("/artists/{artist_id}", response_model=ArtistDetailResponse)
|
@router.get("/artists/{artist_id}", response_model=ArtistDetailResponse)
|
||||||
def get_artist(artist_id: AnyStr, db: SessionDep) -> ArtistDetailResponse:
|
def get_artist(artist_id: UUID, db: SessionDep) -> ArtistDetailResponse:
|
||||||
artist = db.get(Artist, artist_id)
|
artist = db.get(Artist, artist_id)
|
||||||
if artist is None:
|
if artist is None:
|
||||||
raise HTTPException(status_code=404, detail="Artist could not be found")
|
raise HTTPException(status_code=404, detail="Artist could not be found")
|
||||||
@@ -57,11 +60,3 @@ def add_artist(db: SessionDep, artist_creation: ArtistCreation) -> ArtistRespons
|
|||||||
response = ArtistResponse(id=artist.id, name=artist.name)
|
response = ArtistResponse(id=artist.id, name=artist.name)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@router.get("/issues", response_model=List[IssueDetailsResponse])
|
|
||||||
def get_issues(db: SessionDep) -> List[IssueDetailsResponse]:
|
|
||||||
results: List[IssueDetailsResponse] = []
|
|
||||||
issues = db.query(Issue).all()
|
|
||||||
for issue in issues:
|
|
||||||
results.append(get_issue_details(issue))
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
from typing import List, AnyStr
|
from typing import List
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from fastapi import APIRouter, status, HTTPException, Depends
|
from fastapi import APIRouter, status, HTTPException
|
||||||
from sqlalchemy import select, Sequence
|
from sqlalchemy import select, Sequence
|
||||||
from src.core.log_conf import logger
|
|
||||||
from src.apis.utils import SessionDep
|
from src.apis.utils import SessionDep
|
||||||
from src.db.repository.media import create_new_mediafile
|
|
||||||
from src.schema.media.file import MediaFileResponse, Link, get_file_details, set_file
|
from src.schema.media.file import MediaFileResponse, Link, get_file_details, set_file
|
||||||
from src.db.models.media import MediaFile
|
from src.db.models.media import MediaFile
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter(
|
||||||
|
prefix="/media",
|
||||||
|
tags=["media"]
|
||||||
|
)
|
||||||
|
|
||||||
@router.get("/update-titles")
|
@router.get("/update-titles")
|
||||||
def update_titles(db: SessionDep) -> list[MediaFileResponse]:
|
def update_titles(db: SessionDep) -> list[MediaFileResponse]:
|
||||||
results: list[MediaFileResponse] = []
|
results: list[MediaFileResponse] = []
|
||||||
files = db.query(MediaFile).filter(MediaFile.review == True).all()
|
files = db.query(MediaFile).filter(MediaFile.review == 1).all()
|
||||||
for mediafile in files:
|
for mediafile in files:
|
||||||
mediafile.update_title()
|
mediafile.update_title()
|
||||||
db.add(mediafile)
|
db.add(mediafile)
|
||||||
@@ -24,14 +27,13 @@ def update_titles(db: SessionDep) -> list[MediaFileResponse]:
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/files", response_model=List[MediaFileResponse])
|
@router.get("/files", response_model=List[MediaFileResponse])
|
||||||
#def get_all_files(db: SessionDep, review: bool = False, download: bool = False, current_user: Profile = Depends(get_current_user_from_token)) -> List[MediaFileResponse]:
|
|
||||||
def get_all_files(db: SessionDep, review: bool = False, download: bool = False) -> List[MediaFileResponse]:
|
def get_all_files(db: SessionDep, review: bool = False, download: bool = False) -> List[MediaFileResponse]:
|
||||||
results: list[MediaFileResponse] = []
|
results: list[MediaFileResponse] = []
|
||||||
files: Sequence[MediaFile]
|
files: Sequence[MediaFile]
|
||||||
if review:
|
if review:
|
||||||
files = db.query(MediaFile).filter(MediaFile.review == True).all()
|
files = db.query(MediaFile).filter(MediaFile.review == 1).all()
|
||||||
elif download:
|
elif download:
|
||||||
files = db.query(MediaFile).filter(MediaFile.should_download == True).all()
|
files = db.query(MediaFile).filter(MediaFile.should_download == 1).all()
|
||||||
else:
|
else:
|
||||||
files = db.scalars(select(MediaFile)).all()
|
files = db.scalars(select(MediaFile)).all()
|
||||||
for mediafile in files:
|
for mediafile in files:
|
||||||
@@ -40,7 +42,7 @@ def get_all_files(db: SessionDep, review: bool = False, download: bool = False)
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
@router.get("/files/{file_id}", response_model=MediaFileResponse)
|
@router.get("/files/{file_id}", response_model=MediaFileResponse)
|
||||||
def get_file(file_id: AnyStr, db: SessionDep) -> MediaFileResponse:
|
def get_file(file_id: UUID, db: SessionDep) -> MediaFileResponse:
|
||||||
mediafile = db.get(MediaFile, file_id)
|
mediafile = db.get(MediaFile, file_id)
|
||||||
if not mediafile:
|
if not mediafile:
|
||||||
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
||||||
@@ -48,7 +50,7 @@ def get_file(file_id: AnyStr, db: SessionDep) -> MediaFileResponse:
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
@router.put("/files/{file_id}", response_model=MediaFileResponse)
|
@router.put("/files/{file_id}", response_model=MediaFileResponse)
|
||||||
def update_file(file_id: AnyStr, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse:
|
def update_file(file_id: UUID, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse:
|
||||||
mediaFile = db.get(MediaFile, file_id)
|
mediaFile = db.get(MediaFile, file_id)
|
||||||
if not mediaFile:
|
if not mediaFile:
|
||||||
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
||||||
@@ -60,9 +62,14 @@ def update_file(file_id: AnyStr, db: SessionDep, info: MediaFileResponse) -> Med
|
|||||||
|
|
||||||
@router.post("/files", status_code=status.HTTP_201_CREATED)
|
@router.post("/files", status_code=status.HTTP_201_CREATED)
|
||||||
def add_file(new_link: Link, db: SessionDep) -> MediaFileResponse:
|
def add_file(new_link: Link, db: SessionDep) -> MediaFileResponse:
|
||||||
logger.info(f"add url {new_link.url}")
|
print(new_link.url)
|
||||||
try:
|
try:
|
||||||
mediaFile: MediaFile = create_new_mediafile(new_link.url, db)
|
mediaFile: MediaFile = MediaFile()
|
||||||
|
setattr(mediaFile, "url", new_link.url)
|
||||||
|
setattr(mediaFile, "review", 1)
|
||||||
|
setattr(mediaFile, "should_download", 1)
|
||||||
|
db.add(mediaFile)
|
||||||
|
db.commit()
|
||||||
except:
|
except:
|
||||||
raise HTTPException(status_code=409, detail="Link duplicate")
|
raise HTTPException(status_code=409, detail="Link duplicate")
|
||||||
response = get_file_details(mediaFile)
|
response = get_file_details(mediaFile)
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ from src.apis.utils import SessionDep
|
|||||||
from src.schema.tysc.sport import SportResponse
|
from src.schema.tysc.sport import SportResponse
|
||||||
from src.db.models.tysc import Sport
|
from src.db.models.tysc import Sport
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter(
|
||||||
|
prefix="/tysc",
|
||||||
|
tags=["tysc"],
|
||||||
|
responses={404: {"description": "Not found"}},
|
||||||
|
)
|
||||||
|
|
||||||
@router.get("/sports")
|
@router.get("/sports")
|
||||||
def get_all_sports(db: SessionDep) -> List[SportResponse]:
|
def get_all_sports(db: SessionDep) -> List[SportResponse]:
|
||||||
|
|||||||
@@ -9,17 +9,15 @@ load_dotenv(dotenv_path=env_path)
|
|||||||
|
|
||||||
class Settings:
|
class Settings:
|
||||||
PROJECT_NAME: str = "Kontor"
|
PROJECT_NAME: str = "Kontor"
|
||||||
PROJECT_VERSION: str = "0.2.0"
|
PROJECT_VERSION: str = "0.1.0"
|
||||||
|
|
||||||
|
MARIADB_USER: str = os.getenv("MARIADB_USER", "kontor")
|
||||||
|
MARIADB_PASSWORD: str = os.getenv("MARIADB_PASSWORD", "kontor")
|
||||||
|
MARIADB_SERVER: str = os.getenv("MARIADB_SERVER", "mariadb")
|
||||||
|
MARIADB_PORT: str = os.getenv("MARIADB_PORT", 3306)
|
||||||
|
MARIADB_DB: str = os.getenv("MARIADB_DB", "kontor")
|
||||||
|
DATABASE_URL: str = f"mariadb+mariadbconnector://{MARIADB_USER}:{MARIADB_PASSWORD}@{MARIADB_SERVER}:{MARIADB_PORT}/{MARIADB_DB}"
|
||||||
|
|
||||||
DB_USER: str = os.getenv("DB_USER", "kontor")
|
|
||||||
DB_PASSWORD: str = os.getenv("DB_PASSWORD", "kontor")
|
|
||||||
DB_SERVER: str = os.getenv("DB_SERVER", "postgres")
|
|
||||||
DB_PORT: str = os.getenv("DB_PORT", 5432)
|
|
||||||
DB_DBNAME: str = os.getenv("DB_DBNAME", "kontor")
|
|
||||||
DATABASE_URL: str = f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_SERVER}:{DB_PORT}/{DB_DBNAME}"
|
|
||||||
SECRET_KEY: str = os.getenv("SECRET_KEY", "J6GOtcwC2NJI1l0VkHu20PacPFGTxpirBxWwynoHjsc=")
|
|
||||||
ALGORITHM = "HS256"
|
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES = 600 # in mins
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import logging
|
|
||||||
import logging.config
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
LOGGING_CONFIG: dict[str, Any] = {
|
|
||||||
"version": 1,
|
|
||||||
"disable_existing_loggers": False,
|
|
||||||
"formatters": {
|
|
||||||
"default": {
|
|
||||||
"()": "uvicorn.logging.DefaultFormatter",
|
|
||||||
"fmt": "%(asctime)s - %(name)s - %(levelprefix)s %(message)s"
|
|
||||||
},
|
|
||||||
"access": {
|
|
||||||
"()": "uvicorn.logging.AccessFormatter",
|
|
||||||
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
|
|
||||||
},
|
|
||||||
"access_file": {
|
|
||||||
"()": "uvicorn.logging.AccessFormatter",
|
|
||||||
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
|
|
||||||
"use_colors": False,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"handlers": {
|
|
||||||
"default": {
|
|
||||||
"formatter": "default",
|
|
||||||
"class": "logging.StreamHandler",
|
|
||||||
"stream": "ext://sys.stdout",
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"formatter": "access",
|
|
||||||
"class": "logging.StreamHandler",
|
|
||||||
"stream": "ext://sys.stderr",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"loggers": {
|
|
||||||
"root": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
|
||||||
"uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
|
||||||
"uvicorn.error": {"level": "INFO"},
|
|
||||||
"uvicorn.access": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
logging.config.dictConfig(LOGGING_CONFIG)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
from datetime import timedelta
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from src.core.config import settings
|
|
||||||
from jose import jwt
|
|
||||||
|
|
||||||
|
|
||||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
|
||||||
to_encode = data.copy()
|
|
||||||
if expires_delta:
|
|
||||||
expire = datetime.utcnow() + expires_delta
|
|
||||||
else:
|
|
||||||
expire = datetime.utcnow() + timedelta(
|
|
||||||
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
|
||||||
)
|
|
||||||
to_encode.update({"exp": expire})
|
|
||||||
encoded_jwt = jwt.encode(
|
|
||||||
to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM
|
|
||||||
)
|
|
||||||
return encoded_jwt
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean
|
from sqlalchemy import Column, ForeignKey, Integer, String
|
||||||
|
from sqlalchemy.dialects.mysql import BIT
|
||||||
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
||||||
|
|
||||||
from src.db.models.base import Base, BaseMixin
|
from src.db.models.base import Base, BaseMixin
|
||||||
@@ -8,12 +9,12 @@ from src.db.models.base import Base, BaseMixin
|
|||||||
|
|
||||||
class Profile(Base, BaseMixin):
|
class Profile(Base, BaseMixin):
|
||||||
__tablename__ = 'profile'
|
__tablename__ = 'profile'
|
||||||
first_name = Column(String)
|
first_name = Column(String(255))
|
||||||
last_name = Column(String)
|
last_name = Column(String(255))
|
||||||
user_name = Column(String, nullable=False)
|
user_name = Column(String(255), nullable=False)
|
||||||
email = Column(String)
|
email = Column(String(255))
|
||||||
password = Column(String)
|
password = Column(String(255))
|
||||||
enabled = Column(Boolean)
|
enabled = Column(BIT(1))
|
||||||
assignments = relationship("Assignment")
|
assignments = relationship("Assignment")
|
||||||
tokens = relationship("Token")
|
tokens = relationship("Token")
|
||||||
|
|
||||||
@@ -27,23 +28,20 @@ class Profile(Base, BaseMixin):
|
|||||||
full_name += self.last_name
|
full_name += self.last_name
|
||||||
return full_name
|
return full_name
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"Profile({self.id} {self.user_name}, {self.email})"
|
|
||||||
|
|
||||||
|
|
||||||
class Token(Base, BaseMixin):
|
class Token(Base, BaseMixin):
|
||||||
__tablename__ = "token"
|
__tablename__ = "token"
|
||||||
token = Column(String, nullable=False, unique=True)
|
token = Column(String(255), nullable=False, unique=True)
|
||||||
name = Column(String)
|
name = Column(String(255))
|
||||||
last_used_date: Mapped[datetime] = mapped_column()
|
last_used_date: Mapped[datetime] = mapped_column()
|
||||||
enabled = Column(Boolean)
|
enabled = Column(BIT(1))
|
||||||
profile_id = Column(String, ForeignKey("profile.id"), nullable=False)
|
profile_id = Column(String(255), ForeignKey("profile.id"), nullable=False)
|
||||||
profile = relationship("Profile", back_populates="tokens")
|
profile = relationship("Profile", back_populates="tokens")
|
||||||
|
|
||||||
|
|
||||||
class Permission(Base, BaseMixin):
|
class Permission(Base, BaseMixin):
|
||||||
__tablename__ = "permission"
|
__tablename__ = "permission"
|
||||||
name = Column(String, nullable=False)
|
name = Column(String(255), nullable=False)
|
||||||
assignments = relationship("Assignment")
|
assignments = relationship("Assignment")
|
||||||
|
|
||||||
|
|
||||||
@@ -55,14 +53,20 @@ class Assignment(Base, BaseMixin):
|
|||||||
permission = relationship("Permission", back_populates="assignments")
|
permission = relationship("Permission", back_populates="assignments")
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleData(Base, BaseMixin):
|
||||||
|
__tablename__ = "module_data"
|
||||||
|
module_name = Column(String(255), nullable=False)
|
||||||
|
import_data = Column(BIT(1))
|
||||||
|
|
||||||
|
|
||||||
class MailAccount(Base, BaseMixin):
|
class MailAccount(Base, BaseMixin):
|
||||||
__tablename__ = "mail_account"
|
__tablename__ = "mail_account"
|
||||||
host = Column(String)
|
host = Column(String(255))
|
||||||
port = Column(Integer)
|
port = Column(Integer)
|
||||||
protocol = Column(String)
|
protocol = Column(String(255))
|
||||||
user_name = Column(String)
|
user_name = Column(String(255))
|
||||||
password = Column(String)
|
password = Column(String(255))
|
||||||
start_tls = Column(Boolean)
|
start_tls = Column(BIT(1))
|
||||||
|
|
||||||
|
|
||||||
class Mail(Base, BaseMixin):
|
class Mail(Base, BaseMixin):
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import func, Column, String, Boolean
|
from sqlalchemy import func, Column, String
|
||||||
|
from sqlalchemy.dialects.mysql import BIT
|
||||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||||
|
|
||||||
|
|
||||||
@@ -10,8 +11,8 @@ class Base(DeclarativeBase):
|
|||||||
|
|
||||||
|
|
||||||
class BaseMixin:
|
class BaseMixin:
|
||||||
#id = Column(String, primary_key=True, default=uuid.uuid4)
|
id = Column(String(255), primary_key=True, default=uuid.uuid4())
|
||||||
id: Mapped[str] = mapped_column(primary_key=True, default=str(uuid.uuid4()))
|
# id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4())
|
||||||
# created_date = Column(DateTime)
|
# created_date = Column(DateTime)
|
||||||
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||||
# last_modified_date = Column(DateTime)
|
# last_modified_date = Column(DateTime)
|
||||||
@@ -21,10 +22,10 @@ class BaseMixin:
|
|||||||
|
|
||||||
|
|
||||||
class BaseVideoMixin:
|
class BaseVideoMixin:
|
||||||
cloud_link = Column(String)
|
cloud_link = Column(String(255))
|
||||||
file_name = Column(String)
|
file_name = Column(String(255))
|
||||||
path = Column(String)
|
path = Column(String(255))
|
||||||
review = Column(Boolean)
|
review = Column(BIT(1))
|
||||||
title = Column(String)
|
title = Column(String(255))
|
||||||
url = Column(String, unique=True)
|
url = Column(String(255), unique=True)
|
||||||
should_download = Column(Boolean)
|
should_download = Column(BIT(1))
|
||||||
|
|||||||
@@ -6,28 +6,28 @@ from src.db.models.base import Base, BaseMixin
|
|||||||
|
|
||||||
class Article(Base, BaseMixin):
|
class Article(Base, BaseMixin):
|
||||||
__tablename__ = 'article'
|
__tablename__ = 'article'
|
||||||
title = Column(String, unique=True)
|
title = Column(String(length=255), unique=True)
|
||||||
article_authors = relationship("ArticleAuthor")
|
article_authors = relationship("ArticleAuthor")
|
||||||
|
|
||||||
|
|
||||||
class Author(Base, BaseMixin):
|
class Author(Base, BaseMixin):
|
||||||
__tablename__ = 'author'
|
__tablename__ = 'author'
|
||||||
first_name = Column(String)
|
first_name = Column(String(255))
|
||||||
last_name = Column(String)
|
last_name = Column(String(255))
|
||||||
article_authors = relationship("ArticleAuthor")
|
article_authors = relationship("ArticleAuthor")
|
||||||
book_authors = relationship("BookAuthor")
|
book_authors = relationship("BookAuthor")
|
||||||
|
|
||||||
|
|
||||||
class BookshelfPublisher(Base, BaseMixin):
|
class BookshelfPublisher(Base, BaseMixin):
|
||||||
__tablename__ = 'bookshelf_publisher'
|
__tablename__ = 'bookshelf_publisher'
|
||||||
name = Column(String, unique=True)
|
name = Column(String(length=255), unique=True)
|
||||||
books = relationship("Book")
|
books = relationship("Book")
|
||||||
|
|
||||||
|
|
||||||
class Book(Base, BaseMixin):
|
class Book(Base, BaseMixin):
|
||||||
__tablename__ = 'book'
|
__tablename__ = 'book'
|
||||||
isbn = Column(String, unique=True)
|
isbn = Column(String(255), unique=True)
|
||||||
title = Column(String)
|
title = Column(String(255))
|
||||||
year = Column(Integer, nullable=False)
|
year = Column(Integer, nullable=False)
|
||||||
publisher_id = Column(String, ForeignKey('bookshelf_publisher.id'), nullable=False)
|
publisher_id = Column(String, ForeignKey('bookshelf_publisher.id'), nullable=False)
|
||||||
publisher = relationship('BookshelfPublisher', back_populates="books")
|
publisher = relationship('BookshelfPublisher', back_populates="books")
|
||||||
|
|||||||
@@ -1,24 +1,15 @@
|
|||||||
import uuid
|
from typing import Dict, List
|
||||||
from datetime import datetime
|
|
||||||
from typing import AnyStr, Dict, List, Optional, Any
|
|
||||||
from natsort import natsorted
|
from natsort import natsorted
|
||||||
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean, func
|
from sqlalchemy import Column, ForeignKey, Integer, String
|
||||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
from sqlalchemy.dialects.mysql import BIT
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from src.db.models.base import Base, BaseMixin
|
from src.db.models.base import Base, BaseMixin
|
||||||
|
|
||||||
|
|
||||||
class Publisher(Base):
|
class Publisher(Base, BaseMixin):
|
||||||
__tablename__ = "publisher"
|
__tablename__ = "publisher"
|
||||||
id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4)
|
name = Column(String(length=255), unique=True)
|
||||||
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
|
||||||
last_modified_date: Mapped[datetime] = mapped_column(default=func.now())
|
|
||||||
version: Mapped[int] = mapped_column(default=0)
|
|
||||||
name = Column(String, unique=True)
|
|
||||||
weblink = Column(String, nullable=True)
|
|
||||||
parent_publisher_id: Mapped[Optional[str]] = mapped_column(ForeignKey('publisher.id'))
|
|
||||||
parent_publisher: Mapped[Optional['Publisher']] = relationship("Publisher", back_populates="imprints", remote_side=[id])
|
|
||||||
imprints: Mapped[List['Publisher']] = relationship('Publisher', back_populates="parent_publisher")
|
|
||||||
comics = relationship("Comic")
|
comics = relationship("Comic")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@@ -30,12 +21,11 @@ class Publisher(Base):
|
|||||||
|
|
||||||
class Comic(Base, BaseMixin):
|
class Comic(Base, BaseMixin):
|
||||||
__tablename__ = 'comic'
|
__tablename__ = 'comic'
|
||||||
title = Column(String, unique=True)
|
title = Column(String(length=255), unique=True)
|
||||||
publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
|
publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
|
||||||
publisher = relationship("Publisher", back_populates="comics")
|
publisher = relationship("Publisher", back_populates="comics")
|
||||||
current_order = Column(Boolean)
|
current_order = Column(BIT(1))
|
||||||
completed = Column(Boolean)
|
completed = Column(BIT(1))
|
||||||
weblink = Column(String, nullable=True)
|
|
||||||
issues = relationship("Issue", order_by="Issue.issue_number")
|
issues = relationship("Issue", order_by="Issue.issue_number")
|
||||||
story_arcs = relationship("StoryArc")
|
story_arcs = relationship("StoryArc")
|
||||||
trade_paperbacks = relationship("TradePaperback")
|
trade_paperbacks = relationship("TradePaperback")
|
||||||
@@ -48,10 +38,10 @@ class Comic(Base, BaseMixin):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.title}({self.id})'
|
return f'{self.title}({self.id})'
|
||||||
|
|
||||||
def get_artists(self) -> Dict[Any, List[Any]]:
|
def get_artists(self) -> Dict[str, List[str]]:
|
||||||
works: Dict[Any, List[Any]] = {}
|
works: Dict[str, List[str]] = {}
|
||||||
for work in self.comic_works:
|
for work in self.comic_works:
|
||||||
work_type = work.work_type
|
work_type = work.work_type.name
|
||||||
artist = work.artist
|
artist = work.artist
|
||||||
if work_type in works:
|
if work_type in works:
|
||||||
works[work_type].append(artist)
|
works[work_type].append(artist)
|
||||||
@@ -66,16 +56,15 @@ class Comic(Base, BaseMixin):
|
|||||||
|
|
||||||
class Volume(Base, BaseMixin):
|
class Volume(Base, BaseMixin):
|
||||||
__tablename__ = "volume"
|
__tablename__ = "volume"
|
||||||
name = Column(String, nullable=False)
|
name = Column(String(length=255), nullable=False)
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||||
comic = relationship("Comic", back_populates="volumes")
|
comic = relationship("Comic", back_populates="volumes")
|
||||||
story_arcs = relationship("StoryArc")
|
|
||||||
issues = relationship("Issue")
|
issues = relationship("Issue")
|
||||||
|
|
||||||
|
|
||||||
class TradePaperback(Base, BaseMixin):
|
class TradePaperback(Base, BaseMixin):
|
||||||
__tablename__ = "trade_paperback"
|
__tablename__ = "trade_paperback"
|
||||||
name = Column(String, nullable=False)
|
name = Column(String(length=255), nullable=False)
|
||||||
issue_start = Column(Integer)
|
issue_start = Column(Integer)
|
||||||
issue_end = Column(Integer)
|
issue_end = Column(Integer)
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||||
@@ -84,58 +73,31 @@ class TradePaperback(Base, BaseMixin):
|
|||||||
|
|
||||||
class StoryArc(Base, BaseMixin):
|
class StoryArc(Base, BaseMixin):
|
||||||
__tablename__ = "story_arc"
|
__tablename__ = "story_arc"
|
||||||
name = Column(String, nullable=False)
|
name = Column(String(length=255), nullable=False)
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||||
comic = relationship("Comic", back_populates="story_arcs")
|
comic = relationship("Comic", back_populates="story_arcs")
|
||||||
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
|
||||||
volume = relationship("Volume", back_populates="story_arcs")
|
|
||||||
issues = relationship("Issue")
|
|
||||||
|
|
||||||
|
|
||||||
class Issue(Base, BaseMixin):
|
class Issue(Base, BaseMixin):
|
||||||
__tablename__ = "issue"
|
__tablename__ = "issue"
|
||||||
issue_number = Column(String)
|
issue_number = Column(String(255))
|
||||||
title = Column(String, nullable=True)
|
in_stock = Column(BIT(1))
|
||||||
published_on: Mapped[datetime] = mapped_column(nullable=True)
|
is_read = Column(BIT(1))
|
||||||
in_stock = Column(Boolean)
|
|
||||||
is_read = Column(Boolean)
|
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||||
comic = relationship("Comic", back_populates="issues")
|
comic = relationship("Comic", back_populates="issues")
|
||||||
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
||||||
volume = relationship("Volume", back_populates="issues")
|
volume = relationship("Volume", back_populates="issues")
|
||||||
story_arc_id = Column(String, ForeignKey("story_arc.id"), nullable=True)
|
|
||||||
story_arc = relationship("StoryArc", back_populates="issues")
|
|
||||||
issue_works = relationship("IssueWork")
|
|
||||||
|
|
||||||
def get_full_title(self) -> AnyStr:
|
|
||||||
full_title: AnyStr = self.issue_number
|
|
||||||
if self.title:
|
|
||||||
full_title += ": " + self.title
|
|
||||||
return full_title
|
|
||||||
|
|
||||||
def get_artists(self) -> Dict[Any, List[Any]]:
|
|
||||||
works: Dict[Any, List[Any]] = {}
|
|
||||||
for work in self.issue_works:
|
|
||||||
work_type = work.work_type
|
|
||||||
artist = work.artist
|
|
||||||
if work_type in works:
|
|
||||||
works[work_type].append(artist)
|
|
||||||
else:
|
|
||||||
works[work_type] = [artist]
|
|
||||||
return works
|
|
||||||
|
|
||||||
|
|
||||||
class Artist(Base, BaseMixin):
|
class Artist(Base, BaseMixin):
|
||||||
__tablename__ = "artist"
|
__tablename__ = "artist"
|
||||||
name = Column(String, nullable=False)
|
name = Column(String(length=255), nullable=False)
|
||||||
weblink = Column(String, nullable=True)
|
|
||||||
comic_works = relationship("ComicWork")
|
comic_works = relationship("ComicWork")
|
||||||
issue_works = relationship("IssueWork")
|
|
||||||
|
|
||||||
def get_comics(self) -> Dict[Any, List[Comic]]:
|
def get_comics(self) -> Dict[str, List[str]]:
|
||||||
works: Dict[Any, List[Comic]] = {}
|
works: Dict[str, List[str]] = {}
|
||||||
for work in self.comic_works:
|
for work in self.comic_works:
|
||||||
work_type = work.work_type
|
work_type = work.work_type.name
|
||||||
comic = work.comic
|
comic = work.comic
|
||||||
if work_type in works:
|
if work_type in works:
|
||||||
works[work_type].append(comic)
|
works[work_type].append(comic)
|
||||||
@@ -146,20 +108,8 @@ class Artist(Base, BaseMixin):
|
|||||||
|
|
||||||
class WorkType(Base, BaseMixin):
|
class WorkType(Base, BaseMixin):
|
||||||
__tablename__ = "worktype"
|
__tablename__ = "worktype"
|
||||||
name = Column(String, nullable=False, unique=True)
|
name = Column(String(length=255), nullable=False, unique=True)
|
||||||
comic_works = relationship("ComicWork")
|
comic_works = relationship("ComicWork")
|
||||||
issue_works = relationship("IssueWork")
|
|
||||||
|
|
||||||
def get_artists(self) -> Dict[str, List[str]]:
|
|
||||||
works: Dict[str, List[str]] = {}
|
|
||||||
for work in self.comic_works:
|
|
||||||
comic = work.comic.title
|
|
||||||
artist = work.artist
|
|
||||||
if comic in works:
|
|
||||||
works[comic].append(artist)
|
|
||||||
else:
|
|
||||||
works[comic] = [artist]
|
|
||||||
return works
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'Worktype({self.id} {self.version} {self.name} {len(self.comic_works)})'
|
return f'Worktype({self.id} {self.version} {self.name} {len(self.comic_works)})'
|
||||||
@@ -176,14 +126,3 @@ class ComicWork(Base, BaseMixin):
|
|||||||
artist = relationship("Artist", back_populates="comic_works")
|
artist = relationship("Artist", back_populates="comic_works")
|
||||||
work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
|
work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
|
||||||
work_type = relationship("WorkType", back_populates="comic_works")
|
work_type = relationship("WorkType", back_populates="comic_works")
|
||||||
|
|
||||||
|
|
||||||
class IssueWork(Base, BaseMixin):
|
|
||||||
__tablename__ = "issue_work"
|
|
||||||
issue_id = Column(String, ForeignKey("issue.id"), nullable=False)
|
|
||||||
issue = relationship("Issue", back_populates="issue_works")
|
|
||||||
artist_id = Column(String, ForeignKey("artist.id"), nullable=False)
|
|
||||||
artist = relationship("Artist", back_populates="issue_works")
|
|
||||||
work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
|
|
||||||
work_type = relationship("WorkType", back_populates="issue_works")
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import uuid
|
||||||
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, List
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
@@ -12,7 +13,7 @@ from sqlalchemy.orm import sessionmaker
|
|||||||
from src.db.models.tysc import Card, CardSet, Rooster, Team, FieldPosition, Player, Vendor, Sport
|
from src.db.models.tysc import Card, CardSet, Rooster, Team, FieldPosition, Player, Vendor, Sport
|
||||||
from src.db.models.comic import Issue, TradePaperback, StoryArc, Volume, ComicWork, Artist, Comic, Publisher, WorkType
|
from src.db.models.comic import Issue, TradePaperback, StoryArc, Volume, ComicWork, Artist, Comic, Publisher, WorkType
|
||||||
from src.db.models.bookshelf import ArticleAuthor, BookAuthor, BookshelfPublisher, Article, Book, Author
|
from src.db.models.bookshelf import ArticleAuthor, BookAuthor, BookshelfPublisher, Article, Book, Author
|
||||||
from src.db.models.admin import Mail, MailAccount, ModuleData, Token, Assignment, Permission, Profile
|
from src.db.models.admin import Mail, MailAccount, ModuleData, Role, User, Token, AuthorizationMatrix
|
||||||
from src.db.models.metadata import MetaDataTable, MetaDataColumn
|
from src.db.models.metadata import MetaDataTable, MetaDataColumn
|
||||||
from src.db.models.media import MediaVideo, MediaArticle, MediaFile, MediaActor, MediaActorFile
|
from src.db.models.media import MediaVideo, MediaArticle, MediaFile, MediaActor, MediaActorFile
|
||||||
|
|
||||||
@@ -78,10 +79,10 @@ class KontorDB:
|
|||||||
self.registry[MediaVideo.__tablename__] = MediaVideo
|
self.registry[MediaVideo.__tablename__] = MediaVideo
|
||||||
self.registry[MetaDataColumn.__tablename__] = MetaDataColumn
|
self.registry[MetaDataColumn.__tablename__] = MetaDataColumn
|
||||||
self.registry[MetaDataTable.__tablename__] = MetaDataTable
|
self.registry[MetaDataTable.__tablename__] = MetaDataTable
|
||||||
self.registry[Assignment.__tablename__] = Assignment
|
self.registry[AuthorizationMatrix.__tablename__] = AuthorizationMatrix
|
||||||
self.registry[Token.__tablename__] = Token
|
self.registry[Token.__tablename__] = Token
|
||||||
self.registry[Profile.__tablename__] = Profile
|
self.registry[User.__tablename__] = User
|
||||||
self.registry[Permission.__tablename__] = Permission
|
self.registry[Role.__tablename__] = Role
|
||||||
self.registry[ModuleData.__tablename__] = ModuleData
|
self.registry[ModuleData.__tablename__] = ModuleData
|
||||||
self.registry[MailAccount.__tablename__] = MailAccount
|
self.registry[MailAccount.__tablename__] = MailAccount
|
||||||
self.registry[Mail.__tablename__] = Mail
|
self.registry[Mail.__tablename__] = Mail
|
||||||
@@ -359,7 +360,7 @@ class KontorDB:
|
|||||||
update_list[link.id] = link.title
|
update_list[link.id] = link.title
|
||||||
return update_list
|
return update_list
|
||||||
|
|
||||||
def get_download_list(self) -> List[str]:
|
def get_download_list(self) -> list[uuid.UUID]:
|
||||||
download_list = []
|
download_list = []
|
||||||
__session__ = sessionmaker(self.engine)
|
__session__ = sessionmaker(self.engine)
|
||||||
_filter = { 'should_download': True}
|
_filter = { 'should_download': True}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ from pathlib import Path
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from sqlalchemy import Column, String, ForeignKey, Boolean
|
from sqlalchemy import Column, String, ForeignKey
|
||||||
|
from sqlalchemy.dialects.mysql import BIT
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from src.db.models.base import Base, BaseMixin, BaseVideoMixin
|
from src.db.models.base import Base, BaseMixin, BaseVideoMixin
|
||||||
@@ -29,10 +30,10 @@ class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
|||||||
soup = BeautifulSoup(r.content, "html.parser")
|
soup = BeautifulSoup(r.content, "html.parser")
|
||||||
title = soup.title.string
|
title = soup.title.string
|
||||||
self.title = title
|
self.title = title
|
||||||
self.review = False
|
self.review = 0
|
||||||
except:
|
except:
|
||||||
self.title = None
|
self.title = None
|
||||||
self.review = True
|
self.review = 1
|
||||||
self.last_modified_date = datetime.now()
|
self.last_modified_date = datetime.now()
|
||||||
|
|
||||||
def download_file(self, download_dir: str, dl_tool: str):
|
def download_file(self, download_dir: str, dl_tool: str):
|
||||||
@@ -44,12 +45,12 @@ class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
|||||||
lines_list = output.splitlines()
|
lines_list = output.splitlines()
|
||||||
file_name = self.__parse_output__(lines_list)
|
file_name = self.__parse_output__(lines_list)
|
||||||
if file_name is None:
|
if file_name is None:
|
||||||
self.review = True
|
self.review = 1
|
||||||
self.should_download = True
|
self.should_download = 1
|
||||||
self.file_name = None
|
self.file_name = None
|
||||||
else:
|
else:
|
||||||
download_file = Path(file_name)
|
download_file = Path(file_name)
|
||||||
self.should_download = False
|
self.should_download = 0
|
||||||
self.file_name = download_file.name
|
self.file_name = download_file.name
|
||||||
self.cloud_link = str(download_file.absolute())
|
self.cloud_link = str(download_file.absolute())
|
||||||
self.last_modified_date = datetime.now()
|
self.last_modified_date = datetime.now()
|
||||||
@@ -70,42 +71,31 @@ class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
|||||||
|
|
||||||
class MediaActor(Base, BaseMixin):
|
class MediaActor(Base, BaseMixin):
|
||||||
__tablename__ = 'media_actor'
|
__tablename__ = 'media_actor'
|
||||||
name = Column(String)
|
name = Column(String(255))
|
||||||
media_actor_files = relationship("MediaActorFile")
|
media_actor_files = relationship("MediaActorFile")
|
||||||
|
|
||||||
|
|
||||||
class MediaActorFile(Base, BaseMixin):
|
class MediaActorFile(Base, BaseMixin):
|
||||||
__tablename__ = 'media_actor_file'
|
__tablename__ = 'media_actor_file'
|
||||||
media_actor_id = Column(String, ForeignKey("media_actor.id"), nullable=False)
|
media_actor_id = Column(String(255), ForeignKey("media_actor.id"), nullable=False)
|
||||||
media_actor = relationship("MediaActor", back_populates="media_actor_files")
|
media_actor = relationship("MediaActor", back_populates="media_actor_files")
|
||||||
media_file_id = Column(String, ForeignKey("media_file.id"), nullable=True)
|
media_file_id = Column(String(255), ForeignKey("media_file.id"), nullable=True)
|
||||||
media_file = relationship("MediaFile", back_populates="media_actor_files")
|
media_file = relationship("MediaFile", back_populates="media_actor_files")
|
||||||
|
|
||||||
|
|
||||||
class MediaArticle(Base, BaseMixin):
|
class MediaArticle(Base, BaseMixin):
|
||||||
__tablename__ = 'media_article'
|
__tablename__ = 'media_article'
|
||||||
review = Column(Boolean)
|
review = Column(BIT(1))
|
||||||
title = Column(String)
|
title = Column(String(255))
|
||||||
url = Column(String, unique=True)
|
url = Column(String(255), unique=True)
|
||||||
|
|
||||||
|
|
||||||
class MediaVideo(Base, BaseMixin):
|
class MediaVideo(Base, BaseMixin):
|
||||||
__tablename__ = 'media_video'
|
__tablename__ = 'media_video'
|
||||||
cloud_link = Column(String)
|
cloud_link = Column(String(255))
|
||||||
file_name = Column(String)
|
file_name = Column(String(255))
|
||||||
path = Column(String)
|
path = Column(String(255))
|
||||||
review = Column(Boolean)
|
review = Column(BIT(1))
|
||||||
title = Column(String)
|
title = Column(String(255))
|
||||||
url = Column(String, unique=True)
|
url = Column(String(255), unique=True)
|
||||||
should_download = Column(Boolean)
|
should_download = Column(BIT(1))
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'MediaFile({self.id} {self.title} {self.url})'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if self.title is None:
|
|
||||||
return f'{self.url}({self.id})'
|
|
||||||
else:
|
|
||||||
return f'{self.title}({self.id})'
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
from sqlalchemy import Column, String, ForeignKey, Integer
|
||||||
|
from sqlalchemy.dialects.mysql import BIT
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from src.db.models.base import Base, BaseMixin
|
||||||
|
|
||||||
|
|
||||||
|
class MetaDataTable(Base, BaseMixin):
|
||||||
|
__tablename__ = 'meta_data_table'
|
||||||
|
table_name = Column(String(255), unique=True)
|
||||||
|
table_columns = relationship("MetaDataColumn")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'MetaDataTable({self.id} {self.table_name})'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.table_name}({self.id})'
|
||||||
|
|
||||||
|
|
||||||
|
class MetaDataColumn(Base, BaseMixin):
|
||||||
|
__tablename__ = 'meta_data_column'
|
||||||
|
column_name = Column(String(255), nullable=False)
|
||||||
|
column_sync_name = Column(String(255))
|
||||||
|
column_type = Column(String(255))
|
||||||
|
column_modifier = Column(String(255), nullable=True)
|
||||||
|
column_order = Column(Integer)
|
||||||
|
table_id = Column(String, ForeignKey('meta_data_table.id'))
|
||||||
|
table = relationship("MetaDataTable", back_populates="table_columns")
|
||||||
|
column_label = Column(String(255))
|
||||||
|
filter_label = Column(String(255))
|
||||||
|
is_shown = Column(BIT(1))
|
||||||
|
show_filter = Column(BIT(1))
|
||||||
|
ref_column = Column(String, nullable=True)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self.column_name is None:
|
||||||
|
return f'MetaDataColumn({self.id} {self.table.table_name}.__)'
|
||||||
|
else:
|
||||||
|
return f'MetaDataColumn({self.id} {self.table.table_name}.{self.column_name})'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.column_name}({self.id})'
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Boolean
|
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
|
||||||
|
from sqlalchemy.dialects.mysql import BIT
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from src.db.models.base import Base, BaseMixin
|
from src.db.models.base import Base, BaseMixin
|
||||||
@@ -9,15 +10,15 @@ class Sport(Base, BaseMixin):
|
|||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint("name"),
|
UniqueConstraint("name"),
|
||||||
)
|
)
|
||||||
name = Column(String, nullable=False, index=True, unique=True)
|
name = Column(String(255), nullable=False, index=True, unique=True)
|
||||||
teams = relationship("Team")
|
teams = relationship("Team")
|
||||||
positions = relationship("FieldPosition")
|
positions = relationship("FieldPosition")
|
||||||
|
|
||||||
|
|
||||||
class Team(Base, BaseMixin):
|
class Team(Base, BaseMixin):
|
||||||
__tablename__ = "team"
|
__tablename__ = "team"
|
||||||
name = Column(String, nullable=False, index=True, unique=True)
|
name = Column(String(255), nullable=False, index=True, unique=True)
|
||||||
short_name = Column(String, nullable=False, )
|
short_name = Column(String(255), nullable=False, )
|
||||||
sport_id = Column(String, ForeignKey("sport.id"), nullable=False)
|
sport_id = Column(String, ForeignKey("sport.id"), nullable=False)
|
||||||
sport = relationship("Sport", back_populates="teams")
|
sport = relationship("Sport", back_populates="teams")
|
||||||
roosters = relationship("Rooster")
|
roosters = relationship("Rooster")
|
||||||
@@ -29,8 +30,8 @@ class FieldPosition(Base, BaseMixin):
|
|||||||
UniqueConstraint("name", "sport_id"),
|
UniqueConstraint("name", "sport_id"),
|
||||||
UniqueConstraint("short_name", "sport_id"),
|
UniqueConstraint("short_name", "sport_id"),
|
||||||
)
|
)
|
||||||
name = Column(String, nullable=False, index=True)
|
name = Column(String(255), nullable=False, index=True)
|
||||||
short_name = Column(String, nullable=False)
|
short_name = Column(String(255), nullable=False)
|
||||||
sport_id = Column(String, ForeignKey("sport.id"), nullable=False, index=True)
|
sport_id = Column(String, ForeignKey("sport.id"), nullable=False, index=True)
|
||||||
sport = relationship("Sport", back_populates="positions")
|
sport = relationship("Sport", back_populates="positions")
|
||||||
roosters = relationship("Rooster")
|
roosters = relationship("Rooster")
|
||||||
@@ -41,8 +42,8 @@ class Player(Base, BaseMixin):
|
|||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint("first_name", "last_name"),
|
UniqueConstraint("first_name", "last_name"),
|
||||||
)
|
)
|
||||||
first_name = Column(String, nullable=False, index=True)
|
first_name = Column(String(255), nullable=False, index=True)
|
||||||
last_name = Column(String, nullable=False, index=True)
|
last_name = Column(String(255), nullable=False, index=True)
|
||||||
roosters = relationship("Rooster")
|
roosters = relationship("Rooster")
|
||||||
|
|
||||||
def get_full_name(self) -> str:
|
def get_full_name(self) -> str:
|
||||||
@@ -66,7 +67,7 @@ class Rooster(Base, BaseMixin):
|
|||||||
|
|
||||||
class Vendor(Base, BaseMixin):
|
class Vendor(Base, BaseMixin):
|
||||||
__tablename__ = "vendor"
|
__tablename__ = "vendor"
|
||||||
name = Column(String, nullable=False, unique=True, index=True)
|
name = Column(String(255), nullable=False, unique=True, index=True)
|
||||||
card_sets = relationship("CardSet")
|
card_sets = relationship("CardSet")
|
||||||
cards = relationship("Card")
|
cards = relationship("Card")
|
||||||
|
|
||||||
@@ -76,9 +77,9 @@ class CardSet(Base, BaseMixin):
|
|||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint("name", "vendor_id"),
|
UniqueConstraint("name", "vendor_id"),
|
||||||
)
|
)
|
||||||
name = Column(String, index=True)
|
name = Column(String(255), index=True)
|
||||||
parallel_set = Column(Boolean)
|
parallel_set = Column(BIT(1))
|
||||||
insert_set = Column(Boolean)
|
insert_set = Column(BIT(1))
|
||||||
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False, index=True)
|
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False, index=True)
|
||||||
vendor = relationship("Vendor", back_populates="card_sets")
|
vendor = relationship("Vendor", back_populates="card_sets")
|
||||||
cards = relationship("Card")
|
cards = relationship("Card")
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
from typing import AnyStr
|
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from src.db.models.admin import Profile
|
|
||||||
|
|
||||||
|
|
||||||
def get_profile(username: AnyStr, db: Session):
|
|
||||||
profile = db.query(Profile).filter(Profile.email == username).first()
|
|
||||||
return profile
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
from src.db.models.comic import Artist
|
|
||||||
from src.schema.comics.artist import ArtistDetailResponse
|
|
||||||
|
|
||||||
|
|
||||||
def get_artist_details(artist: Artist) -> ArtistDetailResponse:
|
|
||||||
works = {}
|
|
||||||
for work in artist.comic_works:
|
|
||||||
work_type = work.work_type.name
|
|
||||||
comic_title = work.comic.title
|
|
||||||
if work_type in works:
|
|
||||||
works[work_type].append(comic_title)
|
|
||||||
else:
|
|
||||||
works[work_type] = [comic_title]
|
|
||||||
response = ArtistDetailResponse(
|
|
||||||
id=artist.id,
|
|
||||||
name=artist.name,
|
|
||||||
works=works
|
|
||||||
)
|
|
||||||
return response
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
from typing import List, Type, AnyStr
|
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from src.core.log_conf import logger
|
|
||||||
from src.db.models.comic import Comic, Issue
|
|
||||||
from src.schema.comics.comic import ComicSchema
|
|
||||||
from src.schema.comics.issue import IssueDetailsResponse
|
|
||||||
|
|
||||||
|
|
||||||
def list_comics(db: Session) -> List[Type[Comic]]:
|
|
||||||
comics = db.query(Comic).all()
|
|
||||||
return comics
|
|
||||||
|
|
||||||
|
|
||||||
def get_issue_details(issue: Issue) -> IssueDetailsResponse:
|
|
||||||
response = IssueDetailsResponse(
|
|
||||||
id=issue.id,
|
|
||||||
issue_number=issue.issue_number,
|
|
||||||
in_stock=issue.in_stock,
|
|
||||||
is_read=issue.is_read,
|
|
||||||
comic_id=issue.comic_id,
|
|
||||||
volume_id=issue.volume_id
|
|
||||||
)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def update_comic(comic: ComicSchema, comic_id: AnyStr, db: Session) -> type[Comic] | None:
|
|
||||||
logger.info(f"update_comic: {comic} with {comic_id}")
|
|
||||||
comic = db.get(Comic, comic_id)
|
|
||||||
return comic
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import uuid
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import AnyStr
|
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from src.core.log_conf import logger
|
|
||||||
from src.db.models.comic import WorkType
|
|
||||||
from src.schema.comics.worktype import AddWorkType
|
|
||||||
|
|
||||||
|
|
||||||
def create_new_worktype(work: AddWorkType, db: Session) -> WorkType:
|
|
||||||
worktype = WorkType()
|
|
||||||
worktype.id = str(uuid.uuid4())
|
|
||||||
worktype.created_date = datetime.now()
|
|
||||||
worktype.last_modified_date = datetime.now()
|
|
||||||
worktype.name = work.worktype
|
|
||||||
db.add(worktype)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(worktype)
|
|
||||||
logger.info(f"create_new_worktype: {worktype}")
|
|
||||||
return worktype
|
|
||||||
|
|
||||||
|
|
||||||
def update_worktype(work: AddWorkType, worktype_id: AnyStr, db: Session) -> WorkType:
|
|
||||||
logger.info("update worktype")
|
|
||||||
worktype = db.get(WorkType, worktype_id)
|
|
||||||
worktype.name = work.worktype
|
|
||||||
db.add(worktype)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(worktype)
|
|
||||||
return worktype
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
from sqlalchemy.orm import Session
|
|
||||||
from typing import AnyStr
|
|
||||||
import uuid
|
|
||||||
from datetime import datetime
|
|
||||||
from src.core.log_conf import logger
|
|
||||||
from src.db.models.media import MediaFile, MediaVideo
|
|
||||||
from src.webapps.media.forms import AddLinkForm
|
|
||||||
|
|
||||||
|
|
||||||
def create_new_video(video: AddLinkForm, db: Session) -> MediaVideo:
|
|
||||||
print(video.url)
|
|
||||||
media_video = MediaVideo()
|
|
||||||
media_video.id = str(uuid.uuid4())
|
|
||||||
media_video.url = video.url
|
|
||||||
media_video.created_date = datetime.now()
|
|
||||||
media_video.last_modified_date = datetime.now()
|
|
||||||
media_video.review = True
|
|
||||||
media_video.should_download = True
|
|
||||||
db.add(media_video)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(media_video)
|
|
||||||
print(media_video)
|
|
||||||
return media_video
|
|
||||||
|
|
||||||
def create_new_mediafile(link: AnyStr, db: Session) -> MediaFile:
|
|
||||||
logger.info("create MediaFile with url {link}")
|
|
||||||
media_file: MediaFile = MediaFile()
|
|
||||||
media_file.id = str(uuid.uuid4())
|
|
||||||
media_file.url = link
|
|
||||||
media_file.created_date = datetime.now()
|
|
||||||
media_file.last_modified_date = datetime.now()
|
|
||||||
media_file.version = 0
|
|
||||||
media_file.review = True
|
|
||||||
media_file.should_download = True
|
|
||||||
db.add(media_file)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(media_file)
|
|
||||||
logger.info(f"created {media_file}")
|
|
||||||
return media_file
|
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Generator
|
from typing import Generator, Annotated
|
||||||
|
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker, Session
|
||||||
|
|
||||||
from src.core.config import settings
|
from src.core.config import settings
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import databases
|
|
||||||
from src.db.session import SQLALCHEMY_DATABASE_URL
|
|
||||||
|
|
||||||
|
|
||||||
async def check_db_connected():
|
|
||||||
try:
|
|
||||||
if not str(SQLALCHEMY_DATABASE_URL).__contains__("sqlite"):
|
|
||||||
database = databases.Database(SQLALCHEMY_DATABASE_URL)
|
|
||||||
if not database.is_connected:
|
|
||||||
await database.connect()
|
|
||||||
await database.execute("SELECT 1")
|
|
||||||
print("Database is connected (^_^)")
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
"Looks like db is missing or is there is some problem in connection,see below traceback"
|
|
||||||
)
|
|
||||||
raise e
|
|
||||||
|
|
||||||
|
|
||||||
async def check_db_disconnected():
|
|
||||||
try:
|
|
||||||
if not str(SQLALCHEMY_DATABASE_URL).__contains__("sqlite"):
|
|
||||||
database = databases.Database(SQLALCHEMY_DATABASE_URL)
|
|
||||||
if database.is_connected:
|
|
||||||
await database.disconnect()
|
|
||||||
print("Database is Disconnected (-_-) zZZ")
|
|
||||||
except Exception as e:
|
|
||||||
raise e
|
|
||||||
+5
-17
@@ -1,25 +1,12 @@
|
|||||||
import logging
|
|
||||||
import logging.config
|
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from src.apis.base import api_router
|
from src.apis.base import api_router
|
||||||
from src.core.log_conf import LOGGING_CONFIG, logger
|
|
||||||
from src.db.session import engine
|
from src.db.session import engine
|
||||||
from src.db.utils import check_db_connected, check_db_disconnected
|
|
||||||
from src.webapps.base import api_router as web_app_router
|
from src.webapps.base import api_router as web_app_router
|
||||||
from src.core.config import settings
|
from src.core.config import settings
|
||||||
from src.db.models.base import Base
|
from src.db.models.base import Base
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def lifespan(app: FastAPI):
|
|
||||||
await check_db_connected()
|
|
||||||
yield
|
|
||||||
await check_db_disconnected()
|
|
||||||
|
|
||||||
def include_router(app: FastAPI):
|
def include_router(app: FastAPI):
|
||||||
app.include_router(api_router)
|
app.include_router(api_router)
|
||||||
app.include_router(web_app_router)
|
app.include_router(web_app_router)
|
||||||
@@ -30,12 +17,13 @@ def configure_static(app: FastAPI):
|
|||||||
def create_tables():
|
def create_tables():
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
def start_application(log):
|
def start_application():
|
||||||
log.info(f"using database: {settings.DATABASE_URL}")
|
app = FastAPI(title=settings.PROJECT_NAME, version=settings.PROJECT_VERSION)
|
||||||
app = FastAPI(title=settings.PROJECT_NAME, version=settings.PROJECT_VERSION, lifespan=lifespan)
|
|
||||||
include_router(app)
|
include_router(app)
|
||||||
configure_static(app)
|
configure_static(app)
|
||||||
create_tables()
|
create_tables()
|
||||||
return app
|
return app
|
||||||
|
|
||||||
kontor = start_application(logger)
|
|
||||||
|
kontor = start_application()
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class Token(BaseModel):
|
|
||||||
access_token: str
|
|
||||||
token_type: str
|
|
||||||
@@ -1,19 +1,36 @@
|
|||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from src.db.models.comic import Artist
|
||||||
|
|
||||||
|
|
||||||
class ArtistCreation(BaseModel):
|
class ArtistCreation(BaseModel):
|
||||||
id: str
|
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
class ArtistResponse(BaseModel):
|
class ArtistResponse(BaseModel):
|
||||||
id: str
|
id: UUID
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
class ArtistDetailResponse(BaseModel):
|
class ArtistDetailResponse(BaseModel):
|
||||||
id: str
|
id: UUID
|
||||||
name: str
|
name: str
|
||||||
weblink: str
|
|
||||||
works: Dict[str, List[str]]
|
works: Dict[str, List[str]]
|
||||||
|
|
||||||
|
def get_artist_details(artist: Artist) -> ArtistDetailResponse:
|
||||||
|
works = {}
|
||||||
|
for work in artist.comic_works:
|
||||||
|
work_type = work.work_type.name
|
||||||
|
comic_title = work.comic.title
|
||||||
|
if work_type in works:
|
||||||
|
works[work_type].append(comic_title)
|
||||||
|
else:
|
||||||
|
works[work_type] = [comic_title]
|
||||||
|
response = ArtistDetailResponse(
|
||||||
|
id=artist.id,
|
||||||
|
name=artist.name,
|
||||||
|
works=works
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,26 @@
|
|||||||
from typing import List, Dict, Optional
|
from typing import List, Dict
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from pydantic import BaseModel, AnyUrl
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from src.db.models.comic import Comic
|
from src.db.models.comic import Comic
|
||||||
|
|
||||||
|
|
||||||
class ComicResponse(BaseModel):
|
class ComicResponse(BaseModel):
|
||||||
id: str
|
id: UUID
|
||||||
title: str
|
title: str
|
||||||
completed: bool
|
completed: bool
|
||||||
|
|
||||||
class ComicDetailsResponse(BaseModel):
|
class ComicDetailsResponse(BaseModel):
|
||||||
id: str
|
id: UUID
|
||||||
created: str
|
created: str
|
||||||
title: str
|
title: str
|
||||||
completed : bool
|
completed : bool
|
||||||
current_order : bool
|
current_order : bool
|
||||||
weblink: str
|
|
||||||
publisher: str
|
publisher: str
|
||||||
volumes: List[str]
|
volumes: List[str]
|
||||||
works: Dict[str, List[str]]
|
works: Dict[str, List[str]]
|
||||||
|
|
||||||
|
|
||||||
class ComicSchema(BaseModel):
|
|
||||||
id: str
|
|
||||||
title: str
|
|
||||||
weblink: Optional[AnyUrl]
|
|
||||||
completed: Optional[bool]
|
|
||||||
current_order: Optional[bool]
|
|
||||||
|
|
||||||
|
|
||||||
def get_short_info(comic: Comic) -> ComicResponse:
|
def get_short_info(comic: Comic) -> ComicResponse:
|
||||||
response = ComicResponse(
|
response = ComicResponse(
|
||||||
id=comic.id,
|
id=comic.id,
|
||||||
@@ -55,11 +46,11 @@ def get_comic_details(comic: Comic) -> ComicDetailsResponse | None:
|
|||||||
id=comic.id,
|
id=comic.id,
|
||||||
created=str(comic.created_date),
|
created=str(comic.created_date),
|
||||||
title=comic.title,
|
title=comic.title,
|
||||||
completed=comic.completed,
|
completed=(comic.completed == 1),
|
||||||
current_order=comic.current_order,
|
current_order=(comic.current_order == 1),
|
||||||
weblink=comic.weblink,
|
|
||||||
publisher=comic.publisher.name,
|
publisher=comic.publisher.name,
|
||||||
volumes=volumes,
|
volumes=volumes,
|
||||||
works=works
|
works=works
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class IssueDetailsResponse(BaseModel):
|
|
||||||
id: str
|
|
||||||
issue_number: str
|
|
||||||
in_stock: bool
|
|
||||||
is_read: bool
|
|
||||||
comic_id: str
|
|
||||||
volume_id: str | None
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
class AddWorkType(BaseModel):
|
|
||||||
worktype: str
|
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from src.db.models.media import MediaFile
|
from src.db.models.media import MediaFile
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class MediaFileResponse(BaseModel):
|
class MediaFileResponse(BaseModel):
|
||||||
id: str
|
id: UUID
|
||||||
title: str | None = None
|
title: str | None = None
|
||||||
file_name: str | None = None
|
file_name: str | None = None
|
||||||
cloud_link: str | None = None
|
cloud_link: str | None = None
|
||||||
@@ -22,8 +23,8 @@ def get_file_details(mediafile: MediaFile) -> MediaFileResponse | None:
|
|||||||
file_name=mediafile.file_name,
|
file_name=mediafile.file_name,
|
||||||
cloud_link=mediafile.cloud_link,
|
cloud_link=mediafile.cloud_link,
|
||||||
url=str(mediafile.url),
|
url=str(mediafile.url),
|
||||||
review=mediafile.review,
|
review=(mediafile.review == 1),
|
||||||
should_download=mediafile.should_download)
|
should_download=(mediafile.should_download == 1))
|
||||||
#print(f"id: {mediafile.id}: review: {response.review} <- {mediafile.review}")
|
#print(f"id: {mediafile.id}: review: {response.review} <- {mediafile.review}")
|
||||||
#print(f"id: {mediafile.id}: download: {response.should_download} <- {mediafile.should_download}")
|
#print(f"id: {mediafile.id}: download: {response.should_download} <- {mediafile.should_download}")
|
||||||
return response
|
return response
|
||||||
@@ -34,5 +35,11 @@ def set_file(model: MediaFileResponse, mediafile: MediaFile) -> None:
|
|||||||
mediafile.url = model.url
|
mediafile.url = model.url
|
||||||
mediafile.title = model.title
|
mediafile.title = model.title
|
||||||
mediafile.last_modified_date = datetime.now()
|
mediafile.last_modified_date = datetime.now()
|
||||||
mediafile.review = model.review
|
if model.review:
|
||||||
mediafile.should_download = model.should_download
|
mediafile.review = 1
|
||||||
|
else:
|
||||||
|
mediafile.review = 0
|
||||||
|
if model.should_download:
|
||||||
|
mediafile.should_download = 1
|
||||||
|
else:
|
||||||
|
mediafile.should_download = 0
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class AddLink(BaseModel):
|
|
||||||
url: str
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from typing import AnyStr
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class SportResponse(BaseModel):
|
class SportResponse(BaseModel):
|
||||||
id: AnyStr
|
id: UUID
|
||||||
name: str
|
name: str
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Permissions</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% with msg=msg %}
|
|
||||||
{% include "components/alerts.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h1 class="display-5">Permissions..</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-hover">
|
|
||||||
<thead><tr>
|
|
||||||
<th scope="col">Name</th>
|
|
||||||
<th colspan="2">Actions</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for permission in permissions %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row"><a href="/admins/permissions/{{permission.id}}">{{permission.name}}</a></th>
|
|
||||||
<td><a href="/admin/permission/edit/{{permission.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
|
||||||
<td><a href="/admin/permission/delete/{{permission.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div>
|
|
||||||
<a href="/admin/permission/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add Permission</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Profiles</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% with msg=msg %}
|
|
||||||
{% include "components/alerts.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h1 class="display-5">Profiles..</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-hover">
|
|
||||||
<thead><tr>
|
|
||||||
<th scope="col">Username</th>
|
|
||||||
<th scope="col">First Name</th>
|
|
||||||
<th scope="col">Last Name</th>
|
|
||||||
<th colspan="2">Actions</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for profile in profiles %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row"><a href="/admins/profiles/{{profile.id}}">{{profile.user_name}}</a></th>
|
|
||||||
<th scope="row">{{profile.first_name}}</th>
|
|
||||||
<th scope="row">{{profile.last_name}}</th>
|
|
||||||
<th scope="row">{{profile.email}}</th>
|
|
||||||
<td><a href="/admin/profile/edit/{{profile.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
|
||||||
<td><a href="/admin/profile/delete/{{profile.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div>
|
|
||||||
<a href="/admin/profile/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add Profile</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Login</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="display-5">Login to Kontor</h5>
|
|
||||||
<div class="text-danger font-weight-bold">
|
|
||||||
{% for error in errors %}
|
|
||||||
<li>{{error}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<div class="text-success font-weight-bold">
|
|
||||||
{% if msg %}
|
|
||||||
<div class="badge bg-success text-wrap font-weight-bold" style="font-size: large;">
|
|
||||||
{{msg}}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row my-5">
|
|
||||||
<form method="POST">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label>Email</label>
|
|
||||||
<input type="text" required placeholder="Your email" name="email" value="{{email}}" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label>Password</label>
|
|
||||||
<input type="password" required placeholder="Choose a secure password" value="{{password}}" name="password" class="form-control">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -20,16 +20,12 @@
|
|||||||
<th scope="row">Artist Name</th>
|
<th scope="row">Artist Name</th>
|
||||||
<td colspan="2">{{artist.name}}</td>
|
<td colspan="2">{{artist.name}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th scope="row">Link</th>
|
|
||||||
<td colspan="2">{{artist.weblink}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Works</th>
|
<th scope="row">Works</th>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
{% for work in artist.get_comics() %}
|
{% for work in artist.get_comics() %}
|
||||||
<p>
|
<p>
|
||||||
<a href="/comic/worktypes/{{work.id}}">{{work.name}}</a>
|
{{work}}:
|
||||||
<ul>
|
<ul>
|
||||||
{% for comic in artist.get_comics()[work] %}
|
{% for comic in artist.get_comics()[work] %}
|
||||||
<li><a href="/comic/comics/{{comic.id}}">{{comic.title}}</a></li>
|
<li><a href="/comic/comics/{{comic.id}}">{{comic.title}}</a></li>
|
||||||
@@ -47,20 +43,8 @@
|
|||||||
<th scope="row">Data Modified</th>
|
<th scope="row">Data Modified</th>
|
||||||
<td colspan="2">{{artist.last_modified_date}}</td>
|
<td colspan="2">{{artist.last_modified_date}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Version</th>
|
|
||||||
<td colspan="2">{{artist.version}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<a href="/comic/artists" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
|
||||||
<a href="/comic/artist/edit/{{artist.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
|
||||||
<a href="/comic/artist/delete/{{artist.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Edit Artist</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="text-danger font-weight-bold">
|
|
||||||
{% for error in errors %}
|
|
||||||
<li>{{error}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row my-5">
|
|
||||||
<h3 class="text-center display-4">Edit an Artist entry</h3>
|
|
||||||
<form method="POST">
|
|
||||||
<div class="mb-3">
|
|
||||||
<input type="text" required class="form-control" name="artist.name" value="{{artist_name}}" placeholder="Artist name here">
|
|
||||||
<input type="text" required class="form-control" name="artist.link" value="{{artist_link}}" placeholder="Web link for artist here">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
|
||||||
<button type="cancel" class="btn btn-primary">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
{% for artist in artists %}
|
{% for artist in artists %}
|
||||||
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
||||||
{% with obj=artist %}
|
{% with obj=artist %}
|
||||||
{% include "comic/artist_cards.html" %}
|
{% include "components/artist_cards.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% if loop.index %3 %}
|
{% if loop.index %3 %}
|
||||||
|
|||||||
@@ -27,17 +27,12 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th scope="row">Link</th>
|
|
||||||
<td colspan="2">{{comic.weblink}}</td>
|
|
||||||
</tr>
|
|
||||||
{% if comic.get_artists()|length > 0 %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Works</th>
|
<th scope="row">Works</th>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
{% for work in comic.get_artists() %}
|
{% for work in comic.get_artists() %}
|
||||||
<p>
|
<p>
|
||||||
<a href="/comic/worktypes/{{work.id}}">{{work.name}}</a>
|
{{work}}:
|
||||||
<ul>
|
<ul>
|
||||||
{% for artist in comic.get_artists()[work] %}
|
{% for artist in comic.get_artists()[work] %}
|
||||||
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
|
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
|
||||||
@@ -47,29 +42,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
|
||||||
{% if comic.volumes|length > 0 %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Volumes</th>
|
|
||||||
<td colspan="2">
|
|
||||||
<ul>
|
|
||||||
{% for volume in comic.volumes %}
|
|
||||||
<li><a href="/comic/volumes/{{volume.id}}">{{volume.name}}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Issues</th>
|
|
||||||
<td colspan="2">
|
|
||||||
<ul>
|
|
||||||
{% for issue in comic.sorted_issues() %}
|
|
||||||
<li><a href="/comic/issues/{{issue.id}}">{{issue.get_full_title()}}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Data Created</th>
|
<th scope="row">Data Created</th>
|
||||||
<td colspan="2">{{comic.created_date}}</td>
|
<td colspan="2">{{comic.created_date}}</td>
|
||||||
@@ -82,15 +54,18 @@
|
|||||||
<th scope="row">Data Version</th>
|
<th scope="row">Data Version</th>
|
||||||
<td colspan="2">{{comic.version}}</td>
|
<td colspan="2">{{comic.version}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Issues</th>
|
||||||
|
<td colspan="2">
|
||||||
|
<ul>
|
||||||
|
{% for issue in comic.sorted_issues() %}
|
||||||
|
<li><a href="comic/issues/{{issue.id}}">{{issue.issue_number}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<a href="/comic/comics" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
|
||||||
<a href="/comic/comic/edit/{{comic.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
|
||||||
<a href="/comic/comic/delete/{{comic.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Edit Comic</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="text-danger font-weight-bold">
|
|
||||||
{% for error in errors %}
|
|
||||||
<li>{{error}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row my-5">
|
|
||||||
<h3 class="text-center display-4">Edit an Comic entry</h3>
|
|
||||||
<form class="form-horizontal" method="POST">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label col-sm-2" for="title">Title</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" id="title" name="title" value="{{comic_title}}" placeholder="Comic title here">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label col-sm-2" for="weblink">Link</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" id="weblink" name="weblink" value="{{comic_weblink}}" placeholder="Web link for comic here">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-offset-2 col-sm-10">
|
|
||||||
<div class="checkbox">
|
|
||||||
<label><input type="checkbox" id="completed" name="completed" value="{{comic_completed}}"> Completed</label>
|
|
||||||
</div
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-offset-2 col-sm-10">
|
|
||||||
<div class="checkbox">
|
|
||||||
<label><input type="checkbox" id="current_order" name="current_order" value="{{comic_current_order}}"> Current Order</label>
|
|
||||||
</div
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-offset-2 col-sm-10">
|
|
||||||
<button type="submit" class="btn btn-primary" name="action" value="submit">Submit</button>
|
|
||||||
<button type="cancel" class="btn btn-primary" name="action" value="cancel">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -10,39 +10,22 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<form class="d-flex" action="/comic/comics/">
|
<div class="col">
|
||||||
<input class="form-control me-2" name="query" id="autocomplete" type="search" placeholder="Search" aria-label="Search">
|
<h1 class="display-5">Find Jobs..</h1>
|
||||||
Completed<input type="checkbox" name="completed" {% if request.query_params.get("completed")=="on" %}checked{% endif %} aria-label="Completed">
|
</div>
|
||||||
Order<input type="checkbox" name="order" {% if request.query_params.get("order")=="on" %}checked{% endif %} aria-label="Order">
|
|
||||||
<button class="btn btn-outline-success" type="submit">Search</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h1 class="display-5">Comics..</h1>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-hover">
|
|
||||||
<thead><tr>
|
|
||||||
<th scope="col">Title</th>
|
|
||||||
<th scope="col">Publisher</th>
|
|
||||||
<th scope="col">Completed</th>
|
|
||||||
<th colspan="2">Actions</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for comic in comics %}
|
{% for comic in comics %}
|
||||||
<tr>
|
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
||||||
<th scope="row"><a href="/comic/comics/{{comic.id}}">{{comic.title}}</a></th>
|
{% with obj=comic %}
|
||||||
<td><a href="/comic/publishers/{{comic.publisher.id}}">{{comic.publisher.name}}</a></td>
|
{% include "components/comic_cards.html" %}
|
||||||
<td>{% with check=comic.completed %}{% include "components/check.html" %}{% endwith %}</td>
|
{% endwith %}
|
||||||
<td><a href="/comic/comics/edit/{{comic.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
{% if loop.index %3 %}
|
||||||
<td><a href="/comic/comics/delete/{{comic.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div>
|
|
||||||
<a href="/comic/comic/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add Comic</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% else %}
|
||||||
|
</div></div><br><div class="row">
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Issue Detail</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h1 class="display-5">Issue Detail</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-striped table-hover">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Issue Number</th>
|
|
||||||
<td colspan="2">{{issue.issue_number}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Full Title</th>
|
|
||||||
<td colspan="2">{{issue.title}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Published</th>
|
|
||||||
<td colspan="2">{{issue.published_on}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Auf Lager</th>
|
|
||||||
<td colspan="2">
|
|
||||||
{% with check=issue.in_stock %}
|
|
||||||
{% include "components/check.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Gelesen</th>
|
|
||||||
<td colspan="2">
|
|
||||||
{% with check=issue.is_read %}
|
|
||||||
{% include "components/check.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Comic</th>
|
|
||||||
<td colspan="2">
|
|
||||||
<a href="/comic/comics/{{issue.comic_id}}">{{issue.comic.title}}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% if issue.volume %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Volume</th>
|
|
||||||
<td colspan="2">
|
|
||||||
<a href="/comic/comics/{{issue.volume_id}}">{{issue.volume.name}}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Works</th>
|
|
||||||
<td colspan="2">
|
|
||||||
{% for work in issue.get_artists() %}
|
|
||||||
<p>
|
|
||||||
<a href="/comic/worktypes/{{work.id}}">{{work.name}}</a>
|
|
||||||
<ul>
|
|
||||||
{% for artist in issue.get_artists()[work] %}
|
|
||||||
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Created</th>
|
|
||||||
<td colspan="2">{{issue.created_date}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Modified</th>
|
|
||||||
<td colspan="2">{{issue.last_modified_date}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Version</th>
|
|
||||||
<td colspan="2">{{issue.version}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -20,24 +20,6 @@
|
|||||||
<th scope="row">Publisher Name</th>
|
<th scope="row">Publisher Name</th>
|
||||||
<td colspan="2">{{publisher.name}}</td>
|
<td colspan="2">{{publisher.name}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if publisher.parent_publisher_id %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Parent Company</th>
|
|
||||||
<td colspan="2"><a href="/comic/publishers/{{publisher.parent_publisher_id}}">{{publisher.parent_publisher.name}}</a></td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% if publisher.imprints|length > 0 %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Imprints</th>
|
|
||||||
<td colspan="2">
|
|
||||||
<ul>
|
|
||||||
{% for imprint in publisher.imprints %}
|
|
||||||
<li><a href="/comic/publishers/{{imprint.id}}">{{imprint.name}}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Comics</th>
|
<th scope="row">Comics</th>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
@@ -59,12 +41,5 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<a href="/comic/publishers" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
|
||||||
<a href="/comic/publisher/edit/{{publisher.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
|
||||||
<a href="/comic/publisher/delete/{{publisher.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
{% for publisher in publishers %}
|
{% for publisher in publishers %}
|
||||||
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
||||||
{% with obj=publisher %}
|
{% with obj=publisher %}
|
||||||
{% include "comic/publisher_cards.html" %}
|
{% include "components/publisher_cards.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% if loop.index %3 %}
|
{% if loop.index %3 %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>WorkType Detail</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-striped table-hover">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">WorkType Name</th>
|
|
||||||
<td colspan="2">{{worktype.name}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Works</th>
|
|
||||||
<td colspan="2">
|
|
||||||
{% for comic in worktype.get_artists() %}
|
|
||||||
<p>
|
|
||||||
{{comic}}:
|
|
||||||
<ul>
|
|
||||||
{% for artist in worktype.get_artists()[comic] %}
|
|
||||||
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">ID</th>
|
|
||||||
<td colspan="2">{{worktype.id}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Created</th>
|
|
||||||
<td colspan="2">{{worktype.created_date}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Modified</th>
|
|
||||||
<td colspan="2">{{worktype.last_modified_date}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Version</th>
|
|
||||||
<td colspan="2">{{worktype.version}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<a href="/comic/worktypes" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
|
||||||
<a href="/comic/worktype/edit/{{worktype.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
|
||||||
<a href="/comic/worktype/delete/{{worktype.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Edit WorkType</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="text-danger font-weight-bold">
|
|
||||||
{% for error in errors %}
|
|
||||||
<li>{{error}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row my-5">
|
|
||||||
<h3 class="text-center display-4">Add a WorkType</h3>
|
|
||||||
<form method="POST">
|
|
||||||
<div class="mb-3">
|
|
||||||
<input type="text" required class="form-control" name="worktype" value="{{worktype}}" placeholder="WorkType here">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>WorkTypes</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% with msg=msg %}
|
|
||||||
{% include "components/alerts.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
<div class="container">
|
|
||||||
<table class="table table-hover">
|
|
||||||
<thead><tr>
|
|
||||||
<th scope="col">Name</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for worktype in worktypes %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row"><a href="/comic/worktypes/{{worktype.id}}">{{worktype.name}}</a></th>
|
|
||||||
<td><a href="/comic/worktype/edit/{{worktype.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
|
||||||
<td><a href="/comic/worktype/delete/{{worktype.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<a href="/comic/worktype/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add WorkType</a>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
-1
@@ -1,7 +1,6 @@
|
|||||||
<div class="card shadow p-3 mb-2 bg-body rounded" style="width: 18rem;">
|
<div class="card shadow p-3 mb-2 bg-body rounded" style="width: 18rem;">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">{{obj.name}}</h5>
|
<h5 class="card-title">{{obj.name}}</h5>
|
||||||
<p class="card-text">Link : {{obj.weblink}}</p>
|
|
||||||
<a href="/comic/artists/{{obj.id}}" class="btn btn-primary">Read more</a>
|
<a href="/comic/artists/{{obj.id}}" class="btn btn-primary">Read more</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{% if check %}
|
{% if check == 1 %}
|
||||||
<img src="{{ url_for('static', path='images/tick.png') }}" alt="" width="24" height="24">
|
<img src="{{ url_for('static', path='images/tick.png') }}" alt="" width="24" height="24">
|
||||||
{% else %}
|
{% else %}
|
||||||
<img src="{{ url_for('static', path='images/cross.png') }}" alt="" width="24" height="24">
|
<img src="{{ url_for('static', path='images/cross.png') }}" alt="" width="24" height="24">
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light px-5">
|
<nav class="navbar navbar-expand-lg navbar-light bg-light px-5">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="#">Kontor</a>
|
<a class="navbar-brand" href="#">
|
||||||
|
<img src="{{ url_for('static', path='images/logo.png') }}" alt="" width="30" height="24">
|
||||||
|
</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
@@ -16,7 +18,7 @@
|
|||||||
<li><a class="dropdown-item" href="/comic/artists/">Artists</a></li>
|
<li><a class="dropdown-item" href="/comic/artists/">Artists</a></li>
|
||||||
<li><a class="dropdown-item" href="/comic/publishers/">Publishers</a></li>
|
<li><a class="dropdown-item" href="/comic/publishers/">Publishers</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="/comic/worktypes">WorkTypes</a></li>
|
<li><a class="dropdown-item" href="#">Something else here</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
@@ -24,7 +26,6 @@
|
|||||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<li><a class="dropdown-item" href="/media/files/">MediaFiles</a></li>
|
<li><a class="dropdown-item" href="/media/files/">MediaFiles</a></li>
|
||||||
<li><a class="dropdown-item" href="/media/actors/">MediaActors</a></li>
|
<li><a class="dropdown-item" href="/media/actors/">MediaActors</a></li>
|
||||||
<li><a class="dropdown-item" href="/media/videos/">MediaVideos</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
@@ -41,19 +42,18 @@
|
|||||||
<li><a class="dropdown-item" href="/register/">Signup</a></li>
|
<li><a class="dropdown-item" href="/register/">Signup</a></li>
|
||||||
<li><a class="dropdown-item" href="/login/">Login</a></li>
|
<li><a class="dropdown-item" href="/login/">Login</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="/admin/profiles">Profiles</a></li>
|
<li><a class="dropdown-item" href="#">Something else here</a></li>
|
||||||
<li><a class="dropdown-item" href="/admin/permissions">Permissions</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav ml-auto mb-2 mb-lg-0">
|
<ul class="navbar-nav ml-auto mb-2 mb-lg-0">
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
Media
|
Jobs
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<li><a class="dropdown-item" href="/media/add-link/">Add Link</a></li>
|
<li><a class="dropdown-item" href="/comics/">Comics</a></li>
|
||||||
<li><a class="dropdown-item" href="/media/add-file">Add MediaFile</a></li>
|
<li><a class="dropdown-item" href="/comics/">Media</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -41,12 +41,5 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<a href="/media/actors" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
|
||||||
<a href="/media/actor/edit/{{actor.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
|
||||||
<a href="/media/actor/delete/{{actor.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
{% for actor in actors %}
|
{% for actor in actors %}
|
||||||
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
||||||
{% with obj=actor %}
|
{% with obj=actor %}
|
||||||
{% include "media/actor_cards.html" %}
|
{% include "components/actor_cards.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% if loop.index %3 %}
|
{% if loop.index %3 %}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Add a Video Link</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="text-danger font-weight-bold">
|
|
||||||
{% for error in errors %}
|
|
||||||
<li>{{error}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row my-5">
|
|
||||||
<h3 class="text-center display-4">Add a Video Link</h3>
|
|
||||||
<form method="POST">
|
|
||||||
<div class="mb-3">
|
|
||||||
<input type="text" required class="form-control" name="url" value="{{url}}" placeholder="Video Link here">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
@@ -61,12 +61,5 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<a href="/media/files" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
|
||||||
<a href="/media/file/edit/{{mediafile.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
|
||||||
<a href="/media/file/delete/{{mediafile.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -9,31 +9,21 @@
|
|||||||
{% include "components/alerts.html" %}
|
{% include "components/alerts.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
|
||||||
<form class="d-flex" action="/media/files/">
|
|
||||||
<input class="form-control me-2" name="query" id="autocomplete" type="search" placeholder="Search" aria-label="Search">
|
|
||||||
Review<input type="checkbox" name="review" aria-label="Review">
|
|
||||||
Download<input type="checkbox" name="download" aria-label="Download">
|
|
||||||
<button class="btn btn-outline-success" type="submit">Search</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
<th scope="col">Titel</th>
|
<th scope="col">Titel</th>
|
||||||
<th scope="col">Review</th>
|
<th scope="col">URL</th>
|
||||||
<th scope="col">Download</th>
|
<th scope="col">Cloudlink</th>
|
||||||
</tr></thead>
|
</tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for mediafile in mediafiles %}
|
{% for mediafile in mediafiles %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row"><a href="/media/files/{{mediafile.id}}">{{mediafile.title}}</a></th>
|
<th scope="row"><a href="/media/files/{{mediafile.id}}">{{mediafile.title}}</a></th>
|
||||||
<td>{% with check=mediafile.review %}{% include "components/check.html" %}{% endwith %}</td>
|
<td>{{mediafile.url}}</td>
|
||||||
<td>{% with check=mediafile.should_download %}{% include "components/check.html" %}{% endwith %}</td>
|
<td>{{mediafile.cloud_link}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>MediaVideo Detail</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h1 class="display-5">MediaVideo Detail</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-striped table-hover">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">MediaVideo Title</th>
|
|
||||||
<td colspan="2">{{mediavideo.title}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">MediaVideo URL</th>
|
|
||||||
<td colspan="2">{{mediavideo.url}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">MediaVideo Cloudlink</th>
|
|
||||||
<td colspan="2">{{mediavideo.cloud_link}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">MediaVideo Download?</th>
|
|
||||||
<td colspan="2">{{mediavideo.should_download}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">MediaVideo Review?</th>
|
|
||||||
<td colspan="2">{{mediavideo.review}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Created</th>
|
|
||||||
<td colspan="2">{{mediavideo.created_date}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Modified</th>
|
|
||||||
<td colspan="2">{{mediavideo.last_modified_date}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Version</th>
|
|
||||||
<td colspan="2">{{mediavideo.version}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Video Links</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% with msg=msg %}
|
|
||||||
{% include "components/alerts.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
<div class="container">
|
|
||||||
<table class="table table-hover">
|
|
||||||
<thead><tr>
|
|
||||||
<th scope="col">Titel</th>
|
|
||||||
<th scope="col">Review</th>
|
|
||||||
<th scope="col">Download</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for mediavideo in mediavideos %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row"><a href="/media/videos/{{mediavideo.id}}">{{mediavideo.title}}</a></th>
|
|
||||||
<td>{% with check=mediavideo.review %}{% include "components/check.html" %}{% endwith %}</td>
|
|
||||||
<td>{% with check=mediavideo.should_download %}{% include "components/check.html" %}{% endwith %}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de-DE">
|
<html lang="en-us">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
@@ -21,5 +21,6 @@
|
|||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
from typing import AnyStr
|
|
||||||
from fastapi import APIRouter, Request
|
|
||||||
from fastapi.security.utils import get_authorization_scheme_param
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
|
|
||||||
from src.apis.utils import SessionDep
|
|
||||||
from src.apis.version1.admin import get_current_user_from_token
|
|
||||||
from src.db.models.admin import Permission, Profile
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="src/templates")
|
|
||||||
router = APIRouter(include_in_schema=False, prefix="/admin")
|
|
||||||
|
|
||||||
@router.get("/profiles")
|
|
||||||
def get_profiles(db: SessionDep, request: Request, msg: str | None = None):
|
|
||||||
profiles = db.query(Profile).all()
|
|
||||||
return templates.TemplateResponse("admin/profiles.html", {"request": request, "msg": msg, "profiles": profiles})
|
|
||||||
|
|
||||||
@router.get("/profiles/{profile_id}")
|
|
||||||
def comic_details(profile_id: AnyStr, request: Request, db: SessionDep):
|
|
||||||
profile = db.get(Profile, profile_id)
|
|
||||||
return templates.TemplateResponse("admin/profile_detail.html", {"request": request, "profile":profile})
|
|
||||||
|
|
||||||
@router.get("/permissions")
|
|
||||||
def get_permissions(db: SessionDep, request: Request, msg: str | None = None):
|
|
||||||
permissions = db.query(Permission).all()
|
|
||||||
return templates.TemplateResponse("admin/permissions.html", {"request": request, "msg": msg, "permissions": permissions})
|
|
||||||
|
|
||||||
@router.get("/permissions/{permission_id}")
|
|
||||||
def artist_detail(permission_id: AnyStr, request: Request, db: SessionDep):
|
|
||||||
permission= db.get(Permission, str(permission_id))
|
|
||||||
return templates.TemplateResponse("comic/permission_detail.html", {"request": request, "permission": permission})
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
from typing import List
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import Request
|
|
||||||
|
|
||||||
|
|
||||||
class LoginForm:
|
|
||||||
def __init__(self, request: Request):
|
|
||||||
self.request: Request = request
|
|
||||||
self.errors: List = []
|
|
||||||
self.username: Optional[str] = None
|
|
||||||
self.password: Optional[str] = None
|
|
||||||
|
|
||||||
async def load_data(self):
|
|
||||||
form = await self.request.form()
|
|
||||||
# since auth works on username field we are considering email as username
|
|
||||||
self.username = form.get("email")
|
|
||||||
self.password = form.get("password")
|
|
||||||
|
|
||||||
async def is_valid(self):
|
|
||||||
if not self.username or not (self.username.__contains__("@")):
|
|
||||||
self.errors.append("Email is required")
|
|
||||||
if not self.password or not len(self.password) >= 4:
|
|
||||||
self.errors.append("A valid password is required")
|
|
||||||
if not self.errors:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
from src.apis.version1.admin import login_for_access_token
|
|
||||||
from src.db.session import get_db
|
|
||||||
from fastapi import APIRouter
|
|
||||||
from fastapi import Depends
|
|
||||||
from fastapi import HTTPException
|
|
||||||
from fastapi import Request
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
from src.webapps.auth.forms import LoginForm
|
|
||||||
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="src/templates")
|
|
||||||
router = APIRouter(include_in_schema=False)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/login/")
|
|
||||||
def login(request: Request):
|
|
||||||
return templates.TemplateResponse("auth/login.html", {"request": request})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/login/")
|
|
||||||
async def login(request: Request, db: Session = Depends(get_db)):
|
|
||||||
form = LoginForm(request)
|
|
||||||
await form.load_data()
|
|
||||||
if await form.is_valid():
|
|
||||||
try:
|
|
||||||
form.__dict__.update(msg="Login Successful :)")
|
|
||||||
response = templates.TemplateResponse("auth/login.html", form.__dict__)
|
|
||||||
login_for_access_token(response=response, form_data=form, db=db)
|
|
||||||
return response
|
|
||||||
except HTTPException:
|
|
||||||
form.__dict__.update(msg="")
|
|
||||||
form.__dict__.get("errors").append("Incorrect Email or Password")
|
|
||||||
return templates.TemplateResponse("auth/login.html", form.__dict__)
|
|
||||||
return templates.TemplateResponse("auth/login.html", form.__dict__)
|
|
||||||
@@ -1,22 +1,15 @@
|
|||||||
from fastapi import APIRouter, Request
|
from fastapi import APIRouter, Request
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
from src.webapps.admin import route_admin
|
from src.webapps.comic import route_comics
|
||||||
from src.webapps.auth import route_login
|
from src.webapps.media import route_media
|
||||||
from src.webapps.comic import route_comics, route_worktype, route_artists
|
|
||||||
from src.webapps.media import route_media, route_videos
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="src/templates")
|
templates = Jinja2Templates(directory="src/templates")
|
||||||
|
|
||||||
api_router = APIRouter()
|
api_router = APIRouter()
|
||||||
api_router.include_router(route_comics.router)
|
api_router.include_router(route_comics.router)
|
||||||
api_router.include_router(route_artists.router)
|
|
||||||
api_router.include_router(route_worktype.router)
|
|
||||||
api_router.include_router(route_media.router)
|
api_router.include_router(route_media.router)
|
||||||
api_router.include_router(route_videos.router)
|
|
||||||
api_router.include_router(route_login.router)
|
|
||||||
api_router.include_router(route_admin.router)
|
|
||||||
|
|
||||||
@api_router.get("/")
|
@api_router.get("/")
|
||||||
def home(request: Request, msg: str | None = None):
|
def home(request: Request, msg: str = None):
|
||||||
return templates.TemplateResponse("index.html", {"request": request, "msg": msg})
|
return templates.TemplateResponse("index.html", {"request": request, "msg": msg})
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
from fastapi import Request
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
|
|
||||||
class ValidateComicForm:
|
|
||||||
def __init__(self, request: Request, comic_id: str, completed: bool, current_order: bool):
|
|
||||||
self.request = request
|
|
||||||
self.errors: List = []
|
|
||||||
self.id = comic_id
|
|
||||||
self.title: Optional[str] = None
|
|
||||||
self.weblink: Optional[str] = None
|
|
||||||
self.completed = completed
|
|
||||||
self.current_order = current_order
|
|
||||||
|
|
||||||
async def load_data(self):
|
|
||||||
form = await self.request.form()
|
|
||||||
print(f"{form.keys()}")
|
|
||||||
self.title = form.get("title")
|
|
||||||
self.weblink = form.get("weblink")
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
if not self.errors:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.title=}, {self.weblink=}"
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
from fastapi import Request
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
|
|
||||||
class AddWorktypeForm:
|
|
||||||
def __init__(self, request: Request):
|
|
||||||
self.request = request
|
|
||||||
self.errors: List = []
|
|
||||||
self.worktype: Optional[str] = None
|
|
||||||
|
|
||||||
async def load_data(self):
|
|
||||||
form = await self.request.form()
|
|
||||||
self.worktype = form.get("worktype")
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
if not self.worktype or (len(self.worktype) == 0):
|
|
||||||
self.errors.append("WorkType cannot be empty")
|
|
||||||
if not self.errors:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
from fastapi import APIRouter, Request, status
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from fastapi.responses import RedirectResponse
|
|
||||||
|
|
||||||
from src.apis.utils import SessionDep
|
|
||||||
from src.db.models.comic import Artist
|
|
||||||
from typing import AnyStr
|
|
||||||
|
|
||||||
#from src.db.repository.comic import create_new_worktype, update_worktype
|
|
||||||
from src.main import logger
|
|
||||||
#from src.schema.comics.worktype import AddWorkType
|
|
||||||
#from src.webapps.comic.forms import AddWorktypeForm
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="src/templates")
|
|
||||||
router = APIRouter(include_in_schema=False, prefix="/comic")
|
|
||||||
|
|
||||||
@router.get("/artists")
|
|
||||||
def get_artists(db: SessionDep, request: Request, msg: str | None = None):
|
|
||||||
artists = db.query(Artist).all()
|
|
||||||
return templates.TemplateResponse("comic/artists.html", {"request": request, "msg": msg, "artists": artists})
|
|
||||||
|
|
||||||
@router.get("/artists/{artist_id}")
|
|
||||||
def artist_detail(artist_id: AnyStr, request: Request, db: SessionDep):
|
|
||||||
artist = db.get(Artist, str(artist_id))
|
|
||||||
return templates.TemplateResponse("comic/artist_detail.html", {"request": request, "artist": artist})
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/artist/edit/{artist_id}")
|
|
||||||
def edit_artist(db: SessionDep, request: Request, artist_id: str):
|
|
||||||
artist = db.get(Artist, artist_id)
|
|
||||||
return templates.TemplateResponse("comic/artist_edit.html", {"request": request, "artist_name": artist.name, "artist_link": artist.weblink})
|
|
||||||
|
|
||||||
@router.post("/artist/edit/{artist_id}")
|
|
||||||
async def edit_artist(request: Request, db: SessionDep, artist_id: str):
|
|
||||||
form = AddArtistForm(request)
|
|
||||||
await form.load_data()
|
|
||||||
if form.is_valid():
|
|
||||||
try:
|
|
||||||
artist = AddArtist(**form.__dict__)
|
|
||||||
artist = update_artist(artist=artist, artist_id=artist_id, db=db)
|
|
||||||
return RedirectResponse(f"/comic/artists/{artist.id}", status_code=status.HTTP_303_SEE_OTHER)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
form.__dict__.get("errors").append("artist already added")
|
|
||||||
return templates.TemplateResponse("comic/artist_edit.html", form.__dict__)
|
|
||||||
return templates.TemplateResponse("comic/artist_edit.html", form.__dict__)
|
|
||||||
|
|
||||||
@router.get("/artist/delete/{artist_id}")
|
|
||||||
async def delete_artist(db: SessionDep, request: Request, artist_id: str):
|
|
||||||
artist = db.get(Artist, artist_id)
|
|
||||||
db.delete(artist)
|
|
||||||
db.commit()
|
|
||||||
return RedirectResponse("/comic/artists", status_code=status.HTTP_303_SEE_OTHER)
|
|
||||||
@@ -1,90 +1,42 @@
|
|||||||
from fastapi import APIRouter, Form, Request, status
|
from uuid import UUID
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Request
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from fastapi.responses import RedirectResponse
|
|
||||||
|
|
||||||
from src.apis.utils import SessionDep
|
from src.apis.utils import SessionDep
|
||||||
from src.db.models.comic import Comic, Publisher, Issue
|
from src.db.models.comic import Comic, Artist, Publisher
|
||||||
from typing import AnyStr
|
|
||||||
from src.core.log_conf import logger
|
|
||||||
from src.db.repository.comics.comic import update_comic
|
|
||||||
from src.schema.comics.comic import ComicSchema
|
|
||||||
from src.webapps.comic.forms.comic import ValidateComicForm
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="src/templates")
|
templates = Jinja2Templates(directory="src/templates")
|
||||||
router = APIRouter(include_in_schema=False, prefix="/comic")
|
router = APIRouter(include_in_schema=False, prefix="/comic")
|
||||||
|
|
||||||
@router.get("/comics")
|
@router.get("/comics")
|
||||||
def get_comics(db: SessionDep, request: Request, msg: str | None = None):
|
def get_comics(db: SessionDep, request: Request, msg: str = None):
|
||||||
params = request.query_params
|
|
||||||
query = params.get("query")
|
|
||||||
filter = {}
|
|
||||||
completed = params.get('completed') == "on"
|
|
||||||
if completed:
|
|
||||||
filter['completed'] = True
|
|
||||||
order = params.get("order") == "on"
|
|
||||||
if order:
|
|
||||||
filter['current_order'] = True
|
|
||||||
if query is not None and len(query) > 0:
|
|
||||||
filter['title'] = query
|
|
||||||
if len(filter) > 0:
|
|
||||||
if "title" in filter:
|
|
||||||
comics = db.query(Comic).filter(Comic.title.ilike(f'%{query}%'))
|
|
||||||
else:
|
|
||||||
comics = db.query(Comic).filter_by(**filter).all()
|
|
||||||
else:
|
|
||||||
comics = db.query(Comic).all()
|
comics = db.query(Comic).all()
|
||||||
return templates.TemplateResponse("comic/comics.html", {"request": request, "msg": msg, "comics": comics})
|
return templates.TemplateResponse("comic/comics.html", {"request": request, "msg": msg, "comics": comics})
|
||||||
|
|
||||||
@router.get("/comics/{comic_id}")
|
@router.get("/comics/{comic_id}")
|
||||||
def comic_details(comic_id: AnyStr, request: Request, db: SessionDep):
|
def comic_details(comic_id: UUID, request: Request, db: SessionDep):
|
||||||
comic = db.get(Comic, comic_id)
|
comic = db.get(Comic, comic_id)
|
||||||
return templates.TemplateResponse("comic/comic_detail.html", {"request": request, "comic":comic})
|
return templates.TemplateResponse("comic/comic_detail.html", {"request": request, "comic":comic})
|
||||||
|
|
||||||
@router.get("/comic/edit/{comic_id}")
|
@router.get("/artists")
|
||||||
def edit_comic(db: SessionDep, request: Request, comic_id: str):
|
def get_artists(db: SessionDep, request: Request, msg: str = None):
|
||||||
comic = db.get(Comic, comic_id)
|
artists = db.query(Artist).all()
|
||||||
return templates.TemplateResponse("comic/comic_edit.html", {"request": request, "comic_title": comic.title, "comic_weblink": comic.weblink})
|
return templates.TemplateResponse("comic/artists.html", {"request": request, "msg": msg, "artists": artists})
|
||||||
|
|
||||||
|
@router.get("/artists/{artist_id}")
|
||||||
|
def artist_detail(artist_id: UUID, request: Request, db: SessionDep):
|
||||||
@router.post("/comic/edit/{comic_id}")
|
artist = db.get(Artist, artist_id)
|
||||||
async def validate_comic(request: Request, db: SessionDep, comic_id: str, action: str = Form(...), completed: bool = Form(False), current_order: bool = Form(False)):
|
return templates.TemplateResponse("comic/artist_detail.html", {"request": request, "artist": artist})
|
||||||
if action == "cancel":
|
|
||||||
return RedirectResponse(f"/comic/comics/{comic_id}", status_code=status.HTTP_303_SEE_OTHER)
|
|
||||||
form = ValidateComicForm(request, comic_id, completed, current_order)
|
|
||||||
logger.info(f"request: {repr(request)}")
|
|
||||||
await form.load_data()
|
|
||||||
logger.info(f"form: {form}")
|
|
||||||
if form.is_valid():
|
|
||||||
try:
|
|
||||||
comic = ComicSchema(**form.__dict__)
|
|
||||||
comic = update_comic(comic=comic, comic_id=comic_id, db=db)
|
|
||||||
return RedirectResponse(f"/comic/comics/{comic.id}", status_code=status.HTTP_303_SEE_OTHER)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
form.__dict__.get("errors").append("comic already added")
|
|
||||||
return templates.TemplateResponse("comic/comic_edit.html", form.__dict__)
|
|
||||||
return templates.TemplateResponse("comic/comic_edit.html", form.__dict__)
|
|
||||||
|
|
||||||
@router.get("/publishers")
|
@router.get("/publishers")
|
||||||
def get_publishers(db: SessionDep, request: Request, msg: str | None = None):
|
def get_publishers(db: SessionDep, request: Request, msg: str = None):
|
||||||
publishers = db.query(Publisher).all()
|
publishers = db.query(Publisher).all()
|
||||||
return templates.TemplateResponse("comic/publishers.html", {"request": request, "publishers": publishers})
|
return templates.TemplateResponse("comic/publishers.html", {"request": request, "publishers": publishers})
|
||||||
|
|
||||||
@router.get("/publishers/{publisher_id}")
|
@router.get("/publishers/{publisher_id}")
|
||||||
def publisher_details(publisher_id: AnyStr, request: Request, db: SessionDep, msg: str = None):
|
def publisher_details(publisher_id: UUID, request: Request, db: SessionDep, msg: str = None):
|
||||||
publisher = db.get(Publisher, publisher_id)
|
publisher = db.get(Publisher, publisher_id)
|
||||||
if publisher is None:
|
if publisher is None:
|
||||||
msg = "Could not find Publisher"
|
msg = "Could not find Publisher"
|
||||||
return templates.TemplateResponse("comic/publisher_detail.html", {"request": request, "msg": msg, "publisher": publisher})
|
return templates.TemplateResponse("comic/publisher_detail.html", {"request": request, "msg": msg, "publisher": publisher})
|
||||||
|
|
||||||
@router.get("/issues")
|
|
||||||
def get_issues(db: SessionDep, request: Request, msg: str | None = None):
|
|
||||||
issues = db.query(Issue).all()
|
|
||||||
return templates.TemplateResponse("comic/issues.html", {"request": request, "msg": msg, "issues": issues})
|
|
||||||
|
|
||||||
@router.get("/issues/{issue_id}")
|
|
||||||
def issue_details(issue_id: AnyStr, request: Request, db: SessionDep):
|
|
||||||
issue = db.get(Issue, issue_id)
|
|
||||||
return templates.TemplateResponse("comic/issue_detail.html", {"request": request, "issue": issue})
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
from fastapi import APIRouter, Request, status
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from fastapi.responses import RedirectResponse
|
|
||||||
|
|
||||||
from src.apis.utils import SessionDep
|
|
||||||
from src.db.models.comic import WorkType
|
|
||||||
from typing import AnyStr
|
|
||||||
|
|
||||||
from src.db.repository.comics.worktype import create_new_worktype, update_worktype
|
|
||||||
from src.main import logger
|
|
||||||
from src.schema.comics.worktype import AddWorkType
|
|
||||||
from src.webapps.comic.forms.worktype import AddWorktypeForm
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="src/templates")
|
|
||||||
router = APIRouter(include_in_schema=False, prefix="/comic")
|
|
||||||
|
|
||||||
@router.get("/worktypes")
|
|
||||||
def get_worktypes(db: SessionDep, request: Request, msg: str | None = None):
|
|
||||||
worktypes = db.query(WorkType).all()
|
|
||||||
return templates.TemplateResponse("comic/worktypes.html", {"request": request, "msg": msg, "worktypes": worktypes})
|
|
||||||
|
|
||||||
@router.get("/worktypes/{worktype_id}")
|
|
||||||
def worktype_detail(db: SessionDep, request: Request, worktype_id: AnyStr):
|
|
||||||
worktype = db.get(WorkType, worktype_id)
|
|
||||||
return templates.TemplateResponse("comic/worktype_detail.html", {"request": request, "worktype": worktype})
|
|
||||||
|
|
||||||
@router.get("/worktype/add")
|
|
||||||
def add_worktype(request: Request, db: SessionDep):
|
|
||||||
return templates.TemplateResponse("comic/worktype_edit.html", {"request": request})
|
|
||||||
|
|
||||||
@router.post("/worktype/add")
|
|
||||||
async def add_worktype(db: SessionDep, request: Request):
|
|
||||||
form = AddWorktypeForm(request)
|
|
||||||
await form.load_data()
|
|
||||||
if form.is_valid():
|
|
||||||
try:
|
|
||||||
work = AddWorkType(**form.__dict__)
|
|
||||||
worktype = create_new_worktype(work=work, db=db)
|
|
||||||
logger.info(f"add_worktype: redirect to /comic/worktypes/{worktype.id}")
|
|
||||||
return RedirectResponse(f"/comic/worktypes/{worktype.id}", status_code=status.HTTP_303_SEE_OTHER)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
form.__dict__.get("errors").append("worktype already added")
|
|
||||||
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
|
|
||||||
print("form is not valid")
|
|
||||||
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
|
|
||||||
|
|
||||||
@router.get("/worktype/edit/{worktype_id}")
|
|
||||||
def edit_worktype(db: SessionDep, request: Request, worktype_id: str):
|
|
||||||
worktype = db.get(WorkType, worktype_id)
|
|
||||||
return templates.TemplateResponse("comic/worktype_edit.html", {"request": request, "worktype": worktype.name})
|
|
||||||
|
|
||||||
@router.post("/worktype/edit/{worktype_id}")
|
|
||||||
async def edit_worktype(request: Request, db: SessionDep, worktype_id: str):
|
|
||||||
form = AddWorktypeForm(request)
|
|
||||||
await form.load_data()
|
|
||||||
if form.is_valid():
|
|
||||||
try:
|
|
||||||
work = AddWorkType(**form.__dict__)
|
|
||||||
worktype = update_worktype(work=work, worktype_id=worktype_id, db=db)
|
|
||||||
return RedirectResponse(f"/comic/worktypes/{worktype.id}", status_code=status.HTTP_303_SEE_OTHER)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
form.__dict__.get("errors").append("worktype already added")
|
|
||||||
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
|
|
||||||
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
|
|
||||||
|
|
||||||
@router.get("/worktype/delete/{worktype_id}")
|
|
||||||
async def delete_worktype(db: SessionDep, request: Request, worktype_id: str):
|
|
||||||
worktype = db.get(WorkType, worktype_id)
|
|
||||||
db.delete(worktype)
|
|
||||||
db.commit()
|
|
||||||
return RedirectResponse("/comic/worktypes", status_code=status.HTTP_303_SEE_OTHER)
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
from fastapi import Request
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
|
|
||||||
class AddLinkForm:
|
|
||||||
def __init__(self, request: Request):
|
|
||||||
self.request = request
|
|
||||||
self.errors: List = []
|
|
||||||
self.url: Optional[str] = None
|
|
||||||
|
|
||||||
async def load_data(self):
|
|
||||||
form = await self.request.form()
|
|
||||||
self.url = form.get("url")
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
if not self.url or not (self.url.__contains__("http")):
|
|
||||||
self.errors.append("Valid Url is required e.g. https://example.com")
|
|
||||||
if not self.errors:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
@@ -1,50 +1,22 @@
|
|||||||
from typing import AnyStr
|
from uuid import UUID
|
||||||
|
|
||||||
from fastapi import APIRouter, Request
|
from fastapi import APIRouter, Request
|
||||||
from fastapi.security.utils import get_authorization_scheme_param
|
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from sqlalchemy import or_
|
|
||||||
|
|
||||||
from src.apis.utils import SessionDep
|
from src.apis.utils import SessionDep
|
||||||
from src.apis.version1.admin import get_current_user_from_token
|
|
||||||
from src.db.models.admin import Profile
|
|
||||||
from src.db.models.media import MediaFile, MediaActor
|
from src.db.models.media import MediaFile, MediaActor
|
||||||
|
#ifrom src.schema.media.comic import get_comic_details
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="src/templates")
|
templates = Jinja2Templates(directory="src/templates")
|
||||||
router = APIRouter(include_in_schema=False, prefix="/media")
|
router = APIRouter(include_in_schema=False, prefix="/media")
|
||||||
|
|
||||||
@router.get("/files")
|
@router.get("/files")
|
||||||
def get_mediafiles(db: SessionDep, request: Request, msg: str = None):
|
def get_mediafiles(db: SessionDep, request: Request, msg: str = None):
|
||||||
params = request.query_params
|
|
||||||
query = params.get("query")
|
|
||||||
filter = {}
|
|
||||||
review = params.get('review') == "on"
|
|
||||||
if review:
|
|
||||||
filter['review'] = True
|
|
||||||
download = params.get("download") == "on"
|
|
||||||
if download:
|
|
||||||
filter['should_download'] = True
|
|
||||||
if query is not None and len(query) > 0:
|
|
||||||
filter['url'] = query
|
|
||||||
if len(filter) > 0:
|
|
||||||
if "url" in filter:
|
|
||||||
mediafiles = db.query(MediaFile).filter(or_(MediaFile.title.ilike(f'%{query}%'), MediaFile.url.ilike(f"%{query}%")))
|
|
||||||
else:
|
|
||||||
mediafiles = db.query(MediaFile).filter_by(**filter).all()
|
|
||||||
else:
|
|
||||||
mediafiles = db.query(MediaFile).all()
|
mediafiles = db.query(MediaFile).all()
|
||||||
try:
|
|
||||||
token = request.cookies.get("access_token")
|
|
||||||
scheme, param = get_authorization_scheme_param(token) # scheme will hold "Bearer" and param will hold actual token value
|
|
||||||
current_user: Profile = get_current_user_from_token(token=param, db=db)
|
|
||||||
return templates.TemplateResponse("media/files.html", {"request": request, "msg": msg, "mediafiles": mediafiles})
|
return templates.TemplateResponse("media/files.html", {"request": request, "msg": msg, "mediafiles": mediafiles})
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
msg = "Nicht berechtigt!!"
|
|
||||||
return templates.TemplateResponse("media/files.html", {"request": request, "msg": msg, "mediafiles": []})
|
|
||||||
|
|
||||||
@router.get("/files/{file_id}")
|
@router.get("/files/{file_id}")
|
||||||
def file_details(file_id: AnyStr, request: Request, db: SessionDep):
|
def file_details(file_id: UUID, request: Request, db: SessionDep):
|
||||||
mediafile = db.get(MediaFile, file_id)
|
mediafile = db.get(MediaFile, file_id)
|
||||||
return templates.TemplateResponse("media/file_detail.html", {"request": request, "mediafile":mediafile})
|
return templates.TemplateResponse("media/file_detail.html", {"request": request, "mediafile":mediafile})
|
||||||
|
|
||||||
@@ -54,7 +26,7 @@ def get_actors(db: SessionDep, request: Request, msg: str = None):
|
|||||||
return templates.TemplateResponse("media/actors.html", {"request": request, "msg": msg, "actors": actors})
|
return templates.TemplateResponse("media/actors.html", {"request": request, "msg": msg, "actors": actors})
|
||||||
|
|
||||||
@router.get("/actors/{actor_id}")
|
@router.get("/actors/{actor_id}")
|
||||||
def artist_detail(actor_id: AnyStr, request: Request, db: SessionDep):
|
def artist_detail(actor_id: UUID, request: Request, db: SessionDep):
|
||||||
actor = db.get(MediaActor, actor_id)
|
actor = db.get(MediaActor, actor_id)
|
||||||
return templates.TemplateResponse("media/actor_detail.html", {"request": request, "actor": actor})
|
return templates.TemplateResponse("media/actor_detail.html", {"request": request, "actor": actor})
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
from typing import AnyStr
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Request, status, responses
|
|
||||||
from fastapi.security.utils import get_authorization_scheme_param
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
|
|
||||||
from src.apis.utils import SessionDep
|
|
||||||
from src.db.models.media import MediaVideo
|
|
||||||
from src.db.repository.media import create_new_video
|
|
||||||
from src.apis.version1.admin import get_current_user_from_token
|
|
||||||
from src.db.models.admin import Profile
|
|
||||||
from src.schema.media.video import AddLink
|
|
||||||
from src.webapps.media.forms import AddLinkForm
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="src/templates")
|
|
||||||
router = APIRouter(include_in_schema=False, prefix="/media")
|
|
||||||
|
|
||||||
@router.get("/videos")
|
|
||||||
def get_mediavideos(db: SessionDep, request: Request, msg: str = None):
|
|
||||||
mediavideos = db.query(MediaVideo).all()
|
|
||||||
try:
|
|
||||||
token = request.cookies.get("access_token")
|
|
||||||
_, param = get_authorization_scheme_param(token) # scheme will hold "Bearer" and param will hold actual token value
|
|
||||||
current_user: Profile = get_current_user_from_token(token=param, db=db)
|
|
||||||
return templates.TemplateResponse("media/videos.html", {"request": request, "msg": msg, "user": current_user, "mediavideos": mediavideos})
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
return templates.TemplateResponse("media/videos.html", {"request": request, "msg": msg, "user": None, "mediavideos": mediavideos})
|
|
||||||
|
|
||||||
@router.get("/videos/{video_id}")
|
|
||||||
def video_details(video_id: AnyStr, request: Request, db: SessionDep):
|
|
||||||
mediavideo = db.get(MediaVideo, video_id)
|
|
||||||
return templates.TemplateResponse("media/video_detail.html", {"request": request, "mediavideo":mediavideo})
|
|
||||||
|
|
||||||
@router.get("/add-link")
|
|
||||||
def add_video_link(request: Request, db: SessionDep):
|
|
||||||
return templates.TemplateResponse("media/add_video_link.html", {"request": request})
|
|
||||||
|
|
||||||
@router.post("/add-link")
|
|
||||||
async def post_video_link(request: Request, db: SessionDep):
|
|
||||||
form = AddLinkForm(request)
|
|
||||||
await form.load_data()
|
|
||||||
if form.is_valid():
|
|
||||||
try:
|
|
||||||
video = AddLink(**form.__dict__)
|
|
||||||
mediavideo = create_new_video(video=video, db=db)
|
|
||||||
return responses.RedirectResponse(f"media/videos/{mediavideo.id}", status_code=status.HTTP_302_FOUND)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
form.__dict__.get("errors").append("Link already added")
|
|
||||||
return templates.TemplateResponse("media/add_video_link.html", form.__dict__)
|
|
||||||
return templates.TemplateResponse("media/add_video_link.html", form.__dict__)
|
|
||||||
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from typing import Any
|
|
||||||
from typing import Generator
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from fastapi import FastAPI
|
|
||||||
from fastapi.testclient import TestClient
|
|
||||||
from sqlalchemy import create_engine
|
|
||||||
from sqlalchemy.orm import sessionmaker
|
|
||||||
|
|
||||||
from src.apis.base import api_router
|
|
||||||
from src.db.models.base import Base
|
|
||||||
from src.db.session import get_db
|
|
||||||
|
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
||||||
# this is to include backend dir in sys.path so that we can import from db,main.py
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def start_application():
|
|
||||||
app = FastAPI()
|
|
||||||
app.include_router(api_router)
|
|
||||||
return app
|
|
||||||
|
|
||||||
|
|
||||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./test_db.db"
|
|
||||||
engine = create_engine(
|
|
||||||
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
|
|
||||||
)
|
|
||||||
# Use connect_args parameter only with sqlite
|
|
||||||
SessionTesting = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
|
||||||
def app() -> Generator[FastAPI, Any, None]:
|
|
||||||
"""
|
|
||||||
Create a fresh database on each test case.
|
|
||||||
"""
|
|
||||||
Base.metadata.create_all(engine) # Create the tables.
|
|
||||||
_app = start_application()
|
|
||||||
yield _app
|
|
||||||
Base.metadata.drop_all(engine)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
|
||||||
def db_session(app: FastAPI) -> Generator[SessionTesting, Any, None]:
|
|
||||||
connection = engine.connect()
|
|
||||||
transaction = connection.begin()
|
|
||||||
session = SessionTesting(bind=connection)
|
|
||||||
yield session # use the session in tests.
|
|
||||||
session.close()
|
|
||||||
transaction.rollback()
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
|
||||||
def client(
|
|
||||||
app: FastAPI, db_session: SessionTesting
|
|
||||||
) -> Generator[TestClient, Any, None]:
|
|
||||||
"""
|
|
||||||
Create a new FastAPI TestClient that uses the `db_session` fixture to override
|
|
||||||
the `get_db` dependency that is injected into routes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _get_test_db():
|
|
||||||
try:
|
|
||||||
yield db_session
|
|
||||||
finally:
|
|
||||||
pass
|
|
||||||
|
|
||||||
app.dependency_overrides[get_db] = _get_test_db
|
|
||||||
with TestClient(app) as client:
|
|
||||||
yield client
|
|
||||||
@@ -1,7 +1,15 @@
|
|||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
import pytest
|
||||||
|
from src.main import app
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="client")
|
||||||
|
def client_fixture():
|
||||||
|
client = TestClient(app)
|
||||||
|
yield client
|
||||||
|
|
||||||
|
|
||||||
def test_get_artists(client: TestClient):
|
def test_get_artists(client: TestClient):
|
||||||
response = client.get("/api/comic/artists")
|
response = client.get("/comic/artists")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert len(response.json()) == 0
|
assert len(response.json()) == 5
|
||||||
|
|||||||
Generated
+240
-400
File diff suppressed because it is too large
Load Diff
@@ -1,81 +0,0 @@
|
|||||||
"""
|
|
||||||
read file with URLs and store in DB
|
|
||||||
"""
|
|
||||||
import logging.config
|
|
||||||
import requests
|
|
||||||
import yaml
|
|
||||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
|
||||||
from pathlib import Path
|
|
||||||
from platformdirs import PlatformDirs
|
|
||||||
from proton import Message, Event
|
|
||||||
from proton.handlers import MessagingHandler
|
|
||||||
from proton.reactor import Container
|
|
||||||
|
|
||||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
|
||||||
parser.add_argument('-u', '--url', help='link')
|
|
||||||
parser.add_argument('--video', help='store Url as VideoFile', action="store_true")
|
|
||||||
parser.add_argument("--api", help="use Kontor API", action="store_true")
|
|
||||||
parser.add_argument('--config', '-c', default='kontor-docker')
|
|
||||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
|
||||||
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
|
|
||||||
|
|
||||||
class AddLinkMessage(MessagingHandler):
|
|
||||||
def __init__(self, server, url, log):
|
|
||||||
super(AddLinkMessage, self).__init__()
|
|
||||||
log.info("create AddLinkMessage")
|
|
||||||
self.server = server
|
|
||||||
self.address = "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_error(self, event: Event) -> None:
|
|
||||||
self.log.info(f"error: {event}")
|
|
||||||
|
|
||||||
def on_sendable(self, event: Event):
|
|
||||||
self.log.info("send message")
|
|
||||||
event.sender.send(Message(body=self.url, address=self.address, content_type="text/json"))
|
|
||||||
event.connection.close()
|
|
||||||
event.sender.close()
|
|
||||||
|
|
||||||
def on_accepted(self, event: Event) -> None:
|
|
||||||
self.log.info(f"accepted: {event}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
logger = get_logger(args.verbose, args.config)
|
|
||||||
logger.info('kontor.add_link started')
|
|
||||||
link: str = args.url
|
|
||||||
data = {"url": link}
|
|
||||||
if args.api:
|
|
||||||
if args.video:
|
|
||||||
request: str = "http://127.0.0.1:8800/api/video/files"
|
|
||||||
else:
|
|
||||||
request: str = "http://127.0.0.1:8800/api/media/files"
|
|
||||||
response = requests.post(request, json=data)
|
|
||||||
logger.info(f"Status: {response.status_code}")
|
|
||||||
data = response.json()
|
|
||||||
else:
|
|
||||||
Container(AddLinkMessage("amqp://127.0.0.1:5672", data, logger)).run()
|
|
||||||
logger.info('kontor.add_link finished')
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
3.13
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
## ------------------------------- Builder Stage ------------------------------ ##
|
|
||||||
FROM python:3.13-bookworm AS builder
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install --no-install-recommends -y build-essential && \
|
|
||||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Download the latest installer, install it and then remove it
|
|
||||||
ADD https://astral.sh/uv/install.sh /install.sh
|
|
||||||
RUN chmod -R 655 /install.sh && /install.sh && rm /install.sh
|
|
||||||
|
|
||||||
# Set up the UV environment path correctly
|
|
||||||
ENV PATH="/root/.local/bin:${PATH}"
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY ./pyproject.toml .
|
|
||||||
|
|
||||||
RUN uv sync
|
|
||||||
|
|
||||||
# ------------------------------- Production Stage ------------------------------ ##
|
|
||||||
FROM python:3.13-slim-bookworm AS production
|
|
||||||
|
|
||||||
# The following secrets are available during build time
|
|
||||||
#RUN --mount=type=secret,id=DB_PASSWORD \
|
|
||||||
# --mount=type=secret,id=DB_USER \
|
|
||||||
# --mount=type=secret,id=DB_NAME \
|
|
||||||
# --mount=type=secret,id=DB_HOST \
|
|
||||||
# --mount=type=secret,id=DB_PORT \
|
|
||||||
# DB_PASSWORD=/run/secrets/DB_PASSWORD \
|
|
||||||
# DB_USER=$(cat /run/secrets/DB_USER) \
|
|
||||||
# DB_NAME=$(cat /run/secrets/DB_NAME) \
|
|
||||||
# DB_HOST=$(cat /run/secrets/DB_HOST) \
|
|
||||||
# DB_PORT=$(cat /run/secrets/DB_PORT)
|
|
||||||
|
|
||||||
#RUN --mount=type=secret,id=secret-key,target=secrets.json
|
|
||||||
|
|
||||||
RUN useradd --create-home appuser
|
|
||||||
USER appuser
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY /src src
|
|
||||||
COPY --from=builder /app/.venv .venv
|
|
||||||
|
|
||||||
# Set up environment variables for production
|
|
||||||
ENV PATH="/app/.venv/bin:$PATH"
|
|
||||||
|
|
||||||
# Start the application with Uvicorn in production mode, using environment variable references
|
|
||||||
CMD ["python", "src/main.py", "--log-level", "info"]
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
[project]
|
|
||||||
name = "kontor-domain"
|
|
||||||
version = "0.2.0"
|
|
||||||
description = "Example setup of DDD"
|
|
||||||
readme = "README.md"
|
|
||||||
authors = [
|
|
||||||
{name = "Thomas Peetz", email = "thomas.peetz@thpeetz.de"}
|
|
||||||
]
|
|
||||||
maintainers = [
|
|
||||||
{name = "Thomas Peetz", email = "thomas.peetz@thpeetz.de"}
|
|
||||||
]
|
|
||||||
requires-python = ">=3.13"
|
|
||||||
dependencies = [
|
|
||||||
"python-qpid-proton>=0.40.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
def main():
|
|
||||||
print("Hello from kontor-domain!")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
Generated
-57
@@ -1,57 +0,0 @@
|
|||||||
version = 1
|
|
||||||
revision = 2
|
|
||||||
requires-python = ">=3.13"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cffi"
|
|
||||||
version = "1.17.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "pycparser" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "kontor-domain"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = { virtual = "." }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "python-qpid-proton" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata]
|
|
||||||
requires-dist = [{ name = "python-qpid-proton", specifier = ">=0.40.0" }]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pycparser"
|
|
||||||
version = "2.22"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "python-qpid-proton"
|
|
||||||
version = "0.40.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "cffi" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d5/dd/e9e5066009517bdfee92374264a2b6794fa0987bfeddcbf4d2a08dccaf36/python_qpid_proton-0.40.0.tar.gz", hash = "sha256:7680d607cf6e9684f97bf5b2ba16cda7d8512aab9e4ff78f98d44a4644fc819a", size = 354215, upload-time = "2025-05-19T18:45:37.932Z" }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7b/dd/a82c1e377f08d62d83898c1aa9b39aef890e910f683fca6dc5242a123f6b/python_qpid_proton-0.40.0-cp313-cp313-win_amd64.whl", hash = "sha256:a19d8c71c908700ceb38f6cbc1eb4a039428570f96bfc2caeeafdfec804fb94f", size = 277376, upload-time = "2025-05-19T19:39:31.201Z" },
|
|
||||||
]
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "kontor-schema"
|
name = "kontor-schema"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
description = "Kontor Schema Library"
|
description = "Kontor Schema Library"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
|
|||||||
Generated
+59
-59
@@ -1,5 +1,5 @@
|
|||||||
version = 1
|
version = 1
|
||||||
revision = 2
|
revision = 1
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -10,79 +10,79 @@ dependencies = [
|
|||||||
{ name = "soupsieve" },
|
{ name = "soupsieve" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload_time = "2025-04-15T17:05:13.836Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload_time = "2025-04-15T17:05:12.221Z" },
|
{ url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2025.1.31"
|
version = "2025.1.31"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload_time = "2025-01-31T02:16:47.166Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload_time = "2025-01-31T02:16:45.015Z" },
|
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "charset-normalizer"
|
name = "charset-normalizer"
|
||||||
version = "3.4.1"
|
version = "3.4.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload_time = "2024-12-24T18:12:35.43Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698, upload_time = "2024-12-24T18:11:05.834Z" },
|
{ url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162, upload_time = "2024-12-24T18:11:07.064Z" },
|
{ url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263, upload_time = "2024-12-24T18:11:08.374Z" },
|
{ url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966, upload_time = "2024-12-24T18:11:09.831Z" },
|
{ url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992, upload_time = "2024-12-24T18:11:12.03Z" },
|
{ url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162, upload_time = "2024-12-24T18:11:13.372Z" },
|
{ url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972, upload_time = "2024-12-24T18:11:14.628Z" },
|
{ url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095, upload_time = "2024-12-24T18:11:17.672Z" },
|
{ url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668, upload_time = "2024-12-24T18:11:18.989Z" },
|
{ url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073, upload_time = "2024-12-24T18:11:21.507Z" },
|
{ url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload_time = "2024-12-24T18:11:22.774Z" },
|
{ url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload_time = "2024-12-24T18:11:24.139Z" },
|
{ url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload_time = "2024-12-24T18:11:26.535Z" },
|
{ url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload_time = "2024-12-24T18:12:32.852Z" },
|
{ url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "greenlet"
|
name = "greenlet"
|
||||||
version = "3.2.0"
|
version = "3.2.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/9c/666d8c71b18d0189cf801c0e0b31c4bfc609ac823883286045b1f3ae8994/greenlet-3.2.0.tar.gz", hash = "sha256:1d2d43bd711a43db8d9b9187500e6432ddb4fafe112d082ffabca8660a9e01a7", size = 183685, upload_time = "2025-04-15T16:21:26.141Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/b0/9c/666d8c71b18d0189cf801c0e0b31c4bfc609ac823883286045b1f3ae8994/greenlet-3.2.0.tar.gz", hash = "sha256:1d2d43bd711a43db8d9b9187500e6432ddb4fafe112d082ffabca8660a9e01a7", size = 183685 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/c9/43/c0b655d4d7eae19282b028bcec449e5c80626ad0d8d0ca3703f9b1c29258/greenlet-3.2.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:b86a3ccc865ae601f446af042707b749eebc297928ea7bd0c5f60c56525850be", size = 269131, upload_time = "2025-04-15T16:19:19.469Z" },
|
{ url = "https://files.pythonhosted.org/packages/c9/43/c0b655d4d7eae19282b028bcec449e5c80626ad0d8d0ca3703f9b1c29258/greenlet-3.2.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:b86a3ccc865ae601f446af042707b749eebc297928ea7bd0c5f60c56525850be", size = 269131 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7c/7d/c8f51c373c7f7ac0f73d04a6fd77ab34f6f643cb41a0d186d05ba96708e7/greenlet-3.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:144283ad88ed77f3ebd74710dd419b55dd15d18704b0ae05935766a93f5671c5", size = 637323, upload_time = "2025-04-15T16:49:02.677Z" },
|
{ url = "https://files.pythonhosted.org/packages/7c/7d/c8f51c373c7f7ac0f73d04a6fd77ab34f6f643cb41a0d186d05ba96708e7/greenlet-3.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:144283ad88ed77f3ebd74710dd419b55dd15d18704b0ae05935766a93f5671c5", size = 637323 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/65/c3ee41b2e56586737d6e124b250583695628ffa6b324855b3a1267a8d1d9/greenlet-3.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5be69cd50994b8465c3ad1467f9e63001f76e53a89440ad4440d1b6d52591280", size = 651430, upload_time = "2025-04-15T16:50:43.445Z" },
|
{ url = "https://files.pythonhosted.org/packages/89/65/c3ee41b2e56586737d6e124b250583695628ffa6b324855b3a1267a8d1d9/greenlet-3.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5be69cd50994b8465c3ad1467f9e63001f76e53a89440ad4440d1b6d52591280", size = 651430 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f0/07/33bd7a3dcde1db7259371d026ce76be1eb653d2d892334fc79a500b3c5ee/greenlet-3.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:47aeadd1e8fbdef8fdceb8fb4edc0cbb398a57568d56fd68f2bc00d0d809e6b6", size = 645798, upload_time = "2025-04-15T16:55:03.795Z" },
|
{ url = "https://files.pythonhosted.org/packages/f0/07/33bd7a3dcde1db7259371d026ce76be1eb653d2d892334fc79a500b3c5ee/greenlet-3.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:47aeadd1e8fbdef8fdceb8fb4edc0cbb398a57568d56fd68f2bc00d0d809e6b6", size = 645798 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/35/5b/33c221a6a867030b0b770513a1b78f6c30e04294131dafdc8da78906bbe6/greenlet-3.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18adc14ab154ca6e53eecc9dc50ff17aeb7ba70b7e14779b26e16d71efa90038", size = 648271, upload_time = "2025-04-15T16:22:42.458Z" },
|
{ url = "https://files.pythonhosted.org/packages/35/5b/33c221a6a867030b0b770513a1b78f6c30e04294131dafdc8da78906bbe6/greenlet-3.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18adc14ab154ca6e53eecc9dc50ff17aeb7ba70b7e14779b26e16d71efa90038", size = 648271 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4d/dd/d6452248fa6093504e3b7525dc2bdc4e55a4296ec6ee74ba241a51d852e2/greenlet-3.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8622b33d8694ec373ad55050c3d4e49818132b44852158442e1931bb02af336", size = 606779, upload_time = "2025-04-15T16:22:41.417Z" },
|
{ url = "https://files.pythonhosted.org/packages/4d/dd/d6452248fa6093504e3b7525dc2bdc4e55a4296ec6ee74ba241a51d852e2/greenlet-3.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8622b33d8694ec373ad55050c3d4e49818132b44852158442e1931bb02af336", size = 606779 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9d/24/160f04d2589bcb15b8661dcd1763437b22e01643626899a4139bf98f02af/greenlet-3.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:e8ac9a2c20fbff3d0b853e9ef705cdedb70d9276af977d1ec1cde86a87a4c821", size = 1117968, upload_time = "2025-04-15T16:52:53.627Z" },
|
{ url = "https://files.pythonhosted.org/packages/9d/24/160f04d2589bcb15b8661dcd1763437b22e01643626899a4139bf98f02af/greenlet-3.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:e8ac9a2c20fbff3d0b853e9ef705cdedb70d9276af977d1ec1cde86a87a4c821", size = 1117968 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6c/ff/c6e3f3a5168fef5209cfd9498b2b5dd77a0bf29dfc686a03dcc614cf4432/greenlet-3.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:cd37273dc7ca1d5da149b58c8b3ce0711181672ba1b09969663905a765affe21", size = 1145510, upload_time = "2025-04-15T16:23:01.873Z" },
|
{ url = "https://files.pythonhosted.org/packages/6c/ff/c6e3f3a5168fef5209cfd9498b2b5dd77a0bf29dfc686a03dcc614cf4432/greenlet-3.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:cd37273dc7ca1d5da149b58c8b3ce0711181672ba1b09969663905a765affe21", size = 1145510 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/dc/62/5215e374819052e542b5bde06bd7d4a171454b6938c96a2384f21cb94279/greenlet-3.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8a8940a8d301828acd8b9f3f85db23069a692ff2933358861b19936e29946b95", size = 296004, upload_time = "2025-04-15T16:55:46.007Z" },
|
{ url = "https://files.pythonhosted.org/packages/dc/62/5215e374819052e542b5bde06bd7d4a171454b6938c96a2384f21cb94279/greenlet-3.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8a8940a8d301828acd8b9f3f85db23069a692ff2933358861b19936e29946b95", size = 296004 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/62/6d/dc9c909cba5cbf4b0833fce69912927a8ca74791c23c47b9fd4f28092108/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee59db626760f1ca8da697a086454210d36a19f7abecc9922a2374c04b47735b", size = 629900, upload_time = "2025-04-15T16:49:04.099Z" },
|
{ url = "https://files.pythonhosted.org/packages/62/6d/dc9c909cba5cbf4b0833fce69912927a8ca74791c23c47b9fd4f28092108/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee59db626760f1ca8da697a086454210d36a19f7abecc9922a2374c04b47735b", size = 629900 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/5e/a9/f3f304fbbbd604858ff3df303d7fa1d8f7f9e45a6ef74481aaf03aaac021/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7154b13ef87a8b62fc05419f12d75532d7783586ad016c57b5de8a1c6feeb517", size = 635270, upload_time = "2025-04-15T16:50:44.769Z" },
|
{ url = "https://files.pythonhosted.org/packages/5e/a9/f3f304fbbbd604858ff3df303d7fa1d8f7f9e45a6ef74481aaf03aaac021/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7154b13ef87a8b62fc05419f12d75532d7783586ad016c57b5de8a1c6feeb517", size = 635270 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/34/92/4b7b4e2e23ecc723cceef9fe3898e78c8e14e106cc7ba2f276a66161da3e/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:199453d64b02d0c9d139e36d29681efd0e407ed8e2c0bf89d88878d6a787c28f", size = 632534, upload_time = "2025-04-15T16:55:05.203Z" },
|
{ url = "https://files.pythonhosted.org/packages/34/92/4b7b4e2e23ecc723cceef9fe3898e78c8e14e106cc7ba2f276a66161da3e/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:199453d64b02d0c9d139e36d29681efd0e407ed8e2c0bf89d88878d6a787c28f", size = 632534 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/da/7f/91f0ecbe72c9d789fb7f400b39da9d1e87fcc2cf8746a9636479ba79ab01/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0010e928e1901d36625f21d008618273f9dda26b516dbdecf873937d39c9dff0", size = 628826, upload_time = "2025-04-15T16:22:44.545Z" },
|
{ url = "https://files.pythonhosted.org/packages/da/7f/91f0ecbe72c9d789fb7f400b39da9d1e87fcc2cf8746a9636479ba79ab01/greenlet-3.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0010e928e1901d36625f21d008618273f9dda26b516dbdecf873937d39c9dff0", size = 628826 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9f/59/e449a44ce52b13751f55376d85adc155dd311608f6d2aa5b6bd2c8d15486/greenlet-3.2.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6005f7a86de836a1dc4b8d824a2339cdd5a1ca7cb1af55ea92575401f9952f4c", size = 593697, upload_time = "2025-04-15T16:22:43.796Z" },
|
{ url = "https://files.pythonhosted.org/packages/9f/59/e449a44ce52b13751f55376d85adc155dd311608f6d2aa5b6bd2c8d15486/greenlet-3.2.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6005f7a86de836a1dc4b8d824a2339cdd5a1ca7cb1af55ea92575401f9952f4c", size = 593697 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/bb/09/cca3392927c5c990b7a8ede64ccd0712808438d6490d63ce6b8704d6df5f/greenlet-3.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:17fd241c0d50bacb7ce8ff77a30f94a2d0ca69434ba2e0187cf95a5414aeb7e1", size = 1105762, upload_time = "2025-04-15T16:52:55.245Z" },
|
{ url = "https://files.pythonhosted.org/packages/bb/09/cca3392927c5c990b7a8ede64ccd0712808438d6490d63ce6b8704d6df5f/greenlet-3.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:17fd241c0d50bacb7ce8ff77a30f94a2d0ca69434ba2e0187cf95a5414aeb7e1", size = 1105762 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4d/b9/3d201f819afc3b7a8cd7ebe645f1a17799603e2d62c968154518f79f4881/greenlet-3.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:7b17a26abc6a1890bf77d5d6b71c0999705386b00060d15c10b8182679ff2790", size = 1125173, upload_time = "2025-04-15T16:23:03.009Z" },
|
{ url = "https://files.pythonhosted.org/packages/4d/b9/3d201f819afc3b7a8cd7ebe645f1a17799603e2d62c968154518f79f4881/greenlet-3.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:7b17a26abc6a1890bf77d5d6b71c0999705386b00060d15c10b8182679ff2790", size = 1125173 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/7b/773a30602234597fc2882091f8e1d1a38ea0b4419d99ca7ed82c827e2c3a/greenlet-3.2.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:397b6bbda06f8fe895893d96218cd6f6d855a6701dc45012ebe12262423cec8b", size = 269908, upload_time = "2025-04-15T16:20:33.58Z" },
|
{ url = "https://files.pythonhosted.org/packages/80/7b/773a30602234597fc2882091f8e1d1a38ea0b4419d99ca7ed82c827e2c3a/greenlet-3.2.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:397b6bbda06f8fe895893d96218cd6f6d855a6701dc45012ebe12262423cec8b", size = 269908 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.10"
|
version = "3.10"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload_time = "2024-09-15T18:07:39.745Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload_time = "2024-09-15T18:07:37.964Z" },
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kontor-schema"
|
name = "kontor-schema"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "beautifulsoup4" },
|
{ name = "beautifulsoup4" },
|
||||||
@@ -107,18 +107,18 @@ dependencies = [
|
|||||||
{ name = "idna" },
|
{ name = "idna" },
|
||||||
{ name = "urllib3" },
|
{ name = "urllib3" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload_time = "2024-05-29T15:37:49.536Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload_time = "2024-05-29T15:37:47.027Z" },
|
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "soupsieve"
|
name = "soupsieve"
|
||||||
version = "2.7"
|
version = "2.7"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload_time = "2025-04-20T18:50:08.518Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload_time = "2025-04-20T18:50:07.196Z" },
|
{ url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -129,33 +129,33 @@ dependencies = [
|
|||||||
{ name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" },
|
{ name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/68/c3/3f2bfa5e4dcd9938405fe2fab5b6ab94a9248a4f9536ea2fd497da20525f/sqlalchemy-2.0.40.tar.gz", hash = "sha256:d827099289c64589418ebbcaead0145cd19f4e3e8a93919a0100247af245fa00", size = 9664299, upload_time = "2025-03-27T17:52:31.876Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/68/c3/3f2bfa5e4dcd9938405fe2fab5b6ab94a9248a4f9536ea2fd497da20525f/sqlalchemy-2.0.40.tar.gz", hash = "sha256:d827099289c64589418ebbcaead0145cd19f4e3e8a93919a0100247af245fa00", size = 9664299 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/8c/18/4e3a86cc0232377bc48c373a9ba6a1b3fb79ba32dbb4eda0b357f5a2c59d/sqlalchemy-2.0.40-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:915866fd50dd868fdcc18d61d8258db1bf9ed7fbd6dfec960ba43365952f3b01", size = 2107887, upload_time = "2025-03-27T18:40:05.461Z" },
|
{ url = "https://files.pythonhosted.org/packages/8c/18/4e3a86cc0232377bc48c373a9ba6a1b3fb79ba32dbb4eda0b357f5a2c59d/sqlalchemy-2.0.40-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:915866fd50dd868fdcc18d61d8258db1bf9ed7fbd6dfec960ba43365952f3b01", size = 2107887 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/cb/60/9fa692b1d2ffc4cbd5f47753731fd332afed30137115d862d6e9a1e962c7/sqlalchemy-2.0.40-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a4c5a2905a9ccdc67a8963e24abd2f7afcd4348829412483695c59e0af9a705", size = 2098367, upload_time = "2025-03-27T18:40:07.182Z" },
|
{ url = "https://files.pythonhosted.org/packages/cb/60/9fa692b1d2ffc4cbd5f47753731fd332afed30137115d862d6e9a1e962c7/sqlalchemy-2.0.40-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a4c5a2905a9ccdc67a8963e24abd2f7afcd4348829412483695c59e0af9a705", size = 2098367 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4c/9f/84b78357ca641714a439eb3fbbddb17297dacfa05d951dbf24f28d7b5c08/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55028d7a3ebdf7ace492fab9895cbc5270153f75442a0472d8516e03159ab364", size = 3184806, upload_time = "2025-03-27T18:51:29.356Z" },
|
{ url = "https://files.pythonhosted.org/packages/4c/9f/84b78357ca641714a439eb3fbbddb17297dacfa05d951dbf24f28d7b5c08/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55028d7a3ebdf7ace492fab9895cbc5270153f75442a0472d8516e03159ab364", size = 3184806 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4b/7d/e06164161b6bfce04c01bfa01518a20cccbd4100d5c951e5a7422189191a/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cfedff6878b0e0d1d0a50666a817ecd85051d12d56b43d9d425455e608b5ba0", size = 3198131, upload_time = "2025-03-27T18:50:31.616Z" },
|
{ url = "https://files.pythonhosted.org/packages/4b/7d/e06164161b6bfce04c01bfa01518a20cccbd4100d5c951e5a7422189191a/sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cfedff6878b0e0d1d0a50666a817ecd85051d12d56b43d9d425455e608b5ba0", size = 3198131 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6d/51/354af20da42d7ec7b5c9de99edafbb7663a1d75686d1999ceb2c15811302/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bb19e30fdae77d357ce92192a3504579abe48a66877f476880238a962e5b96db", size = 3131364, upload_time = "2025-03-27T18:51:31.336Z" },
|
{ url = "https://files.pythonhosted.org/packages/6d/51/354af20da42d7ec7b5c9de99edafbb7663a1d75686d1999ceb2c15811302/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bb19e30fdae77d357ce92192a3504579abe48a66877f476880238a962e5b96db", size = 3131364 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/7a/2f/48a41ff4e6e10549d83fcc551ab85c268bde7c03cf77afb36303c6594d11/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:16d325ea898f74b26ffcd1cf8c593b0beed8714f0317df2bed0d8d1de05a8f26", size = 3159482, upload_time = "2025-03-27T18:50:33.201Z" },
|
{ url = "https://files.pythonhosted.org/packages/7a/2f/48a41ff4e6e10549d83fcc551ab85c268bde7c03cf77afb36303c6594d11/sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:16d325ea898f74b26ffcd1cf8c593b0beed8714f0317df2bed0d8d1de05a8f26", size = 3159482 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/33/ac/e5e0a807163652a35be878c0ad5cfd8b1d29605edcadfb5df3c512cdf9f3/sqlalchemy-2.0.40-cp313-cp313-win32.whl", hash = "sha256:a669cbe5be3c63f75bcbee0b266779706f1a54bcb1000f302685b87d1b8c1500", size = 2080704, upload_time = "2025-03-27T18:46:00.193Z" },
|
{ url = "https://files.pythonhosted.org/packages/33/ac/e5e0a807163652a35be878c0ad5cfd8b1d29605edcadfb5df3c512cdf9f3/sqlalchemy-2.0.40-cp313-cp313-win32.whl", hash = "sha256:a669cbe5be3c63f75bcbee0b266779706f1a54bcb1000f302685b87d1b8c1500", size = 2080704 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1c/cb/f38c61f7f2fd4d10494c1c135ff6a6ddb63508d0b47bccccd93670637309/sqlalchemy-2.0.40-cp313-cp313-win_amd64.whl", hash = "sha256:641ee2e0834812d657862f3a7de95e0048bdcb6c55496f39c6fa3d435f6ac6ad", size = 2104564, upload_time = "2025-03-27T18:46:01.442Z" },
|
{ url = "https://files.pythonhosted.org/packages/1c/cb/f38c61f7f2fd4d10494c1c135ff6a6ddb63508d0b47bccccd93670637309/sqlalchemy-2.0.40-cp313-cp313-win_amd64.whl", hash = "sha256:641ee2e0834812d657862f3a7de95e0048bdcb6c55496f39c6fa3d435f6ac6ad", size = 2104564 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/7c/5fc8e802e7506fe8b55a03a2e1dab156eae205c91bee46305755e086d2e2/sqlalchemy-2.0.40-py3-none-any.whl", hash = "sha256:32587e2e1e359276957e6fe5dad089758bc042a971a8a09ae8ecf7a8fe23d07a", size = 1903894, upload_time = "2025-03-27T18:40:43.796Z" },
|
{ url = "https://files.pythonhosted.org/packages/d1/7c/5fc8e802e7506fe8b55a03a2e1dab156eae205c91bee46305755e086d2e2/sqlalchemy-2.0.40-py3-none-any.whl", hash = "sha256:32587e2e1e359276957e6fe5dad089758bc042a971a8a09ae8ecf7a8fe23d07a", size = 1903894 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.13.2"
|
version = "4.13.2"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload_time = "2025-04-10T14:19:05.416Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload_time = "2025-04-10T14:19:03.967Z" },
|
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "urllib3"
|
name = "urllib3"
|
||||||
version = "2.4.0"
|
version = "2.4.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload_time = "2025-04-10T15:23:39.232Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload_time = "2025-04-10T15:23:37.377Z" },
|
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
"""
|
|
||||||
read file with URLs and store in DB
|
|
||||||
"""
|
|
||||||
import logging.config
|
|
||||||
import requests
|
|
||||||
import yaml
|
|
||||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
|
||||||
from pathlib import Path
|
|
||||||
from platformdirs import PlatformDirs
|
|
||||||
from proton import Message, Event
|
|
||||||
from proton.handlers import MessagingHandler
|
|
||||||
from proton.reactor import Container
|
|
||||||
|
|
||||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
|
||||||
parser.add_argument('-u', '--url', help='link')
|
|
||||||
parser.add_argument('--video', help='store Url as VideoFile', action="store_true")
|
|
||||||
parser.add_argument("--api", help="use Kontor API", action="store_true")
|
|
||||||
parser.add_argument('--config', '-c', default='kontor-docker')
|
|
||||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
|
||||||
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
|
|
||||||
|
|
||||||
class AddLinkMessage(MessagingHandler):
|
|
||||||
def __init__(self, server, url, log):
|
|
||||||
super(AddLinkMessage, self).__init__()
|
|
||||||
log.info("create AddLinkMessage")
|
|
||||||
self.server = server
|
|
||||||
self.address = "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_error(self, event: Event) -> None:
|
|
||||||
self.log.info(f"error: {event}")
|
|
||||||
|
|
||||||
def on_sendable(self, event: Event):
|
|
||||||
self.log.info("send message")
|
|
||||||
event.sender.send(Message(body=self.url, address=self.address, content_type="text/json"))
|
|
||||||
event.connection.close()
|
|
||||||
event.sender.close()
|
|
||||||
|
|
||||||
def on_accepted(self, event: Event) -> None:
|
|
||||||
self.log.info(f"accepted: {event}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
logger = get_logger(args.verbose, args.config)
|
|
||||||
logger.info('kontor.add_link started')
|
|
||||||
link: str = args.url
|
|
||||||
data = {"url": link}
|
|
||||||
if args.api:
|
|
||||||
if args.video:
|
|
||||||
request: str = "http://127.0.0.1:8800/api/video/files"
|
|
||||||
else:
|
|
||||||
request: str = "http://127.0.0.1:8800/api/media/files"
|
|
||||||
response = requests.post(request, json=data)
|
|
||||||
logger.info(f"Status: {response.status_code}")
|
|
||||||
data = response.json()
|
|
||||||
else:
|
|
||||||
Container(AddLinkMessage("amqp://127.0.0.1:5672", data, logger)).run()
|
|
||||||
logger.info('kontor.add_link finished')
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user