2 Commits

Author SHA1 Message Date
tpeetz 7eae6e07f4 Merge branch 'develop/0.2.0' into 'main'
Vorbereitung Release 0.2.0

See merge request tpeetz/kontor!39
2026-01-29 23:50:41 +01:00
tpeetz b26b5ecc9c Vorbereitung Release 0.2.0 2026-01-29 23:50:41 +01:00
422 changed files with 16146 additions and 20536 deletions
-19
View File
@@ -1,19 +0,0 @@
name: Gitea Actions Demo
run-name: ${{ gitea.actor }} ist testing out Gitea Actions
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: inky
steps:
- run: echo "The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "The workflow is now ready to test your code on the runner."
- name: List files in the directory
run: |
ls ${{ gitea.workspace }}
- run: echo "The job's status is ${{ job.status }}."
+3 -15
View File
@@ -2,19 +2,7 @@
.idea/ .idea/
.angular/ .angular/
__pycache__/ __pycache__/
bonus/ node_modules/
icons/ .editorconfig
icons-shadowless/
springboot/.factorypath
java-ee/.settings
java-ee/.project
kontor-schema/env
kontor-schema/kontor_schema.egg-info
kontor-gui/.pdm-python
kontor-gui/dist
fastapi/.coverage
kontor-api/.coverage
db-password.txt db-password.txt
kontor-api/tests/test_main.py couchdb-password.txt
kontor-api/tests/test_db.db
kontor-api/test_db.db
+6 -4
View File
@@ -1,10 +1,12 @@
kontor_api := kontor-api kontor_api := kontor-api
kontor_spring := kontor-spring kontor_spring := kontor-spring
kontor_servicemix := kontor-servicemix kontor_angular := kontor-angular
TARGET=docker
.PHONY: all $(kontor_spring) $(kontor_api) .PHONY: all $(kontor_spring) $(kontor_api) $(kontor_angular)
all: $(kontor_spring) $(kontor_api) $(kontor_servicemix)
$(kontor_spring) $(kontor_api) $(kontor_servicemix): all: $(kontor_spring) $(kontor_api) $(kontor_angular)
$(kontor_spring) $(kontor_api) $(kontor_angular):
$(MAKE) --directory=$@ $(TARGET) $(MAKE) --directory=$@ $(TARGET)
+38
View File
@@ -0,0 +1,38 @@
services:
postgres:
image: postgres
restart: unless-stopped
environment:
- POSTGRES_DB=kontor
- POSTGRES_USER=kontor
#- POSTGRES_PASSWORD_FILE=/run/secrets/db-password
- POSTGRES_PASSWORD=kontor
healthcheck:
test: ["CMD-SHELL", "pg_isready -U kontor"]
interval: 1s
timeout: 5s
retries: 10
ports:
- 5432:5432
networks:
- database
volumes:
- postgres-data:/var/lib/postgresql/data:rw
secrets:
- db-password
adminer:
image: adminer
ports:
- 8090:8080
networks:
- database
- frontend
volumes:
postgres-data:
secrets:
db-password:
file: db-password.txt
networks:
database:
name: database
external: true
+77 -31
View File
@@ -1,35 +1,25 @@
include:
- ./compose-postgres.yaml
services: services:
postgres: activemq:
image: postgres image: apache/activemq-artemis:latest-alpine
restart: unless-stopped restart: unless-stopped
environment:
- POSTGRES_DB=kontor
- POSTGRES_USER=kontor
#- POSTGRES_PASSWORD_FILE=/run/secrets/db-password
- POSTGRES_PASSWORD=kontor
healthcheck:
test: ["CMD-SHELL", "pg_isready -U kontor"]
interval: 1s
timeout: 5s
retries: 10
ports: ports:
- 5432:5432 - 61616:61616
- 8161:8161
- 5672:5672
networks: networks:
- integration - integration
- frontend - frontend
volumes: volumes:
- postgres-data:/var/lib/postgresql/data:rw - activemq-data:/var/lib/artemis-instance
secrets:
- db-password
adminer:
image: adminer
ports:
- 8090:8080
networks:
- database
- frontend
kontor: kontor:
build:
context: ./kontor-spring
dockerfile: Dockerfile
tags:
- kontor:0.2.0-SNAPSHOT
image: kontor:0.2.0-SNAPSHOT image: kontor:0.2.0-SNAPSHOT
restart: unless-stopped restart: unless-stopped
networks: networks:
@@ -37,13 +27,18 @@ services:
- integration - integration
- frontend - frontend
ports: ports:
- 8000:8000 - 8100:8100
volumes: volumes:
- images-data:/data/images - images-data:/data/images
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
kontor-api: kontor-api:
build:
context: ./kontor-api
dockerfile: Dockerfile
tags:
- kontor-api:0.2.0-SNAPSHOT
image: kontor-api:0.2.0-SNAPSHOT image: kontor-api:0.2.0-SNAPSHOT
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
@@ -125,19 +120,70 @@ services:
- frontend - frontend
ports: ports:
- 8800:8800 - 8800:8800
volumes:
- images-data:/data/images
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
kontor-angular:
build:
context: ./kontor-angular
dockerfile: Dockerfile
tags:
- kontor-angular:0.2.0-SNAPSHOT
image: kontor-angular:0.2.0-SNAPSHOT
restart: unless-stopped
networks:
- database
- integration
- frontend
ports:
- 8200:80
depends_on:
kontor-api:
condition: service_healthy
kontor-vue:
build:
context: ./kontor-vue
dockerfile: Dockerfile
tags:
- kontor-vue:0.2.0-SNAPSHOT
image: kontor-vue:0.2.0-SNAPSHOT
restart: unless-stopped
networks:
- database
- integration
- frontend
ports:
- 8300:80
depends_on:
postgres:
condition: service_healthy
kontor-javalin:
build:
context: ./kontor-javalin
dockerfile: Dockerfile
tags:
- kontor-javalin:0.2.0-SNAPSHOT
image: kontor-javalin:0.2.0-SNAPSHOT
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://kontor-javalin:8400/health"]
interval: 10s
timeout: 5s
retries: 3
networks:
- database
- integration
- frontend
ports:
- 8400:8400
depends_on:
postgres:
condition: service_healthy
networks: networks:
integration: integration:
name: integration name: integration
frontend: frontend:
volumes: volumes:
postgres-data: activemq-data:
images-data: images-data:
secrets:
db-password:
file: db-password.txt
+9
View File
@@ -0,0 +1,9 @@
build/
.settings/
.gradle/
.classpath
.project
.vscode/
bin/
.idea/
/.eclipse-pmd
+38
View File
@@ -0,0 +1,38 @@
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
before_script:
- source "/home/gitlab-runner/.sdkman/bin/sdkman-init.sh"
- sdk u java 11.0.12-open
- chmod +x ./gradlew
stages:
- build
- test
- analysis
- publish
Build Application:
stage: build
script: ./gradlew --build-cache compileJava
Create Documentation:
stage: build
script: ./gradlew asciidoctorPdf
Test Application:
stage: test
script: ./gradlew check
sonarqube-check:
stage: analysis
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
script: ./gradlew sonarqube
allow_failure: true
Publish Artifacts:
stage: publish
script: ./gradlew publish
+60
View File
@@ -0,0 +1,60 @@
# kontor-quarkus Project
This project uses Quarkus, the Supersonic Subatomic Java Framework.
If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ .
## Running the application in dev mode
You can run your application in dev mode that enables live coding using:
```shell script
./gradlew quarkusDev
```
> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at http://localhost:8080/q/dev/.
## Packaging and running the application
The application can be packaged using:
```shell script
./gradlew build
```
It produces the `quarkus-run.jar` file in the `build/quarkus-app/` directory.
Be aware that its not an _über-jar_ as the dependencies are copied into the `build/quarkus-app/lib/` directory.
The application is now runnable using `java -jar build/quarkus-app/quarkus-run.jar`.
If you want to build an _über-jar_, execute the following command:
```shell script
./gradlew build -Dquarkus.package.type=uber-jar
```
The application, packaged as an _über-jar_, is now runnable using `java -jar build/*-runner.jar`.
## Creating a native executable
You can create a native executable using:
```shell script
./gradlew build -Dquarkus.package.type=native
```
Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
```shell script
./gradlew build -Dquarkus.package.type=native -Dquarkus.native.container-build=true
```
You can then execute your native executable with: `./build/kontor-quarkus-1.0.0-SNAPSHOT-runner`
If you want to learn more about building native executables, please consult https://quarkus.io/guides/gradle-tooling.
## Related Guides
- RESTEasy Reactive ([guide](https://quarkus.io/guides/resteasy-reactive)): A JAX-RS implementation utilizing build time processing and Vert.x. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it.
## Provided Code
### RESTEasy Reactive
Easily start your Reactive RESTful Web Services
[Related guide section...](https://quarkus.io/guides/getting-started-reactive#reactive-jax-rs-resources)
+158
View File
@@ -0,0 +1,158 @@
plugins {
id 'java'
id 'maven-publish'
id 'jacoco'
id 'jacoco-report-aggregation'
id 'pmd'
id 'checkstyle'
id 'jvm-test-suite'
id 'io.quarkus'
id "org.sonarqube" version "3.3"
alias(versionsLibs.plugins.asciidoctorConvert)
alias(versionsLibs.plugins.asciidoctorPdf)
alias(versionsLibs.plugins.asciidoctorGems)
}
repositories {
ruby.gems()
maven {
url "https://nexus.thpeetz.de/nexus/content/groups/public/"
}
mavenCentral()
mavenLocal()
}
dependencies {
implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
implementation 'io.quarkus:quarkus-resteasy-reactive'
implementation 'io.quarkus:quarkus-resteasy-reactive-jackson'
implementation 'io.quarkus:quarkus-arc'
testImplementation 'io.quarkus:quarkus-junit5'
testImplementation 'io.rest-assured:rest-assured'
testImplementation("io.quarkus:quarkus-jacoco")
}
final BUILD_DATE = new Date().format('dd.MM.yyyy').toString()
def pdfFile = layout.buildDirectory.file("docs/asciidocPdf/" + project.name + ".pdf")
def pdfArtifact = artifacts.add('archives', pdfFile.get().asFile) {
type 'pdf'
builtBy asciidoctorPdf
}
tasks.withType(GenerateModuleMetadata).configureEach {
suppressedValidationErrors.add('enforced-platform')
}
publishing {
publications {
quarkusApp(MavenPublication){
from components.java
}
docs(MavenPublication) {
groupId = group + '.docs'
artifactId = project.name
artifact pdfArtifact
}
}
repositories {
maven {
name = 'nexusPeetz'
def releasesRepoUrl = "https://nexus.thpeetz.de/nexus/content/repositories/releases/"
def snapshotsRepoUrl = "https://nexus.thpeetz.de/nexus/content/repositories/snapshots/"
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials(PasswordCredentials)
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
tasks.withType(GenerateModuleMetadata).configureEach {
suppressedValidationErrors.add('enforced-platform')
}
compileJava {
options.encoding = 'UTF-8'
options.compilerArgs << '-parameters'
}
compileTestJava {
options.encoding = 'UTF-8'
}
jacocoTestReport {
reports {
xml.required = true
}
}
test.finalizedBy jacocoTestReport
pmd {
//ruleSetFiles = files("custom-pmd-ruleset.xml")
ruleSets = ["category/java/errorprone.xml", "category/java/bestpractices.xml"]
ignoreFailures = true
}
tasks.withType(Checkstyle) {
reports {
xml.required = true
html.required = true
}
}
testing {
suites {
test {
useJUnitJupiter()
}
}
}
pmdTest.dependsOn("compileQuarkusTestGeneratedSourcesJava")
checkstyleTest.dependsOn("compileQuarkusTestGeneratedSourcesJava")
jacocoTestReport.dependsOn("compileQuarkusGeneratedSourcesJava")
pmdMain.dependsOn("compileQuarkusGeneratedSourcesJava")
sonarqube {
properties {
property "sonar.host.url", "https://sonar.thpeetz.de"
property "sonar.qualitygate.wait", true
property "sonar.sourceEncoding", "UTF-8"
property "sonar.projectKey", "kontor_kontor-quarkus_AYQek8Mxz0hBjLSV8I8O"
property "sonar.login", "5ecd90dee57806857e07443a9b0efd3cd7774a81"
}
}
tasks.named('sonarqube').configure {
dependsOn 'test', 'pmdMain', 'checkstyleMain', 'jacocoTestReport'
}
asciidoctorPdf {
baseDirFollowsSourceFile()
asciidoctorj {
attributes 'build-gradle': file('build.gradle'),
'endpoint-url': 'https://www.thpeetz.de',
'source-highlighter': 'rouge',
'imagesdir': './images',
'toc': 'left',
'toc-title': 'Inhaltsverzeichnis',
'revdate': BUILD_DATE,
'revnumber': { project.version.toString() },
'revremark': 'Entwurf',
'chapter-label': '',
'icons': 'font',
'idprefix': 'id_',
'idseparator': '-',
'docinfo1': ''
}
}
wrapper {
gradleVersion = "7.5"
}
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
<module name="TreeWalker">
<module name="UnusedImports" />
<module name="AvoidStarImport" />
<module name="ConstantName" />
<module name="NeedBraces" />
<module name="RightCurly" />
<module name="ModifierOrder">
<property name="severity" value="warning"/>
</module>
</module>
</module>
+9
View File
@@ -0,0 +1,9 @@
#Gradle properties
group=de.thpeetz.kontor
version=1.0.0-SNAPSHOT
quarkusPluginId=io.quarkus
quarkusPluginVersion=2.13.2.Final
quarkusPlatformGroupId=io.quarkus.platform
quarkusPlatformArtifactId=quarkus-bom
quarkusPlatformVersion=2.13.2.Final
Binary file not shown.
@@ -1,7 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
+104
View File
@@ -0,0 +1,104 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@@ -0,0 +1,84 @@
= Projektbeschreibung kontor-quarkus: Pflichtenheft und Projekthandbuch
:author: Thomas Peetz
:email: <thomas.peetz@thpeetz.de>
:doctype: article
:sectnums:
:sectnumlevels: 4
:toc:
:toclevels: 4
:table-caption!:
:counter: table-number: 0
//[title="Dokumenthistorie", caption="Tabelle {counter:table-number} ", id="Tabelle-{counter:table-number}", options="header"]
//[title="Dokumenthistorie", id="Table-{counter:table-number}", options="header", cols="4"]
[title="Dokumenthistorie", id="Table-{counter:table-number}", options="header"]
|===
| Version | Datum | Autor | Änderungsgrund / Bemerkungen
| 0.0.1 | 16.05.2022 | Thomas Peetz | Ersterstellung
|===
== Einführung
=== Zweck
=== Stakeholder des Systems
=== Systemumfang
==== Zielsetzung des Systems
=== Systemübersicht
==== Systemkontext
==== Systemarchitektur
==== Systemschnittstellen
===== Realisierte Schnittstellen
===== Verwendete Schnittstellen
==== Logisches Datenmodell
==== Einschränkungen
== Anforderungen der Domäne
=== Systemfunktionen
==== Anwendungsfälle
==== Akteure
==== Zielgruppen
=== Anforderungen
==== Anforderungen an externe Schnittstellen
==== Funktionale Anforderungen
==== Qualitätsanforderungen
==== Randbedingungen
==== Weitere Anforderungen
==== Wartungs- und Supportinformationen
=== Verifikation
[bibliography]
== Referenzen
[glossary]
== Glossar
== Verzeichnisse
=== Abbildungsverzeichnis
=== Tabellenverzeichnis
<<Table-1, Tabelle 1>> <<Table-1>>
@@ -0,0 +1,94 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./gradlew build
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/kontor-quarkus-jvm .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus-jvm
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus-jvm
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi8/openjdk-11:1.14
ENV LANGUAGE='en_US:en'
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=185 build/quarkus-app/lib/ /deployments/lib/
COPY --chown=185 build/quarkus-app/*.jar /deployments/
COPY --chown=185 build/quarkus-app/app/ /deployments/app/
COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 185
ENV AB_JOLOKIA_OFF=""
ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
@@ -0,0 +1,90 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./gradlew build -Dquarkus.package.type=legacy-jar
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/kontor-quarkus-legacy-jar .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus-legacy-jar
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus-legacy-jar
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi8/openjdk-11:1.14
ENV LANGUAGE='en_US:en'
COPY build/lib/* /deployments/lib/
COPY build/*-runner.jar /deployments/quarkus-run.jar
EXPOSE 8080
USER 185
ENV AB_JOLOKIA_OFF=""
ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
@@ -0,0 +1,27 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
#
# Before building the container image run:
#
# ./gradlew build -Dquarkus.package.type=native
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native -t quarkus/kontor-quarkus .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus
#
###
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root build/*-runner /work/application
EXPOSE 8080
USER 1001
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
@@ -0,0 +1,30 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
# It uses a micro base image, tuned for Quarkus native executables.
# It reduces the size of the resulting container image.
# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
#
# Before building the container image run:
#
# ./gradlew build -Dquarkus.package.type=native
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/kontor-quarkus .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus
#
###
FROM quay.io/quarkus/quarkus-micro-image:1.0
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root build/*-runner /work/application
EXPOSE 8080
USER 1001
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
@@ -0,0 +1,95 @@
package de.thpeetz.kontor.comics;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import de.thpeetz.kontor.comics.service.ArtistService;
import de.thpeetz.kontor.comics.service.ComicsService;
import de.thpeetz.kontor.comics.service.PublisherService;
@Path("/kontor/comics")
public class ComicsResource {
@Inject
transient ComicsService comicsService;
@Inject
transient ArtistService artistService;
@Inject
transient PublisherService publisherService;
@GET
@Produces(MediaType.TEXT_HTML)
@Path("/")
public String showComicList() {
return comicsService.showComicList();
}
@GET
@Produces(MediaType.TEXT_HTML)
@Path("/artist")
public String showArtistList() {
return artistService.showArtistList();
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/artist")
public String showArtistListJson() {
return artistService.showArtistList();
}
@GET
@Produces(MediaType.TEXT_HTML)
@Path("/artist/view/{artist_id}")
public String showArtist(String artist_id) {
return artistService.showArtist(artist_id);
}
@GET
@Produces(MediaType.TEXT_HTML)
@Path("/artist/create")
public String showArtistCreation() {
return artistService.showArtistCreation();
}
@POST
@Produces(MediaType.TEXT_HTML)
@Path("/artist/create")
public String validateArtistDetails() {
return artistService.validateArtistDetails();
}
@GET
@Produces(MediaType.TEXT_HTML)
@Path("/publisher")
public String showPublisherList() {
return publisherService.showPublisherList();
}
@GET
@Produces(MediaType.TEXT_HTML)
@Path("/publisher/view/{publisher_id}")
public String showPublisher(String publisher_id) {
return publisherService.showPublisher(publisher_id);
}
@GET
@Produces(MediaType.TEXT_HTML)
@Path("/publisher/create")
public String showPublisherCreation() {
return publisherService.showPublisherCreation();
}
@POST
@Produces(MediaType.TEXT_HTML)
@Path("/publisher/create")
public String validatePublisherDetails() {
return publisherService.validatePublisherDetails();
}
}
@@ -0,0 +1,28 @@
package de.thpeetz.kontor.comics.service;
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class ArtistService {
public String showArtistList() {
// TODO Auto-generated method stub
return null;
}
public String showArtist(String artist_id) {
// TODO Auto-generated method stub
return null;
}
public String showArtistCreation() {
// TODO Auto-generated method stub
return null;
}
public String validateArtistDetails() {
// TODO Auto-generated method stub
return null;
}
}
@@ -0,0 +1,12 @@
package de.thpeetz.kontor.comics.service;
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class ComicsService {
public String showComicList() {
return "comics";
}
}
@@ -0,0 +1,28 @@
package de.thpeetz.kontor.comics.service;
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class PublisherService {
public String showPublisherList() {
// TODO Auto-generated method stub
return null;
}
public String showPublisher(String publisher_id) {
// TODO Auto-generated method stub
return null;
}
public String showPublisherCreation() {
// TODO Auto-generated method stub
return null;
}
public String validatePublisherDetails() {
// TODO Auto-generated method stub
return null;
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
# Your configuration properties
quarkus.http.test-port=8083
quarkus.http.test-ssl-port=8446
@@ -0,0 +1,8 @@
package de.thpeetz.kontor.comics;
import io.quarkus.test.junit.QuarkusIntegrationTest;
@QuarkusIntegrationTest
public class ComicsResourceIT extends ComicsResourceTest {
}
@@ -0,0 +1,19 @@
package de.thpeetz.kontor.comics;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
class ComicsResourceTest {
@Test
void testComicsEndpoint() {
given().when().get("/kontor/comics")
.then()
.statusCode(200)
.body(is("comics"));
}
}
@@ -1,5 +1,5 @@
### STAGE 1: Build ### ### STAGE 1: Build ###
FROM docker.io/node:22-alpine AS build FROM node:22.15-alpine AS build
WORKDIR /app WORKDIR /app
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
RUN npm install RUN npm install
@@ -7,9 +7,8 @@ COPY . .
RUN npm run build RUN npm run build
### STAGE 2: Run ### ### STAGE 2: Run ###
FROM docker.io/library/nginx:stable-alpine FROM nginx:1.17.1-alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist/kontor-angular/browser /usr/share/nginx/html COPY --from=build /app/dist/kontor-angular/browser /usr/share/nginx/html
EXPOSE 80 EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
-1
View File
@@ -1,5 +1,4 @@
server { server {
listen 8800;
# Root-Verzeichnis für den Server setzen (wir kopieren unsere Anwendung hierher) # Root-Verzeichnis für den Server setzen (wir kopieren unsere Anwendung hierher)
root /usr/share/nginx/html; root /usr/share/nginx/html;
-38
View File
@@ -1,38 +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
RUN apt-get update && apt-get install --no-install-recommends -y curl
WORKDIR /app
COPY /src src
COPY --from=builder /app/.venv .venv
# Set up environment variables for production
ENV PATH="/app/.venv/bin:$PATH"
# Expose the specified port for FastAPI
EXPOSE 8200
# Start the application with Uvicorn in production mode, using environment variable references
CMD ["uvicorn", "src.main:kontor", "--log-level", "info", "--host", "0.0.0.0" , "--port", "8200"]
+4 -2
View File
@@ -1,7 +1,7 @@
## ------------------------------- 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 && \
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
@@ -34,6 +34,8 @@ FROM python:3.13-slim-bookworm AS production
#RUN --mount=type=secret,id=secret-key,target=secrets.json #RUN --mount=type=secret,id=secret-key,target=secrets.json
RUN apt-get update && apt-get install --no-install-recommends -y curl
RUN useradd --create-home appuser RUN useradd --create-home appuser
USER appuser USER appuser
@@ -49,5 +51,5 @@ ENV PATH="/app/.venv/bin:$PATH"
EXPOSE $PORT EXPOSE $PORT
# Start the application with Uvicorn in production mode, using environment variable references # Start the application with Uvicorn in production mode, using environment variable references
CMD ["uvicorn", "src.main:kontor", "--log-level", "info", "--host", "0.0.0.0" , "--port", "8800"] CMD ["uvicorn", "src.main:kontor", "--log-level", "info", "--host", "0.0.0.0" , "--port", "8500"]
+1
View File
@@ -28,4 +28,5 @@ dependencies = [
"jinja2>=3.1.6", "jinja2>=3.1.6",
"asyncpg>=0.30.0", "asyncpg>=0.30.0",
"bcrypt>=4.3.0", "bcrypt>=4.3.0",
"fastapi-jwt-auth>=0.5.0",
] ]
+9 -235
View File
@@ -1,237 +1,11 @@
""" from fastapi import APIRouter
add router for different parts (like comics, tysc, media)
"""
from fastapi import APIRouter, Depends from src.apis.version1 import comic, mediaactor, mediafile, mediaactorfile, tysc, admin
from src.apis.version1.admin import mailaccount
from src.apis.version1.comics import (
artist,
publisher,
comic,
issue,
worktype,
volume,
storyarc,
comicwork,
issuework,
)
from src.apis.version1.media import (
actor,
file,
mediaactorfile,
mediavideo,
mediaarticle,
)
from src.apis.version1.tysc import (
card,
cardset,
fieldposition,
player,
rooster,
sport,
team,
vendor,
)
from src.core.security import get_current_user_from_token
from src.apis.version1 import comic, media, tysc, admin api_router = APIRouter(prefix="/api")
api_router.include_router(comic.router, prefix="/comics", tags=["comics"])
api_router = APIRouter(prefix="/api") api_router.include_router(mediafile.router, prefix="/media", tags=["media"])
api_router.include_router( api_router.include_router(mediaactor.router, prefix="/media", tags=["media"])
comic.router, api_router.include_router(mediaactorfile.router, prefix="/media", tags=["media"])
prefix="/comics", api_router.include_router(tysc.router, prefix="/tysc", tags=["tysc"])
tags=["comics"], api_router.include_router(admin.router, prefix="/login", tags=["login"])
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
publisher.router,
prefix="/comics",
tags=["comics"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
artist.router,
prefix="/comics",
tags=["comics"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
issue.router,
prefix="/comics",
tags=["comics"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
worktype.router,
prefix="/comics",
tags=["comics"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
volume.router,
prefix="/comics",
tags=["comics"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
storyarc.router,
prefix="/comics",
tags=["comics"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
comicwork.router,
prefix="/comics",
tags=["comics"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
issuework.router,
prefix="/comics",
tags=["comics"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
file.router,
prefix="/media",
tags=["media"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
mediavideo.router,
prefix="/media",
tags=["media"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
mediaarticle.router,
prefix="/media",
tags=["media"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
actor.router,
prefix="/media",
tags=["media"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
mediaactorfile.router,
prefix="/media",
tags=["media"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
sport.router,
prefix="/tysc",
tags=["tysc"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
player.router,
prefix="/tysc",
tags=["tysc"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
team.router,
prefix="/tysc",
tags=["tysc"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
fieldposition.router,
prefix="/tysc",
tags=["tysc"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
rooster.router,
prefix="/tysc",
tags=["tysc"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
vendor.router,
prefix="/tysc",
tags=["tysc"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
cardset.router,
prefix="/tysc",
tags=["tysc"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
card.router,
prefix="/tysc",
tags=["tysc"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
article.router,
prefix="/bookshelf",
tags=["bookshelf"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
bookshelf_publisher.router,
prefix="/bookshelf",
tags=["bookshelf"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
book.router,
prefix="/bookshelf",
tags=["bookshelf"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
author.router,
prefix="/bookshelf",
tags=["bookshelf"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
articleauthor.router,
prefix="/bookshelf",
tags=["bookshelf"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
bookauthor.router,
prefix="/bookshelf",
tags=["bookshelf"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
profile.router,
prefix="/user",
tags=["user"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
token.router,
prefix="/user",
tags=["user"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
permission.router,
prefix="/user",
tags=["user"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
assignment.router,
prefix="/user",
tags=["user"],
dependencies=[Depends(get_current_user_from_token)],
)
api_router.include_router(
mailaccount.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_current_user_from_token)],
)
-48
View File
@@ -1,48 +0,0 @@
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 sqlalchemy.orm import Session
from src.db.session import 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
+25 -42
View File
@@ -1,35 +1,37 @@
import logging
from datetime import timedelta from datetime import timedelta
from typing import Annotated
import bcrypt from fastapi import APIRouter, Body, HTTPException, status, Depends, Response
from fastapi import APIRouter, HTTPException, status, Response, Depends
from fastapi.security import OAuth2PasswordRequestForm 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.config import settings
from src.core.security import create_access_token from src.core.security import create_access_token, authenticate_user, get_current_active_user
from src.db.models.admin import Profile from src.db.models.admin import Profile
from src.db.repository.admin import get_profile from src.schema.admin import Token, ProfileModel
from src.schema.admin import Token from src.webapps.auth.forms import LoginForm
router = APIRouter() router = APIRouter()
def authenticate_user(username: str, password: str, db: SessionDep) -> Profile | None: @router.post("/token")
user = get_profile(username=username, db=db) def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> Token:
print(user) user = authenticate_user(form_data.username, form_data.password)
if not user: if not user:
return None raise HTTPException(
if bcrypt.checkpw(password.encode(), user.password.encode()): status_code=status.HTTP_401_UNAUTHORIZED,
print("User successful authenticated") detail="Incorrect username or password",
else: headers={"WWW-Authenticate": "Bearer"},
logging.info("Authentication failed!") )
return user access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.email, "scope": " ".join(form_data.scopes)}, expires_delta=access_token_expires
)
return Token(access_token=access_token, token_type="bearer")
@router.post("/token", response_model=Token) # @router.post("/token-cookie", response_model=Token)
def login_for_access_token(response: Response, db: SessionDep, form_data: OAuth2PasswordRequestForm = Depends()): def login_for_token_cookie(response: Response, form_data: LoginForm = Depends()):
user = authenticate_user(form_data.username, form_data.password, db) user = authenticate_user(form_data.username, form_data.password) # type: ignore
if not user: if not user:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
@@ -45,25 +47,6 @@ def login_for_access_token(response: Response, db: SessionDep, form_data: OAuth2
return {"access_token": access_token, "token_type": "bearer"} return {"access_token": access_token, "token_type": "bearer"}
oauth2_scheme = OAuth2PasswordBearerWithCookie(tokenUrl="/api/login/token") @router.get("/profile", response_model=ProfileModel)
async def read_profile(current_user: Annotated[Profile, Depends(get_current_active_user)]):
return current_user
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,74 +0,0 @@
from datetime import timedelta
from fastapi import APIRouter, Depends, HTTPException, Response, status
from fastapi.security import OAuth2PasswordRequestForm
from src.core.config import settings
from src.core.log_conf import logger
from src.core.security import (
authenticate_user_by_email,
authenticate_user_by_username,
create_access_token,
)
from src.schema.admin.login import LoginRequest
from src.schema.admin.token import Token
from src.webapps.auth.forms import LoginForm
login_router = APIRouter()
@login_router.post(
"/login",
tags=["login"],
summary="Login and get token",
response_description="Return HTTP status code 200 (OK)",
status_code=status.HTTP_200_OK,
)
def login(request: LoginRequest) -> Token:
logger.info("login with %s", request.email)
user = authenticate_user_by_email(str(request.email), str(request.password))
scopes = ["admin", "read"]
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.email, "scope": " ".join(scopes)},
expires_delta=access_token_expires,
)
return Token(access_token=access_token, token_type="bearer")
@login_router.post("/token", tags=["login"], summary="Login for access token")
# async def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> Token:
async def login_for_access_token(
form_data: OAuth2PasswordRequestForm = Depends(),
) -> Token:
user = authenticate_user_by_username(form_data.username, form_data.password)
if not user:
raise HTTPException(status_code=400, detail="Incorrect username or password")
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.user_name, "scope": " ".join(form_data.scopes)},
expires_delta=access_token_expires,
)
return Token(access_token=access_token, token_type="bearer")
def login_for_token_cookie(response: Response, form_data: LoginForm = Depends()):
user = authenticate_user_by_email(str(form_data.username), str(form_data.password))
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"}
@@ -1,32 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.admin import MailAccount
from src.db.session import SessionDep
from src.schema.admin.mailaccount import MailAccountResponse, to_response
router = APIRouter()
@router.get("/mailaccounts", response_model=List[MailAccountResponse])
def get_all_mailaccounts(db: SessionDep) -> List[MailAccountResponse]:
"""
return all MailAccounts as JSON.
"""
results: List[MailAccountResponse] = []
mailaccounts = db.query(MailAccount).all()
for mailaccount in mailaccounts:
response = to_response(mailaccount)
results.append(response)
return results
@router.get("/mailaccounts/{mailaccount_id}", response_model=MailAccountResponse)
def get_mailaccount(mailaccount_id: str, db: SessionDep) -> MailAccountResponse:
"""
return MailAccounts by id.
"""
mailaccount = db.get(MailAccount, mailaccount_id)
if mailaccount is None:
raise HTTPException(status_code=409, detail="Mailaccount could not be found")
response = to_response(mailaccount)
return response
@@ -1,20 +0,0 @@
from typing import List
from fastapi import APIRouter
from src.db.models.bookshelf import Article
from src.db.session import SessionDep
from src.schema.bookshelf.article import ArticleResponse, to_response
router = APIRouter()
@router.get("/articles", response_model=List[ArticleResponse])
def get_all_artists(db: SessionDep) -> List[ArticleResponse]:
results: List[ArticleResponse] = []
articles = db.query(Article).all()
for article in articles:
response = to_response(article)
results.append(response)
return results
@@ -1,19 +0,0 @@
from typing import List
from fastapi import APIRouter
from src.db.models.bookshelf import ArticleAuthor
from src.db.session import SessionDep
from src.schema.bookshelf.articleauthor import ArticleAuthorResponse, to_response
router = APIRouter()
@router.get("/articleauthors", response_model=List[ArticleAuthorResponse])
def get_all_artists(db: SessionDep) -> List[ArticleAuthorResponse]:
results: List[ArticleAuthorResponse] = []
articleauthors = db.query(ArticleAuthor).all()
for articleauthor in articleauthors:
response = to_response(articleauthor)
results.append(response)
return results
@@ -1,26 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.bookshelf import Author
from src.db.session import SessionDep
from src.schema.bookshelf.author import AuthorResponse, to_response
router = APIRouter()
@router.get("/authors", response_model=List[AuthorResponse])
def get_all_authors(db: SessionDep) -> List[AuthorResponse]:
results: List[AuthorResponse] = []
authors = db.query(Author).all()
for author in authors:
response = to_response(author)
results.append(response)
return results
@router.get("/authors/{author_id}", response_model=AuthorResponse)
def get_author(author_id: str, db: SessionDep) -> AuthorResponse:
author = db.get(Author, author_id)
if author is None:
raise HTTPException(status_code=404, detail="Author could not be found")
response = to_response(author)
return response
@@ -1,19 +0,0 @@
from typing import List
from fastapi import APIRouter
from src.db.models.bookshelf import Book
from src.db.session import SessionDep
from src.schema.bookshelf.book import BookResponse, to_response
router = APIRouter()
@router.get("/books", response_model=List[BookResponse])
def get_all_artists(db: SessionDep) -> List[BookResponse]:
results: List[BookResponse] = []
books = db.query(Book).all()
for book in books:
response = to_response(book)
results.append(response)
return results
@@ -1,19 +0,0 @@
from typing import List
from fastapi import APIRouter
from src.db.models.bookshelf import BookAuthor
from src.db.session import SessionDep
from src.schema.bookshelf.bookauthor import BookAuthorResponse, to_response
router = APIRouter()
@router.get("/bookauthors", response_model=List[BookAuthorResponse])
def get_all_artists(db: SessionDep) -> List[BookAuthorResponse]:
results: List[BookAuthorResponse] = []
bookauthors = db.query(BookAuthor).all()
for bookauthor in bookauthors:
response = to_response(bookauthor)
results.append(response)
return results
@@ -1,19 +0,0 @@
from typing import List
from fastapi import APIRouter
from src.db.models.bookshelf import BookshelfPublisher
from src.db.session import SessionDep
from src.schema.bookshelf.publisher import PublisherResponse, to_response
router = APIRouter()
@router.get("/publishers", response_model=List[PublisherResponse])
def get_all_artists(db: SessionDep) -> List[PublisherResponse]:
results: List[PublisherResponse] = []
publishers = db.query(BookshelfPublisher).all()
for publisher in publishers:
response = to_response(publisher)
results.append(response)
return results
+48 -12
View File
@@ -1,13 +1,25 @@
from typing import List, AnyStr from typing import List
from fastapi import APIRouter, HTTPException, status from fastapi import APIRouter, HTTPException, status
from src.apis.utils import SessionDep from src.core.log_conf import logger
from src.db.models.comic import Artist, Comic, Issue, Publisher
from src.db.repository.comics.artist import get_artist_details from src.db.repository.comics.artist import get_artist_details
from src.db.repository.comics.comic import list_comics, get_issue_details from src.db.repository.comics.comic import (
from src.schema.comics.comic import ComicResponse, ComicDetailsResponse, get_comic_details, get_short_info get_comic_details,
from src.schema.comics.artist import ArtistCreation, ArtistDetailResponse, ArtistResponse get_issue_details,
from src.db.models.comic import Comic, Artist, Issue get_short_info,
from src.schema.comics.issue import IssueDetailsResponse list_comics,
)
from src.db.repository.comics.publisher import get_publisher_details
from src.db.session import SessionDep
from src.schema.comics.artist import ArtistCreation, ArtistResponse
from src.schema.comics.artist_details import ArtistDetailResponse
from src.schema.comics.comic import ComicResponse
from src.schema.comics.comic_details import ComicDetailsResponse
from src.schema.comics.issue_details import IssueDetailsResponse
from src.schema.comics.publisher import PublisherResponse
from src.schema.comics.publisher_details import PublisherDetailsResponse
router = APIRouter() router = APIRouter()
@@ -21,30 +33,36 @@ def get_all_comics(db: SessionDep) -> List[ComicResponse]:
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: str, 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")
logger.info(f"create ComicDetailsResponse for {comic}")
response: ComicDetailsResponse = get_comic_details(comic) response: ComicDetailsResponse = get_comic_details(comic)
logger.info(f"ComicDetailsResponse: {response}")
return response return response
@router.get("/artists", response_model=List[ArtistResponse]) @router.get("/artists", response_model=List[ArtistResponse])
def get_all_artists(db: SessionDep) -> List[ArtistResponse]: def get_all_artists(db: SessionDep) -> List[ArtistResponse]:
results: List[ArtistResponse] = [] results: List[ArtistResponse] = []
artists = db.query(Artist).all() artists = db.query(Artist).all()
for artist in artists: for artist in artists:
results.append(ArtistResponse(id=artist.id, name=artist.name)) results.append(ArtistResponse(id=artist.id, name=str(artist.name))) # type: ignore
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: str, 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")
response: ArtistDetailResponse = get_artist_details(artist) response: ArtistDetailResponse = get_artist_details(artist)
return response return response
@router.post("/artists", status_code=status.HTTP_201_CREATED) @router.post("/artists", status_code=status.HTTP_201_CREATED)
def add_artist(db: SessionDep, artist_creation: ArtistCreation) -> ArtistResponse: def add_artist(db: SessionDep, artist_creation: ArtistCreation) -> ArtistResponse:
artist: Artist = Artist() artist: Artist = Artist()
@@ -54,9 +72,28 @@ def add_artist(db: SessionDep, artist_creation: ArtistCreation) -> ArtistRespons
db.commit() db.commit()
except: except:
raise HTTPException(status_code=409, detail="Artist already added") raise HTTPException(status_code=409, detail="Artist already added")
response = ArtistResponse(id=artist.id, name=artist.name) response = ArtistResponse(id=artist.id, name=str(artist.name))
return response return response
@router.get("/publishers", response_model=List[PublisherResponse])
def get_all_publishers(db: SessionDep) -> List[PublisherResponse]:
results: List[PublisherResponse] = []
publishers = db.query(Publisher).all()
for publisher in publishers:
results.append(PublisherResponse(id=publisher.id, name=str(publisher.name)))
return results
@router.get("/publishers/{publisher_id}", response_model=PublisherDetailsResponse)
def get_publisher(publisher_id: str, db: SessionDep) -> PublisherDetailsResponse:
publisher = db.get(Publisher, publisher_id)
if publisher is None:
raise HTTPException(status_code=404, detail="Publisher could not be found")
response: PublisherDetailsResponse = get_publisher_details(publisher)
return response
@router.get("/issues", response_model=List[IssueDetailsResponse]) @router.get("/issues", response_model=List[IssueDetailsResponse])
def get_issues(db: SessionDep) -> List[IssueDetailsResponse]: def get_issues(db: SessionDep) -> List[IssueDetailsResponse]:
results: List[IssueDetailsResponse] = [] results: List[IssueDetailsResponse] = []
@@ -64,4 +101,3 @@ def get_issues(db: SessionDep) -> List[IssueDetailsResponse]:
for issue in issues: for issue in issues:
results.append(get_issue_details(issue)) results.append(get_issue_details(issue))
return results return results
@@ -1,53 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException, status
from src.db.models.comic import Artist
from src.db.repository.comics.artist import get_artist_details
from src.db.session import SessionDep
from src.schema.comics.artist import ArtistCreation, ArtistResponse, artist_to_response
from src.schema.comics.artist_details import ArtistDetailResponse
router = APIRouter()
@router.get("/artists", response_model=List[ArtistResponse])
def get_all_artists(db: SessionDep) -> List[ArtistResponse]:
results: List[ArtistResponse] = []
artists = db.query(Artist).all()
for artist in artists:
response = artist_to_response(artist)
results.append(response)
return results
@router.get("/artists/{artist_id}", response_model=ArtistResponse)
def get_artist(artist_id: str, db: SessionDep) -> ArtistResponse:
artist = db.get(Artist, artist_id)
if artist is None:
raise HTTPException(status_code=404, detail="Artist could not be found")
response: ArtistResponse = artist_to_response(artist)
return response
@router.get("/artists/details/{artist_id}", response_model=ArtistDetailResponse)
def get_artist_details_by_id(artist_id: str, db: SessionDep) -> ArtistDetailResponse:
artist = db.get(Artist, artist_id)
if artist is None:
raise HTTPException(status_code=404, detail="Artist could not be found")
response: ArtistDetailResponse = get_artist_details(artist)
return response
@router.post("/artists", status_code=status.HTTP_201_CREATED)
def add_artist(db: SessionDep, artist_creation: ArtistCreation) -> ArtistResponse:
artist: Artist = Artist()
setattr(artist, "name", artist_creation.name)
try:
db.add(artist)
db.commit()
except:
raise HTTPException(status_code=409, detail="Artist already added")
response = artist_to_response(artist)
return response
@@ -1,73 +0,0 @@
from typing import List, AnyStr
from fastapi import APIRouter, HTTPException, status
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.artist import ArtistCreation, ArtistDetailResponse, ArtistResponse
from src.db.models.comic import Comic, Artist, Issue
from src.schema.comics.issue import IssueDetailsResponse
router = APIRouter()
@router.get("/comics")
def get_all_comics(db: SessionDep) -> List[ComicResponse]:
results: List[ComicResponse] = []
comics = db.query(Comic).all()
for comic in comics:
response = comic_to_response(comic)
results.append(response)
return results
@router.get("/comics/{comic_id}", response_model=ComicDetailsResponse)
def get_comic(comic_id: AnyStr, db: SessionDep) -> ComicDetailsResponse:
comic = db.get(Comic, comic_id)
if comic is None:
raise HTTPException(status_code=404, detail="Comic could not be found")
logger.info(f"create ComicDetailsResponse for {comic}")
response: ComicDetailsResponse = get_comic_details(comic)
logger.info(f"ComicDetailsResponse: {response}")
return response
@router.get("/artists", response_model=List[ArtistResponse])
def get_all_artists(db: SessionDep) -> List[ArtistResponse]: # type: ignore
results: List[ArtistResponse] = []
artists = db.query(Artist).all()
for artist in artists:
results.append(ArtistResponse(id=artist.id, name=str(artist.name))) # type: ignore
return results
@router.get("/artists/{artist_id}", response_model=ArtistDetailResponse)
def get_artist(artist_id: AnyStr, db: SessionDep) -> ArtistDetailResponse:
artist = db.get(Artist, artist_id)
if artist is None:
raise HTTPException(status_code=404, detail="Artist could not be found")
response: ArtistDetailResponse = get_artist_details(artist)
return response
@router.post("/artists", status_code=status.HTTP_201_CREATED)
def add_artist(db: SessionDep, artist_creation: ArtistCreation) -> ArtistResponse: # type: ignore
artist: Artist = Artist()
setattr(artist, "name", artist_creation.name)
try:
db.add(artist)
db.commit()
except:
raise HTTPException(status_code=409, detail="Artist already added")
response = ArtistResponse(id=artist.id, name=str(artist.name))
return response
@router.get("/issues", response_model=List[IssueDetailsResponse])
def get_issues(db: SessionDep) -> List[IssueDetailsResponse]: # type: ignore
results: List[IssueDetailsResponse] = []
issues = db.query(Issue).all()
for issue in issues:
results.append(get_issue_details(issue))
return results
@@ -1,27 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.comic import ComicWork
from src.db.session import SessionDep
from src.schema.comics.comicwork import ComicWorkResponse, comicwork_to_response
router = APIRouter()
@router.get("/comicworks", response_model=List[ComicWorkResponse])
def get_comicworks(db: SessionDep) -> List[ComicWorkResponse]:
results: List[ComicWorkResponse] = []
worktypes = db.query(ComicWork).all()
for worktype in worktypes:
response = comicwork_to_response(worktype)
results.append(response)
return results
@router.get("/comicworks/{comicwork_id}", response_model=ComicWorkResponse)
def get_comicwork(comicwork_id: str, db: SessionDep) -> ComicWorkResponse:
worktype = db.get(ComicWork, comicwork_id)
if worktype is None:
raise HTTPException(status_code=404, detail="Comicwork could not be found")
response = comicwork_to_response(worktype)
return response
@@ -1,27 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.comic import Issue
from src.db.session import SessionDep
from src.schema.comics.issue import IssueResponse, issue_to_response
router = APIRouter()
@router.get("/issues", response_model=List[IssueResponse])
def get_issues(db: SessionDep) -> List[IssueResponse]:
results: List[IssueResponse] = []
issues = db.query(Issue).all()
for issue in issues:
response = issue_to_response(issue)
results.append(response)
return results
@router.get("/issues/{issue_id}", response_model=IssueResponse)
def get_issue(issue_id: str, db: SessionDep) -> IssueResponse:
issue = db.get(Issue, issue_id)
if issue is None:
raise HTTPException(status_code=404, detail="Issue could not be found")
response = issue_to_response(issue)
return response
@@ -1,27 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.comic import IssueWork
from src.db.session import SessionDep
from src.schema.comics.issuework import IssueWorkResponse, issuework_to_response
router = APIRouter()
@router.get("/issueworks", response_model=List[IssueWorkResponse])
def get_issueworks(db: SessionDep) -> List[IssueWorkResponse]:
results: List[IssueWorkResponse] = []
worktypes = db.query(IssueWork).all()
for worktype in worktypes:
response = issuework_to_response(worktype)
results.append(response)
return results
@router.get("/issueworks/{issuework_id}", response_model=IssueWorkResponse)
def get_issuework(issuework_id: str, db: SessionDep) -> IssueWorkResponse:
worktype = db.get(IssueWork, issuework_id)
if worktype is None:
raise HTTPException(status_code=404, detail="Issuework could not be found")
response = issuework_to_response(worktype)
return response
@@ -1,37 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.comic import Publisher
from src.db.repository.comics.publisher import get_publisher_details
from src.db.session import SessionDep
from src.schema.comics.publisher import PublisherResponse, publisher_to_response
from src.schema.comics.publisher_details import PublisherDetailsResponse
router = APIRouter()
@router.get("/publishers", response_model=List[PublisherResponse])
def get_all_publishers(db: SessionDep) -> List[PublisherResponse]:
results: List[PublisherResponse] = []
publishers = db.query(Publisher).all()
for publisher in publishers:
response: PublisherResponse = publisher_to_response(publisher)
results.append(response)
return results
@router.get("/publishers/{publisher_id}", response_model=PublisherResponse)
def get_publisher(publisher_id: str, db: SessionDep) -> PublisherResponse:
publisher = db.get(Publisher, publisher_id)
if publisher is None:
raise HTTPException(status_code=404, detail="Publisher could not be found")
response: PublisherResponse = publisher_to_response(publisher)
return response
@router.get("/publishers/details/{publisher_id}", response_model=PublisherDetailsResponse)
def get_publisher_details_by_id(publisher_id: str, db: SessionDep) -> PublisherDetailsResponse:
publisher = db.get(Publisher, publisher_id)
if publisher is None:
raise HTTPException(status_code=404, detail="Publisher could not be found")
response: PublisherDetailsResponse = get_publisher_details(publisher)
return response
@@ -1,26 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.comic import StoryArc
from src.db.session import SessionDep
from src.schema.comics.storyarc import StoryArcResponse, storyarc_to_response
router = APIRouter()
@router.get("/storyarcs", response_model=List[StoryArcResponse])
def get_storyarcs(db: SessionDep) -> List[StoryArcResponse]:
results: List[StoryArcResponse] = []
storyarcs = db.query(StoryArc).all()
for storyarc in storyarcs:
response = storyarc_to_response(storyarc)
results.append(response)
return results
@router.get("/storyarcs/{storyarc_id}", response_model=StoryArcResponse)
def get_storyarc(story_arc_id: str, db: SessionDep) -> StoryArcResponse:
storyarc = db.get(StoryArc, story_arc_id)
if storyarc is None:
raise HTTPException(status_code=404, detail="Storyarc could not be found")
response = storyarc_to_response(storyarc)
return response
@@ -1,26 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.comic import Volume
from src.db.session import SessionDep
from src.schema.comics.volume import VolumeResponse, volume_to_response
router = APIRouter()
@router.get("/volumes", response_model=List[VolumeResponse])
def volumes(db: SessionDep) -> List[VolumeResponse]:
results: List[VolumeResponse] = []
worktypes = db.query(Volume).all()
for worktype in worktypes:
response = volume_to_response(worktype)
results.append(response)
return results
@router.get("/volumes/{volume_id}", response_model=VolumeResponse)
def get_volume(volume_id: str, db: SessionDep) -> VolumeResponse:
worktype = db.get(Volume, volume_id)
if worktype is None:
raise HTTPException(status_code=404, detail="Volume could not be found")
response = volume_to_response(worktype)
return response
@@ -1,27 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.comic import WorkType
from src.db.session import SessionDep
from src.schema.comics.worktype import WorktypeResponse, worktype_to_response
router = APIRouter()
@router.get("/worktypes", response_model=List[WorktypeResponse])
def get_issues(db: SessionDep) -> List[WorktypeResponse]:
results: List[WorktypeResponse] = []
worktypes = db.query(WorkType).all()
for worktype in worktypes:
response = worktype_to_response(worktype)
results.append(response)
return results
@router.get("/worktypes/{worktype_id}", response_model=WorktypeResponse)
def get_issue(worktype_id: str, db: SessionDep) -> WorktypeResponse:
worktype = db.get(WorkType, worktype_id)
if worktype is None:
raise HTTPException(status_code=404, detail="Worktype could not be found")
response = worktype_to_response(worktype)
return response
+1 -1
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, status from fastapi import APIRouter, status
from src.schema.admin.healthcheck import HealthCheck from src.schema.admin import HealthCheck
health_router = APIRouter() health_router = APIRouter()
+41
View File
@@ -0,0 +1,41 @@
from datetime import timedelta
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel
from src.core.config import settings
from src.core.log_conf import logger
from src.core.security import authenticate_user, create_access_token
from src.schema.admin import Token
login_router = APIRouter()
class LoginRequest(BaseModel):
email: str | None = None
password: str | None = None
@login_router.post(
"/login",
tags=["login"],
summary="Login and get token",
response_description="Return HTTP status code 200 (OK)",
status_code=status.HTTP_200_OK,
)
def login(request: LoginRequest) -> Token:
logger.info(f"login with {request.email}")
user = authenticate_user(request.email, request.password) # type: ignore
scopes = ["admin", "read"]
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.email, "scope": " ".join(scopes)},
expires_delta=access_token_expires,
)
return Token(access_token=access_token, token_type="bearer")
-69
View File
@@ -1,69 +0,0 @@
from typing import List, AnyStr
from fastapi import APIRouter, status, HTTPException, Depends
from sqlalchemy import select, Sequence
from src.core.log_conf import logger
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.db.models.media import MediaFile
router = APIRouter()
@router.get("/update-titles")
def update_titles(db: SessionDep) -> list[MediaFileResponse]:
results: list[MediaFileResponse] = []
files = db.query(MediaFile).filter(MediaFile.review == True).all()
for mediafile in files:
mediafile.update_title()
db.add(mediafile)
response = get_file_details(mediafile)
results.append(response)
db.commit()
return results
@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]:
results: list[MediaFileResponse] = []
files: Sequence[MediaFile]
if review:
files = db.query(MediaFile).filter(MediaFile.review == True).all()
elif download:
files = db.query(MediaFile).filter(MediaFile.should_download == True).all()
else:
files = db.scalars(select(MediaFile)).all()
for mediafile in files:
response = get_file_details(mediafile)
results.append(response)
return results
@router.get("/files/{file_id}", response_model=MediaFileResponse)
def get_file(file_id: AnyStr, db: SessionDep) -> MediaFileResponse:
mediafile = db.get(MediaFile, file_id)
if not mediafile:
raise HTTPException(status_code=404, detail="MediaFile could not be found")
response = get_file_details(mediafile)
return response
@router.put("/files/{file_id}", response_model=MediaFileResponse)
def update_file(file_id: AnyStr, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse:
mediaFile = db.get(MediaFile, file_id)
if not mediaFile:
raise HTTPException(status_code=404, detail="MediaFile could not be found")
set_file(info, mediaFile)
db.add(mediaFile)
db.commit()
return info
@router.post("/files", status_code=status.HTTP_201_CREATED)
def add_file(new_link: Link, db: SessionDep) -> MediaFileResponse:
logger.info(f"add url {new_link.url}")
try:
mediaFile: MediaFile = create_new_mediafile(new_link.url, db)
except:
raise HTTPException(status_code=409, detail="Link duplicate")
response = get_file_details(mediaFile)
return response
@@ -1,45 +0,0 @@
from typing import List
from fastapi import APIRouter, status, HTTPException
from src.core.log_conf import logger
from src.db.repository.media.actor import delete_mediaactor, import_mediaactor
from src.db.session import SessionDep
from src.schema.media.actor import MediaActorModel, MediaActorResponse, actor_to_response
from src.db.models.media import MediaActor
router = APIRouter()
@router.get("/actors", response_model=List[MediaActorResponse])
def get_all_actors(db: SessionDep) -> List[MediaActorResponse]:
results: list[MediaActorResponse] = []
actors = db.query(MediaActor).all()
for mediaactor in actors:
response = actor_to_response(mediaactor)
results.append(response)
return results
@router.get("/actors/{actor_id}", response_model=MediaActorResponse)
def get_actor(actor_id: str, db: SessionDep) -> MediaActorResponse:
media_actor = db.get(MediaActor, actor_id)
if not media_actor:
raise HTTPException(status_code=404, detail="MediaActor could not be found")
response = actor_to_response(media_actor)
return response
@router.delete("/actors/{actor_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_actor(actor_id: str, db: SessionDep):
media_actor = db.get(MediaActor, actor_id)
if not media_actor:
raise HTTPException(status_code=404, detail="MediaActor could not be found")
logger.info(f"delete MediaActor: {actor_id}")
delete_mediaactor(db, media_actor.id)
@router.post("/actors", status_code=status.HTTP_201_CREATED)
def add_actor(new_actor: MediaActorModel, db: SessionDep) -> MediaActorResponse:
logger.info(f"add actor {new_actor.url}")
try:
mediaActor: MediaActor = import_mediaactor(db, new_actor)
except Exception as exception:
raise HTTPException(status_code=409, detail=f"Link duplicate: {exception}")
response = actor_to_response(mediaActor)
return response
-163
View File
@@ -1,163 +0,0 @@
from typing import List
from fastapi import APIRouter, status, HTTPException
from src.core.log_conf import logger
from src.db.repository.media.actorfile import (
create_new_mediaactorfile,
delete_mediaactorfile,
)
from src.db.repository.media.file import delete_mediafile, import_mediafile
from src.db.session import SessionDep
from src.schema.media.actor import MediaActorResponse, actor_to_response
from src.schema.media.actorfile import MediaActorFileResponse, actorfile_to_response
from src.schema.media.file import (
MediaFileModel,
MediaFileResponse,
file_to_response,
file_to_model,
)
from src.db.models.media import MediaFile
router = APIRouter()
@router.get("/update-titles")
def update_titles(db: SessionDep) -> List[MediaFileResponse]:
"""
Update title for given MediaFile.
"""
results: list[MediaFileResponse] = []
files = db.query(MediaFile).filter(MediaFile.review.is_(True)).all()
for mediafile in files:
mediafile.update_title()
db.add(mediafile)
response = file_to_response(mediafile)
results.append(response)
db.commit()
return results
@router.get("/files", response_model=List[MediaFileResponse])
def get_all_files(
db: SessionDep, review: bool = False, download: bool = False
) -> List[MediaFileResponse]:
"""
Get all MediaFiles.
"""
results: List[MediaFileResponse] = []
files: List[MediaFile]
if review:
files = db.query(MediaFile).filter(MediaFile.review.is_(True)).all()
elif download:
files = db.query(MediaFile).filter(MediaFile.should_download.is_(True)).all()
else:
files = db.query(MediaFile).all()
for mediafile in files:
response = file_to_response(mediafile)
results.append(response)
return results
@router.get("/files/{file_id}", response_model=MediaFileResponse)
def get_file(file_id: str, db: SessionDep) -> MediaFileResponse:
"""
Get MediaFile with given id or return HTTPException.
"""
mediafile = db.get(MediaFile, file_id)
if not mediafile:
raise HTTPException(status_code=404, detail="MediaFile could not be found")
response = file_to_response(mediafile)
return response
@router.delete("/files/{file_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_file(file_id: str, db: SessionDep):
"""
Delete MediaFile by given id.
"""
mediafile = db.get(MediaFile, file_id)
if not mediafile:
raise HTTPException(status_code=404, detail="MediaFile could not be found")
logger.info("delete MediaFile: %s", file_id)
actor_files = mediafile.media_actor_files
logger.info("MediaActorFiles links %s", len(actor_files))
if len(actor_files) > 0:
logger.info("delete MediaActor relations first")
for actor_file in actor_files:
delete_mediaactorfile(db, actor_file.id)
delete_mediafile(db, mediafile.id)
@router.get("/files/{file_id}/actors", response_model=list[MediaActorResponse])
def get_file_actors(file_id: str, db: SessionDep) -> list[MediaActorResponse]:
"""
Get list of Actors for given MediaFile.
"""
mediafile = db.get(MediaFile, file_id)
if not mediafile:
raise HTTPException(status_code=404, detail="MediaFile could not be found")
actor_files = mediafile.media_actor_files
logger.info("already known actors: %s", actor_files)
results: list[MediaActorResponse] = []
for actor_file in actor_files:
response = actor_to_response(actor_file.media_actor)
results.append(response)
return results
@router.put("/files/{file_id}/actors", response_model=List[MediaActorFileResponse])
def update_file_actors(
file_id: str, db: SessionDep, actors: List[MediaActorResponse]
) -> List[MediaActorFileResponse]:
mediafile = db.get(MediaFile, file_id)
if not mediafile:
raise HTTPException(status_code=404, detail="MediaFile could not be found")
actor_files = mediafile.media_actor_files
logger.info("already known actors: %s", actor_files)
for actor in actors:
already_associated = False
for actor_file in actor_files:
if actor.id == actor_file.media_actor_id:
logger.info("alreay associated - do nothing")
already_associated = True
break
if not already_associated:
create_new_mediaactorfile(db, actor.id, mediafile.id)
db.refresh(mediafile)
actor_files = mediafile.media_actor_files
results: List[MediaActorFileResponse] = []
for actor_file in actor_files:
response = actorfile_to_response(actor_file)
results.append(response)
return results
@router.put("/files/{file_id}", response_model=MediaFileResponse)
def update_file(
file_id: str, db: SessionDep, info: MediaFileResponse
) -> MediaFileResponse:
"""
Update MediaFile with given id and data.
"""
media_file = db.get(MediaFile, file_id)
if not media_file:
raise HTTPException(status_code=404, detail="MediaFile could not be found")
file_to_model(info, media_file)
db.add(media_file)
db.commit()
mediafile = db.get(MediaFile, file_id)
if not mediafile:
raise HTTPException(status_code=404, detail="MediaFile could not be updated")
response = file_to_response(mediafile)
return response
@router.post("/files", status_code=status.HTTP_201_CREATED)
def add_file(new_file: MediaFileModel, db: SessionDep) -> MediaFileResponse:
logger.info("add mediafile %s", new_file)
try:
mediaFile: MediaFile = import_mediafile(db, new_file)
except:
raise HTTPException(status_code=409, detail="MediaFile duplicate")
response = file_to_response(mediaFile)
return response
@@ -1,25 +0,0 @@
from typing import List
from fastapi import APIRouter
from src.db.models.media import MediaArticle
from src.db.session import SessionDep
from src.schema.media.article import MediaArticleResponse, to_response
router = APIRouter()
@router.get("/articles", response_model=List[MediaArticleResponse])
def get_all_files(
db: SessionDep, review: bool = False, download: bool = False
) -> List[MediaArticleResponse]:
"""
Get all MediaVideos.
"""
results: List[MediaArticleResponse] = []
articles: List[MediaArticle]
articles = db.query(MediaArticle).all()
for article in articles:
response = to_response(article)
results.append(response)
return results
@@ -1,42 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.media import MediaVideo
from src.db.session import SessionDep
from src.schema.media.video import MediaVideoResponse, video_to_response
router = APIRouter()
@router.get("/videos", response_model=List[MediaVideoResponse])
def get_all_videos(
db: SessionDep, review: bool = False, download: bool = False
) -> List[MediaVideoResponse]:
"""
Get all MediaVideos.
"""
results: List[MediaVideoResponse] = []
files: List[MediaVideo]
if review:
files = db.query(MediaVideo).filter(MediaVideo.review.is_(True)).all()
elif download:
files = db.query(MediaVideo).filter(MediaVideo.should_download.is_(True)).all()
else:
files = db.query(MediaVideo).all()
for mediafile in files:
response = video_to_response(mediafile)
results.append(response)
return results
@router.get("/videos/{video_id}", response_model=MediaVideoResponse)
def get_video(video_id: str, db: SessionDep) -> MediaVideoResponse:
"""
Get MediaVideo by id.
"""
video = db.get(MediaVideo, video_id)
if video is None:
raise HTTPException(status_code=404, detail="MediaVideo could not be found")
response = video_to_response(video)
return response
@@ -0,0 +1,48 @@
from fastapi import APIRouter, status, HTTPException
from sqlalchemy import select
from src.core.log_conf import logger
from src.db.repository.media import create_new_mediaactor, delete_mediaactor
from src.db.session import SessionDep
from src.schema.media.actor import Actor, MediaActorResponse, get_actor_details
from src.db.models.media import MediaActor
router = APIRouter()
@router.get("/actors", response_model=list[MediaActorResponse])
# 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_actors(db: SessionDep, review: bool = False, download: bool = False) -> list[MediaActorResponse]: # type: ignore
results: list[MediaActorResponse] = []
actors = db.scalars(select(MediaActor)).all()
for mediaactor in actors:
response = MediaActorResponse(id=mediaactor.id, name=str(mediaactor.name), url=str(mediaactor.url))
results.append(response)
return results
@router.get("/actors/{actor_id}", response_model=MediaActorResponse)
def get_actor(actor_id: str, db: SessionDep) -> MediaActorResponse: # type: ignore
media_actor = db.get(MediaActor, actor_id)
if not media_actor:
raise HTTPException(status_code=404, detail="MediaActor could not be found")
response = get_actor_details(media_actor)
return response
@router.delete("/actors/{actor_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_actor(actor_id: str, db: SessionDep): # type: ignore
media_actor = db.get(MediaActor, actor_id)
if not media_actor:
raise HTTPException(status_code=404, detail="MediaActor could not be found")
logger.info(f"delete MediaActor: {actor_id}")
actor_files = media_actor.media_actor_files
logger.info(f"MediaActorFiles links {len(actor_files)}")
if len(actor_files) == 0:
delete_mediaactor(db, media_actor.id)
@router.post("/actors", status_code=status.HTTP_201_CREATED)
def add_actor(new_actor: Actor, db: SessionDep) -> MediaActorResponse: # type: ignore
logger.info(f"add actor {new_actor.url}")
try:
mediaActor: MediaActor = create_new_mediaactor(new_actor, db)
except:
raise HTTPException(status_code=409, detail="Link duplicate")
response = get_actor_details(mediaActor)
return response
@@ -1,32 +1,33 @@
from typing import List
from fastapi import APIRouter, status, HTTPException from fastapi import APIRouter, status, HTTPException
from sqlalchemy import select
from src.db.models.media import MediaActorFile from src.db.models.media import MediaActorFile
from src.db.repository.media.actorfile import delete_mediaactorfile from src.db.repository.media import delete_mediaactorfile
from src.db.session import SessionDep from src.db.session import SessionDep
from src.schema.media.actorfile import MediaActorFileResponse, actorfile_to_response from src.schema.media.actorfile import MediaActorFileResponse, get_actorfile_details
router = APIRouter() router = APIRouter()
@router.get("/actorfiles", response_model=List[MediaActorFileResponse]) @router.get("/actorfiles", response_model=list[MediaActorFileResponse])
def get_all_actorfiles(db: SessionDep) -> List[MediaActorFileResponse]: def get_all_actorfiles(db: SessionDep) -> list[MediaActorFileResponse]: # type: ignore
results: List[MediaActorFileResponse] = [] results: list[MediaActorFileResponse] = []
actorfiles = db.query(MediaActorFile).all() actorfiles = db.scalars(select(MediaActorFile)).all()
for media_actorfile in actorfiles: for mediaactorfile in actorfiles:
response = actorfile_to_response(media_actorfile) response = MediaActorFileResponse(id=mediaactorfile.id, actor_id=str(mediaactorfile.media_actor_id), file_id=str(mediaactorfile.media_file_id))
results.append(response) results.append(response)
return results return results
@router.get("/actorfiles/{actorfile_id}", response_model=MediaActorFileResponse) @router.get("/actorfiles/{actorfile_id}", response_model=MediaActorFileResponse)
def get_actorfile(actorfile_id: str, db: SessionDep) -> MediaActorFileResponse: def get_actorfile(actorfile_id: str, db: SessionDep) -> MediaActorFileResponse: # type: ignore
media_actorfile = db.get(MediaActorFile, actorfile_id) media_actorfile = db.get(MediaActorFile, actorfile_id)
if not media_actorfile: if not media_actorfile:
raise HTTPException(status_code=404, detail="MediaActor could not be found") raise HTTPException(status_code=404, detail="MediaActor could not be found")
response = actorfile_to_response(media_actorfile) response = get_actorfile_details(media_actorfile)
return response return response
@router.delete("/actorfiles/{actorfile_id}", status_code=status.HTTP_204_NO_CONTENT) @router.delete("/actorfiles/{actorfile_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_actorfile(actorfile_id: str, db: SessionDep): def delete_actorfile(actorfile_id: str, db: SessionDep): # type: ignore
media_actorfile = db.get(MediaActorFile, actorfile_id) media_actorfile = db.get(MediaActorFile, actorfile_id)
if not media_actorfile: if not media_actorfile:
raise HTTPException(status_code=404, detail="MediaActor could not be found") raise HTTPException(status_code=404, detail="MediaActor could not be found")
delete_mediaactorfile(db, media_actorfile.id) delete_mediaactorfile(db, media_actorfile.id)
+120
View File
@@ -0,0 +1,120 @@
from fastapi import APIRouter, status, HTTPException, Depends
from sqlalchemy import select, Sequence
from src.core.log_conf import logger
from src.db.repository.media import create_new_mediaactorfile, create_new_mediafile, delete_mediafile
from src.db.session import SessionDep
from src.schema.media.actor import MediaActorResponse
from src.schema.media.actorfile import MediaActorFileResponse
from src.schema.media.file import MediaFileResponse, Link, get_file_details, set_file
from src.db.models.media import MediaFile
router = APIRouter()
@router.get("/update-titles")
def update_titles(db: SessionDep) -> list[MediaFileResponse]: # type: ignore
results: list[MediaFileResponse] = []
files = db.query(MediaFile).filter(MediaFile.review == True).all()
for mediafile in files:
mediafile.update_title()
db.add(mediafile)
response = get_file_details(mediafile)
results.append(response)
db.commit()
return results
@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]: # type: ignore
results: list[MediaFileResponse] = []
files: Sequence[MediaFile]
if review:
files = db.query(MediaFile).filter(MediaFile.review == True).all() # type: ignore
elif download:
files = db.query(MediaFile).filter(MediaFile.should_download == True).all() # type: ignore
else:
files = db.scalars(select(MediaFile)).all() # type: ignore
for mediafile in files: # type: ignore
response = get_file_details(mediafile)
results.append(response)
return results
@router.get("/files/{file_id}", response_model=MediaFileResponse)
def get_file(file_id: str, db: SessionDep) -> MediaFileResponse: # type: ignore
mediafile = db.get(MediaFile, file_id)
if not mediafile:
raise HTTPException(status_code=404, detail="MediaFile could not be found")
response = get_file_details(mediafile)
return response
@router.delete("/files/{file_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_file(file_id: str, db: SessionDep): # type: ignore
mediafile = db.get(MediaFile, file_id)
if not mediafile:
raise HTTPException(status_code=404, detail="MediaFile could not be found")
logger.info(f"delete MediaFile: {file_id}")
actor_files = mediafile.media_actor_files
logger.info(f"MediaActorFiles links {len(actor_files)}")
if len(actor_files) == 0:
delete_mediafile(db, mediafile.id)
@router.get("/files/{file_id}/actors", response_model=list[MediaActorResponse])
def get_file_actors(file_id: str, db: SessionDep) -> list[MediaActorResponse]: # type: ignore
mediafile = db.get(MediaFile, file_id)
if not mediafile:
raise HTTPException(status_code=404, detail="MediaFile could not be found")
actor_files = mediafile.media_actor_files
logger.info(f"already known actors: {actor_files}")
results: list[MediaActorResponse] = []
for actor_file in actor_files:
response = MediaActorResponse(id=actor_file.media_actor.id, name=actor_file.media_actor.name, url=actor_file.media_actor.url)
results.append(response)
return results
@router.put("/files/{file_id}/actors", response_model=list[MediaActorFileResponse])
def update_file_actors(file_id: str, db: SessionDep, actors: list[MediaActorResponse]) -> list[MediaActorFileResponse]: # type: ignore
mediafile = db.get(MediaFile, file_id)
if not mediafile:
raise HTTPException(status_code=404, detail="MediaFile could not be found")
actor_files = mediafile.media_actor_files
logger.info(f"already known actors: {actor_files}")
for actor in actors:
already_associated = False
for actor_file in actor_files:
if actor.id == actor_file.media_actor_id:
logger.info("alreay associated - do nothing")
already_associated = True
break
if not already_associated:
create_new_mediaactorfile(db, actor.id, mediafile.id)
db.refresh(mediafile)
actor_files = mediafile.media_actor_files
results: list[MediaActorFileResponse] = []
for actor_file in actor_files:
response = MediaActorFileResponse(id=actor_file.id, actor_id=actor_file.media_actor_id, file_id=actor_file.media_file_id)
results.append(response)
return results
@router.put("/files/{file_id}", response_model=MediaFileResponse)
def update_file(file_id: str, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse: # type: ignore
mediaFile = db.get(MediaFile, file_id)
if not mediaFile:
raise HTTPException(status_code=404, detail="MediaFile could not be found")
set_file(info, mediaFile)
db.add(mediaFile)
db.commit()
mediafile = db.get(MediaFile, file_id)
if not mediafile:
raise HTTPException(status_code=404, detail="MediaFile could not be updated")
response = get_file_details(mediafile)
return response
@router.post("/files", status_code=status.HTTP_201_CREATED)
def add_file(new_link: Link, db: SessionDep) -> MediaFileResponse: # type: ignore
logger.info(f"add url {new_link.url}")
try:
mediaFile: MediaFile = create_new_mediafile(new_link.url, db)
except:
raise HTTPException(status_code=409, detail="Link duplicate")
response = get_file_details(mediaFile)
return response
+1 -1
View File
@@ -1,7 +1,7 @@
from typing import List from typing import List
from fastapi import APIRouter from fastapi import APIRouter
from src.apis.utils import SessionDep from src.db.session 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
-27
View File
@@ -1,27 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.tysc import Card
from src.db.session import SessionDep
from src.schema.tysc.card import CardResponse, to_response
router = APIRouter()
@router.get("/cards")
def get_all_cards(db: SessionDep) -> List[CardResponse]:
results: List[CardResponse] = []
cards = db.query(Card).all()
for card in cards:
response = to_response(card)
results.append(response)
return results
@router.get("/cards/{card_id}", response_model=CardResponse)
def get_card(card_id: str, db: SessionDep) -> CardResponse:
card = db.get(Card, card_id)
if card is None:
raise HTTPException(status_code=404, detail="Card could not be found")
response = to_response(card)
return response
@@ -1,27 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.tysc import CardSet
from src.db.session import SessionDep
from src.schema.tysc.cardset import CardSetResponse, to_response
router = APIRouter()
@router.get("/cardsets")
def get_all_cardsets(db: SessionDep) -> List[CardSetResponse]:
results: List[CardSetResponse] = []
cardsets = db.query(CardSet).all()
for cardset in cardsets:
response = to_response(cardset)
results.append(response)
return results
@router.get("/cardsets/{cardset_id}", response_model=CardSetResponse)
def get_cardset(cardset_id: str, db: SessionDep) -> CardSetResponse:
cardset = db.get(CardSet, cardset_id)
if cardset is None:
raise HTTPException(status_code=404, detail="Cardset could not be found")
response = to_response(cardset)
return response
@@ -1,27 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.tysc import FieldPosition
from src.db.session import SessionDep
from src.schema.tysc.fieldposition import FieldPositionResponse, to_response
router = APIRouter()
@router.get("/positions")
def get_all_positions(db: SessionDep) -> List[FieldPositionResponse]:
results: list[FieldPositionResponse] = []
positions = db.query(FieldPosition).all()
for position in positions:
response = to_response(position)
results.append(response)
return results
@router.get("/positions/{position_id}", response_model=FieldPositionResponse)
def get_position(position_id: str, db: SessionDep) -> FieldPositionResponse:
position = db.get(FieldPosition, position_id)
if position is None:
raise HTTPException(status_code=404, detail="Fieldposition could not be found")
response = to_response(position)
return response
@@ -1,27 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.tysc import Player
from src.db.session import SessionDep
from src.schema.tysc.player import PlayerResponse, to_response
router = APIRouter()
@router.get("/players")
def get_all_players(db: SessionDep) -> List[PlayerResponse]:
results: List[PlayerResponse] = []
players = db.query(Player).all()
for player in players:
response = to_response(player)
results.append(response)
return results
@router.get("/players/{player_id}", response_model=PlayerResponse)
def get_player(player_id: str, db: SessionDep) -> PlayerResponse:
player = db.get(Player, player_id)
if player is None:
raise HTTPException(status_code=404, detail="Player could not be found")
response = to_response(player)
return response
@@ -1,27 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.tysc import Rooster
from src.db.session import SessionDep
from src.schema.tysc.rooster import RoosterResponse, to_response
router = APIRouter()
@router.get("/roosters")
def get_all_roosters(db: SessionDep) -> List[RoosterResponse]:
results: list[RoosterResponse] = []
roosters = db.query(Rooster).all()
for rooster in roosters:
response = to_response(rooster)
results.append(response)
return results
@router.get("/roosters/{rooster_id}", response_model=RoosterResponse)
def get_rooster(rooster_id: str, db: SessionDep) -> RoosterResponse:
rooster = db.get(Rooster, rooster_id)
if rooster is None:
raise HTTPException(status_code=404, detail="Rooster could not be found")
response = to_response(rooster)
return response
@@ -1,28 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.core.log_conf import logger
from src.db.session import SessionDep
from src.schema.tysc.sport import SportResponse, to_response
from src.db.models.tysc import Sport
router = APIRouter()
@router.get("/sports")
def get_all_sports(db: SessionDep) -> List[SportResponse]:
results: list[SportResponse] = []
sports = db.query(Sport).all()
for sport in sports:
response = to_response(sport)
results.append(response)
return results
@router.get("/sports/{sport_id}", response_model=SportResponse)
def get_sport(sport_id: str, db: SessionDep) -> SportResponse:
sport = db.get(Sport, sport_id)
if sport is None:
raise HTTPException(status_code=404, detail="Sport could not be found")
logger.debug(f"create SportResponse for {sport}")
response: SportResponse = to_response(sport)
return response
-27
View File
@@ -1,27 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.tysc import Team
from src.db.session import SessionDep
from src.schema.tysc.team import TeamResponse, to_response
router = APIRouter()
@router.get("/teams")
def get_all_teams(db: SessionDep) -> List[TeamResponse]:
results: list[TeamResponse] = []
teams = db.query(Team).all()
for team in teams:
response = to_response(team)
results.append(response)
return results
@router.get("/teams/{team_id}", response_model=TeamResponse)
def get_team(team_id: str, db: SessionDep) -> TeamResponse:
team = db.get(Team, team_id)
if team is None:
raise HTTPException(status_code=404, detail="Team could not be found")
response = to_response(team)
return response
@@ -1,33 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.tysc import Vendor
from src.db.session import SessionDep
from src.schema.tysc.vendor import VendorResponse, to_response
router = APIRouter()
@router.get("/vendors")
def get_all_vendors(db: SessionDep) -> List[VendorResponse]:
"""
retrieve all vendors as json response.
"""
results: list[VendorResponse] = []
vendors = db.query(Vendor).all()
for vendor in vendors:
response = to_response(vendor)
results.append(response)
return results
@router.get("/vendors/{vendor_id}", response_model=VendorResponse)
def get_vendor(vendor_id: str, db: SessionDep) -> VendorResponse:
"""
retrieve vendor by id as json response.
"""
vendor = db.get(Vendor, vendor_id)
if vendor is None:
raise HTTPException(status_code=404, detail="Vendor could not be found")
response = to_response(vendor)
return response
@@ -1,26 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.admin import Assignment
from src.db.session import SessionDep
from src.schema.user.assignment import AssignmentResponse, to_response
router = APIRouter()
@router.get("/assignments", response_model=List[AssignmentResponse])
def get_all_assignments(db: SessionDep) -> List[AssignmentResponse]:
results: List[AssignmentResponse] = []
assignments = db.query(Assignment).all()
for assignment in assignments:
response = to_response(assignment)
results.append(response)
return results
@router.get("/assignments/{assignment_id}", response_model=AssignmentResponse)
def get_assignment(assignment_id: str, db: SessionDep) -> AssignmentResponse:
assignment = db.get(Assignment, assignment_id)
if assignment is None:
raise HTTPException(status_code=404, detail="Assignment could not be found")
response = to_response(assignment)
return response
@@ -1,26 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException
from src.db.models.admin import Permission
from src.db.session import SessionDep
from src.schema.user.permission import PermissionResponse, to_response
router = APIRouter()
@router.get("/permissions", response_model=List[PermissionResponse])
def get_all_permissions(db: SessionDep) -> List[PermissionResponse]:
results: List[PermissionResponse] = []
permissions = db.query(Permission).all()
for permission in permissions:
response = to_response(permission)
results.append(response)
return results
@router.get("/permissions/{permission_id}", response_model=PermissionResponse)
def get_permission(permission_id: str, db: SessionDep) -> PermissionResponse:
permission = db.get(Permission, permission_id)
if permission is None:
raise HTTPException(status_code=404, detail="Permission could not be found")
response = to_response(permission)
return response
@@ -1,53 +0,0 @@
from typing import List
from fastapi import APIRouter, HTTPException, status
from sqlalchemy import select
from src.core.log_conf import logger
from src.core.security import CurrentUser
from src.db.models.admin import Profile
from src.db.repository.user import create_new_profile
from src.db.session import SessionDep
from src.schema.user.profile import ProfileResponse, ProfileModel, to_response
router = APIRouter()
@router.get("/profile", response_model=ProfileModel)
async def read_profile(current_user: CurrentUser):
return current_user
@router.get("/profiles", response_model=List[ProfileResponse])
def get_all_profiles(db: SessionDep) -> List[ProfileResponse]:
results: List[ProfileResponse] = []
profiles = db.scalars(select(Profile)).all()
for profile in profiles:
response = to_response(profile)
results.append(response)
return results
@router.get("/profiles/{profile_id}", response_model=ProfileResponse)
def get_profile(profile_id: str, db: SessionDep) -> ProfileResponse:
profile = db.get(Profile, profile_id)
if not profile:
raise HTTPException(status_code=404, detail="Profile could not be found")
response = to_response(profile)
return response
@router.delete("/profiles/{profile_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_profile(profile_id: str, db: SessionDep): # type: ignore
profile = db.get(Profile, profile_id)
if not profile:
raise HTTPException(status_code=404, detail="Profile could not be found")
logger.info(f"delete Profile: {profile_id}")
delete_profile(profile_id=profile_id, db=db)
@router.post("/profiles", status_code=status.HTTP_201_CREATED)
def add_profile(new_profile: ProfileModel, db: SessionDep) -> ProfileResponse:
logger.info(f"add profile {new_profile.username}")
try:
profile: Profile = create_new_profile(new_profile, db)
except:
raise HTTPException(status_code=409, detail="Profile duplicate")
response = to_response(profile)
return response
@@ -1,19 +0,0 @@
from typing import List
from fastapi import APIRouter
from src.db.models.admin import Token
from src.db.session import SessionDep
from src.schema.user.token import TokenResponse, to_response
router = APIRouter()
@router.get("/tokens", response_model=List[TokenResponse])
def get_all_profiles(db: SessionDep) -> List[TokenResponse]:
results: List[TokenResponse] = []
tokens = db.query(Token).all()
for token in tokens:
response = to_response(token)
results.append(response)
return results
+3 -3
View File
@@ -9,17 +9,17 @@ load_dotenv(dotenv_path=env_path)
class Settings: class Settings:
PROJECT_NAME: str = "Kontor" PROJECT_NAME: str = "Kontor"
PROJECT_VERSION: str = "0.3.0" PROJECT_VERSION: str = "0.2.0"
DB_USER: str = os.getenv("DB_USER", "kontor") DB_USER: str = os.getenv("DB_USER", "kontor")
DB_PASSWORD: str = os.getenv("DB_PASSWORD", "kontor") DB_PASSWORD: str = os.getenv("DB_PASSWORD", "kontor")
DB_SERVER: str = os.getenv("DB_SERVER", "postgres") DB_SERVER: str = os.getenv("DB_SERVER", "postgres")
DB_PORT: str = os.getenv("DB_PORT", 5432) DB_PORT: int = int(os.getenv("DB_PORT", 5432))
DB_DBNAME: str = os.getenv("DB_DBNAME", "kontor") DB_DBNAME: str = os.getenv("DB_DBNAME", "kontor")
DATABASE_URL: str = f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_SERVER}:{DB_PORT}/{DB_DBNAME}" DATABASE_URL: str = f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_SERVER}:{DB_PORT}/{DB_DBNAME}"
SECRET_KEY: str = os.getenv("SECRET_KEY", "J6GOtcwC2NJI1l0VkHu20PacPFGTxpirBxWwynoHjsc=") SECRET_KEY: str = os.getenv("SECRET_KEY", "J6GOtcwC2NJI1l0VkHu20PacPFGTxpirBxWwynoHjsc=")
ALGORITHM = "HS256" ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 600 # in mins ACCESS_TOKEN_EXPIRE_MINUTES = 60*24*7 # one week in mins
settings = Settings() settings = Settings()
+10 -5
View File
@@ -8,15 +8,15 @@ LOGGING_CONFIG: dict[str, Any] = {
"formatters": { "formatters": {
"default": { "default": {
"()": "uvicorn.logging.DefaultFormatter", "()": "uvicorn.logging.DefaultFormatter",
"fmt": "%(asctime)s - %(name)s - %(levelprefix)s %(message)s" "fmt": "%(asctime)s - %(name)s - %(levelprefix)s %(message)s",
}, },
"access": { "access": {
"()": "uvicorn.logging.AccessFormatter", "()": "uvicorn.logging.AccessFormatter",
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501 "fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
}, },
"access_file": { "access_file": {
"()": "uvicorn.logging.AccessFormatter", "()": "uvicorn.logging.AccessFormatter",
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501 "fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
"use_colors": False, "use_colors": False,
}, },
}, },
@@ -34,11 +34,16 @@ LOGGING_CONFIG: dict[str, Any] = {
}, },
"loggers": { "loggers": {
"root": {"handlers": ["default"], "level": "INFO", "propagate": False}, "root": {"handlers": ["default"], "level": "INFO", "propagate": False},
"kontor": {"handlers": ["default"], "level": "INFO", "propagate": False},
"uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False}, "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
"uvicorn.error": {"level": "INFO"}, "uvicorn.error": {"level": "INFO"},
"uvicorn.access": {"handlers": ["default"], "level": "INFO", "propagate": False}, "uvicorn.access": {
"handlers": ["default"],
"level": "WARNING",
"propagate": False,
},
}, },
} }
logging.config.dictConfig(LOGGING_CONFIG) logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger(__name__) logger = logging.getLogger("kontor")
-35
View File
@@ -1,35 +0,0 @@
import json
import time
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
from src.core.log_conf import logger
class RequestLoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next) -> Response:
start_time = time.time()
path = request.url.path
if path != "/health":
# Log request info
request_info ={
"method": request.method,
"path": path,
"client_ip": request.client.host if request.client else "unknown"
}
logger.info("Incoming: %s", json.dumps(request_info))
# Process request
response = await call_next(request)
if path != "/health":
# Log response info
duration_ms = (time.time()- start_time)*1000
response_info = {
"status_code": response.status_code,
"duration_ms": round(duration_ms, 2)
}
logger.info("completed: %s", json.dumps(response_info))
return response
+141 -6
View File
@@ -1,17 +1,76 @@
from datetime import datetime import logging
from datetime import timedelta from datetime import datetime, timedelta, timezone
from typing import Optional from typing import Annotated, Dict, List, Optional
import bcrypt
from fastapi import Depends, HTTPException, Request, Security, status
from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
from fastapi.security import OAuth2, OAuth2PasswordBearer, SecurityScopes
from fastapi.security.utils import get_authorization_scheme_param
from jose import JWTError, jwt
from pydantic import ValidationError
from src.core.config import settings from src.core.config import settings
from jose import jwt from src.core.log_conf import logger
from src.db.models.admin import Profile
from src.db.repository.admin import get_profile
from src.db.session import SessionLocal
from src.schema.admin import ProfileModel, TokenData
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="/api/login/token",
scopes={"me": "read", "admin": "read"},
)
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}) # type: ignore
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") # type: ignore # 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
def authenticate_user(username: str, password: str) -> Optional[Profile]:
with SessionLocal() as db:
user = get_profile(username=username, db=db)
logger.debug(user)
if not user:
return None
if bcrypt.checkpw(password.encode(), user.password.encode()):
print("User successful authenticated")
else:
logger.info("Authentication failed!")
return user
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy() to_encode = data.copy()
if expires_delta: if expires_delta:
expire = datetime.utcnow() + expires_delta expire = datetime.now(timezone.utc) + expires_delta
else: else:
expire = datetime.utcnow() + timedelta( expire = datetime.now(timezone.utc) + timedelta(
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
) )
to_encode.update({"exp": expire}) to_encode.update({"exp": expire})
@@ -19,3 +78,79 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM
) )
return encoded_jwt return encoded_jwt
async def get_current_user(
security_scopes: SecurityScopes, token: Annotated[str, Depends(oauth2_scheme)]
):
if security_scopes.scopes:
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
else:
authenticate_value = "Bearer"
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": authenticate_value},
)
try:
payload = jwt.decode(
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
)
username: str = payload.get("sub") # type: ignore
logger.info("username/email extracted is ", username)
if username is None:
raise credentials_exception
scope: str = payload.get("scope", "")
token_scopes: List[str] = scope.split(" ")
token_data = TokenData(scopes=token_scopes, username=username)
except (JWTError, ValidationError):
raise credentials_exception
with SessionLocal() as db:
user = get_profile(username=token_data.username, db=db) # type: ignore
if user is None:
raise credentials_exception
for scope in security_scopes.scopes:
if scope not in token_scopes:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="not enough permissions",
headers={"WWW-Authenticate": authenticate_value},
)
return user
async def get_current_active_user(
current_user: Annotated[Profile, Security(get_current_user, scopes=["me"])],
) -> ProfileModel:
if not current_user.enabled: # type: ignore
raise HTTPException(status_code=400, detail="Inactive user")
user_model = ProfileModel(
username=current_user.user_name,
email=current_user.email, # type: ignore
first_name=current_user.first_name,
last_name=current_user.last_name, # type: ignore
active=current_user.enabled,
) # type: ignore
return user_model
def get_current_user_from_token(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") # type: ignore
logger.info("username/email extracted is ", username)
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
with SessionLocal() as db:
user = get_profile(username=username, db=db)
if user is None:
raise credentials_exception
return user
+35 -36
View File
@@ -1,7 +1,6 @@
from datetime import datetime from datetime import datetime
from typing import List
from sqlalchemy import ForeignKey from sqlalchemy import Column, ForeignKey, Integer, String, Boolean
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
@@ -9,23 +8,23 @@ from src.db.models.base import Base, BaseMixin
class Profile(Base, BaseMixin): class Profile(Base, BaseMixin):
__tablename__ = 'profile' __tablename__ = 'profile'
first_name: Mapped[str] first_name = Column(String)
last_name: Mapped[str] last_name = Column(String)
user_name: Mapped[str] = mapped_column(nullable=False) user_name = Column(String, nullable=False)
email: Mapped[str] email = Column(String)
password: Mapped[str] password = Column(String)
enabled: Mapped[bool] enabled = Column(Boolean)
assignments: Mapped[List["Assignment"]] = relationship(back_populates="profile") assignments = relationship("Assignment")
tokens: Mapped[List["Token"]] = relationship(back_populates="profile") tokens = relationship("Token")
def get_full_name(self) -> str: def get_full_name(self) -> str:
full_name: str = "" full_name = ""
if self.first_name is not None: if self.first_name is not None:
full_name += str(self.first_name) full_name += self.first_name
if self.last_name is not None: if self.last_name is not None:
if len(full_name) > 0: if len(full_name) > 0:
full_name += " " full_name += " "
full_name += str(self.last_name) full_name += self.last_name
return full_name return full_name
def __str__(self): def __str__(self):
@@ -34,42 +33,42 @@ class Profile(Base, BaseMixin):
class Token(Base, BaseMixin): class Token(Base, BaseMixin):
__tablename__ = "token" __tablename__ = "token"
token: Mapped[str] = mapped_column(nullable=False, unique=True) token = Column(String, nullable=False, unique=True)
name: Mapped[str] name = Column(String)
last_used_date: Mapped[datetime] last_used_date: Mapped[datetime] = mapped_column()
enabled: Mapped[bool] enabled = Column(Boolean)
profile_id = mapped_column(ForeignKey("profile.id"), nullable=False) profile_id = Column(String, ForeignKey("profile.id"), nullable=False)
profile: Mapped[Profile] = relationship(back_populates="tokens") profile = relationship("Profile", back_populates="tokens")
class Permission(Base, BaseMixin): class Permission(Base, BaseMixin):
__tablename__ = "permission" __tablename__ = "permission"
name: Mapped[str] = mapped_column(nullable=False) name = Column(String, nullable=False)
assignments: Mapped[List["Assignment"]] = relationship(back_populates="permission") assignments = relationship("Assignment")
class Assignment(Base, BaseMixin): class Assignment(Base, BaseMixin):
__tablename__ = "assignment" __tablename__ = "assignment"
profile_id = mapped_column(ForeignKey("profile.id"), nullable=False) profile_id = Column(String, ForeignKey("profile.id"), nullable=False)
profile: Mapped[Profile] = relationship(back_populates="assignments") profile = relationship("Profile", back_populates="assignments")
permission_id = mapped_column(ForeignKey("permission.id"), nullable=False) permission_id = Column(String, ForeignKey("permission.id"), nullable=False)
permission: Mapped[Permission] = relationship(back_populates="assignments") permission = relationship("Permission", back_populates="assignments")
class MailAccount(Base, BaseMixin): class MailAccount(Base, BaseMixin):
__tablename__ = "mail_account" __tablename__ = "mail_account"
host: Mapped[str] host = Column(String)
port: Mapped[int] port = Column(Integer)
protocol: Mapped[str] protocol = Column(String)
user_name: Mapped[str] user_name = Column(String)
password: Mapped[str] password = Column(String)
start_tls: Mapped[bool] start_tls = Column(Boolean)
class Mail(Base, BaseMixin): class Mail(Base, BaseMixin):
__tablename__ = "mail" __tablename__ = "mail"
folder: Mapped[str] folder: Mapped[str] = mapped_column()
subject: Mapped[str] subject: Mapped[str] = mapped_column()
body: Mapped[str] body: Mapped[str] = mapped_column()
sent_date: Mapped[datetime] sent_date: Mapped[datetime] = mapped_column()
received_date: Mapped[datetime] received_date: Mapped[datetime] = mapped_column()
+8 -5
View File
@@ -1,8 +1,7 @@
from typing import Optional
import uuid import uuid
from datetime import datetime from datetime import datetime
from sqlalchemy import func from sqlalchemy import func, Column, String, Boolean
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
@@ -11,17 +10,21 @@ class Base(DeclarativeBase):
class BaseMixin: class BaseMixin:
#id = Column(String, 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=str(uuid.uuid4()))
# 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: Mapped[datetime] = mapped_column(default=func.now()) last_modified_date: Mapped[datetime] = mapped_column(default=func.now())
# version = Column(Integer)
version: Mapped[int] = mapped_column(default=0) version: Mapped[int] = mapped_column(default=0)
class BaseVideoMixin: class BaseVideoMixin:
cloud_link = Column(String) cloud_link = Column(String, nullable=True)
file_name = Column(String) file_name = Column(String, nullable=True)
path = Column(String) path = Column(String)
review = Column(Boolean) review = Column(Boolean)
title = Column(String) title = Column(String)
url = Column(String, unique=True) url = Column(String, nullable=True)
should_download = Column(Boolean) should_download = Column(Boolean)
+22 -24
View File
@@ -1,52 +1,50 @@
from typing import List
from sqlalchemy import Column, ForeignKey, Integer, String from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import relationship
from src.db.models.base import Base, BaseMixin from src.db.models.base import Base, BaseMixin
class Article(Base, BaseMixin): class Article(Base, BaseMixin):
__tablename__ = 'article' __tablename__ = 'article'
title: Mapped[str] = mapped_column(unique=True) title = Column(String, unique=True)
article_authors = relationship("ArticleAuthor") article_authors = relationship("ArticleAuthor")
class Author(Base, BaseMixin): class Author(Base, BaseMixin):
__tablename__ = 'author' __tablename__ = 'author'
first_name: Mapped[str] first_name = Column(String)
last_name: Mapped[str] last_name = Column(String)
article_authors: Mapped[List["ArticleAuthor"]] = relationship(back_populates="author") article_authors = relationship("ArticleAuthor")
book_authors: Mapped[List["BookAuthor"]] = relationship(back_populates="author") book_authors = relationship("BookAuthor")
class BookshelfPublisher(Base, BaseMixin): class BookshelfPublisher(Base, BaseMixin):
__tablename__ = 'bookshelf_publisher' __tablename__ = 'bookshelf_publisher'
name: Mapped[str] = mapped_column(unique=True) name = Column(String, unique=True)
books: Mapped[List["Book"]] = relationship(back_populates="publisher") books = relationship("Book")
class Book(Base, BaseMixin): class Book(Base, BaseMixin):
__tablename__ = 'book' __tablename__ = 'book'
isbn: Mapped[str] = mapped_column(unique=True) isbn = Column(String, unique=True)
title: Mapped[str] title = Column(String)
year: Mapped[int] = mapped_column(nullable=False) year = Column(Integer, nullable=False)
publisher_id: Mapped[str] = mapped_column(ForeignKey("bookshelf_publisher.id"), nullable=False) publisher_id = Column(String, ForeignKey('bookshelf_publisher.id'), nullable=False)
publisher: Mapped[BookshelfPublisher] = relationship(back_populates="books") publisher = relationship('BookshelfPublisher', back_populates="books")
book_authors: Mapped[List["BookAuthor"]] = relationship(back_populates="book") book_authors = relationship("BookAuthor")
class ArticleAuthor(Base, BaseMixin): class ArticleAuthor(Base, BaseMixin):
__tablename__ = 'article_author' __tablename__ = 'article_author'
article_id: Mapped[str] = mapped_column(ForeignKey("article.id"), nullable=False) article_id = Column(String, ForeignKey('article.id'), nullable=False)
article: Mapped[Article] = relationship(back_populates="article_authors") article = relationship('Article', back_populates="article_authors")
author_id: Mapped[str] = mapped_column(ForeignKey("author.id"), nullable=False) author_id = Column(String, ForeignKey('author.id'), nullable=False)
author: Mapped[Author] = relationship(back_populates="article_authors") author = relationship('Author', back_populates="article_authors")
class BookAuthor(Base, BaseMixin): class BookAuthor(Base, BaseMixin):
__tablename__ = 'book_author' __tablename__ = 'book_author'
author_id: Mapped[str] = mapped_column(ForeignKey("author.id"), nullable=False) author_id = Column(String, ForeignKey('author.id'), nullable=False)
author: Mapped[Author] = relationship(back_populates="book_authors") author = relationship('Author', back_populates="book_authors")
book_id: Mapped[str] = mapped_column(ForeignKey("book.id"), nullable=False) book_id = Column(String, ForeignKey('book.id'), nullable=False)
book: Mapped[Book] = relationship(back_populates="book_authors") book = relationship('Book', back_populates="book_authors")
+61 -91
View File
@@ -2,7 +2,7 @@ import uuid
from datetime import datetime from datetime import datetime
from typing import AnyStr, Dict, List, Optional, Any from typing import AnyStr, Dict, List, Optional, Any
from natsort import natsorted from natsort import natsorted
from sqlalchemy import ForeignKey, func from sqlalchemy import Column, ForeignKey, Integer, String, Boolean, func
from sqlalchemy.orm import relationship, Mapped, mapped_column from sqlalchemy.orm import relationship, Mapped, mapped_column
from src.db.models.base import Base, BaseMixin from src.db.models.base import Base, BaseMixin
@@ -14,12 +14,12 @@ class Publisher(Base):
created_date: Mapped[datetime] = mapped_column(default=func.now()) created_date: Mapped[datetime] = mapped_column(default=func.now())
last_modified_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) version: Mapped[int] = mapped_column(default=0)
name: Mapped[str] = mapped_column(unique=True) name = Column(String, unique=True)
weblink: Mapped[Optional[str]] weblink = Column(String, nullable=True)
parent_publisher_id: Mapped[Optional[str]] = mapped_column(ForeignKey('publisher.id')) parent_publisher_id: Mapped[Optional[str]] = mapped_column(ForeignKey('publisher.id'))
parent_publisher: Mapped[Optional['Publisher']] = relationship(back_populates="imprints", remote_side=[id]) parent_publisher: Mapped[Optional['Publisher']] = relationship("Publisher", back_populates="imprints", remote_side=[id])
imprints: Mapped[List['Publisher']] = relationship(back_populates="parent_publisher") imprints: Mapped[List['Publisher']] = relationship('Publisher', back_populates="parent_publisher")
comics: Mapped[List["Comic"]] = relationship(back_populates="publisher") comics = relationship("Comic")
def __repr__(self): def __repr__(self):
return f'Publisher({self.id} {self.name})' return f'Publisher({self.id} {self.name})'
@@ -30,17 +30,17 @@ class Publisher(Base):
class Comic(Base, BaseMixin): class Comic(Base, BaseMixin):
__tablename__ = 'comic' __tablename__ = 'comic'
title: Mapped[str] = mapped_column(unique=True) title = Column(String, unique=True)
publisher_id: Mapped[str] = mapped_column(ForeignKey('publisher.id'), nullable=False) publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
publisher: Mapped[Publisher] = relationship(back_populates="comics") publisher = relationship("Publisher", back_populates="comics")
current_order: Mapped[bool] current_order = Column(Boolean)
completed: Mapped[bool] completed = Column(Boolean)
weblink: Mapped[Optional[str]] weblink = Column(String, nullable=True)
issues: Mapped[List["Issue"]] = relationship(back_populates="comic", order_by="Issue.issue_number") issues = relationship("Issue", order_by="Issue.issue_number")
story_arcs: Mapped[List["StoryArc"]] = relationship(back_populates="comic") story_arcs = relationship("StoryArc")
trade_paperbacks: Mapped[List["TradePaperback"]] = relationship(back_populates="comic") trade_paperbacks = relationship("TradePaperback")
volumes: Mapped[List["Volume"]] = relationship(back_populates="comic") volumes = relationship("Volume")
comic_works: Mapped[List["ComicWork"]] = relationship(back_populates="comic") comic_works = relationship("ComicWork")
def __repr__(self): def __repr__(self):
return f'Comic({self.id} {self.version} {self.title} {self.publisher.name})' return f'Comic({self.id} {self.version} {self.title} {self.publisher.name})'
@@ -66,69 +66,51 @@ class Comic(Base, BaseMixin):
class Volume(Base, BaseMixin): class Volume(Base, BaseMixin):
__tablename__ = "volume" __tablename__ = "volume"
name: Mapped[str] = mapped_column(nullable=False) name = Column(String, nullable=False)
comic_id: Mapped[str] = mapped_column(ForeignKey("comic.id"), nullable=False) comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
comic: Mapped[Comic] = relationship(back_populates="volumes") comic = relationship("Comic", back_populates="volumes")
story_arcs: Mapped[List["StoryArc"]] = relationship(back_populates="volume") story_arcs = relationship("StoryArc")
issues: Mapped[List["Issue"]] = relationship(back_populates="volume") issues = relationship("Issue")
class TradePaperback(Base, BaseMixin): class TradePaperback(Base, BaseMixin):
__tablename__ = "trade_paperback" __tablename__ = "trade_paperback"
name: Mapped[str] = mapped_column(nullable=False) name = Column(String, nullable=False)
issue_start: Mapped[int] issue_start = Column(Integer)
issue_end: Mapped[int] issue_end = Column(Integer)
comic_id: Mapped[str] = mapped_column(ForeignKey("comic.id"), nullable=False) comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
comic: Mapped[Comic] = relationship(back_populates="trade_paperbacks") comic = relationship("Comic", back_populates="trade_paperbacks")
class StoryArc(Base, BaseMixin): class StoryArc(Base, BaseMixin):
__tablename__ = "story_arc" __tablename__ = "story_arc"
name: Mapped[str] = mapped_column(nullable=False) name = Column(String, nullable=False)
comic_id: Mapped[str] = mapped_column(ForeignKey("comic.id"), nullable=False) comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
comic: Mapped[Comic] = relationship(back_populates="story_arcs") comic = relationship("Comic", back_populates="story_arcs")
volume_id: Mapped[str] = mapped_column(ForeignKey("volume.id"), nullable=True) volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
volume: Mapped[Volume] = relationship(back_populates="story_arcs") volume = relationship("Volume", back_populates="story_arcs")
issues: Mapped[List["Issue"]] = relationship(back_populates="story_arc") issues = relationship("Issue")
class Issue(Base, BaseMixin): class Issue(Base, BaseMixin):
__tablename__ = "issue" __tablename__ = "issue"
issue_number: Mapped[str] issue_number = Column(String)
title: Mapped[Optional[str]] title = Column(String, nullable=True)
published_on: Mapped[Optional[datetime]] published_on: Mapped[datetime] = mapped_column(nullable=True)
in_stock: Mapped[bool] in_stock = Column(Boolean)
is_read: Mapped[bool] is_read = Column(Boolean)
comic_id: Mapped[str] = mapped_column(ForeignKey("comic.id"), nullable=False) comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
comic: Mapped[Comic] = relationship(back_populates="issues") comic = relationship("Comic", back_populates="issues")
volume_id: Mapped[str] = mapped_column(ForeignKey("volume.id"), nullable=True) volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
volume: Mapped[Volume] = relationship(back_populates="issues") volume = relationship("Volume", back_populates="issues")
story_arc_id: Mapped[str] = mapped_column(ForeignKey("story_arc.id"), nullable=True) story_arc_id = Column(String, ForeignKey("story_arc.id"), nullable=True)
story_arc: Mapped[StoryArc] = relationship(back_populates="issues") story_arc = relationship("StoryArc", back_populates="issues")
issue_works: Mapped[List["IssueWork"]] = relationship(back_populates="issue") issue_works = relationship("IssueWork")
def get_full_title(self) -> AnyStr: def get_full_title(self) -> str:
full_title: AnyStr = self.issue_number full_title: str = str(self.issue_number)
if self.title: if self.title:
full_title += ": " + self.title full_title += str(": " + 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
def get_full_title(self) -> AnyStr:
full_title: AnyStr = self.issue_number
if self.title:
full_title += ": " + self.title
return full_title return full_title
def get_artists(self) -> Dict[Any, List[Any]]: def get_artists(self) -> Dict[Any, List[Any]]:
@@ -145,10 +127,10 @@ class Issue(Base, BaseMixin):
class Artist(Base, BaseMixin): class Artist(Base, BaseMixin):
__tablename__ = "artist" __tablename__ = "artist"
name: Mapped[str] = mapped_column(nullable=False) name = Column(String, nullable=False)
weblink: Mapped[str] = mapped_column(nullable=True) weblink = Column(String, nullable=True)
comic_works: Mapped[List["ComicWork"]] = relationship(back_populates="artist") comic_works = relationship("ComicWork")
issue_works: Mapped[List["IssueWork"]] = relationship(back_populates="artist") issue_works = relationship("IssueWork")
def get_comics(self) -> Dict[Any, List[Comic]]: def get_comics(self) -> Dict[Any, List[Comic]]:
works: Dict[Any, List[Comic]] = {} works: Dict[Any, List[Comic]] = {}
@@ -164,20 +146,9 @@ class Artist(Base, BaseMixin):
class WorkType(Base, BaseMixin): class WorkType(Base, BaseMixin):
__tablename__ = "worktype" __tablename__ = "worktype"
name: Mapped[str] = mapped_column(nullable=False, unique=True) name = Column(String, nullable=False, unique=True)
comic_works: Mapped[List["ComicWork"]] = relationship(back_populates="work_type") comic_works = relationship("ComicWork")
issue_works: Mapped[List["IssueWork"]] = relationship(back_populates="work_type") issue_works = relationship("IssueWork")
def get_artists(self) -> Dict[str, List[Artist]]:
works: Dict[str, List[Artist]] = {}
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 get_artists(self) -> Dict[str, List[str]]: def get_artists(self) -> Dict[str, List[str]]:
works: Dict[str, List[str]] = {} works: Dict[str, List[str]] = {}
@@ -199,12 +170,12 @@ class WorkType(Base, BaseMixin):
class ComicWork(Base, BaseMixin): class ComicWork(Base, BaseMixin):
__tablename__ = "comic_work" __tablename__ = "comic_work"
comic_id: Mapped[str] = mapped_column(ForeignKey("comic.id"), nullable=False) comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
comic: Mapped[Comic] = relationship(back_populates="comic_works") comic = relationship("Comic", back_populates="comic_works")
artist_id: Mapped[str] = mapped_column(ForeignKey("artist.id"), nullable=False) artist_id = Column(String, ForeignKey("artist.id"), nullable=False)
artist: Mapped[Artist] = relationship(back_populates="comic_works") artist = relationship("Artist", back_populates="comic_works")
work_type_id: Mapped[str] = mapped_column(ForeignKey("worktype.id"), nullable=False) work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
work_type: Mapped[WorkType] = relationship(back_populates="comic_works") work_type = relationship("WorkType", back_populates="comic_works")
class IssueWork(Base, BaseMixin): class IssueWork(Base, BaseMixin):
@@ -215,4 +186,3 @@ class IssueWork(Base, BaseMixin):
artist = relationship("Artist", back_populates="issue_works") artist = relationship("Artist", back_populates="issue_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="issue_works") work_type = relationship("WorkType", back_populates="issue_works")
+74 -51
View File
@@ -3,96 +3,119 @@ import re
import subprocess import subprocess
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import List, Optional
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from sqlalchemy import ForeignKey from sqlalchemy import Column, String, ForeignKey, Boolean
from sqlalchemy.orm import Mapped, relationship, mapped_column from sqlalchemy.orm import relationship
from src.db.models.base import Base, BaseMixin, BaseVideoMixin from src.db.models.base import Base, BaseMixin, BaseVideoMixin
class MediaFile(Base, BaseMixin, BaseVideoMixin): class MediaFile(Base, BaseMixin, BaseVideoMixin):
""" __tablename__ = 'media_file'
MediaFile represents video link. media_actor_files = relationship("MediaActorFile")
"""
__tablename__ = "media_file"
media_actor_files: Mapped[List["MediaActorFile"]] = relationship(
back_populates="media_file"
)
def __repr__(self): def __repr__(self):
return f"MediaFile({self.id} {self.title} {self.title})" return f'MediaFile({self.id} {self.title} {self.title})'
def __str__(self): def __str__(self):
return f"{self.title}({self.id})" return f'{self.title}({self.id})'
def update_title(self): def update_title(self) -> None:
""" logging.info(f"update title for {self.url}")
Update title from url. try:
""" r = requests.get(str(self.url))
soup = BeautifulSoup(r.content, "html.parser")
title = soup.title.get_text() # type: ignore
self.title = title
self.review = False
except:
self.title = None
self.review = True
self.last_modified_date = datetime.now()
def download_file(self, download_dir: str, dl_tool: str):
logging.info(f"download file for {self.url} to {download_dir}")
result = subprocess.run([dl_tool, self.url], cwd=download_dir, capture_output=True, text=True) # type: ignore
if result.returncode == 0:
output = result.stdout
output = re.sub(' +', ' ', output)
lines_list = output.splitlines()
file_name = self.__parse_output__(lines_list)
if file_name is None:
self.review = True
self.should_download = True
self.file_name = None
else:
download_file = Path(file_name)
self.should_download = False
self.file_name = download_file.name
self.cloud_link = str(download_file.absolute())
self.last_modified_date = datetime.now()
def __parse_output__(self, lines_list):
self.file_name = None
for line in lines_list:
if 'has already been downloaded' in line:
end_len = len(' has already been downloaded')
self.file_name = line[11:-end_len]
if 'Destination' in line:
line_len = len(line)
start_len = len('[download] Destination: ')
file_len = line_len - start_len
self.file_name = line[-file_len:]
return self.file_name
class MediaActor(Base, BaseMixin): class MediaActor(Base, BaseMixin):
__tablename__ = 'media_actor' __tablename__ = 'media_actor'
name = Column(String) name = Column(String)
url = Column(String, unique=True, nullable=True)
media_actor_files = relationship("MediaActorFile") media_actor_files = relationship("MediaActorFile")
def __repr__(self) -> str: def __repr__(self) -> str:
return f"MediaActor({self.id} {self.name} {self.url})" return f'MediaActor({self.id} {self.name} {self.url})'
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.url}({self.id})" return f'{self.url}({self.id})'
class MediaActorFile(Base, BaseMixin): class MediaActorFile(Base, BaseMixin):
__tablename__ = "media_actor_file" __tablename__ = 'media_actor_file'
media_actor_id: Mapped[str] = mapped_column( media_actor_id = Column(String, ForeignKey("media_actor.id"), nullable=False)
ForeignKey("media_actor.id"), nullable=False media_actor = relationship("MediaActor", back_populates="media_actor_files")
) media_file_id = Column(String, ForeignKey("media_file.id"), nullable=True)
media_actor: Mapped[MediaActor] = relationship(back_populates="media_actor_files") media_file = relationship("MediaFile", back_populates="media_actor_files")
media_file_id: Mapped[str] = mapped_column(
ForeignKey("media_file.id"), nullable=True
)
media_file: Mapped[MediaFile] = relationship(back_populates="media_actor_files")
def __repr__(self): def __repr__(self):
return f"MediaActorFile({self.id} {self.media_actor_id} {self.media_file_id})" return f'MediaActorFile({self.id} {self.media_actor_id} {self.media_file_id})'
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.id} {self.media_actor_id} {self.media_file_id}" return f'{self.id} {self.media_actor_id} {self.media_file_id}'
class MediaArticle(Base, BaseMixin): class MediaArticle(Base, BaseMixin):
__tablename__ = "media_article" __tablename__ = 'media_article'
review: Mapped[bool] review = Column(Boolean)
title: Mapped[str] title = Column(String)
url: Mapped[str] = mapped_column(unique=True) url = Column(String, unique=True)
class MediaVideo(Base, BaseMixin): class MediaVideo(Base, BaseMixin):
""" __tablename__ = 'media_video'
MediaFile represents video link. cloud_link = Column(String)
""" file_name = Column(String)
path = Column(String)
__tablename__ = "media_video" review = Column(Boolean)
cloud_link: Mapped[str] title = Column(String)
file_name: Mapped[str] url = Column(String, unique=True)
path: Mapped[str] should_download = Column(Boolean)
review: Mapped[bool]
title: Mapped[str]
url: Mapped[str] = mapped_column(unique=True)
should_download: Mapped[bool]
def __repr__(self): def __repr__(self):
return f"MediaFile({self.id} {self.title} {self.url})" return f'MediaFile({self.id} {self.title} {self.url})'
def __str__(self): def __str__(self):
if self.title is None: if self.title is None:
return f"{self.url}({self.id})" return f'{self.url}({self.id})'
else: else:
return f'{self.title}({self.id})' return f'{self.title}({self.id})'
+43 -44
View File
@@ -1,6 +1,5 @@
from typing import List from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Boolean
from sqlalchemy import ForeignKey, UniqueConstraint from sqlalchemy.orm import relationship
from sqlalchemy.orm import relationship, mapped_column, Mapped
from src.db.models.base import Base, BaseMixin from src.db.models.base import Base, BaseMixin
@@ -10,18 +9,18 @@ class Sport(Base, BaseMixin):
__table_args__ = ( __table_args__ = (
UniqueConstraint("name"), UniqueConstraint("name"),
) )
name: Mapped[str] = mapped_column(nullable=False, index=True, unique=True) name = Column(String, nullable=False, index=True, unique=True)
teams: Mapped[List["Team"]] = relationship(back_populates="sport") teams = relationship("Team")
positions: Mapped[List["FieldPosition"]] = relationship(back_populates="sport") positions = relationship("FieldPosition")
class Team(Base, BaseMixin): class Team(Base, BaseMixin):
__tablename__ = "team" __tablename__ = "team"
name: Mapped[str] = mapped_column(nullable=False, index=True, unique=True) name = Column(String, nullable=False, index=True, unique=True)
short_name: Mapped[str] = mapped_column(nullable=False) short_name = Column(String, nullable=False, )
sport_id: Mapped[str] = mapped_column(ForeignKey("sport.id"), nullable=False) sport_id = Column(String, ForeignKey("sport.id"), nullable=False)
sport: Mapped[Sport] = relationship(back_populates="teams") sport = relationship("Sport", back_populates="teams")
roosters: Mapped[List["Rooster"]] = relationship(back_populates="team") roosters = relationship("Rooster")
class FieldPosition(Base, BaseMixin): class FieldPosition(Base, BaseMixin):
@@ -30,11 +29,11 @@ class FieldPosition(Base, BaseMixin):
UniqueConstraint("name", "sport_id"), UniqueConstraint("name", "sport_id"),
UniqueConstraint("short_name", "sport_id"), UniqueConstraint("short_name", "sport_id"),
) )
name: Mapped[str] = mapped_column(nullable=False, index=True) name = Column(String, nullable=False, index=True)
short_name: Mapped[str] = mapped_column(nullable=False) short_name = Column(String, nullable=False)
sport_id: Mapped[str] = mapped_column(ForeignKey("sport.id"), nullable=False, index=True) sport_id = Column(String, ForeignKey("sport.id"), nullable=False, index=True)
sport: Mapped[Sport] = relationship(back_populates="positions") sport = relationship("Sport", back_populates="positions")
roosters: Mapped[List["Rooster"]] = relationship(back_populates="position") roosters = relationship("Rooster")
class Player(Base, BaseMixin): class Player(Base, BaseMixin):
@@ -42,9 +41,9 @@ class Player(Base, BaseMixin):
__table_args__ = ( __table_args__ = (
UniqueConstraint("first_name", "last_name"), UniqueConstraint("first_name", "last_name"),
) )
first_name: Mapped[str] = mapped_column(nullable=False, index=True) first_name = Column(String, nullable=False, index=True)
last_name: Mapped[str] = mapped_column(nullable=False, index=True) last_name = Column(String, nullable=False, index=True)
roosters: Mapped[List["Rooster"]] = relationship(back_populates="player") roosters = relationship("Rooster")
def get_full_name(self) -> str: def get_full_name(self) -> str:
return f"{self.last_name}, {self.first_name}" return f"{self.last_name}, {self.first_name}"
@@ -55,21 +54,21 @@ class Rooster(Base, BaseMixin):
__table_args__ = ( __table_args__ = (
UniqueConstraint("year", "team_id", "player_id", "position_id"), UniqueConstraint("year", "team_id", "player_id", "position_id"),
) )
year: Mapped[int] year = Column(Integer)
team_id: Mapped[str] = mapped_column(ForeignKey("team.id"), nullable=False, index=True) team_id = Column(String, ForeignKey("team.id"), nullable=False, index=True)
team: Mapped[Team] = relationship(back_populates="roosters") team = relationship("Team", back_populates="roosters")
player_id: Mapped[str] = mapped_column(ForeignKey("player.id"), nullable=False, index=True) player_id = Column(String, ForeignKey("player.id"), nullable=False, index=True)
player: Mapped[Player] = relationship(back_populates="roosters") player = relationship("Player", back_populates="roosters")
position_id: Mapped[str] = mapped_column(ForeignKey("field_position.id"), nullable=False, index=True) position_id = Column(String, ForeignKey("field_position.id"), nullable=False, index=True)
position: Mapped[FieldPosition] = relationship(back_populates="roosters") position = relationship("FieldPosition", back_populates="roosters")
cards: Mapped[List["Card"]] = relationship(back_populates="rooster") cards = relationship("Card")
class Vendor(Base, BaseMixin): class Vendor(Base, BaseMixin):
__tablename__ = "vendor" __tablename__ = "vendor"
name: Mapped[str] = mapped_column(nullable=False, unique=True, index=True) name = Column(String, nullable=False, unique=True, index=True)
card_sets: Mapped[List["CardSet"]] = relationship(back_populates="vendor") card_sets = relationship("CardSet")
cards: Mapped[List["Card"]] = relationship(back_populates="vendor") cards = relationship("Card")
class CardSet(Base, BaseMixin): class CardSet(Base, BaseMixin):
@@ -77,12 +76,12 @@ class CardSet(Base, BaseMixin):
__table_args__ = ( __table_args__ = (
UniqueConstraint("name", "vendor_id"), UniqueConstraint("name", "vendor_id"),
) )
name: Mapped[str] = mapped_column(index=True) name = Column(String, index=True)
parallel_set: Mapped[bool] parallel_set = Column(Boolean)
insert_set: Mapped[bool] insert_set = Column(Boolean)
vendor_id: Mapped[str] = mapped_column(ForeignKey("vendor.id"), nullable=False, index=True) vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False, index=True)
vendor: Mapped[Vendor] = relationship(back_populates="card_sets") vendor = relationship("Vendor", back_populates="card_sets")
cards: Mapped[List["Card"]] = relationship(back_populates="card_set") cards = relationship("Card")
class Card(Base, BaseMixin): class Card(Base, BaseMixin):
@@ -90,11 +89,11 @@ class Card(Base, BaseMixin):
__table_args__ = ( __table_args__ = (
UniqueConstraint("card_number", "year", "vendor_id", "card_set_id"), UniqueConstraint("card_number", "year", "vendor_id", "card_set_id"),
) )
card_number: Mapped[int] = mapped_column(index=True) card_number = Column(Integer, index=True)
year: Mapped[int] = mapped_column(index=True) year = Column(Integer, index=True)
card_set_id: Mapped[str] = mapped_column(ForeignKey("card_set.id"), nullable=False) card_set_id = Column(String, ForeignKey("card_set.id"), nullable=False)
card_set: Mapped[CardSet] = relationship(back_populates="cards") card_set = relationship("CardSet", back_populates="cards")
rooster_id: Mapped[str] = mapped_column(ForeignKey("rooster.id"), nullable=False) rooster_id = Column(String, ForeignKey("rooster.id"), nullable=False)
rooster: Mapped[Rooster] = relationship(back_populates="cards") rooster = relationship("Rooster", back_populates="cards")
vendor_id: Mapped[str] = mapped_column(ForeignKey("vendor.id"), nullable=False) vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False)
vendor: Mapped[Vendor] = relationship(back_populates="cards") vendor = relationship("Vendor", back_populates="cards")
+2 -6
View File
@@ -1,14 +1,10 @@
from typing import AnyStr from typing import AnyStr, Optional
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from src.db.models.admin import Profile from src.db.models.admin import Profile
def get_profile(username: AnyStr, db: Session): def get_profile(username: AnyStr, db: Session) -> Optional[Profile]:
profile = db.query(Profile).filter(Profile.email == username).first() profile = db.query(Profile).filter(Profile.email == username).first()
return profile return profile
def is_database_empty(db: Session) -> bool:
profiles = db.query(Profile).all()
return len(profiles) == 0
+36 -9
View File
@@ -1,19 +1,46 @@
import uuid
from typing import List, Optional
from sqlalchemy.orm import Session
from src.core.log_conf import logger
from src.db.models.comic import Artist from src.db.models.comic import Artist
from src.schema.comics.artist import ArtistDetailResponse from src.schema.comics.artist import AddArtist
from src.schema.comics.artist_details import ArtistDetailResponse, ArtistWorktypeComicResponse, ArtistWorktypeIssueResponse
from src.schema.comics.comic import ComicResponse
from src.schema.comics.worktype import WorktypeResponse
def get_artist_details(artist: Artist) -> ArtistDetailResponse: def get_artist_details(artist: Artist) -> ArtistDetailResponse:
works = {} comic_works: List[ArtistWorktypeComicResponse] = []
comic_works_map = {}
for work in artist.comic_works: for work in artist.comic_works:
work_type = work.work_type.name worktype_id = work.work_type.id
comic_title = work.comic.title if worktype_id in comic_works_map:
if work_type in works: comic = ComicResponse(id=work.comic.id, title=work.comic.title, completed=work.comic.completed)
works[work_type].append(comic_title) comic_works_map[worktype_id].comics.append(comic)
else: else:
works[work_type] = [comic_title] comic_works_map[worktype_id] = ArtistWorktypeComicResponse(
worktype=WorktypeResponse(id=worktype_id, name=work.work_type.name),
comics=[ComicResponse(id=work.comic.id, title=work.comic.title, completed=work.comic.completed)]
)
for value in comic_works_map.values():
comic_works.append(value)
issue_works: List[ArtistWorktypeIssueResponse] = []
response = ArtistDetailResponse( response = ArtistDetailResponse(
id=artist.id, id=artist.id,
name=artist.name, name=str(artist.name),
works=works weblink=str(artist.weblink),
comic_works=comic_works,
issue_works=issue_works,
) )
return response return response
def update_artist(add_artist: AddArtist, artist_id: str, db: Session) -> Artist:
logger.info("update artist")
artist: Optional[Artist] = db.get(Artist, artist_id)
artist.name = add_artist.name
db.add(artist)
db.commit()
db.refresh(artist)
return artist
+68 -15
View File
@@ -1,34 +1,87 @@
from typing import List, Type, AnyStr from typing import Dict, List
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from src.core.log_conf import logger from src.core.log_conf import logger
from src.db.models.comic import Comic, Issue from src.db.models.comic import Comic, Issue
from src.schema.comics.comic import ComicSchema from src.schema.comics.artist import ArtistResponse
from src.schema.comics.issue import IssueDetailsResponse from src.schema.comics.comic import ComicResponse, ComicSchema
from src.schema.comics.comic_details import ComicDetailsResponse, ComicWorktypeArtistResponse
from src.schema.comics.issue import IssueResponse
from src.schema.comics.issue_details import IssueDetailsResponse
from src.schema.comics.publisher import PublisherResponse
from src.schema.comics.volume import VolumeResponse
from src.schema.comics.worktype import WorktypeResponse
def list_comics(db: Session) -> List[Type[Comic]]: def list_comics(db: Session) -> List[Comic]:
comics = db.query(Comic).all() comics = db.query(Comic).all()
return comics return comics
def get_issue_details(issue: Issue) -> IssueDetailsResponse: def get_issue_details(issue: Issue) -> IssueDetailsResponse:
volume = None
if issue.volume:
volume = volume_to_response(issue.volume)
response = IssueDetailsResponse( response = IssueDetailsResponse(
id=issue.id, id=issue.id,
issue_number=issue.issue_number, issue_number=str(issue.issue_number),
in_stock=issue.in_stock, in_stock=bool(issue.in_stock),
is_read=issue.is_read, is_read=bool(issue.is_read),
comic_id=issue.comic_id, comic=ComicResponse(id=issue.comic.id, title=issue.comic.title, completed=issue.comic.completed),
volume_id=issue.volume_id volume=VolumeResponse(id=issue.volume.id, name=issue.volume.name)
) )
return response return response
def update_comic(comic: ComicSchema, comic_id: AnyStr, db: Session) -> type[Comic] | None: def update_comic(comic: ComicSchema, comic_id: str, db: Session) -> type[Comic] | None:
logger.info(f"update_comic: {comic} with {comic_id}") logger.info(f"update_comic: {comic} with {comic_id}")
comic = db.get(Comic, comic_id) comic = db.get(Comic, comic_id) # type: ignore
return comic return comic # type: ignore
def get_short_info(comic: Comic) -> ComicResponse:
response = ComicResponse(
id=comic.id,
title=str(comic.title),
completed=bool(comic.completed == 1)
)
return response
def get_comic_details(comic: Comic) -> ComicDetailsResponse:
volumes: List[VolumeResponse] = []
for volume in comic.volumes:
volumes.append(VolumeResponse(id=volume.id, name=volume.name))
issues: List[IssueResponse] = []
for issue in comic.issues:
issues.append(IssueResponse(
id=issue.id,
issue_number=issue.issue_number,
in_stock=issue.in_stock,
is_read=issue.is_read
))
works: List[ComicWorktypeArtistResponse] = []
works_map: Dict[str, ComicWorktypeArtistResponse] = {}
for work in comic.comic_works:
worktype_id = work.work_type.id
if worktype_id in works_map:
artist = ArtistResponse(id=work.artist.id, name=work.artist.name)
works_map[worktype_id].artists.append(artist)
logger.info(f"add artist to response map: {artist} -> {works_map}")
print(f"add artist to response map: {artist} -> {works_map}")
else:
works_map[worktype_id] = ComicWorktypeArtistResponse(
worktype=WorktypeResponse(id=worktype_id, name=work.work_type.name),
artists=[ArtistResponse(id=work.artist.id, name=work.artist.name)]
)
for value in works_map.values():
works.append(value)
response = ComicDetailsResponse(
id=str(comic.id),
created=str(comic.created_date),
title=str(comic.title),
completed=bool(comic.completed),
current_order=bool(comic.current_order),
weblink=str(comic.weblink),
publisher=PublisherResponse(id=comic.publisher.id, name=comic.publisher.name),
issues=issues,
volumes=volumes,
works=works
)
return response
@@ -1,6 +1,6 @@
import uuid import uuid
from datetime import datetime from datetime import datetime
from typing import AnyStr, Optional from typing import AnyStr
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -22,12 +22,11 @@ def create_new_worktype(work: AddWorkType, db: Session) -> WorkType:
return worktype return worktype
def update_worktype(work: AddWorkType, worktype_id: AnyStr, db: Session) -> Optional[WorkType]: def update_worktype(work: AddWorkType, worktype_id: AnyStr, db: Session) -> WorkType:
logger.info("update worktype") logger.info("update worktype")
worktype: Optional[WorkType] = db.get(WorkType, worktype_id) worktype = db.get(WorkType, worktype_id)
if worktype is not None: worktype.name = work.worktype
worktype.name = work.worktype db.add(worktype)
db.add(worktype) db.commit()
db.commit() db.refresh(worktype)
db.refresh(worktype)
return worktype return worktype
+53 -7
View File
@@ -1,9 +1,9 @@
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import AnyStr
import uuid import uuid
from datetime import datetime from datetime import datetime
from src.core.log_conf import logger from src.core.log_conf import logger
from src.db.models.media import MediaFile, MediaVideo from src.db.models.media import MediaActor, MediaActorFile, MediaFile, MediaVideo
from src.schema.media.actor import Actor
from src.webapps.media.forms import AddLinkForm from src.webapps.media.forms import AddLinkForm
@@ -11,22 +11,22 @@ def create_new_video(video: AddLinkForm, db: Session) -> MediaVideo:
print(video.url) print(video.url)
media_video = MediaVideo() media_video = MediaVideo()
media_video.id = str(uuid.uuid4()) media_video.id = str(uuid.uuid4())
media_video.url = video.url media_video.url = video.url # type: ignore
media_video.created_date = datetime.now() media_video.created_date = datetime.now()
media_video.last_modified_date = datetime.now() media_video.last_modified_date = datetime.now()
media_video.review = True media_video.review = True # type: ignore
media_video.should_download = True media_video.should_download = True # type: ignore
db.add(media_video) db.add(media_video)
db.commit() db.commit()
db.refresh(media_video) db.refresh(media_video)
print(media_video) print(media_video)
return media_video return media_video
def create_new_mediafile(link: AnyStr, db: Session) -> MediaFile: def create_new_mediafile(link: str, db: Session) -> MediaFile:
logger.info("create MediaFile with url {link}") logger.info("create MediaFile with url {link}")
media_file: MediaFile = MediaFile() media_file: MediaFile = MediaFile()
media_file.id = str(uuid.uuid4()) media_file.id = str(uuid.uuid4())
media_file.url = link media_file.url = link # type: ignore
media_file.created_date = datetime.now() media_file.created_date = datetime.now()
media_file.last_modified_date = datetime.now() media_file.last_modified_date = datetime.now()
media_file.version = 0 media_file.version = 0
@@ -38,3 +38,49 @@ def create_new_mediafile(link: AnyStr, db: Session) -> MediaFile:
logger.info(f"created {media_file}") logger.info(f"created {media_file}")
return media_file return media_file
def delete_mediafile(db: Session, media_file_id: str):
logger.info(f"delete MediaFile with id {media_file_id}")
media_file = db.get(MediaFile, media_file_id)
db.delete(media_file)
db.commit()
def create_new_mediaactor(new_actor: Actor, db: Session) -> MediaActor:
logger.info(f"create MediaActor with url {new_actor.url}")
media_actor: MediaActor = MediaActor()
media_actor.id = str(uuid.uuid4())
media_actor.name = str(new_actor.name) # type: ignore
media_actor.url = str(new_actor.url) # type: ignore
media_actor.created_date = datetime.now()
media_actor.last_modified_date = datetime.now()
media_actor.version = 0
db.add(media_actor)
db.commit()
db.refresh(media_actor)
logger.info(f"created {media_actor}")
return media_actor
def delete_mediaactor(db: Session, actor_id: str):
logger.info(f"delete MediaActor with id {actor_id}")
media_actor = db.get(MediaActor, actor_id)
db.delete(media_actor)
db.commit()
def create_new_mediaactorfile(db: Session, actor_id: str, file_id: str) -> MediaActorFile:
logger.info(f"create MediaActorFile with actor {actor_id} and file {file_id}")
media_actor_file: MediaActorFile = MediaActorFile()
media_actor_file.id = str(uuid.uuid4())
media_actor_file.created_date = datetime.now()
media_actor_file.last_modified_date = datetime.now()
media_actor_file.version = 0
media_actor_file.media_actor_id = actor_id # type: ignore
media_actor_file.media_file_id = file_id # type: ignore
db.add(media_actor_file)
db.commit()
db.refresh(media_actor_file)
return media_actor_file
def delete_mediaactorfile(db: Session, actorfile_id: str):
logger.info(f"delete MediaActorFile with id {actorfile_id}")
media_actorfile = db.get(MediaActorFile, actorfile_id)
db.delete(media_actorfile)
db.commit()
@@ -1,66 +0,0 @@
from datetime import datetime
import uuid
from sqlalchemy.orm import Session
from src.core.log_conf import logger
from src.db.models.media import MediaActor
from src.db.repository.media.actorfile import delete_mediaactorfile
from src.schema.media.actor import MediaActorModel
def create_new_mediaactor(new_actor: MediaActorModel, db: Session) -> MediaActor:
logger.info(f"create MediaActor with url {new_actor.url}")
media_actor: MediaActor = MediaActor()
media_actor.id = str(uuid.uuid4())
if new_actor.name is not None:
media_actor.name = new_actor.name
media_actor.url = new_actor.url
media_actor.created_date = datetime.now()
media_actor.last_modified_date = datetime.now()
media_actor.version = 0
db.add(media_actor)
db.commit()
db.refresh(media_actor)
logger.info(f"created {media_actor}")
return media_actor
def delete_mediaactor(db: Session, actor_id: str):
logger.info(f"delete MediaActor with id {actor_id}")
media_actor = db.get(MediaActor, actor_id)
if media_actor is not None:
actor_files = media_actor.media_actor_files
for actor_file in actor_files:
delete_mediaactorfile(db, actorfile_id=actor_file.id)
db.refresh(media_actor)
db.delete(media_actor)
db.commit()
def import_mediaactor(db: Session, new_actor: MediaActorModel) -> MediaActor:
"""
import MediaFile and set missing values with default ones.
"""
logger.info("import MediaActor with %s", new_actor)
media_actor: MediaActor = MediaActor()
media_actor.id = new_actor.id
if new_actor.created_date:
media_actor.created_date = new_actor.created_date
else:
media_actor.created_date = datetime.now()
if new_actor.last_modified_date:
media_actor.last_modified_date = new_actor.last_modified_date
else:
media_actor.last_modified_date = datetime.now()
media_actor.version = new_actor.version
if new_actor.name:
media_actor.name = new_actor.name
else:
media_actor.name = ""
if new_actor.url:
media_actor.url = new_actor.url
else:
media_actor.url = ""
db.add(media_actor)
db.commit()
db.refresh(media_actor)
return media_actor
@@ -1,49 +0,0 @@
from datetime import datetime
import uuid
from sqlalchemy.orm import Session
from src.core.log_conf import logger
from src.db.models.media import MediaActorFile
from src.schema.media.actorfile import MediaActorFileModel
def create_new_mediaactorfile(
db: Session, actor_id: str, file_id: str
) -> MediaActorFile:
"""
Create relation for MediaFile and MediaActor
"""
logger.info("create MediaActorFile with actor %s and file %s", actor_id, file_id)
media_actor_file: MediaActorFile = MediaActorFile()
media_actor_file.id = str(uuid.uuid4())
media_actor_file.created_date = datetime.now()
media_actor_file.last_modified_date = datetime.now()
media_actor_file.version = 0
media_actor_file.media_actor_id = actor_id
media_actor_file.media_file_id = file_id
db.add(media_actor_file)
db.commit()
db.refresh(media_actor_file)
return media_actor_file
def delete_mediaactorfile(db: Session, actorfile_id: str):
"""
Delete relation between MediaFile and MediaActor.
"""
logger.info("delete MediaActorFile with id %s", actorfile_id)
media_actorfile = db.get(MediaActorFile, actorfile_id)
db.delete(media_actorfile)
db.commit()
def import_mediaactorfile(
db: Session, new_actorfile: MediaActorFileModel
) -> MediaActorFile:
"""
Import MediaFile and set missing values with default ones.
"""
logger.info("import MediaActorFile with %s", new_actorfile)
media_actor_file: MediaActorFile = MediaActorFile()
return media_actor_file
@@ -1,78 +0,0 @@
from datetime import datetime
import uuid
from sqlalchemy.orm import Session
from src.core.log_conf import logger
from src.db.models.media import MediaFile
from src.schema.media.file import MediaFileModel
def create_new_mediafile(link: str, db: Session) -> MediaFile:
"""
Create MediaFile with gievne URL.
"""
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("created %s", media_file)
return media_file
def delete_mediafile(db: Session, media_file_id: str):
"""
Delete MediaFile with given ID from db.
"""
logger.info("delete MediaFile with id %s", media_file_id)
media_file = db.get(MediaFile, media_file_id)
db.delete(media_file)
db.commit()
def import_mediafile(db: Session, new_file: MediaFileModel) -> MediaFile:
"""
import MediaActor and set missing values with defautl ones.
"""
logger.info("import MediaFile with %s", new_file)
media_file: MediaFile = MediaFile()
media_file.id = new_file.id
if new_file.created_date:
media_file.created_date = new_file.created_date
else:
media_file.created_date = datetime.now()
if new_file.last_modified_date:
media_file.last_modified_date = new_file.last_modified_date
else:
media_file.last_modified_date = datetime.now()
media_file.version = new_file.version
if new_file.title:
media_file.title = new_file.title
else:
media_file.title = ""
if new_file.file_name:
media_file.file_name = new_file.file_name
else:
media_file.file_name = ""
if new_file.cloud_link:
media_file.cloud_link = new_file.cloud_link
else:
media_file.cloud_link = ""
if new_file.url:
media_file.url = new_file.url
else:
media_file.url = ""
media_file.review = new_file.review
media_file.should_download = new_file.should_download
db.add(media_file)
db.commit()
db.refresh(media_file)
return media_file
@@ -1,23 +0,0 @@
from datetime import datetime
import uuid
from sqlalchemy.orm import Session
from src.db.models.media import 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 = str(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
-42
View File
@@ -1,42 +0,0 @@
from datetime import datetime
from typing import Optional
import uuid
from src.core.log_conf import logger
from src.db.models.admin import Assignment, Profile
from sqlalchemy.orm import Session
from src.schema.user.profile import ProfileModel
def create_new_profile(new_profile: ProfileModel, db: Session) -> Profile:
logger.info(f"create MediaActor with url {new_profile.username}")
profile: Profile = Profile()
profile.id = str(uuid.uuid4())
profile.user_name = new_profile.username
profile.first_name = new_profile.first_name
profile.last_name = new_profile.last_name
profile.created_date = datetime.now()
profile.last_modified_date = datetime.now()
profile.version = 0
db.add(profile)
db.commit()
db.refresh(profile)
logger.info(f"created {profile}")
return profile
def delete_profile(db: Session, profile_id: str):
logger.info(f"delete Profile with id {profile_id}")
profile: Optional[Profile] = db.get(Profile, profile_id)
if profile is not None:
assignments = profile.assignments
for assignment in assignments:
delete_assignment(db, assignment.id)
db.delete(profile)
db.commit()
def delete_assignment(db: Session, assignment_id: str) -> None:
logger.info(f"delete Assignment with id {assignment_id}")
assignment: Optional[Assignment] = db.get(Assignment, assignment_id)
if assignment is not None:
db.delete(assignment)
db.commit()
+4 -6
View File
@@ -1,8 +1,8 @@
from typing import Generator from typing import Generator, Annotated
from fastapi import Depends from fastapi import Depends
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
@@ -12,10 +12,8 @@ engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(bind=engine) SessionLocal = sessionmaker(bind=engine)
def get_db() -> Generator[Session, None, None]: def get_db() -> Generator:
""" """
with SessionLocal() as db: with SessionLocal() as db:
yield db yield db
SessionDep: type[Session] = Annotated[Session, Depends(get_db)]
SessionDep = Annotated[Session, Depends(get_db)]
+4 -2
View File
@@ -1,4 +1,6 @@
import databases import databases
from src.core.log_conf import logger
from src.db.session import SQLALCHEMY_DATABASE_URL from src.db.session import SQLALCHEMY_DATABASE_URL
@@ -9,7 +11,7 @@ async def check_db_connected():
if not database.is_connected: if not database.is_connected:
await database.connect() await database.connect()
await database.execute("SELECT 1") await database.execute("SELECT 1")
print("Database is connected (^_^)") logger.info("Database is connected (^_^)")
except Exception as e: except Exception as e:
print( print(
"Looks like db is missing or is there is some problem in connection,see below traceback" "Looks like db is missing or is there is some problem in connection,see below traceback"
@@ -23,6 +25,6 @@ async def check_db_disconnected():
database = databases.Database(SQLALCHEMY_DATABASE_URL) database = databases.Database(SQLALCHEMY_DATABASE_URL)
if database.is_connected: if database.is_connected:
await database.disconnect() await database.disconnect()
print("Database is Disconnected (-_-) zZZ") logger.info("Database is Disconnected (-_-) zZZ")
except Exception as e: except Exception as e:
raise e raise e

Some files were not shown because too many files have changed in this diff Show More