Compare commits
61 Commits
v0.1.0
..
729842a71c
| Author | SHA1 | Date | |
|---|---|---|---|
| 729842a71c | |||
| 3d1eeeab53 | |||
| 4871f56320 | |||
| ec404c9956 | |||
| fc9db8200f | |||
| 69caa825de | |||
| 123be2e9c0 | |||
| 928536b414 | |||
| 8ed8599f17 | |||
| 435de2ec92 | |||
| 529a199262 | |||
| 510e2f8130 | |||
| da4f286180 | |||
| d9136e45f6 | |||
| b610947403 | |||
| 6e520a46f0 | |||
| b4a0c2d7a5 | |||
| ea9f596abe | |||
| 4bb7d61f80 | |||
| cc8a166f5c | |||
| a70bf8ae96 | |||
| 902ee03e3f | |||
| f1f49ab014 | |||
| 7a16225c3b | |||
| 8e13af5b8c | |||
| 767c069404 | |||
| dd5281e2a1 | |||
| aefd56d1ff | |||
| 293a0b3478 | |||
| 076466b895 | |||
| 370738ff14 | |||
| 1151b0e45e | |||
| 7c5c571716 | |||
| 1b636efdb7 | |||
| 374c242890 | |||
| 87b1c24783 | |||
| 456162da44 | |||
| 8cfb60f9a1 | |||
| 4c70046a32 | |||
| 546f8ebbdf | |||
| bd86379d07 | |||
| edcaed1b1a | |||
| 20ed0b2f40 | |||
| 3197288eee | |||
| 84d64f04d2 | |||
| 1ecf64a228 | |||
| 41d513e402 | |||
| aa4b47e032 | |||
| 3537642df9 | |||
| 06a48a03ac | |||
| d60e606663 | |||
| 13dad3961c | |||
| 4cf1941f44 | |||
| ace568a800 | |||
| c77adb0e04 | |||
| 7ff2bf912d | |||
| 72c1a7d265 | |||
| 2efacf6d67 | |||
| d6549132ea | |||
| bbf422cc5d | |||
| 4c96de27db |
@@ -13,4 +13,8 @@ kontor-schema/kontor_schema.egg-info
|
|||||||
kontor-gui/.pdm-python
|
kontor-gui/.pdm-python
|
||||||
kontor-gui/dist
|
kontor-gui/dist
|
||||||
fastapi/.coverage
|
fastapi/.coverage
|
||||||
|
kontor-api/.coverage
|
||||||
db-password.txt
|
db-password.txt
|
||||||
|
kontor-api/tests/test_main.py
|
||||||
|
kontor-api/tests/test_db.db
|
||||||
|
kontor-api/test_db.db
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
kontor_api := kontor-api
|
kontor_api := kontor-api
|
||||||
kontor_spring := kontor-spring
|
kontor_spring := kontor-spring
|
||||||
|
kontor_servicemix := kontor-servicemix
|
||||||
|
|
||||||
.PHONY: all $(kontor_spring) $(kontor_api)
|
.PHONY: all $(kontor_spring) $(kontor_api)
|
||||||
all: $(kontor_spring) $(kontor_api)
|
all: $(kontor_spring) $(kontor_api) $(kontor_servicemix)
|
||||||
|
|
||||||
$(kontor_spring) $(kontor_api):
|
$(kontor_spring) $(kontor_api) $(kontor_servicemix):
|
||||||
$(MAKE) --directory=$@ $(TARGET)
|
$(MAKE) --directory=$@ $(TARGET)
|
||||||
|
|
||||||
|
|||||||
+37
-15
@@ -1,44 +1,66 @@
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
mariadb:
|
postgres:
|
||||||
image: mariadb
|
image: postgres
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: kontor
|
- POSTGRES_DB=kontor
|
||||||
MYSQL_USER: kontor
|
- POSTGRES_USER=kontor
|
||||||
MYSQL_PASSWORD: kontor
|
#- POSTGRES_PASSWORD_FILE=/run/secrets/db-password
|
||||||
MYSQL_DATABASE: kontor
|
- POSTGRES_PASSWORD=kontor
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U kontor"]
|
||||||
|
interval: 1s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
ports:
|
ports:
|
||||||
- 3316:3306
|
- 5432:5432
|
||||||
networks:
|
networks:
|
||||||
- database
|
- database
|
||||||
volumes:
|
volumes:
|
||||||
- mariadb-storage:/var/lib/mysql:rw
|
- postgres-data:/var/lib/postgresql/data:rw
|
||||||
|
secrets:
|
||||||
|
- db-password
|
||||||
|
adminer:
|
||||||
|
image: adminer
|
||||||
|
ports:
|
||||||
|
- 8090:8080
|
||||||
|
networks:
|
||||||
|
- database
|
||||||
|
- frontend
|
||||||
kontor:
|
kontor:
|
||||||
image: kontor
|
image: kontor:0.2.0-SNAPSHOT
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- database
|
- database
|
||||||
- frontend
|
- frontend
|
||||||
ports:
|
ports:
|
||||||
- 8000:8000
|
- 8000:8000
|
||||||
|
volumes:
|
||||||
|
- images-data:/data/images
|
||||||
depends_on:
|
depends_on:
|
||||||
- mariadb
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
kontor-api:
|
kontor-api:
|
||||||
image: kontor-api
|
image: kontor-api:0.2.0-SNAPSHOT
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- database
|
- database
|
||||||
- frontend
|
- frontend
|
||||||
ports:
|
ports:
|
||||||
- 8800:8800
|
- 8800:8800
|
||||||
|
volumes:
|
||||||
|
- images-data:/data/images
|
||||||
depends_on:
|
depends_on:
|
||||||
- mariadb
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
networks:
|
networks:
|
||||||
database:
|
database:
|
||||||
frontend:
|
frontend:
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mariadb-storage:
|
postgres-data:
|
||||||
|
images-data:
|
||||||
|
secrets:
|
||||||
|
db-password:
|
||||||
|
file: db-password.txt
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
build/
|
|
||||||
.settings/
|
|
||||||
.gradle/
|
|
||||||
.classpath
|
|
||||||
.project
|
|
||||||
.vscode/
|
|
||||||
bin/
|
|
||||||
.idea/
|
|
||||||
/.eclipse-pmd
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
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"
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#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
|
|
||||||
BIN
Binary file not shown.
Vendored
-185
@@ -1,185 +0,0 @@
|
|||||||
#!/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" "$@"
|
|
||||||
Vendored
-104
@@ -1,104 +0,0 @@
|
|||||||
@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
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
= 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>>
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package de.thpeetz.kontor.comics.service;
|
|
||||||
|
|
||||||
import javax.enterprise.context.ApplicationScoped;
|
|
||||||
|
|
||||||
@ApplicationScoped
|
|
||||||
public class ComicsService {
|
|
||||||
|
|
||||||
public String showComicList() {
|
|
||||||
return "comics";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
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
@@ -1,3 +0,0 @@
|
|||||||
# Your configuration properties
|
|
||||||
quarkus.http.test-port=8083
|
|
||||||
quarkus.http.test-ssl-port=8446
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package de.thpeetz.kontor.comics;
|
|
||||||
|
|
||||||
import io.quarkus.test.junit.QuarkusIntegrationTest;
|
|
||||||
|
|
||||||
@QuarkusIntegrationTest
|
|
||||||
public class ComicsResourceIT extends ComicsResourceTest {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1 +1,4 @@
|
|||||||
.env
|
.env
|
||||||
|
.coverage
|
||||||
|
app.log
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +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 \
|
RUN apt-get update && apt-get install --no-install-recommends -y build-essential && \
|
||||||
build-essential libmariadb-dev && \
|
|
||||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Download the latest installer, install it and then remove it
|
# Download the latest installer, install it and then remove it
|
||||||
@@ -42,7 +41,6 @@ WORKDIR /app
|
|||||||
|
|
||||||
COPY /src src
|
COPY /src src
|
||||||
COPY --from=builder /app/.venv .venv
|
COPY --from=builder /app/.venv .venv
|
||||||
COPY --from=builder /usr/lib/x86_64-linux-gnu/libmariadb.so.3 /usr/lib/x86_64-linux-gnu
|
|
||||||
|
|
||||||
# Set up environment variables for production
|
# Set up environment variables for production
|
||||||
ENV PATH="/app/.venv/bin:$PATH"
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
|
|||||||
+3
-3
@@ -4,11 +4,11 @@ clean:
|
|||||||
find . -name '*.py[co]' -delete
|
find . -name '*.py[co]' -delete
|
||||||
|
|
||||||
test:
|
test:
|
||||||
DB_HOST=localhost uv run pytest -v --cov --cov-report=term --cov-report=html:coverage-report
|
DB_SERVER=localhost uv run pytest -v --cov --cov-report=term --cov-report=html:coverage-report
|
||||||
|
|
||||||
docker: clean
|
docker: clean
|
||||||
docker build --target=production -t kontor-api -t kontor-api:0.1.0 -t kontor-api .
|
docker build --target=production -t kontor-api:0.2.0-SNAPSHOT .
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
MARIADB_SERVER=localhost uv run fastapi dev src/main.py --port 8008
|
DB_SERVER=127.0.0.1 uv run fastapi dev src/main.py --port 8008
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ dependencies = [
|
|||||||
"beautifulsoup4>=4.13.4",
|
"beautifulsoup4>=4.13.4",
|
||||||
"fastapi[standard]>=0.115.12",
|
"fastapi[standard]>=0.115.12",
|
||||||
"httpx==0.24.1",
|
"httpx==0.24.1",
|
||||||
"mariadb>=1.1.12",
|
|
||||||
"sqlalchemy>=2.0.40",
|
"sqlalchemy>=2.0.40",
|
||||||
"platformdirs>=4.3.7",
|
"platformdirs>=4.3.7",
|
||||||
"pathlib>=1.0.1",
|
"pathlib>=1.0.1",
|
||||||
@@ -22,4 +21,11 @@ dependencies = [
|
|||||||
"python-jose>=3.4.0",
|
"python-jose>=3.4.0",
|
||||||
"python-multipart>=0.0.20",
|
"python-multipart>=0.0.20",
|
||||||
"natsort>=8.4.0",
|
"natsort>=8.4.0",
|
||||||
|
"psycopg2-binary>=2.9.10",
|
||||||
|
"pytest-cov>=6.1.1",
|
||||||
|
"databases[sqlite]>=0.9.0",
|
||||||
|
"pydantic[email]>=2.11.3",
|
||||||
|
"jinja2>=3.1.6",
|
||||||
|
"asyncpg>=0.30.0",
|
||||||
|
"bcrypt>=4.3.0",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from src.apis.version1 import comic, media, tysc
|
from src.apis.version1 import comic, media, tysc, admin
|
||||||
|
|
||||||
api_router = APIRouter(prefix="/api")
|
api_router = APIRouter(prefix="/api")
|
||||||
api_router.include_router(comic.router, prefix="/comics", tags=["comics"])
|
api_router.include_router(comic.router, prefix="/comics", tags=["comics"])
|
||||||
api_router.include_router(media.router, prefix="/media", tags=["media"])
|
api_router.include_router(media.router, prefix="/media", tags=["media"])
|
||||||
api_router.include_router(tysc.router, prefix="/tysc", tags=["tysc"])
|
api_router.include_router(tysc.router, prefix="/tysc", tags=["tysc"])
|
||||||
|
api_router.include_router(admin.router, prefix="/login", tags=["login"])
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
from typing import Dict
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi import HTTPException
|
||||||
|
from fastapi import Request
|
||||||
|
from fastapi import status
|
||||||
|
from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
|
||||||
|
from fastapi.security import OAuth2
|
||||||
|
from fastapi.security.utils import get_authorization_scheme_param
|
||||||
|
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -6,3 +15,34 @@ from sqlalchemy.orm import Session
|
|||||||
from src.db.session import get_db
|
from src.db.session import get_db
|
||||||
|
|
||||||
SessionDep = Annotated[Session, Depends(get_db)]
|
SessionDep = Annotated[Session, Depends(get_db)]
|
||||||
|
|
||||||
|
|
||||||
|
class OAuth2PasswordBearerWithCookie(OAuth2):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
tokenUrl: str,
|
||||||
|
scheme_name: Optional[str] = None,
|
||||||
|
scopes: Optional[Dict[str, str]] = None,
|
||||||
|
auto_error: bool = True,
|
||||||
|
):
|
||||||
|
if not scopes:
|
||||||
|
scopes = {}
|
||||||
|
flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes})
|
||||||
|
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
|
||||||
|
|
||||||
|
async def __call__(self, request: Request) -> Optional[str]:
|
||||||
|
authorization: str = request.cookies.get(
|
||||||
|
"access_token"
|
||||||
|
) # changed to accept access token from httpOnly Cookie
|
||||||
|
|
||||||
|
scheme, param = get_authorization_scheme_param(authorization)
|
||||||
|
if not authorization or scheme.lower() != "bearer":
|
||||||
|
if self.auto_error:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Not authenticated",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
return param
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import logging
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import bcrypt
|
||||||
|
from fastapi import APIRouter, HTTPException, status, Response, Depends
|
||||||
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
|
from jose import jwt, JWTError
|
||||||
|
from src.apis.utils import SessionDep, OAuth2PasswordBearerWithCookie
|
||||||
|
from src.core.config import settings
|
||||||
|
from src.core.security import create_access_token
|
||||||
|
from src.db.models.admin import Profile
|
||||||
|
from src.db.repository.admin import get_profile
|
||||||
|
from src.schema.admin import Token
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
def authenticate_user(username: str, password: str, db: SessionDep) -> Profile | None:
|
||||||
|
user = get_profile(username=username, db=db)
|
||||||
|
print(user)
|
||||||
|
if not user:
|
||||||
|
return None
|
||||||
|
if bcrypt.checkpw(password.encode(), user.password.encode()):
|
||||||
|
print("User successful authenticated")
|
||||||
|
else:
|
||||||
|
logging.info("Authentication failed!")
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/token", response_model=Token)
|
||||||
|
def login_for_access_token(response: Response, db: SessionDep, form_data: OAuth2PasswordRequestForm = Depends()):
|
||||||
|
user = authenticate_user(form_data.username, form_data.password, db)
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Incorrect username or password",
|
||||||
|
)
|
||||||
|
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||||
|
access_token = create_access_token(
|
||||||
|
data={"sub": user.email}, expires_delta=access_token_expires
|
||||||
|
)
|
||||||
|
response.set_cookie(
|
||||||
|
key="access_token", value=f"Bearer {access_token}", httponly=True
|
||||||
|
)
|
||||||
|
return {"access_token": access_token, "token_type": "bearer"}
|
||||||
|
|
||||||
|
|
||||||
|
oauth2_scheme = OAuth2PasswordBearerWithCookie(tokenUrl="/api/login/token")
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_user_from_token(db: SessionDep, token: str = Depends(oauth2_scheme)):
|
||||||
|
credentials_exception = HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Could not validate credentials",
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
payload = jwt.decode(
|
||||||
|
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
||||||
|
)
|
||||||
|
username: str = payload.get("sub")
|
||||||
|
print("username/email extracted is ", username)
|
||||||
|
if username is None:
|
||||||
|
raise credentials_exception
|
||||||
|
except JWTError:
|
||||||
|
raise credentials_exception
|
||||||
|
user = get_profile(username=username, db=db)
|
||||||
|
if user is None:
|
||||||
|
raise credentials_exception
|
||||||
|
return user
|
||||||
@@ -1,31 +1,28 @@
|
|||||||
from uuid import UUID
|
from typing import List, AnyStr
|
||||||
from typing import List
|
|
||||||
from fastapi import APIRouter, HTTPException, status
|
from fastapi import APIRouter, HTTPException, status
|
||||||
from sqlalchemy import select
|
|
||||||
|
|
||||||
from src.apis.utils import SessionDep
|
from src.apis.utils import SessionDep
|
||||||
|
from src.db.repository.comics.artist import get_artist_details
|
||||||
|
from src.db.repository.comics.comic import list_comics, get_issue_details
|
||||||
from src.schema.comics.comic import ComicResponse, ComicDetailsResponse, get_comic_details, get_short_info
|
from src.schema.comics.comic import ComicResponse, ComicDetailsResponse, get_comic_details, get_short_info
|
||||||
from src.schema.comics.artist import ArtistCreation, ArtistDetailResponse, ArtistResponse, get_artist_details
|
from src.schema.comics.artist import ArtistCreation, ArtistDetailResponse, ArtistResponse
|
||||||
from src.db.models.comic import Comic, Artist
|
from src.db.models.comic import Comic, Artist, Issue
|
||||||
|
from src.schema.comics.issue import IssueDetailsResponse
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter()
|
||||||
prefix="/comic",
|
|
||||||
tags=["comics"],
|
|
||||||
responses={404: {"description": "Not found"}},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/comics")
|
@router.get("/comics")
|
||||||
def get_all_comics(db: SessionDep) -> List[ComicResponse]:
|
def get_all_comics(db: SessionDep) -> List[ComicResponse]:
|
||||||
results: List[ComicResponse] = []
|
results: List[ComicResponse] = []
|
||||||
comics = db.scalars(select(Comic)).all()
|
comics = list_comics(db)
|
||||||
for comic in comics:
|
for comic in comics:
|
||||||
response = get_short_info(comic)
|
response = get_short_info(comic)
|
||||||
results.append(response)
|
results.append(response)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@router.get("/comics/{comic_id}", response_model=ComicDetailsResponse)
|
@router.get("/comics/{comic_id}", response_model=ComicDetailsResponse)
|
||||||
def get_comic(comic_id: UUID, db: SessionDep) -> ComicDetailsResponse:
|
def get_comic(comic_id: AnyStr, 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")
|
||||||
@@ -41,7 +38,7 @@ def get_all_artists(db: SessionDep) -> List[ArtistResponse]:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
@router.get("/artists/{artist_id}", response_model=ArtistDetailResponse)
|
@router.get("/artists/{artist_id}", response_model=ArtistDetailResponse)
|
||||||
def get_artist(artist_id: UUID, db: SessionDep) -> ArtistDetailResponse:
|
def get_artist(artist_id: AnyStr, 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")
|
||||||
@@ -60,3 +57,11 @@ def add_artist(db: SessionDep, artist_creation: ArtistCreation) -> ArtistRespons
|
|||||||
response = ArtistResponse(id=artist.id, name=artist.name)
|
response = ArtistResponse(id=artist.id, name=artist.name)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@router.get("/issues", response_model=List[IssueDetailsResponse])
|
||||||
|
def get_issues(db: SessionDep) -> List[IssueDetailsResponse]:
|
||||||
|
results: List[IssueDetailsResponse] = []
|
||||||
|
issues = db.query(Issue).all()
|
||||||
|
for issue in issues:
|
||||||
|
results.append(get_issue_details(issue))
|
||||||
|
return results
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
from typing import List
|
from typing import List, AnyStr
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from fastapi import APIRouter, status, HTTPException
|
from fastapi import APIRouter, status, HTTPException, Depends
|
||||||
from sqlalchemy import select, Sequence
|
from sqlalchemy import select, Sequence
|
||||||
|
from src.core.log_conf import logger
|
||||||
from src.apis.utils import SessionDep
|
from src.apis.utils import SessionDep
|
||||||
|
from src.db.repository.media import create_new_mediafile
|
||||||
from src.schema.media.file import MediaFileResponse, Link, get_file_details, set_file
|
from src.schema.media.file import MediaFileResponse, Link, get_file_details, set_file
|
||||||
from src.db.models.media import MediaFile
|
from src.db.models.media import MediaFile
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter()
|
||||||
prefix="/media",
|
|
||||||
tags=["media"]
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.get("/update-titles")
|
@router.get("/update-titles")
|
||||||
def update_titles(db: SessionDep) -> list[MediaFileResponse]:
|
def update_titles(db: SessionDep) -> list[MediaFileResponse]:
|
||||||
results: list[MediaFileResponse] = []
|
results: list[MediaFileResponse] = []
|
||||||
files = db.query(MediaFile).filter(MediaFile.review == 1).all()
|
files = db.query(MediaFile).filter(MediaFile.review == True).all()
|
||||||
for mediafile in files:
|
for mediafile in files:
|
||||||
mediafile.update_title()
|
mediafile.update_title()
|
||||||
db.add(mediafile)
|
db.add(mediafile)
|
||||||
@@ -27,13 +24,14 @@ def update_titles(db: SessionDep) -> list[MediaFileResponse]:
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/files", response_model=List[MediaFileResponse])
|
@router.get("/files", response_model=List[MediaFileResponse])
|
||||||
|
#def get_all_files(db: SessionDep, review: bool = False, download: bool = False, current_user: Profile = Depends(get_current_user_from_token)) -> List[MediaFileResponse]:
|
||||||
def get_all_files(db: SessionDep, review: bool = False, download: bool = False) -> List[MediaFileResponse]:
|
def get_all_files(db: SessionDep, review: bool = False, download: bool = False) -> List[MediaFileResponse]:
|
||||||
results: list[MediaFileResponse] = []
|
results: list[MediaFileResponse] = []
|
||||||
files: Sequence[MediaFile]
|
files: Sequence[MediaFile]
|
||||||
if review:
|
if review:
|
||||||
files = db.query(MediaFile).filter(MediaFile.review == 1).all()
|
files = db.query(MediaFile).filter(MediaFile.review == True).all()
|
||||||
elif download:
|
elif download:
|
||||||
files = db.query(MediaFile).filter(MediaFile.should_download == 1).all()
|
files = db.query(MediaFile).filter(MediaFile.should_download == True).all()
|
||||||
else:
|
else:
|
||||||
files = db.scalars(select(MediaFile)).all()
|
files = db.scalars(select(MediaFile)).all()
|
||||||
for mediafile in files:
|
for mediafile in files:
|
||||||
@@ -42,7 +40,7 @@ def get_all_files(db: SessionDep, review: bool = False, download: bool = False)
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
@router.get("/files/{file_id}", response_model=MediaFileResponse)
|
@router.get("/files/{file_id}", response_model=MediaFileResponse)
|
||||||
def get_file(file_id: UUID, db: SessionDep) -> MediaFileResponse:
|
def get_file(file_id: AnyStr, db: SessionDep) -> MediaFileResponse:
|
||||||
mediafile = db.get(MediaFile, file_id)
|
mediafile = db.get(MediaFile, file_id)
|
||||||
if not mediafile:
|
if not mediafile:
|
||||||
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
||||||
@@ -50,7 +48,7 @@ def get_file(file_id: UUID, db: SessionDep) -> MediaFileResponse:
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
@router.put("/files/{file_id}", response_model=MediaFileResponse)
|
@router.put("/files/{file_id}", response_model=MediaFileResponse)
|
||||||
def update_file(file_id: UUID, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse:
|
def update_file(file_id: AnyStr, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse:
|
||||||
mediaFile = db.get(MediaFile, file_id)
|
mediaFile = db.get(MediaFile, file_id)
|
||||||
if not mediaFile:
|
if not mediaFile:
|
||||||
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
||||||
@@ -62,14 +60,9 @@ def update_file(file_id: UUID, db: SessionDep, info: MediaFileResponse) -> Media
|
|||||||
|
|
||||||
@router.post("/files", status_code=status.HTTP_201_CREATED)
|
@router.post("/files", status_code=status.HTTP_201_CREATED)
|
||||||
def add_file(new_link: Link, db: SessionDep) -> MediaFileResponse:
|
def add_file(new_link: Link, db: SessionDep) -> MediaFileResponse:
|
||||||
print(new_link.url)
|
logger.info(f"add url {new_link.url}")
|
||||||
try:
|
try:
|
||||||
mediaFile: MediaFile = MediaFile()
|
mediaFile: MediaFile = create_new_mediafile(new_link.url, db)
|
||||||
setattr(mediaFile, "url", new_link.url)
|
|
||||||
setattr(mediaFile, "review", 1)
|
|
||||||
setattr(mediaFile, "should_download", 1)
|
|
||||||
db.add(mediaFile)
|
|
||||||
db.commit()
|
|
||||||
except:
|
except:
|
||||||
raise HTTPException(status_code=409, detail="Link duplicate")
|
raise HTTPException(status_code=409, detail="Link duplicate")
|
||||||
response = get_file_details(mediaFile)
|
response = get_file_details(mediaFile)
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ from src.apis.utils import SessionDep
|
|||||||
from src.schema.tysc.sport import SportResponse
|
from src.schema.tysc.sport import SportResponse
|
||||||
from src.db.models.tysc import Sport
|
from src.db.models.tysc import Sport
|
||||||
|
|
||||||
router = APIRouter(
|
router = APIRouter()
|
||||||
prefix="/tysc",
|
|
||||||
tags=["tysc"],
|
|
||||||
responses={404: {"description": "Not found"}},
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.get("/sports")
|
@router.get("/sports")
|
||||||
def get_all_sports(db: SessionDep) -> List[SportResponse]:
|
def get_all_sports(db: SessionDep) -> List[SportResponse]:
|
||||||
|
|||||||
@@ -9,15 +9,17 @@ load_dotenv(dotenv_path=env_path)
|
|||||||
|
|
||||||
class Settings:
|
class Settings:
|
||||||
PROJECT_NAME: str = "Kontor"
|
PROJECT_NAME: str = "Kontor"
|
||||||
PROJECT_VERSION: str = "0.1.0"
|
PROJECT_VERSION: str = "0.2.0"
|
||||||
|
|
||||||
MARIADB_USER: str = os.getenv("MARIADB_USER", "kontor")
|
|
||||||
MARIADB_PASSWORD: str = os.getenv("MARIADB_PASSWORD", "kontor")
|
|
||||||
MARIADB_SERVER: str = os.getenv("MARIADB_SERVER", "mariadb")
|
|
||||||
MARIADB_PORT: str = os.getenv("MARIADB_PORT", 3306)
|
|
||||||
MARIADB_DB: str = os.getenv("MARIADB_DB", "kontor")
|
|
||||||
DATABASE_URL: str = f"mariadb+mariadbconnector://{MARIADB_USER}:{MARIADB_PASSWORD}@{MARIADB_SERVER}:{MARIADB_PORT}/{MARIADB_DB}"
|
|
||||||
|
|
||||||
|
DB_USER: str = os.getenv("DB_USER", "kontor")
|
||||||
|
DB_PASSWORD: str = os.getenv("DB_PASSWORD", "kontor")
|
||||||
|
DB_SERVER: str = os.getenv("DB_SERVER", "postgres")
|
||||||
|
DB_PORT: str = os.getenv("DB_PORT", 5432)
|
||||||
|
DB_DBNAME: str = os.getenv("DB_DBNAME", "kontor")
|
||||||
|
DATABASE_URL: str = f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_SERVER}:{DB_PORT}/{DB_DBNAME}"
|
||||||
|
SECRET_KEY: str = os.getenv("SECRET_KEY", "J6GOtcwC2NJI1l0VkHu20PacPFGTxpirBxWwynoHjsc=")
|
||||||
|
ALGORITHM = "HS256"
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES = 600 # in mins
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import logging
|
||||||
|
import logging.config
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
LOGGING_CONFIG: dict[str, Any] = {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": False,
|
||||||
|
"formatters": {
|
||||||
|
"default": {
|
||||||
|
"()": "uvicorn.logging.DefaultFormatter",
|
||||||
|
"fmt": "%(asctime)s - %(name)s - %(levelprefix)s %(message)s"
|
||||||
|
},
|
||||||
|
"access": {
|
||||||
|
"()": "uvicorn.logging.AccessFormatter",
|
||||||
|
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
|
||||||
|
},
|
||||||
|
"access_file": {
|
||||||
|
"()": "uvicorn.logging.AccessFormatter",
|
||||||
|
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
|
||||||
|
"use_colors": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"default": {
|
||||||
|
"formatter": "default",
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
"stream": "ext://sys.stdout",
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"formatter": "access",
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
"stream": "ext://sys.stderr",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
"root": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
||||||
|
"uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
||||||
|
"uvicorn.error": {"level": "INFO"},
|
||||||
|
"uvicorn.access": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
logging.config.dictConfig(LOGGING_CONFIG)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from src.core.config import settings
|
||||||
|
from jose import jwt
|
||||||
|
|
||||||
|
|
||||||
|
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
||||||
|
to_encode = data.copy()
|
||||||
|
if expires_delta:
|
||||||
|
expire = datetime.utcnow() + expires_delta
|
||||||
|
else:
|
||||||
|
expire = datetime.utcnow() + timedelta(
|
||||||
|
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
||||||
|
)
|
||||||
|
to_encode.update({"exp": expire})
|
||||||
|
encoded_jwt = jwt.encode(
|
||||||
|
to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM
|
||||||
|
)
|
||||||
|
return encoded_jwt
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean
|
||||||
from sqlalchemy.dialects.mysql import BIT
|
|
||||||
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
||||||
|
|
||||||
from src.db.models.base import Base, BaseMixin
|
from src.db.models.base import Base, BaseMixin
|
||||||
@@ -9,12 +8,12 @@ from src.db.models.base import Base, BaseMixin
|
|||||||
|
|
||||||
class Profile(Base, BaseMixin):
|
class Profile(Base, BaseMixin):
|
||||||
__tablename__ = 'profile'
|
__tablename__ = 'profile'
|
||||||
first_name = Column(String(255))
|
first_name = Column(String)
|
||||||
last_name = Column(String(255))
|
last_name = Column(String)
|
||||||
user_name = Column(String(255), nullable=False)
|
user_name = Column(String, nullable=False)
|
||||||
email = Column(String(255))
|
email = Column(String)
|
||||||
password = Column(String(255))
|
password = Column(String)
|
||||||
enabled = Column(BIT(1))
|
enabled = Column(Boolean)
|
||||||
assignments = relationship("Assignment")
|
assignments = relationship("Assignment")
|
||||||
tokens = relationship("Token")
|
tokens = relationship("Token")
|
||||||
|
|
||||||
@@ -28,20 +27,23 @@ class Profile(Base, BaseMixin):
|
|||||||
full_name += self.last_name
|
full_name += self.last_name
|
||||||
return full_name
|
return full_name
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Profile({self.id} {self.user_name}, {self.email})"
|
||||||
|
|
||||||
|
|
||||||
class Token(Base, BaseMixin):
|
class Token(Base, BaseMixin):
|
||||||
__tablename__ = "token"
|
__tablename__ = "token"
|
||||||
token = Column(String(255), nullable=False, unique=True)
|
token = Column(String, nullable=False, unique=True)
|
||||||
name = Column(String(255))
|
name = Column(String)
|
||||||
last_used_date: Mapped[datetime] = mapped_column()
|
last_used_date: Mapped[datetime] = mapped_column()
|
||||||
enabled = Column(BIT(1))
|
enabled = Column(Boolean)
|
||||||
profile_id = Column(String(255), ForeignKey("profile.id"), nullable=False)
|
profile_id = Column(String, ForeignKey("profile.id"), nullable=False)
|
||||||
profile = relationship("Profile", back_populates="tokens")
|
profile = relationship("Profile", back_populates="tokens")
|
||||||
|
|
||||||
|
|
||||||
class Permission(Base, BaseMixin):
|
class Permission(Base, BaseMixin):
|
||||||
__tablename__ = "permission"
|
__tablename__ = "permission"
|
||||||
name = Column(String(255), nullable=False)
|
name = Column(String, nullable=False)
|
||||||
assignments = relationship("Assignment")
|
assignments = relationship("Assignment")
|
||||||
|
|
||||||
|
|
||||||
@@ -53,20 +55,14 @@ class Assignment(Base, BaseMixin):
|
|||||||
permission = relationship("Permission", back_populates="assignments")
|
permission = relationship("Permission", back_populates="assignments")
|
||||||
|
|
||||||
|
|
||||||
class ModuleData(Base, BaseMixin):
|
|
||||||
__tablename__ = "module_data"
|
|
||||||
module_name = Column(String(255), nullable=False)
|
|
||||||
import_data = Column(BIT(1))
|
|
||||||
|
|
||||||
|
|
||||||
class MailAccount(Base, BaseMixin):
|
class MailAccount(Base, BaseMixin):
|
||||||
__tablename__ = "mail_account"
|
__tablename__ = "mail_account"
|
||||||
host = Column(String(255))
|
host = Column(String)
|
||||||
port = Column(Integer)
|
port = Column(Integer)
|
||||||
protocol = Column(String(255))
|
protocol = Column(String)
|
||||||
user_name = Column(String(255))
|
user_name = Column(String)
|
||||||
password = Column(String(255))
|
password = Column(String)
|
||||||
start_tls = Column(BIT(1))
|
start_tls = Column(Boolean)
|
||||||
|
|
||||||
|
|
||||||
class Mail(Base, BaseMixin):
|
class Mail(Base, BaseMixin):
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import func, Column, String
|
from sqlalchemy import func, Column, String, Boolean
|
||||||
from sqlalchemy.dialects.mysql import BIT
|
|
||||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||||
|
|
||||||
|
|
||||||
@@ -11,8 +10,8 @@ class Base(DeclarativeBase):
|
|||||||
|
|
||||||
|
|
||||||
class BaseMixin:
|
class BaseMixin:
|
||||||
id = Column(String(255), primary_key=True, default=uuid.uuid4())
|
#id = Column(String, primary_key=True, default=uuid.uuid4)
|
||||||
# id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4())
|
id: Mapped[str] = mapped_column(primary_key=True, default=str(uuid.uuid4()))
|
||||||
# created_date = Column(DateTime)
|
# created_date = Column(DateTime)
|
||||||
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||||
# last_modified_date = Column(DateTime)
|
# last_modified_date = Column(DateTime)
|
||||||
@@ -22,10 +21,10 @@ class BaseMixin:
|
|||||||
|
|
||||||
|
|
||||||
class BaseVideoMixin:
|
class BaseVideoMixin:
|
||||||
cloud_link = Column(String(255))
|
cloud_link = Column(String)
|
||||||
file_name = Column(String(255))
|
file_name = Column(String)
|
||||||
path = Column(String(255))
|
path = Column(String)
|
||||||
review = Column(BIT(1))
|
review = Column(Boolean)
|
||||||
title = Column(String(255))
|
title = Column(String)
|
||||||
url = Column(String(255), unique=True)
|
url = Column(String, unique=True)
|
||||||
should_download = Column(BIT(1))
|
should_download = Column(Boolean)
|
||||||
|
|||||||
@@ -6,28 +6,28 @@ from src.db.models.base import Base, BaseMixin
|
|||||||
|
|
||||||
class Article(Base, BaseMixin):
|
class Article(Base, BaseMixin):
|
||||||
__tablename__ = 'article'
|
__tablename__ = 'article'
|
||||||
title = Column(String(length=255), 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 = Column(String(255))
|
first_name = Column(String)
|
||||||
last_name = Column(String(255))
|
last_name = Column(String)
|
||||||
article_authors = relationship("ArticleAuthor")
|
article_authors = relationship("ArticleAuthor")
|
||||||
book_authors = relationship("BookAuthor")
|
book_authors = relationship("BookAuthor")
|
||||||
|
|
||||||
|
|
||||||
class BookshelfPublisher(Base, BaseMixin):
|
class BookshelfPublisher(Base, BaseMixin):
|
||||||
__tablename__ = 'bookshelf_publisher'
|
__tablename__ = 'bookshelf_publisher'
|
||||||
name = Column(String(length=255), unique=True)
|
name = Column(String, unique=True)
|
||||||
books = relationship("Book")
|
books = relationship("Book")
|
||||||
|
|
||||||
|
|
||||||
class Book(Base, BaseMixin):
|
class Book(Base, BaseMixin):
|
||||||
__tablename__ = 'book'
|
__tablename__ = 'book'
|
||||||
isbn = Column(String(255), unique=True)
|
isbn = Column(String, unique=True)
|
||||||
title = Column(String(255))
|
title = Column(String)
|
||||||
year = Column(Integer, nullable=False)
|
year = Column(Integer, nullable=False)
|
||||||
publisher_id = Column(String, ForeignKey('bookshelf_publisher.id'), nullable=False)
|
publisher_id = Column(String, ForeignKey('bookshelf_publisher.id'), nullable=False)
|
||||||
publisher = relationship('BookshelfPublisher', back_populates="books")
|
publisher = relationship('BookshelfPublisher', back_populates="books")
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
from typing import Dict, List
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import AnyStr, Dict, List, Optional, Any
|
||||||
from natsort import natsorted
|
from natsort import natsorted
|
||||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean, func
|
||||||
from sqlalchemy.dialects.mysql import BIT
|
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
|
|
||||||
from src.db.models.base import Base, BaseMixin
|
from src.db.models.base import Base, BaseMixin
|
||||||
|
|
||||||
|
|
||||||
class Publisher(Base, BaseMixin):
|
class Publisher(Base):
|
||||||
__tablename__ = "publisher"
|
__tablename__ = "publisher"
|
||||||
name = Column(String(length=255), unique=True)
|
id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||||
|
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||||
|
last_modified_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||||
|
version: Mapped[int] = mapped_column(default=0)
|
||||||
|
name = Column(String, unique=True)
|
||||||
|
weblink = Column(String, nullable=True)
|
||||||
|
parent_publisher_id: Mapped[Optional[str]] = mapped_column(ForeignKey('publisher.id'))
|
||||||
|
parent_publisher: Mapped[Optional['Publisher']] = relationship("Publisher", back_populates="imprints", remote_side=[id])
|
||||||
|
imprints: Mapped[List['Publisher']] = relationship('Publisher', back_populates="parent_publisher")
|
||||||
comics = relationship("Comic")
|
comics = relationship("Comic")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@@ -21,11 +30,12 @@ class Publisher(Base, BaseMixin):
|
|||||||
|
|
||||||
class Comic(Base, BaseMixin):
|
class Comic(Base, BaseMixin):
|
||||||
__tablename__ = 'comic'
|
__tablename__ = 'comic'
|
||||||
title = Column(String(length=255), unique=True)
|
title = Column(String, unique=True)
|
||||||
publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
|
publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
|
||||||
publisher = relationship("Publisher", back_populates="comics")
|
publisher = relationship("Publisher", back_populates="comics")
|
||||||
current_order = Column(BIT(1))
|
current_order = Column(Boolean)
|
||||||
completed = Column(BIT(1))
|
completed = Column(Boolean)
|
||||||
|
weblink = Column(String, nullable=True)
|
||||||
issues = relationship("Issue", order_by="Issue.issue_number")
|
issues = relationship("Issue", order_by="Issue.issue_number")
|
||||||
story_arcs = relationship("StoryArc")
|
story_arcs = relationship("StoryArc")
|
||||||
trade_paperbacks = relationship("TradePaperback")
|
trade_paperbacks = relationship("TradePaperback")
|
||||||
@@ -38,10 +48,10 @@ class Comic(Base, BaseMixin):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.title}({self.id})'
|
return f'{self.title}({self.id})'
|
||||||
|
|
||||||
def get_artists(self) -> Dict[str, List[str]]:
|
def get_artists(self) -> Dict[Any, List[Any]]:
|
||||||
works: Dict[str, List[str]] = {}
|
works: Dict[Any, List[Any]] = {}
|
||||||
for work in self.comic_works:
|
for work in self.comic_works:
|
||||||
work_type = work.work_type.name
|
work_type = work.work_type
|
||||||
artist = work.artist
|
artist = work.artist
|
||||||
if work_type in works:
|
if work_type in works:
|
||||||
works[work_type].append(artist)
|
works[work_type].append(artist)
|
||||||
@@ -56,15 +66,16 @@ class Comic(Base, BaseMixin):
|
|||||||
|
|
||||||
class Volume(Base, BaseMixin):
|
class Volume(Base, BaseMixin):
|
||||||
__tablename__ = "volume"
|
__tablename__ = "volume"
|
||||||
name = Column(String(length=255), nullable=False)
|
name = Column(String, nullable=False)
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||||
comic = relationship("Comic", back_populates="volumes")
|
comic = relationship("Comic", back_populates="volumes")
|
||||||
|
story_arcs = relationship("StoryArc")
|
||||||
issues = relationship("Issue")
|
issues = relationship("Issue")
|
||||||
|
|
||||||
|
|
||||||
class TradePaperback(Base, BaseMixin):
|
class TradePaperback(Base, BaseMixin):
|
||||||
__tablename__ = "trade_paperback"
|
__tablename__ = "trade_paperback"
|
||||||
name = Column(String(length=255), nullable=False)
|
name = Column(String, nullable=False)
|
||||||
issue_start = Column(Integer)
|
issue_start = Column(Integer)
|
||||||
issue_end = Column(Integer)
|
issue_end = Column(Integer)
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||||
@@ -73,31 +84,58 @@ class TradePaperback(Base, BaseMixin):
|
|||||||
|
|
||||||
class StoryArc(Base, BaseMixin):
|
class StoryArc(Base, BaseMixin):
|
||||||
__tablename__ = "story_arc"
|
__tablename__ = "story_arc"
|
||||||
name = Column(String(length=255), nullable=False)
|
name = Column(String, nullable=False)
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||||
comic = relationship("Comic", back_populates="story_arcs")
|
comic = relationship("Comic", back_populates="story_arcs")
|
||||||
|
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
||||||
|
volume = relationship("Volume", back_populates="story_arcs")
|
||||||
|
issues = relationship("Issue")
|
||||||
|
|
||||||
|
|
||||||
class Issue(Base, BaseMixin):
|
class Issue(Base, BaseMixin):
|
||||||
__tablename__ = "issue"
|
__tablename__ = "issue"
|
||||||
issue_number = Column(String(255))
|
issue_number = Column(String)
|
||||||
in_stock = Column(BIT(1))
|
title = Column(String, nullable=True)
|
||||||
is_read = Column(BIT(1))
|
published_on: Mapped[datetime] = mapped_column(nullable=True)
|
||||||
|
in_stock = Column(Boolean)
|
||||||
|
is_read = Column(Boolean)
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||||
comic = relationship("Comic", back_populates="issues")
|
comic = relationship("Comic", back_populates="issues")
|
||||||
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
||||||
volume = relationship("Volume", back_populates="issues")
|
volume = relationship("Volume", back_populates="issues")
|
||||||
|
story_arc_id = Column(String, ForeignKey("story_arc.id"), nullable=True)
|
||||||
|
story_arc = relationship("StoryArc", back_populates="issues")
|
||||||
|
issue_works = relationship("IssueWork")
|
||||||
|
|
||||||
|
def get_full_title(self) -> AnyStr:
|
||||||
|
full_title: AnyStr = self.issue_number
|
||||||
|
if self.title:
|
||||||
|
full_title += ": " + self.title
|
||||||
|
return full_title
|
||||||
|
|
||||||
|
def get_artists(self) -> Dict[Any, List[Any]]:
|
||||||
|
works: Dict[Any, List[Any]] = {}
|
||||||
|
for work in self.issue_works:
|
||||||
|
work_type = work.work_type
|
||||||
|
artist = work.artist
|
||||||
|
if work_type in works:
|
||||||
|
works[work_type].append(artist)
|
||||||
|
else:
|
||||||
|
works[work_type] = [artist]
|
||||||
|
return works
|
||||||
|
|
||||||
|
|
||||||
class Artist(Base, BaseMixin):
|
class Artist(Base, BaseMixin):
|
||||||
__tablename__ = "artist"
|
__tablename__ = "artist"
|
||||||
name = Column(String(length=255), nullable=False)
|
name = Column(String, nullable=False)
|
||||||
|
weblink = Column(String, nullable=True)
|
||||||
comic_works = relationship("ComicWork")
|
comic_works = relationship("ComicWork")
|
||||||
|
issue_works = relationship("IssueWork")
|
||||||
|
|
||||||
def get_comics(self) -> Dict[str, List[str]]:
|
def get_comics(self) -> Dict[Any, List[Comic]]:
|
||||||
works: Dict[str, List[str]] = {}
|
works: Dict[Any, List[Comic]] = {}
|
||||||
for work in self.comic_works:
|
for work in self.comic_works:
|
||||||
work_type = work.work_type.name
|
work_type = work.work_type
|
||||||
comic = work.comic
|
comic = work.comic
|
||||||
if work_type in works:
|
if work_type in works:
|
||||||
works[work_type].append(comic)
|
works[work_type].append(comic)
|
||||||
@@ -108,8 +146,20 @@ class Artist(Base, BaseMixin):
|
|||||||
|
|
||||||
class WorkType(Base, BaseMixin):
|
class WorkType(Base, BaseMixin):
|
||||||
__tablename__ = "worktype"
|
__tablename__ = "worktype"
|
||||||
name = Column(String(length=255), nullable=False, unique=True)
|
name = Column(String, nullable=False, unique=True)
|
||||||
comic_works = relationship("ComicWork")
|
comic_works = relationship("ComicWork")
|
||||||
|
issue_works = relationship("IssueWork")
|
||||||
|
|
||||||
|
def get_artists(self) -> Dict[str, List[str]]:
|
||||||
|
works: Dict[str, List[str]] = {}
|
||||||
|
for work in self.comic_works:
|
||||||
|
comic = work.comic.title
|
||||||
|
artist = work.artist
|
||||||
|
if comic in works:
|
||||||
|
works[comic].append(artist)
|
||||||
|
else:
|
||||||
|
works[comic] = [artist]
|
||||||
|
return works
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'Worktype({self.id} {self.version} {self.name} {len(self.comic_works)})'
|
return f'Worktype({self.id} {self.version} {self.name} {len(self.comic_works)})'
|
||||||
@@ -126,3 +176,14 @@ class ComicWork(Base, BaseMixin):
|
|||||||
artist = relationship("Artist", back_populates="comic_works")
|
artist = relationship("Artist", back_populates="comic_works")
|
||||||
work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
|
work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
|
||||||
work_type = relationship("WorkType", back_populates="comic_works")
|
work_type = relationship("WorkType", back_populates="comic_works")
|
||||||
|
|
||||||
|
|
||||||
|
class IssueWork(Base, BaseMixin):
|
||||||
|
__tablename__ = "issue_work"
|
||||||
|
issue_id = Column(String, ForeignKey("issue.id"), nullable=False)
|
||||||
|
issue = relationship("Issue", back_populates="issue_works")
|
||||||
|
artist_id = Column(String, ForeignKey("artist.id"), nullable=False)
|
||||||
|
artist = relationship("Artist", back_populates="issue_works")
|
||||||
|
work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
|
||||||
|
work_type = relationship("WorkType", back_populates="issue_works")
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any, List
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
@@ -13,7 +12,7 @@ from sqlalchemy.orm import sessionmaker
|
|||||||
from src.db.models.tysc import Card, CardSet, Rooster, Team, FieldPosition, Player, Vendor, Sport
|
from src.db.models.tysc import Card, CardSet, Rooster, Team, FieldPosition, Player, Vendor, Sport
|
||||||
from src.db.models.comic import Issue, TradePaperback, StoryArc, Volume, ComicWork, Artist, Comic, Publisher, WorkType
|
from src.db.models.comic import Issue, TradePaperback, StoryArc, Volume, ComicWork, Artist, Comic, Publisher, WorkType
|
||||||
from src.db.models.bookshelf import ArticleAuthor, BookAuthor, BookshelfPublisher, Article, Book, Author
|
from src.db.models.bookshelf import ArticleAuthor, BookAuthor, BookshelfPublisher, Article, Book, Author
|
||||||
from src.db.models.admin import Mail, MailAccount, ModuleData, Role, User, Token, AuthorizationMatrix
|
from src.db.models.admin import Mail, MailAccount, ModuleData, Token, Assignment, Permission, Profile
|
||||||
from src.db.models.metadata import MetaDataTable, MetaDataColumn
|
from src.db.models.metadata import MetaDataTable, MetaDataColumn
|
||||||
from src.db.models.media import MediaVideo, MediaArticle, MediaFile, MediaActor, MediaActorFile
|
from src.db.models.media import MediaVideo, MediaArticle, MediaFile, MediaActor, MediaActorFile
|
||||||
|
|
||||||
@@ -79,10 +78,10 @@ class KontorDB:
|
|||||||
self.registry[MediaVideo.__tablename__] = MediaVideo
|
self.registry[MediaVideo.__tablename__] = MediaVideo
|
||||||
self.registry[MetaDataColumn.__tablename__] = MetaDataColumn
|
self.registry[MetaDataColumn.__tablename__] = MetaDataColumn
|
||||||
self.registry[MetaDataTable.__tablename__] = MetaDataTable
|
self.registry[MetaDataTable.__tablename__] = MetaDataTable
|
||||||
self.registry[AuthorizationMatrix.__tablename__] = AuthorizationMatrix
|
self.registry[Assignment.__tablename__] = Assignment
|
||||||
self.registry[Token.__tablename__] = Token
|
self.registry[Token.__tablename__] = Token
|
||||||
self.registry[User.__tablename__] = User
|
self.registry[Profile.__tablename__] = Profile
|
||||||
self.registry[Role.__tablename__] = Role
|
self.registry[Permission.__tablename__] = Permission
|
||||||
self.registry[ModuleData.__tablename__] = ModuleData
|
self.registry[ModuleData.__tablename__] = ModuleData
|
||||||
self.registry[MailAccount.__tablename__] = MailAccount
|
self.registry[MailAccount.__tablename__] = MailAccount
|
||||||
self.registry[Mail.__tablename__] = Mail
|
self.registry[Mail.__tablename__] = Mail
|
||||||
@@ -360,7 +359,7 @@ class KontorDB:
|
|||||||
update_list[link.id] = link.title
|
update_list[link.id] = link.title
|
||||||
return update_list
|
return update_list
|
||||||
|
|
||||||
def get_download_list(self) -> list[uuid.UUID]:
|
def get_download_list(self) -> List[str]:
|
||||||
download_list = []
|
download_list = []
|
||||||
__session__ = sessionmaker(self.engine)
|
__session__ = sessionmaker(self.engine)
|
||||||
_filter = { 'should_download': True}
|
_filter = { 'should_download': True}
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from sqlalchemy import Column, String, ForeignKey
|
from sqlalchemy import Column, String, ForeignKey, Boolean
|
||||||
from sqlalchemy.dialects.mysql import BIT
|
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from src.db.models.base import Base, BaseMixin, BaseVideoMixin
|
from src.db.models.base import Base, BaseMixin, BaseVideoMixin
|
||||||
@@ -30,10 +29,10 @@ class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
|||||||
soup = BeautifulSoup(r.content, "html.parser")
|
soup = BeautifulSoup(r.content, "html.parser")
|
||||||
title = soup.title.string
|
title = soup.title.string
|
||||||
self.title = title
|
self.title = title
|
||||||
self.review = 0
|
self.review = False
|
||||||
except:
|
except:
|
||||||
self.title = None
|
self.title = None
|
||||||
self.review = 1
|
self.review = True
|
||||||
self.last_modified_date = datetime.now()
|
self.last_modified_date = datetime.now()
|
||||||
|
|
||||||
def download_file(self, download_dir: str, dl_tool: str):
|
def download_file(self, download_dir: str, dl_tool: str):
|
||||||
@@ -45,12 +44,12 @@ class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
|||||||
lines_list = output.splitlines()
|
lines_list = output.splitlines()
|
||||||
file_name = self.__parse_output__(lines_list)
|
file_name = self.__parse_output__(lines_list)
|
||||||
if file_name is None:
|
if file_name is None:
|
||||||
self.review = 1
|
self.review = True
|
||||||
self.should_download = 1
|
self.should_download = True
|
||||||
self.file_name = None
|
self.file_name = None
|
||||||
else:
|
else:
|
||||||
download_file = Path(file_name)
|
download_file = Path(file_name)
|
||||||
self.should_download = 0
|
self.should_download = False
|
||||||
self.file_name = download_file.name
|
self.file_name = download_file.name
|
||||||
self.cloud_link = str(download_file.absolute())
|
self.cloud_link = str(download_file.absolute())
|
||||||
self.last_modified_date = datetime.now()
|
self.last_modified_date = datetime.now()
|
||||||
@@ -71,31 +70,42 @@ class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
|||||||
|
|
||||||
class MediaActor(Base, BaseMixin):
|
class MediaActor(Base, BaseMixin):
|
||||||
__tablename__ = 'media_actor'
|
__tablename__ = 'media_actor'
|
||||||
name = Column(String(255))
|
name = Column(String)
|
||||||
media_actor_files = relationship("MediaActorFile")
|
media_actor_files = relationship("MediaActorFile")
|
||||||
|
|
||||||
|
|
||||||
class MediaActorFile(Base, BaseMixin):
|
class MediaActorFile(Base, BaseMixin):
|
||||||
__tablename__ = 'media_actor_file'
|
__tablename__ = 'media_actor_file'
|
||||||
media_actor_id = Column(String(255), ForeignKey("media_actor.id"), nullable=False)
|
media_actor_id = Column(String, ForeignKey("media_actor.id"), nullable=False)
|
||||||
media_actor = relationship("MediaActor", back_populates="media_actor_files")
|
media_actor = relationship("MediaActor", back_populates="media_actor_files")
|
||||||
media_file_id = Column(String(255), ForeignKey("media_file.id"), nullable=True)
|
media_file_id = Column(String, ForeignKey("media_file.id"), nullable=True)
|
||||||
media_file = relationship("MediaFile", back_populates="media_actor_files")
|
media_file = relationship("MediaFile", back_populates="media_actor_files")
|
||||||
|
|
||||||
|
|
||||||
class MediaArticle(Base, BaseMixin):
|
class MediaArticle(Base, BaseMixin):
|
||||||
__tablename__ = 'media_article'
|
__tablename__ = 'media_article'
|
||||||
review = Column(BIT(1))
|
review = Column(Boolean)
|
||||||
title = Column(String(255))
|
title = Column(String)
|
||||||
url = Column(String(255), unique=True)
|
url = Column(String, unique=True)
|
||||||
|
|
||||||
|
|
||||||
class MediaVideo(Base, BaseMixin):
|
class MediaVideo(Base, BaseMixin):
|
||||||
__tablename__ = 'media_video'
|
__tablename__ = 'media_video'
|
||||||
cloud_link = Column(String(255))
|
cloud_link = Column(String)
|
||||||
file_name = Column(String(255))
|
file_name = Column(String)
|
||||||
path = Column(String(255))
|
path = Column(String)
|
||||||
review = Column(BIT(1))
|
review = Column(Boolean)
|
||||||
title = Column(String(255))
|
title = Column(String)
|
||||||
url = Column(String(255), unique=True)
|
url = Column(String, unique=True)
|
||||||
should_download = Column(BIT(1))
|
should_download = Column(Boolean)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'MediaFile({self.id} {self.title} {self.url})'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.title is None:
|
||||||
|
return f'{self.url}({self.id})'
|
||||||
|
else:
|
||||||
|
return f'{self.title}({self.id})'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
from sqlalchemy import Column, String, ForeignKey, Integer
|
|
||||||
from sqlalchemy.dialects.mysql import BIT
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
|
|
||||||
from src.db.models.base import Base, BaseMixin
|
|
||||||
|
|
||||||
|
|
||||||
class MetaDataTable(Base, BaseMixin):
|
|
||||||
__tablename__ = 'meta_data_table'
|
|
||||||
table_name = Column(String(255), unique=True)
|
|
||||||
table_columns = relationship("MetaDataColumn")
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'MetaDataTable({self.id} {self.table_name})'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'{self.table_name}({self.id})'
|
|
||||||
|
|
||||||
|
|
||||||
class MetaDataColumn(Base, BaseMixin):
|
|
||||||
__tablename__ = 'meta_data_column'
|
|
||||||
column_name = Column(String(255), nullable=False)
|
|
||||||
column_sync_name = Column(String(255))
|
|
||||||
column_type = Column(String(255))
|
|
||||||
column_modifier = Column(String(255), nullable=True)
|
|
||||||
column_order = Column(Integer)
|
|
||||||
table_id = Column(String, ForeignKey('meta_data_table.id'))
|
|
||||||
table = relationship("MetaDataTable", back_populates="table_columns")
|
|
||||||
column_label = Column(String(255))
|
|
||||||
filter_label = Column(String(255))
|
|
||||||
is_shown = Column(BIT(1))
|
|
||||||
show_filter = Column(BIT(1))
|
|
||||||
ref_column = Column(String, nullable=True)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self.column_name is None:
|
|
||||||
return f'MetaDataColumn({self.id} {self.table.table_name}.__)'
|
|
||||||
else:
|
|
||||||
return f'MetaDataColumn({self.id} {self.table.table_name}.{self.column_name})'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'{self.column_name}({self.id})'
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
|
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Boolean
|
||||||
from sqlalchemy.dialects.mysql import BIT
|
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from src.db.models.base import Base, BaseMixin
|
from src.db.models.base import Base, BaseMixin
|
||||||
@@ -10,15 +9,15 @@ class Sport(Base, BaseMixin):
|
|||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint("name"),
|
UniqueConstraint("name"),
|
||||||
)
|
)
|
||||||
name = Column(String(255), nullable=False, index=True, unique=True)
|
name = Column(String, nullable=False, index=True, unique=True)
|
||||||
teams = relationship("Team")
|
teams = relationship("Team")
|
||||||
positions = relationship("FieldPosition")
|
positions = relationship("FieldPosition")
|
||||||
|
|
||||||
|
|
||||||
class Team(Base, BaseMixin):
|
class Team(Base, BaseMixin):
|
||||||
__tablename__ = "team"
|
__tablename__ = "team"
|
||||||
name = Column(String(255), nullable=False, index=True, unique=True)
|
name = Column(String, nullable=False, index=True, unique=True)
|
||||||
short_name = Column(String(255), nullable=False, )
|
short_name = Column(String, nullable=False, )
|
||||||
sport_id = Column(String, ForeignKey("sport.id"), nullable=False)
|
sport_id = Column(String, ForeignKey("sport.id"), nullable=False)
|
||||||
sport = relationship("Sport", back_populates="teams")
|
sport = relationship("Sport", back_populates="teams")
|
||||||
roosters = relationship("Rooster")
|
roosters = relationship("Rooster")
|
||||||
@@ -30,8 +29,8 @@ class FieldPosition(Base, BaseMixin):
|
|||||||
UniqueConstraint("name", "sport_id"),
|
UniqueConstraint("name", "sport_id"),
|
||||||
UniqueConstraint("short_name", "sport_id"),
|
UniqueConstraint("short_name", "sport_id"),
|
||||||
)
|
)
|
||||||
name = Column(String(255), nullable=False, index=True)
|
name = Column(String, nullable=False, index=True)
|
||||||
short_name = Column(String(255), nullable=False)
|
short_name = Column(String, nullable=False)
|
||||||
sport_id = Column(String, ForeignKey("sport.id"), nullable=False, index=True)
|
sport_id = Column(String, ForeignKey("sport.id"), nullable=False, index=True)
|
||||||
sport = relationship("Sport", back_populates="positions")
|
sport = relationship("Sport", back_populates="positions")
|
||||||
roosters = relationship("Rooster")
|
roosters = relationship("Rooster")
|
||||||
@@ -42,8 +41,8 @@ class Player(Base, BaseMixin):
|
|||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint("first_name", "last_name"),
|
UniqueConstraint("first_name", "last_name"),
|
||||||
)
|
)
|
||||||
first_name = Column(String(255), nullable=False, index=True)
|
first_name = Column(String, nullable=False, index=True)
|
||||||
last_name = Column(String(255), nullable=False, index=True)
|
last_name = Column(String, nullable=False, index=True)
|
||||||
roosters = relationship("Rooster")
|
roosters = relationship("Rooster")
|
||||||
|
|
||||||
def get_full_name(self) -> str:
|
def get_full_name(self) -> str:
|
||||||
@@ -67,7 +66,7 @@ class Rooster(Base, BaseMixin):
|
|||||||
|
|
||||||
class Vendor(Base, BaseMixin):
|
class Vendor(Base, BaseMixin):
|
||||||
__tablename__ = "vendor"
|
__tablename__ = "vendor"
|
||||||
name = Column(String(255), nullable=False, unique=True, index=True)
|
name = Column(String, nullable=False, unique=True, index=True)
|
||||||
card_sets = relationship("CardSet")
|
card_sets = relationship("CardSet")
|
||||||
cards = relationship("Card")
|
cards = relationship("Card")
|
||||||
|
|
||||||
@@ -77,9 +76,9 @@ class CardSet(Base, BaseMixin):
|
|||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint("name", "vendor_id"),
|
UniqueConstraint("name", "vendor_id"),
|
||||||
)
|
)
|
||||||
name = Column(String(255), index=True)
|
name = Column(String, index=True)
|
||||||
parallel_set = Column(BIT(1))
|
parallel_set = Column(Boolean)
|
||||||
insert_set = Column(BIT(1))
|
insert_set = Column(Boolean)
|
||||||
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False, index=True)
|
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False, index=True)
|
||||||
vendor = relationship("Vendor", back_populates="card_sets")
|
vendor = relationship("Vendor", back_populates="card_sets")
|
||||||
cards = relationship("Card")
|
cards = relationship("Card")
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from typing import AnyStr
|
||||||
|
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from src.db.models.admin import Profile
|
||||||
|
|
||||||
|
|
||||||
|
def get_profile(username: AnyStr, db: Session):
|
||||||
|
profile = db.query(Profile).filter(Profile.email == username).first()
|
||||||
|
return profile
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
from src.db.models.comic import Artist
|
||||||
|
from src.schema.comics.artist import ArtistDetailResponse
|
||||||
|
|
||||||
|
|
||||||
|
def get_artist_details(artist: Artist) -> ArtistDetailResponse:
|
||||||
|
works = {}
|
||||||
|
for work in artist.comic_works:
|
||||||
|
work_type = work.work_type.name
|
||||||
|
comic_title = work.comic.title
|
||||||
|
if work_type in works:
|
||||||
|
works[work_type].append(comic_title)
|
||||||
|
else:
|
||||||
|
works[work_type] = [comic_title]
|
||||||
|
response = ArtistDetailResponse(
|
||||||
|
id=artist.id,
|
||||||
|
name=artist.name,
|
||||||
|
works=works
|
||||||
|
)
|
||||||
|
return response
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
from typing import List, Type, AnyStr
|
||||||
|
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from src.core.log_conf import logger
|
||||||
|
from src.db.models.comic import Comic, Issue
|
||||||
|
from src.schema.comics.comic import ComicSchema
|
||||||
|
from src.schema.comics.issue import IssueDetailsResponse
|
||||||
|
|
||||||
|
|
||||||
|
def list_comics(db: Session) -> List[Type[Comic]]:
|
||||||
|
comics = db.query(Comic).all()
|
||||||
|
return comics
|
||||||
|
|
||||||
|
|
||||||
|
def get_issue_details(issue: Issue) -> IssueDetailsResponse:
|
||||||
|
response = IssueDetailsResponse(
|
||||||
|
id=issue.id,
|
||||||
|
issue_number=issue.issue_number,
|
||||||
|
in_stock=issue.in_stock,
|
||||||
|
is_read=issue.is_read,
|
||||||
|
comic_id=issue.comic_id,
|
||||||
|
volume_id=issue.volume_id
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def update_comic(comic: ComicSchema, comic_id: AnyStr, db: Session) -> type[Comic] | None:
|
||||||
|
logger.info(f"update_comic: {comic} with {comic_id}")
|
||||||
|
comic = db.get(Comic, comic_id)
|
||||||
|
return comic
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import AnyStr
|
||||||
|
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from src.core.log_conf import logger
|
||||||
|
from src.db.models.comic import WorkType
|
||||||
|
from src.schema.comics.worktype import AddWorkType
|
||||||
|
|
||||||
|
|
||||||
|
def create_new_worktype(work: AddWorkType, db: Session) -> WorkType:
|
||||||
|
worktype = WorkType()
|
||||||
|
worktype.id = str(uuid.uuid4())
|
||||||
|
worktype.created_date = datetime.now()
|
||||||
|
worktype.last_modified_date = datetime.now()
|
||||||
|
worktype.name = work.worktype
|
||||||
|
db.add(worktype)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(worktype)
|
||||||
|
logger.info(f"create_new_worktype: {worktype}")
|
||||||
|
return worktype
|
||||||
|
|
||||||
|
|
||||||
|
def update_worktype(work: AddWorkType, worktype_id: AnyStr, db: Session) -> WorkType:
|
||||||
|
logger.info("update worktype")
|
||||||
|
worktype = db.get(WorkType, worktype_id)
|
||||||
|
worktype.name = work.worktype
|
||||||
|
db.add(worktype)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(worktype)
|
||||||
|
return worktype
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from typing import AnyStr
|
||||||
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
from src.core.log_conf import logger
|
||||||
|
from src.db.models.media import MediaFile, MediaVideo
|
||||||
|
from src.webapps.media.forms import AddLinkForm
|
||||||
|
|
||||||
|
|
||||||
|
def create_new_video(video: AddLinkForm, db: Session) -> MediaVideo:
|
||||||
|
print(video.url)
|
||||||
|
media_video = MediaVideo()
|
||||||
|
media_video.id = str(uuid.uuid4())
|
||||||
|
media_video.url = video.url
|
||||||
|
media_video.created_date = datetime.now()
|
||||||
|
media_video.last_modified_date = datetime.now()
|
||||||
|
media_video.review = True
|
||||||
|
media_video.should_download = True
|
||||||
|
db.add(media_video)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(media_video)
|
||||||
|
print(media_video)
|
||||||
|
return media_video
|
||||||
|
|
||||||
|
def create_new_mediafile(link: AnyStr, db: Session) -> MediaFile:
|
||||||
|
logger.info("create MediaFile with url {link}")
|
||||||
|
media_file: MediaFile = MediaFile()
|
||||||
|
media_file.id = str(uuid.uuid4())
|
||||||
|
media_file.url = link
|
||||||
|
media_file.created_date = datetime.now()
|
||||||
|
media_file.last_modified_date = datetime.now()
|
||||||
|
media_file.version = 0
|
||||||
|
media_file.review = True
|
||||||
|
media_file.should_download = True
|
||||||
|
db.add(media_file)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(media_file)
|
||||||
|
logger.info(f"created {media_file}")
|
||||||
|
return media_file
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Generator, Annotated
|
from typing import Generator
|
||||||
|
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker, Session
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
from src.core.config import settings
|
from src.core.config import settings
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import databases
|
||||||
|
from src.db.session import SQLALCHEMY_DATABASE_URL
|
||||||
|
|
||||||
|
|
||||||
|
async def check_db_connected():
|
||||||
|
try:
|
||||||
|
if not str(SQLALCHEMY_DATABASE_URL).__contains__("sqlite"):
|
||||||
|
database = databases.Database(SQLALCHEMY_DATABASE_URL)
|
||||||
|
if not database.is_connected:
|
||||||
|
await database.connect()
|
||||||
|
await database.execute("SELECT 1")
|
||||||
|
print("Database is connected (^_^)")
|
||||||
|
except Exception as e:
|
||||||
|
print(
|
||||||
|
"Looks like db is missing or is there is some problem in connection,see below traceback"
|
||||||
|
)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
async def check_db_disconnected():
|
||||||
|
try:
|
||||||
|
if not str(SQLALCHEMY_DATABASE_URL).__contains__("sqlite"):
|
||||||
|
database = databases.Database(SQLALCHEMY_DATABASE_URL)
|
||||||
|
if database.is_connected:
|
||||||
|
await database.disconnect()
|
||||||
|
print("Database is Disconnected (-_-) zZZ")
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
+17
-5
@@ -1,12 +1,25 @@
|
|||||||
|
import logging
|
||||||
|
import logging.config
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from src.apis.base import api_router
|
from src.apis.base import api_router
|
||||||
|
from src.core.log_conf import LOGGING_CONFIG, logger
|
||||||
from src.db.session import engine
|
from src.db.session import engine
|
||||||
|
from src.db.utils import check_db_connected, check_db_disconnected
|
||||||
from src.webapps.base import api_router as web_app_router
|
from src.webapps.base import api_router as web_app_router
|
||||||
from src.core.config import settings
|
from src.core.config import settings
|
||||||
from src.db.models.base import Base
|
from src.db.models.base import Base
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
await check_db_connected()
|
||||||
|
yield
|
||||||
|
await check_db_disconnected()
|
||||||
|
|
||||||
def include_router(app: FastAPI):
|
def include_router(app: FastAPI):
|
||||||
app.include_router(api_router)
|
app.include_router(api_router)
|
||||||
app.include_router(web_app_router)
|
app.include_router(web_app_router)
|
||||||
@@ -17,13 +30,12 @@ def configure_static(app: FastAPI):
|
|||||||
def create_tables():
|
def create_tables():
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
def start_application():
|
def start_application(log):
|
||||||
app = FastAPI(title=settings.PROJECT_NAME, version=settings.PROJECT_VERSION)
|
log.info(f"using database: {settings.DATABASE_URL}")
|
||||||
|
app = FastAPI(title=settings.PROJECT_NAME, version=settings.PROJECT_VERSION, lifespan=lifespan)
|
||||||
include_router(app)
|
include_router(app)
|
||||||
configure_static(app)
|
configure_static(app)
|
||||||
create_tables()
|
create_tables()
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
kontor = start_application(logger)
|
||||||
kontor = start_application()
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Token(BaseModel):
|
||||||
|
access_token: str
|
||||||
|
token_type: str
|
||||||
@@ -1,36 +1,19 @@
|
|||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from src.db.models.comic import Artist
|
|
||||||
|
|
||||||
|
|
||||||
class ArtistCreation(BaseModel):
|
class ArtistCreation(BaseModel):
|
||||||
|
id: str
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
class ArtistResponse(BaseModel):
|
class ArtistResponse(BaseModel):
|
||||||
id: UUID
|
id: str
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
class ArtistDetailResponse(BaseModel):
|
class ArtistDetailResponse(BaseModel):
|
||||||
id: UUID
|
id: str
|
||||||
name: str
|
name: str
|
||||||
|
weblink: str
|
||||||
works: Dict[str, List[str]]
|
works: Dict[str, List[str]]
|
||||||
|
|
||||||
def get_artist_details(artist: Artist) -> ArtistDetailResponse:
|
|
||||||
works = {}
|
|
||||||
for work in artist.comic_works:
|
|
||||||
work_type = work.work_type.name
|
|
||||||
comic_title = work.comic.title
|
|
||||||
if work_type in works:
|
|
||||||
works[work_type].append(comic_title)
|
|
||||||
else:
|
|
||||||
works[work_type] = [comic_title]
|
|
||||||
response = ArtistDetailResponse(
|
|
||||||
id=artist.id,
|
|
||||||
name=artist.name,
|
|
||||||
works=works
|
|
||||||
)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,35 @@
|
|||||||
from typing import List, Dict
|
from typing import List, Dict, Optional
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, AnyUrl
|
||||||
|
|
||||||
from src.db.models.comic import Comic
|
from src.db.models.comic import Comic
|
||||||
|
|
||||||
|
|
||||||
class ComicResponse(BaseModel):
|
class ComicResponse(BaseModel):
|
||||||
id: UUID
|
id: str
|
||||||
title: str
|
title: str
|
||||||
completed: bool
|
completed: bool
|
||||||
|
|
||||||
class ComicDetailsResponse(BaseModel):
|
class ComicDetailsResponse(BaseModel):
|
||||||
id: UUID
|
id: str
|
||||||
created: str
|
created: str
|
||||||
title: str
|
title: str
|
||||||
completed : bool
|
completed : bool
|
||||||
current_order : bool
|
current_order : bool
|
||||||
|
weblink: str
|
||||||
publisher: str
|
publisher: str
|
||||||
volumes: List[str]
|
volumes: List[str]
|
||||||
works: Dict[str, List[str]]
|
works: Dict[str, List[str]]
|
||||||
|
|
||||||
|
|
||||||
|
class ComicSchema(BaseModel):
|
||||||
|
id: str
|
||||||
|
title: str
|
||||||
|
weblink: Optional[AnyUrl]
|
||||||
|
completed: Optional[bool]
|
||||||
|
current_order: Optional[bool]
|
||||||
|
|
||||||
|
|
||||||
def get_short_info(comic: Comic) -> ComicResponse:
|
def get_short_info(comic: Comic) -> ComicResponse:
|
||||||
response = ComicResponse(
|
response = ComicResponse(
|
||||||
id=comic.id,
|
id=comic.id,
|
||||||
@@ -46,11 +55,11 @@ def get_comic_details(comic: Comic) -> ComicDetailsResponse | None:
|
|||||||
id=comic.id,
|
id=comic.id,
|
||||||
created=str(comic.created_date),
|
created=str(comic.created_date),
|
||||||
title=comic.title,
|
title=comic.title,
|
||||||
completed=(comic.completed == 1),
|
completed=comic.completed,
|
||||||
current_order=(comic.current_order == 1),
|
current_order=comic.current_order,
|
||||||
|
weblink=comic.weblink,
|
||||||
publisher=comic.publisher.name,
|
publisher=comic.publisher.name,
|
||||||
volumes=volumes,
|
volumes=volumes,
|
||||||
works=works
|
works=works
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class IssueDetailsResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
issue_number: str
|
||||||
|
in_stock: bool
|
||||||
|
is_read: bool
|
||||||
|
comic_id: str
|
||||||
|
volume_id: str | None
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class AddWorkType(BaseModel):
|
||||||
|
worktype: str
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from src.db.models.media import MediaFile
|
from src.db.models.media import MediaFile
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class MediaFileResponse(BaseModel):
|
class MediaFileResponse(BaseModel):
|
||||||
id: UUID
|
id: str
|
||||||
title: str | None = None
|
title: str | None = None
|
||||||
file_name: str | None = None
|
file_name: str | None = None
|
||||||
cloud_link: str | None = None
|
cloud_link: str | None = None
|
||||||
@@ -23,8 +22,8 @@ def get_file_details(mediafile: MediaFile) -> MediaFileResponse | None:
|
|||||||
file_name=mediafile.file_name,
|
file_name=mediafile.file_name,
|
||||||
cloud_link=mediafile.cloud_link,
|
cloud_link=mediafile.cloud_link,
|
||||||
url=str(mediafile.url),
|
url=str(mediafile.url),
|
||||||
review=(mediafile.review == 1),
|
review=mediafile.review,
|
||||||
should_download=(mediafile.should_download == 1))
|
should_download=mediafile.should_download)
|
||||||
#print(f"id: {mediafile.id}: review: {response.review} <- {mediafile.review}")
|
#print(f"id: {mediafile.id}: review: {response.review} <- {mediafile.review}")
|
||||||
#print(f"id: {mediafile.id}: download: {response.should_download} <- {mediafile.should_download}")
|
#print(f"id: {mediafile.id}: download: {response.should_download} <- {mediafile.should_download}")
|
||||||
return response
|
return response
|
||||||
@@ -35,11 +34,5 @@ def set_file(model: MediaFileResponse, mediafile: MediaFile) -> None:
|
|||||||
mediafile.url = model.url
|
mediafile.url = model.url
|
||||||
mediafile.title = model.title
|
mediafile.title = model.title
|
||||||
mediafile.last_modified_date = datetime.now()
|
mediafile.last_modified_date = datetime.now()
|
||||||
if model.review:
|
mediafile.review = model.review
|
||||||
mediafile.review = 1
|
mediafile.should_download = model.should_download
|
||||||
else:
|
|
||||||
mediafile.review = 0
|
|
||||||
if model.should_download:
|
|
||||||
mediafile.should_download = 1
|
|
||||||
else:
|
|
||||||
mediafile.should_download = 0
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class AddLink(BaseModel):
|
||||||
|
url: str
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
from uuid import UUID
|
|
||||||
|
|
||||||
|
from typing import AnyStr
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class SportResponse(BaseModel):
|
class SportResponse(BaseModel):
|
||||||
id: UUID
|
id: AnyStr
|
||||||
name: str
|
name: str
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{% extends "shared/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>Permissions</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% with msg=msg %}
|
||||||
|
{% include "components/alerts.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h1 class="display-5">Permissions..</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead><tr>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th colspan="2">Actions</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for permission in permissions %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><a href="/admins/permissions/{{permission.id}}">{{permission.name}}</a></th>
|
||||||
|
<td><a href="/admin/permission/edit/{{permission.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
||||||
|
<td><a href="/admin/permission/delete/{{permission.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
<a href="/admin/permission/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add Permission</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{% extends "shared/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>Profiles</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% with msg=msg %}
|
||||||
|
{% include "components/alerts.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h1 class="display-5">Profiles..</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead><tr>
|
||||||
|
<th scope="col">Username</th>
|
||||||
|
<th scope="col">First Name</th>
|
||||||
|
<th scope="col">Last Name</th>
|
||||||
|
<th colspan="2">Actions</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for profile in profiles %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><a href="/admins/profiles/{{profile.id}}">{{profile.user_name}}</a></th>
|
||||||
|
<th scope="row">{{profile.first_name}}</th>
|
||||||
|
<th scope="row">{{profile.last_name}}</th>
|
||||||
|
<th scope="row">{{profile.email}}</th>
|
||||||
|
<td><a href="/admin/profile/edit/{{profile.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
||||||
|
<td><a href="/admin/profile/delete/{{profile.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
<a href="/admin/profile/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add Profile</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{% extends "shared/base.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>Login</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<h5 class="display-5">Login to Kontor</h5>
|
||||||
|
<div class="text-danger font-weight-bold">
|
||||||
|
{% for error in errors %}
|
||||||
|
<li>{{error}}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="text-success font-weight-bold">
|
||||||
|
{% if msg %}
|
||||||
|
<div class="badge bg-success text-wrap font-weight-bold" style="font-size: large;">
|
||||||
|
{{msg}}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row my-5">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label>Email</label>
|
||||||
|
<input type="text" required placeholder="Your email" name="email" value="{{email}}" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label>Password</label>
|
||||||
|
<input type="password" required placeholder="Choose a secure password" value="{{password}}" name="password" class="form-control">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
+1
@@ -1,6 +1,7 @@
|
|||||||
<div class="card shadow p-3 mb-2 bg-body rounded" style="width: 18rem;">
|
<div class="card shadow p-3 mb-2 bg-body rounded" style="width: 18rem;">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">{{obj.name}}</h5>
|
<h5 class="card-title">{{obj.name}}</h5>
|
||||||
|
<p class="card-text">Link : {{obj.weblink}}</p>
|
||||||
<a href="/comic/artists/{{obj.id}}" class="btn btn-primary">Read more</a>
|
<a href="/comic/artists/{{obj.id}}" class="btn btn-primary">Read more</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -20,12 +20,16 @@
|
|||||||
<th scope="row">Artist Name</th>
|
<th scope="row">Artist Name</th>
|
||||||
<td colspan="2">{{artist.name}}</td>
|
<td colspan="2">{{artist.name}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Link</th>
|
||||||
|
<td colspan="2">{{artist.weblink}}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Works</th>
|
<th scope="row">Works</th>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
{% for work in artist.get_comics() %}
|
{% for work in artist.get_comics() %}
|
||||||
<p>
|
<p>
|
||||||
{{work}}:
|
<a href="/comic/worktypes/{{work.id}}">{{work.name}}</a>
|
||||||
<ul>
|
<ul>
|
||||||
{% for comic in artist.get_comics()[work] %}
|
{% for comic in artist.get_comics()[work] %}
|
||||||
<li><a href="/comic/comics/{{comic.id}}">{{comic.title}}</a></li>
|
<li><a href="/comic/comics/{{comic.id}}">{{comic.title}}</a></li>
|
||||||
@@ -43,8 +47,20 @@
|
|||||||
<th scope="row">Data Modified</th>
|
<th scope="row">Data Modified</th>
|
||||||
<td colspan="2">{{artist.last_modified_date}}</td>
|
<td colspan="2">{{artist.last_modified_date}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Data Version</th>
|
||||||
|
<td colspan="2">{{artist.version}}</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<a href="/comic/artists" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
||||||
|
<a href="/comic/artist/edit/{{artist.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
||||||
|
<a href="/comic/artist/delete/{{artist.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{% extends "shared/base.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>Edit Artist</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="text-danger font-weight-bold">
|
||||||
|
{% for error in errors %}
|
||||||
|
<li>{{error}}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row my-5">
|
||||||
|
<h3 class="text-center display-4">Edit an Artist entry</h3>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<input type="text" required class="form-control" name="artist.name" value="{{artist_name}}" placeholder="Artist name here">
|
||||||
|
<input type="text" required class="form-control" name="artist.link" value="{{artist_link}}" placeholder="Web link for artist here">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
<button type="cancel" class="btn btn-primary">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
{% for artist in artists %}
|
{% for artist in artists %}
|
||||||
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
||||||
{% with obj=artist %}
|
{% with obj=artist %}
|
||||||
{% include "components/artist_cards.html" %}
|
{% include "comic/artist_cards.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% if loop.index %3 %}
|
{% if loop.index %3 %}
|
||||||
|
|||||||
@@ -27,12 +27,17 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Link</th>
|
||||||
|
<td colspan="2">{{comic.weblink}}</td>
|
||||||
|
</tr>
|
||||||
|
{% if comic.get_artists()|length > 0 %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Works</th>
|
<th scope="row">Works</th>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
{% for work in comic.get_artists() %}
|
{% for work in comic.get_artists() %}
|
||||||
<p>
|
<p>
|
||||||
{{work}}:
|
<a href="/comic/worktypes/{{work.id}}">{{work.name}}</a>
|
||||||
<ul>
|
<ul>
|
||||||
{% for artist in comic.get_artists()[work] %}
|
{% for artist in comic.get_artists()[work] %}
|
||||||
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
|
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
|
||||||
@@ -42,6 +47,29 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if comic.volumes|length > 0 %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Volumes</th>
|
||||||
|
<td colspan="2">
|
||||||
|
<ul>
|
||||||
|
{% for volume in comic.volumes %}
|
||||||
|
<li><a href="/comic/volumes/{{volume.id}}">{{volume.name}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Issues</th>
|
||||||
|
<td colspan="2">
|
||||||
|
<ul>
|
||||||
|
{% for issue in comic.sorted_issues() %}
|
||||||
|
<li><a href="/comic/issues/{{issue.id}}">{{issue.get_full_title()}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Data Created</th>
|
<th scope="row">Data Created</th>
|
||||||
<td colspan="2">{{comic.created_date}}</td>
|
<td colspan="2">{{comic.created_date}}</td>
|
||||||
@@ -54,18 +82,15 @@
|
|||||||
<th scope="row">Data Version</th>
|
<th scope="row">Data Version</th>
|
||||||
<td colspan="2">{{comic.version}}</td>
|
<td colspan="2">{{comic.version}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th scope="row">Issues</th>
|
|
||||||
<td colspan="2">
|
|
||||||
<ul>
|
|
||||||
{% for issue in comic.sorted_issues() %}
|
|
||||||
<li><a href="comic/issues/{{issue.id}}">{{issue.issue_number}}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<a href="/comic/comics" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
||||||
|
<a href="/comic/comic/edit/{{comic.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
||||||
|
<a href="/comic/comic/delete/{{comic.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
{% extends "shared/base.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>Edit Comic</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="text-danger font-weight-bold">
|
||||||
|
{% for error in errors %}
|
||||||
|
<li>{{error}}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row my-5">
|
||||||
|
<h3 class="text-center display-4">Edit an Comic entry</h3>
|
||||||
|
<form class="form-horizontal" method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2" for="title">Title</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" class="form-control" id="title" name="title" value="{{comic_title}}" placeholder="Comic title here">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2" for="weblink">Link</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input type="text" class="form-control" id="weblink" name="weblink" value="{{comic_weblink}}" placeholder="Web link for comic here">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-offset-2 col-sm-10">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label><input type="checkbox" id="completed" name="completed" value="{{comic_completed}}"> Completed</label>
|
||||||
|
</div
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-offset-2 col-sm-10">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label><input type="checkbox" id="current_order" name="current_order" value="{{comic_current_order}}"> Current Order</label>
|
||||||
|
</div
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-offset-2 col-sm-10">
|
||||||
|
<button type="submit" class="btn btn-primary" name="action" value="submit">Submit</button>
|
||||||
|
<button type="cancel" class="btn btn-primary" name="action" value="cancel">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -10,22 +10,39 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<form class="d-flex" action="/comic/comics/">
|
||||||
<h1 class="display-5">Find Jobs..</h1>
|
<input class="form-control me-2" name="query" id="autocomplete" type="search" placeholder="Search" aria-label="Search">
|
||||||
</div>
|
Completed<input type="checkbox" name="completed" {% if request.query_params.get("completed")=="on" %}checked{% endif %} aria-label="Completed">
|
||||||
|
Order<input type="checkbox" name="order" {% if request.query_params.get("order")=="on" %}checked{% endif %} aria-label="Order">
|
||||||
|
<button class="btn btn-outline-success" type="submit">Search</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<h1 class="display-5">Comics..</h1>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead><tr>
|
||||||
|
<th scope="col">Title</th>
|
||||||
|
<th scope="col">Publisher</th>
|
||||||
|
<th scope="col">Completed</th>
|
||||||
|
<th colspan="2">Actions</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>
|
||||||
{% for comic in comics %}
|
{% for comic in comics %}
|
||||||
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
<tr>
|
||||||
{% with obj=comic %}
|
<th scope="row"><a href="/comic/comics/{{comic.id}}">{{comic.title}}</a></th>
|
||||||
{% include "components/comic_cards.html" %}
|
<td><a href="/comic/publishers/{{comic.publisher.id}}">{{comic.publisher.name}}</a></td>
|
||||||
{% endwith %}
|
<td>{% with check=comic.completed %}{% include "components/check.html" %}{% endwith %}</td>
|
||||||
{% if loop.index %3 %}
|
<td><a href="/comic/comics/edit/{{comic.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
||||||
</div>
|
<td><a href="/comic/comics/delete/{{comic.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
||||||
{% else %}
|
</tr>
|
||||||
</div></div><br><div class="row">
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
<a href="/comic/comic/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add Comic</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
{% extends "shared/base.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>Issue Detail</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h1 class="display-5">Issue Detail</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Issue Number</th>
|
||||||
|
<td colspan="2">{{issue.issue_number}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Full Title</th>
|
||||||
|
<td colspan="2">{{issue.title}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Published</th>
|
||||||
|
<td colspan="2">{{issue.published_on}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Auf Lager</th>
|
||||||
|
<td colspan="2">
|
||||||
|
{% with check=issue.in_stock %}
|
||||||
|
{% include "components/check.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Gelesen</th>
|
||||||
|
<td colspan="2">
|
||||||
|
{% with check=issue.is_read %}
|
||||||
|
{% include "components/check.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Comic</th>
|
||||||
|
<td colspan="2">
|
||||||
|
<a href="/comic/comics/{{issue.comic_id}}">{{issue.comic.title}}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% if issue.volume %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Volume</th>
|
||||||
|
<td colspan="2">
|
||||||
|
<a href="/comic/comics/{{issue.volume_id}}">{{issue.volume.name}}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Works</th>
|
||||||
|
<td colspan="2">
|
||||||
|
{% for work in issue.get_artists() %}
|
||||||
|
<p>
|
||||||
|
<a href="/comic/worktypes/{{work.id}}">{{work.name}}</a>
|
||||||
|
<ul>
|
||||||
|
{% for artist in issue.get_artists()[work] %}
|
||||||
|
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Data Created</th>
|
||||||
|
<td colspan="2">{{issue.created_date}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Data Modified</th>
|
||||||
|
<td colspan="2">{{issue.last_modified_date}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Data Version</th>
|
||||||
|
<td colspan="2">{{issue.version}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -20,6 +20,24 @@
|
|||||||
<th scope="row">Publisher Name</th>
|
<th scope="row">Publisher Name</th>
|
||||||
<td colspan="2">{{publisher.name}}</td>
|
<td colspan="2">{{publisher.name}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% if publisher.parent_publisher_id %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Parent Company</th>
|
||||||
|
<td colspan="2"><a href="/comic/publishers/{{publisher.parent_publisher_id}}">{{publisher.parent_publisher.name}}</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if publisher.imprints|length > 0 %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Imprints</th>
|
||||||
|
<td colspan="2">
|
||||||
|
<ul>
|
||||||
|
{% for imprint in publisher.imprints %}
|
||||||
|
<li><a href="/comic/publishers/{{imprint.id}}">{{imprint.name}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Comics</th>
|
<th scope="row">Comics</th>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
@@ -41,5 +59,12 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<a href="/comic/publishers" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
||||||
|
<a href="/comic/publisher/edit/{{publisher.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
||||||
|
<a href="/comic/publisher/delete/{{publisher.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
{% for publisher in publishers %}
|
{% for publisher in publishers %}
|
||||||
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
||||||
{% with obj=publisher %}
|
{% with obj=publisher %}
|
||||||
{% include "components/publisher_cards.html" %}
|
{% include "comic/publisher_cards.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% if loop.index %3 %}
|
{% if loop.index %3 %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
{% extends "shared/base.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>WorkType Detail</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">WorkType Name</th>
|
||||||
|
<td colspan="2">{{worktype.name}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Works</th>
|
||||||
|
<td colspan="2">
|
||||||
|
{% for comic in worktype.get_artists() %}
|
||||||
|
<p>
|
||||||
|
{{comic}}:
|
||||||
|
<ul>
|
||||||
|
{% for artist in worktype.get_artists()[comic] %}
|
||||||
|
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">ID</th>
|
||||||
|
<td colspan="2">{{worktype.id}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Data Created</th>
|
||||||
|
<td colspan="2">{{worktype.created_date}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Data Modified</th>
|
||||||
|
<td colspan="2">{{worktype.last_modified_date}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Data Version</th>
|
||||||
|
<td colspan="2">{{worktype.version}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<a href="/comic/worktypes" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
||||||
|
<a href="/comic/worktype/edit/{{worktype.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
||||||
|
<a href="/comic/worktype/delete/{{worktype.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{% extends "shared/base.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>Edit WorkType</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="text-danger font-weight-bold">
|
||||||
|
{% for error in errors %}
|
||||||
|
<li>{{error}}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row my-5">
|
||||||
|
<h3 class="text-center display-4">Add a WorkType</h3>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<input type="text" required class="form-control" name="worktype" value="{{worktype}}" placeholder="WorkType here">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{% extends "shared/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>WorkTypes</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% with msg=msg %}
|
||||||
|
{% include "components/alerts.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
<div class="container">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead><tr>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for worktype in worktypes %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><a href="/comic/worktypes/{{worktype.id}}">{{worktype.name}}</a></th>
|
||||||
|
<td><a href="/comic/worktype/edit/{{worktype.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
||||||
|
<td><a href="/comic/worktype/delete/{{worktype.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<a href="/comic/worktype/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add WorkType</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{% if check == 1 %}
|
{% if check %}
|
||||||
<img src="{{ url_for('static', path='images/tick.png') }}" alt="" width="24" height="24">
|
<img src="{{ url_for('static', path='images/tick.png') }}" alt="" width="24" height="24">
|
||||||
{% else %}
|
{% else %}
|
||||||
<img src="{{ url_for('static', path='images/cross.png') }}" alt="" width="24" height="24">
|
<img src="{{ url_for('static', path='images/cross.png') }}" alt="" width="24" height="24">
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light px-5">
|
<nav class="navbar navbar-expand-lg navbar-light bg-light px-5">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="#">
|
<a class="navbar-brand" href="#">Kontor</a>
|
||||||
<img src="{{ url_for('static', path='images/logo.png') }}" alt="" width="30" height="24">
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
@@ -18,7 +16,7 @@
|
|||||||
<li><a class="dropdown-item" href="/comic/artists/">Artists</a></li>
|
<li><a class="dropdown-item" href="/comic/artists/">Artists</a></li>
|
||||||
<li><a class="dropdown-item" href="/comic/publishers/">Publishers</a></li>
|
<li><a class="dropdown-item" href="/comic/publishers/">Publishers</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#">Something else here</a></li>
|
<li><a class="dropdown-item" href="/comic/worktypes">WorkTypes</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
@@ -26,6 +24,7 @@
|
|||||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<li><a class="dropdown-item" href="/media/files/">MediaFiles</a></li>
|
<li><a class="dropdown-item" href="/media/files/">MediaFiles</a></li>
|
||||||
<li><a class="dropdown-item" href="/media/actors/">MediaActors</a></li>
|
<li><a class="dropdown-item" href="/media/actors/">MediaActors</a></li>
|
||||||
|
<li><a class="dropdown-item" href="/media/videos/">MediaVideos</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
@@ -42,18 +41,19 @@
|
|||||||
<li><a class="dropdown-item" href="/register/">Signup</a></li>
|
<li><a class="dropdown-item" href="/register/">Signup</a></li>
|
||||||
<li><a class="dropdown-item" href="/login/">Login</a></li>
|
<li><a class="dropdown-item" href="/login/">Login</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="#">Something else here</a></li>
|
<li><a class="dropdown-item" href="/admin/profiles">Profiles</a></li>
|
||||||
|
<li><a class="dropdown-item" href="/admin/permissions">Permissions</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav ml-auto mb-2 mb-lg-0">
|
<ul class="navbar-nav ml-auto mb-2 mb-lg-0">
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
Jobs
|
Media
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<li><a class="dropdown-item" href="/comics/">Comics</a></li>
|
<li><a class="dropdown-item" href="/media/add-link/">Add Link</a></li>
|
||||||
<li><a class="dropdown-item" href="/comics/">Media</a></li>
|
<li><a class="dropdown-item" href="/media/add-file">Add MediaFile</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -41,5 +41,12 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<a href="/media/actors" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
||||||
|
<a href="/media/actor/edit/{{actor.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
||||||
|
<a href="/media/actor/delete/{{actor.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
{% for actor in actors %}
|
{% for actor in actors %}
|
||||||
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
||||||
{% with obj=actor %}
|
{% with obj=actor %}
|
||||||
{% include "components/actor_cards.html" %}
|
{% include "media/actor_cards.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% if loop.index %3 %}
|
{% if loop.index %3 %}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends "shared/base.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>Add a Video Link</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="text-danger font-weight-bold">
|
||||||
|
{% for error in errors %}
|
||||||
|
<li>{{error}}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row my-5">
|
||||||
|
<h3 class="text-center display-4">Add a Video Link</h3>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<input type="text" required class="form-control" name="url" value="{{url}}" placeholder="Video Link here">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
@@ -61,5 +61,12 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<a href="/media/files" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
||||||
|
<a href="/media/file/edit/{{mediafile.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
||||||
|
<a href="/media/file/delete/{{mediafile.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -9,21 +9,31 @@
|
|||||||
{% include "components/alerts.html" %}
|
{% include "components/alerts.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<form class="d-flex" action="/media/files/">
|
||||||
|
<input class="form-control me-2" name="query" id="autocomplete" type="search" placeholder="Search" aria-label="Search">
|
||||||
|
Review<input type="checkbox" name="review" aria-label="Review">
|
||||||
|
Download<input type="checkbox" name="download" aria-label="Download">
|
||||||
|
<button class="btn btn-outline-success" type="submit">Search</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
<th scope="col">Titel</th>
|
<th scope="col">Titel</th>
|
||||||
<th scope="col">URL</th>
|
<th scope="col">Review</th>
|
||||||
<th scope="col">Cloudlink</th>
|
<th scope="col">Download</th>
|
||||||
</tr></thead>
|
</tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for mediafile in mediafiles %}
|
{% for mediafile in mediafiles %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row"><a href="/media/files/{{mediafile.id}}">{{mediafile.title}}</a></th>
|
<th scope="row"><a href="/media/files/{{mediafile.id}}">{{mediafile.title}}</a></th>
|
||||||
<td>{{mediafile.url}}</td>
|
<td>{% with check=mediafile.review %}{% include "components/check.html" %}{% endwith %}</td>
|
||||||
<td>{{mediafile.cloud_link}}</td>
|
<td>{% with check=mediafile.should_download %}{% include "components/check.html" %}{% endwith %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
{% extends "shared/base.html" %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>MediaVideo Detail</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h1 class="display-5">MediaVideo Detail</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">MediaVideo Title</th>
|
||||||
|
<td colspan="2">{{mediavideo.title}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">MediaVideo URL</th>
|
||||||
|
<td colspan="2">{{mediavideo.url}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">MediaVideo Cloudlink</th>
|
||||||
|
<td colspan="2">{{mediavideo.cloud_link}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">MediaVideo Download?</th>
|
||||||
|
<td colspan="2">{{mediavideo.should_download}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">MediaVideo Review?</th>
|
||||||
|
<td colspan="2">{{mediavideo.review}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Data Created</th>
|
||||||
|
<td colspan="2">{{mediavideo.created_date}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Data Modified</th>
|
||||||
|
<td colspan="2">{{mediavideo.last_modified_date}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Data Version</th>
|
||||||
|
<td colspan="2">{{mediavideo.version}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends "shared/base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
<title>Video Links</title>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% with msg=msg %}
|
||||||
|
{% include "components/alerts.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
<div class="container">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead><tr>
|
||||||
|
<th scope="col">Titel</th>
|
||||||
|
<th scope="col">Review</th>
|
||||||
|
<th scope="col">Download</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for mediavideo in mediavideos %}
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><a href="/media/videos/{{mediavideo.id}}">{{mediavideo.title}}</a></th>
|
||||||
|
<td>{% with check=mediavideo.review %}{% include "components/check.html" %}{% endwith %}</td>
|
||||||
|
<td>{% with check=mediavideo.should_download %}{% include "components/check.html" %}{% endwith %}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en-us">
|
<html lang="de-DE">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
@@ -21,6 +21,5 @@
|
|||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
from typing import AnyStr
|
||||||
|
from fastapi import APIRouter, Request
|
||||||
|
from fastapi.security.utils import get_authorization_scheme_param
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
from src.apis.utils import SessionDep
|
||||||
|
from src.apis.version1.admin import get_current_user_from_token
|
||||||
|
from src.db.models.admin import Permission, Profile
|
||||||
|
|
||||||
|
templates = Jinja2Templates(directory="src/templates")
|
||||||
|
router = APIRouter(include_in_schema=False, prefix="/admin")
|
||||||
|
|
||||||
|
@router.get("/profiles")
|
||||||
|
def get_profiles(db: SessionDep, request: Request, msg: str | None = None):
|
||||||
|
profiles = db.query(Profile).all()
|
||||||
|
return templates.TemplateResponse("admin/profiles.html", {"request": request, "msg": msg, "profiles": profiles})
|
||||||
|
|
||||||
|
@router.get("/profiles/{profile_id}")
|
||||||
|
def comic_details(profile_id: AnyStr, request: Request, db: SessionDep):
|
||||||
|
profile = db.get(Profile, profile_id)
|
||||||
|
return templates.TemplateResponse("admin/profile_detail.html", {"request": request, "profile":profile})
|
||||||
|
|
||||||
|
@router.get("/permissions")
|
||||||
|
def get_permissions(db: SessionDep, request: Request, msg: str | None = None):
|
||||||
|
permissions = db.query(Permission).all()
|
||||||
|
return templates.TemplateResponse("admin/permissions.html", {"request": request, "msg": msg, "permissions": permissions})
|
||||||
|
|
||||||
|
@router.get("/permissions/{permission_id}")
|
||||||
|
def artist_detail(permission_id: AnyStr, request: Request, db: SessionDep):
|
||||||
|
permission= db.get(Permission, str(permission_id))
|
||||||
|
return templates.TemplateResponse("comic/permission_detail.html", {"request": request, "permission": permission})
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
from typing import List
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi import Request
|
||||||
|
|
||||||
|
|
||||||
|
class LoginForm:
|
||||||
|
def __init__(self, request: Request):
|
||||||
|
self.request: Request = request
|
||||||
|
self.errors: List = []
|
||||||
|
self.username: Optional[str] = None
|
||||||
|
self.password: Optional[str] = None
|
||||||
|
|
||||||
|
async def load_data(self):
|
||||||
|
form = await self.request.form()
|
||||||
|
# since auth works on username field we are considering email as username
|
||||||
|
self.username = form.get("email")
|
||||||
|
self.password = form.get("password")
|
||||||
|
|
||||||
|
async def is_valid(self):
|
||||||
|
if not self.username or not (self.username.__contains__("@")):
|
||||||
|
self.errors.append("Email is required")
|
||||||
|
if not self.password or not len(self.password) >= 4:
|
||||||
|
self.errors.append("A valid password is required")
|
||||||
|
if not self.errors:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
from src.apis.version1.admin import login_for_access_token
|
||||||
|
from src.db.session import get_db
|
||||||
|
from fastapi import APIRouter
|
||||||
|
from fastapi import Depends
|
||||||
|
from fastapi import HTTPException
|
||||||
|
from fastapi import Request
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from src.webapps.auth.forms import LoginForm
|
||||||
|
|
||||||
|
|
||||||
|
templates = Jinja2Templates(directory="src/templates")
|
||||||
|
router = APIRouter(include_in_schema=False)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/login/")
|
||||||
|
def login(request: Request):
|
||||||
|
return templates.TemplateResponse("auth/login.html", {"request": request})
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/login/")
|
||||||
|
async def login(request: Request, db: Session = Depends(get_db)):
|
||||||
|
form = LoginForm(request)
|
||||||
|
await form.load_data()
|
||||||
|
if await form.is_valid():
|
||||||
|
try:
|
||||||
|
form.__dict__.update(msg="Login Successful :)")
|
||||||
|
response = templates.TemplateResponse("auth/login.html", form.__dict__)
|
||||||
|
login_for_access_token(response=response, form_data=form, db=db)
|
||||||
|
return response
|
||||||
|
except HTTPException:
|
||||||
|
form.__dict__.update(msg="")
|
||||||
|
form.__dict__.get("errors").append("Incorrect Email or Password")
|
||||||
|
return templates.TemplateResponse("auth/login.html", form.__dict__)
|
||||||
|
return templates.TemplateResponse("auth/login.html", form.__dict__)
|
||||||
@@ -1,15 +1,22 @@
|
|||||||
from fastapi import APIRouter, Request
|
from fastapi import APIRouter, Request
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
from src.webapps.comic import route_comics
|
from src.webapps.admin import route_admin
|
||||||
from src.webapps.media import route_media
|
from src.webapps.auth import route_login
|
||||||
|
from src.webapps.comic import route_comics, route_worktype, route_artists
|
||||||
|
from src.webapps.media import route_media, route_videos
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="src/templates")
|
templates = Jinja2Templates(directory="src/templates")
|
||||||
|
|
||||||
api_router = APIRouter()
|
api_router = APIRouter()
|
||||||
api_router.include_router(route_comics.router)
|
api_router.include_router(route_comics.router)
|
||||||
|
api_router.include_router(route_artists.router)
|
||||||
|
api_router.include_router(route_worktype.router)
|
||||||
api_router.include_router(route_media.router)
|
api_router.include_router(route_media.router)
|
||||||
|
api_router.include_router(route_videos.router)
|
||||||
|
api_router.include_router(route_login.router)
|
||||||
|
api_router.include_router(route_admin.router)
|
||||||
|
|
||||||
@api_router.get("/")
|
@api_router.get("/")
|
||||||
def home(request: Request, msg: str = None):
|
def home(request: Request, msg: str | None = None):
|
||||||
return templates.TemplateResponse("index.html", {"request": request, "msg": msg})
|
return templates.TemplateResponse("index.html", {"request": request, "msg": msg})
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
from fastapi import Request
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class ValidateComicForm:
|
||||||
|
def __init__(self, request: Request, comic_id: str, completed: bool, current_order: bool):
|
||||||
|
self.request = request
|
||||||
|
self.errors: List = []
|
||||||
|
self.id = comic_id
|
||||||
|
self.title: Optional[str] = None
|
||||||
|
self.weblink: Optional[str] = None
|
||||||
|
self.completed = completed
|
||||||
|
self.current_order = current_order
|
||||||
|
|
||||||
|
async def load_data(self):
|
||||||
|
form = await self.request.form()
|
||||||
|
print(f"{form.keys()}")
|
||||||
|
self.title = form.get("title")
|
||||||
|
self.weblink = form.get("weblink")
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
if not self.errors:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.title=}, {self.weblink=}"
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from fastapi import Request
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class AddWorktypeForm:
|
||||||
|
def __init__(self, request: Request):
|
||||||
|
self.request = request
|
||||||
|
self.errors: List = []
|
||||||
|
self.worktype: Optional[str] = None
|
||||||
|
|
||||||
|
async def load_data(self):
|
||||||
|
form = await self.request.form()
|
||||||
|
self.worktype = form.get("worktype")
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
if not self.worktype or (len(self.worktype) == 0):
|
||||||
|
self.errors.append("WorkType cannot be empty")
|
||||||
|
if not self.errors:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
from fastapi import APIRouter, Request, status
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from fastapi.responses import RedirectResponse
|
||||||
|
|
||||||
|
from src.apis.utils import SessionDep
|
||||||
|
from src.db.models.comic import Artist
|
||||||
|
from typing import AnyStr
|
||||||
|
|
||||||
|
#from src.db.repository.comic import create_new_worktype, update_worktype
|
||||||
|
from src.main import logger
|
||||||
|
#from src.schema.comics.worktype import AddWorkType
|
||||||
|
#from src.webapps.comic.forms import AddWorktypeForm
|
||||||
|
|
||||||
|
templates = Jinja2Templates(directory="src/templates")
|
||||||
|
router = APIRouter(include_in_schema=False, prefix="/comic")
|
||||||
|
|
||||||
|
@router.get("/artists")
|
||||||
|
def get_artists(db: SessionDep, request: Request, msg: str | None = None):
|
||||||
|
artists = db.query(Artist).all()
|
||||||
|
return templates.TemplateResponse("comic/artists.html", {"request": request, "msg": msg, "artists": artists})
|
||||||
|
|
||||||
|
@router.get("/artists/{artist_id}")
|
||||||
|
def artist_detail(artist_id: AnyStr, request: Request, db: SessionDep):
|
||||||
|
artist = db.get(Artist, str(artist_id))
|
||||||
|
return templates.TemplateResponse("comic/artist_detail.html", {"request": request, "artist": artist})
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/artist/edit/{artist_id}")
|
||||||
|
def edit_artist(db: SessionDep, request: Request, artist_id: str):
|
||||||
|
artist = db.get(Artist, artist_id)
|
||||||
|
return templates.TemplateResponse("comic/artist_edit.html", {"request": request, "artist_name": artist.name, "artist_link": artist.weblink})
|
||||||
|
|
||||||
|
@router.post("/artist/edit/{artist_id}")
|
||||||
|
async def edit_artist(request: Request, db: SessionDep, artist_id: str):
|
||||||
|
form = AddArtistForm(request)
|
||||||
|
await form.load_data()
|
||||||
|
if form.is_valid():
|
||||||
|
try:
|
||||||
|
artist = AddArtist(**form.__dict__)
|
||||||
|
artist = update_artist(artist=artist, artist_id=artist_id, db=db)
|
||||||
|
return RedirectResponse(f"/comic/artists/{artist.id}", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
form.__dict__.get("errors").append("artist already added")
|
||||||
|
return templates.TemplateResponse("comic/artist_edit.html", form.__dict__)
|
||||||
|
return templates.TemplateResponse("comic/artist_edit.html", form.__dict__)
|
||||||
|
|
||||||
|
@router.get("/artist/delete/{artist_id}")
|
||||||
|
async def delete_artist(db: SessionDep, request: Request, artist_id: str):
|
||||||
|
artist = db.get(Artist, artist_id)
|
||||||
|
db.delete(artist)
|
||||||
|
db.commit()
|
||||||
|
return RedirectResponse("/comic/artists", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
@@ -1,42 +1,90 @@
|
|||||||
from uuid import UUID
|
from fastapi import APIRouter, Form, Request, status
|
||||||
|
|
||||||
from fastapi import APIRouter, Request
|
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from fastapi.responses import RedirectResponse
|
||||||
|
|
||||||
from src.apis.utils import SessionDep
|
from src.apis.utils import SessionDep
|
||||||
from src.db.models.comic import Comic, Artist, Publisher
|
from src.db.models.comic import Comic, Publisher, Issue
|
||||||
|
from typing import AnyStr
|
||||||
|
from src.core.log_conf import logger
|
||||||
|
from src.db.repository.comics.comic import update_comic
|
||||||
|
from src.schema.comics.comic import ComicSchema
|
||||||
|
from src.webapps.comic.forms.comic import ValidateComicForm
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="src/templates")
|
templates = Jinja2Templates(directory="src/templates")
|
||||||
router = APIRouter(include_in_schema=False, prefix="/comic")
|
router = APIRouter(include_in_schema=False, prefix="/comic")
|
||||||
|
|
||||||
@router.get("/comics")
|
@router.get("/comics")
|
||||||
def get_comics(db: SessionDep, request: Request, msg: str = None):
|
def get_comics(db: SessionDep, request: Request, msg: str | None = None):
|
||||||
|
params = request.query_params
|
||||||
|
query = params.get("query")
|
||||||
|
filter = {}
|
||||||
|
completed = params.get('completed') == "on"
|
||||||
|
if completed:
|
||||||
|
filter['completed'] = True
|
||||||
|
order = params.get("order") == "on"
|
||||||
|
if order:
|
||||||
|
filter['current_order'] = True
|
||||||
|
if query is not None and len(query) > 0:
|
||||||
|
filter['title'] = query
|
||||||
|
if len(filter) > 0:
|
||||||
|
if "title" in filter:
|
||||||
|
comics = db.query(Comic).filter(Comic.title.ilike(f'%{query}%'))
|
||||||
|
else:
|
||||||
|
comics = db.query(Comic).filter_by(**filter).all()
|
||||||
|
else:
|
||||||
comics = db.query(Comic).all()
|
comics = db.query(Comic).all()
|
||||||
return templates.TemplateResponse("comic/comics.html", {"request": request, "msg": msg, "comics": comics})
|
return templates.TemplateResponse("comic/comics.html", {"request": request, "msg": msg, "comics": comics})
|
||||||
|
|
||||||
@router.get("/comics/{comic_id}")
|
@router.get("/comics/{comic_id}")
|
||||||
def comic_details(comic_id: UUID, request: Request, db: SessionDep):
|
def comic_details(comic_id: AnyStr, request: Request, db: SessionDep):
|
||||||
comic = db.get(Comic, comic_id)
|
comic = db.get(Comic, comic_id)
|
||||||
return templates.TemplateResponse("comic/comic_detail.html", {"request": request, "comic":comic})
|
return templates.TemplateResponse("comic/comic_detail.html", {"request": request, "comic":comic})
|
||||||
|
|
||||||
@router.get("/artists")
|
@router.get("/comic/edit/{comic_id}")
|
||||||
def get_artists(db: SessionDep, request: Request, msg: str = None):
|
def edit_comic(db: SessionDep, request: Request, comic_id: str):
|
||||||
artists = db.query(Artist).all()
|
comic = db.get(Comic, comic_id)
|
||||||
return templates.TemplateResponse("comic/artists.html", {"request": request, "msg": msg, "artists": artists})
|
return templates.TemplateResponse("comic/comic_edit.html", {"request": request, "comic_title": comic.title, "comic_weblink": comic.weblink})
|
||||||
|
|
||||||
@router.get("/artists/{artist_id}")
|
|
||||||
def artist_detail(artist_id: UUID, request: Request, db: SessionDep):
|
|
||||||
artist = db.get(Artist, artist_id)
|
@router.post("/comic/edit/{comic_id}")
|
||||||
return templates.TemplateResponse("comic/artist_detail.html", {"request": request, "artist": artist})
|
async def validate_comic(request: Request, db: SessionDep, comic_id: str, action: str = Form(...), completed: bool = Form(False), current_order: bool = Form(False)):
|
||||||
|
if action == "cancel":
|
||||||
|
return RedirectResponse(f"/comic/comics/{comic_id}", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
form = ValidateComicForm(request, comic_id, completed, current_order)
|
||||||
|
logger.info(f"request: {repr(request)}")
|
||||||
|
await form.load_data()
|
||||||
|
logger.info(f"form: {form}")
|
||||||
|
if form.is_valid():
|
||||||
|
try:
|
||||||
|
comic = ComicSchema(**form.__dict__)
|
||||||
|
comic = update_comic(comic=comic, comic_id=comic_id, db=db)
|
||||||
|
return RedirectResponse(f"/comic/comics/{comic.id}", status_code=status.HTTP_303_SEE_OTHER)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
form.__dict__.get("errors").append("comic already added")
|
||||||
|
return templates.TemplateResponse("comic/comic_edit.html", form.__dict__)
|
||||||
|
return templates.TemplateResponse("comic/comic_edit.html", form.__dict__)
|
||||||
|
|
||||||
@router.get("/publishers")
|
@router.get("/publishers")
|
||||||
def get_publishers(db: SessionDep, request: Request, msg: str = None):
|
def get_publishers(db: SessionDep, request: Request, msg: str | None = None):
|
||||||
publishers = db.query(Publisher).all()
|
publishers = db.query(Publisher).all()
|
||||||
return templates.TemplateResponse("comic/publishers.html", {"request": request, "publishers": publishers})
|
return templates.TemplateResponse("comic/publishers.html", {"request": request, "publishers": publishers})
|
||||||
|
|
||||||
@router.get("/publishers/{publisher_id}")
|
@router.get("/publishers/{publisher_id}")
|
||||||
def publisher_details(publisher_id: UUID, request: Request, db: SessionDep, msg: str = None):
|
def publisher_details(publisher_id: AnyStr, request: Request, db: SessionDep, msg: str = None):
|
||||||
publisher = db.get(Publisher, publisher_id)
|
publisher = db.get(Publisher, publisher_id)
|
||||||
if publisher is None:
|
if publisher is None:
|
||||||
msg = "Could not find Publisher"
|
msg = "Could not find Publisher"
|
||||||
return templates.TemplateResponse("comic/publisher_detail.html", {"request": request, "msg": msg, "publisher": publisher})
|
return templates.TemplateResponse("comic/publisher_detail.html", {"request": request, "msg": msg, "publisher": publisher})
|
||||||
|
|
||||||
|
@router.get("/issues")
|
||||||
|
def get_issues(db: SessionDep, request: Request, msg: str | None = None):
|
||||||
|
issues = db.query(Issue).all()
|
||||||
|
return templates.TemplateResponse("comic/issues.html", {"request": request, "msg": msg, "issues": issues})
|
||||||
|
|
||||||
|
@router.get("/issues/{issue_id}")
|
||||||
|
def issue_details(issue_id: AnyStr, request: Request, db: SessionDep):
|
||||||
|
issue = db.get(Issue, issue_id)
|
||||||
|
return templates.TemplateResponse("comic/issue_detail.html", {"request": request, "issue": issue})
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user