Compare commits
166 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cfaaa4d87 | |||
| 5850b1cfb0 | |||
| 8833d2ca6a | |||
| 1290a45fb5 | |||
| 3afbbf900d | |||
| f71419d37b | |||
| f5f673fd4e | |||
| 8ec0784490 | |||
| 13faff8e01 | |||
| 0a4f6bb0f9 | |||
| 3b4e5634b1 | |||
| 8c98e6de26 | |||
| 525d82cbda | |||
| e26aaa1420 | |||
| ec7f05d8fc | |||
| 727e95e732 | |||
| 75d3bf6ffb | |||
| 94782fc468 | |||
| 101ece44d8 | |||
| e880594a5b | |||
| c222d4cd7a | |||
| cb0fa3f728 | |||
| 4709e431b7 | |||
| bfccca72a1 | |||
| 8a3eebaab5 | |||
| 6716103d0c | |||
| ee78af1abe | |||
| 1fc726ee4b | |||
| 882f48de0b | |||
| 097a3efd4a | |||
| 934ef826c9 | |||
| bb049441b4 | |||
| 2bdbef8f8a | |||
| 20f29d88fa | |||
| 1997957069 | |||
| 116bb77e4c | |||
| a6eeea6c1f | |||
| 4a61d6a727 | |||
| 98e3d91edd | |||
| a169f6a6c1 | |||
| b14a267b5b | |||
| a43e2c806c | |||
| 10834df92b | |||
| 3d97c3360a | |||
| 76d91dd506 | |||
| 84aff2b7d7 | |||
| 2f8c10a692 | |||
| c5f74e007a | |||
| 243f693f9d | |||
| 976b4f3f47 | |||
| 1b18dae311 | |||
| 38e77b25b1 | |||
| bbc08863f0 | |||
| d58f18d206 | |||
| fd5bc54eee | |||
| cbc57d22f8 | |||
| a5cdf8867a | |||
| 45971518ee | |||
| 7db244f599 | |||
| 41e2b11da2 | |||
| 0d1b2e416e | |||
| d8eecb4dab | |||
| 3bb3b22cea | |||
| 8ffd421c1b | |||
| 6d54c7f315 | |||
| f5fb743503 | |||
| ca9022d289 | |||
| ed0e108599 | |||
| 7dc18b10cb | |||
| 171bc1676a | |||
| 1a5cd6ffe8 | |||
| 71ecfaff1f | |||
| f33aaadce7 | |||
| 591171b223 | |||
| da453d642d | |||
| e733fa21e6 | |||
| c61e49720e | |||
| d01489b1fa | |||
| 93c7498a83 | |||
| fe89cc6e0f | |||
| 1e9ca7c1a4 | |||
| 400aff6524 | |||
| 845f085a76 | |||
| 88c623edb7 | |||
| 0cc73c09aa | |||
| e8660faa06 | |||
| 4e884fdbe5 | |||
| 4c330a1138 | |||
| 65288a53a1 | |||
| 60fba0d3e9 | |||
| ada723dc48 | |||
| f07c7b74ee | |||
| f3c59c11ba | |||
| 3c4d3ad326 | |||
| 917e287784 | |||
| 611d6b9e61 | |||
| 58263cb854 | |||
| 33dcbc4413 | |||
| f3cf1a17f3 | |||
| 3466c10a88 | |||
| 4cea554116 | |||
| 332e09b283 | |||
| d3c720f995 | |||
| 453ad2af7b | |||
| 54bc17ee7d | |||
| 89b7b87b8c | |||
| 3f0a37ff19 | |||
| 954dab289a | |||
| 5affa16505 | |||
| d2b1cb999a | |||
| f1d36acff7 | |||
| 7e8a6b3c91 | |||
| b424e95e05 | |||
| 28746adfbb | |||
| 0d2f27f771 | |||
| d6410e2584 | |||
| ce1514f20a | |||
| c8ea7c4188 | |||
| 245ee2378e | |||
| afb3ac88c8 | |||
| 3a0a0055a6 | |||
| 283577df9b | |||
| 276302570f | |||
| f74c07af9a | |||
| d0eae1980a | |||
| 820ae3d374 | |||
| bf14c9a020 | |||
| a3652cc9b8 | |||
| 8f2e99195a | |||
| 2ae11e24ef | |||
| a1e8474149 | |||
| 9b329c0ac4 | |||
| 8120c92b32 | |||
| 5c261529fc | |||
| 6923b3e5ee | |||
| fc4110b11d | |||
| e078f43cc6 | |||
| 24e4c0b58e | |||
| 8d31a92692 | |||
| c619745a4a | |||
| c769115331 | |||
| 1a7da0ab9f | |||
| 78632e0e12 | |||
| 3aed8af868 | |||
| c6d1e4d7e7 | |||
| d98dc79cf8 | |||
| 9dcc09c586 | |||
| 57e7b9e999 | |||
| d97145b629 | |||
| 8ba23b6365 | |||
| 66d61e2c1f | |||
| 83f645d4c6 | |||
| 7d84e341d6 | |||
| c3ec0977c0 | |||
| d51bc47ffe | |||
| faa1d73eea | |||
| fe407d6d89 | |||
| 0759c99c1d | |||
| 77bb86a9fa | |||
| 4d7d7391c3 | |||
| c5aa3b32ef | |||
| 4ff999a74e | |||
| bdf2b2ba43 | |||
| 04dfe483e4 | |||
| 4a279738e3 | |||
| 3f65ec55fc |
@@ -13,8 +13,4 @@ kontor-schema/kontor_schema.egg-info
|
|||||||
kontor-gui/.pdm-python
|
kontor-gui/.pdm-python
|
||||||
kontor-gui/dist
|
kontor-gui/dist
|
||||||
fastapi/.coverage
|
fastapi/.coverage
|
||||||
kontor-api/.coverage
|
|
||||||
db-password.txt
|
db-password.txt
|
||||||
kontor-api/tests/test_main.py
|
|
||||||
kontor-api/tests/test_db.db
|
|
||||||
kontor-api/test_db.db
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
kontor_api := kontor-api
|
kontor_api := kontor-api
|
||||||
kontor_spring := kontor-spring
|
kontor_spring := kontor-spring
|
||||||
kontor_servicemix := kontor-servicemix
|
|
||||||
|
|
||||||
.PHONY: all $(kontor_spring) $(kontor_api)
|
.PHONY: all $(kontor_spring) $(kontor_api)
|
||||||
all: $(kontor_spring) $(kontor_api) $(kontor_servicemix)
|
all: $(kontor_spring) $(kontor_api)
|
||||||
|
|
||||||
$(kontor_spring) $(kontor_api) $(kontor_servicemix):
|
$(kontor_spring) $(kontor_api):
|
||||||
$(MAKE) --directory=$@ $(TARGET)
|
$(MAKE) --directory=$@ $(TARGET)
|
||||||
|
|
||||||
|
|||||||
+16
-38
@@ -1,66 +1,44 @@
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
mariadb:
|
||||||
image: postgres
|
image: mariadb
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_DB=kontor
|
MYSQL_ROOT_PASSWORD: kontor
|
||||||
- POSTGRES_USER=kontor
|
MYSQL_USER: kontor
|
||||||
#- POSTGRES_PASSWORD_FILE=/run/secrets/db-password
|
MYSQL_PASSWORD: kontor
|
||||||
- POSTGRES_PASSWORD=kontor
|
MYSQL_DATABASE: kontor
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U kontor"]
|
|
||||||
interval: 1s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 3316:3306
|
||||||
networks:
|
networks:
|
||||||
- database
|
- database
|
||||||
volumes:
|
volumes:
|
||||||
- postgres-data:/var/lib/postgresql/data:rw
|
- mariadb-storage:/var/lib/mysql:rw
|
||||||
secrets:
|
|
||||||
- db-password
|
|
||||||
adminer:
|
|
||||||
image: adminer
|
|
||||||
ports:
|
|
||||||
- 8090:8080
|
|
||||||
networks:
|
|
||||||
- database
|
|
||||||
- frontend
|
|
||||||
kontor:
|
kontor:
|
||||||
image: kontor:0.2.0-SNAPSHOT
|
image: kontor
|
||||||
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:
|
||||||
postgres:
|
- mariadb
|
||||||
condition: service_healthy
|
|
||||||
kontor-api:
|
kontor-api:
|
||||||
image: kontor-api:0.2.0-SNAPSHOT
|
image: kontor-api
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- database
|
- database
|
||||||
- frontend
|
- frontend
|
||||||
ports:
|
ports:
|
||||||
- 8800:8800
|
- 8800:8800
|
||||||
volumes:
|
|
||||||
- images-data:/data/images
|
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
- mariadb
|
||||||
condition: service_healthy
|
|
||||||
networks:
|
networks:
|
||||||
database:
|
database:
|
||||||
frontend:
|
frontend:
|
||||||
volumes:
|
|
||||||
postgres-data:
|
volumes:
|
||||||
images-data:
|
mariadb-storage:
|
||||||
secrets:
|
|
||||||
db-password:
|
|
||||||
file: db-password.txt
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
build/
|
||||||
|
.settings/
|
||||||
|
.gradle/
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.vscode/
|
||||||
|
bin/
|
||||||
|
.idea/
|
||||||
|
/.eclipse-pmd
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
variables:
|
||||||
|
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- source "/home/gitlab-runner/.sdkman/bin/sdkman-init.sh"
|
||||||
|
- sdk u java 11.0.12-open
|
||||||
|
- chmod +x ./gradlew
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
- test
|
||||||
|
- analysis
|
||||||
|
- publish
|
||||||
|
|
||||||
|
Build Application:
|
||||||
|
stage: build
|
||||||
|
script: ./gradlew --build-cache compileJava
|
||||||
|
|
||||||
|
Create Documentation:
|
||||||
|
stage: build
|
||||||
|
script: ./gradlew asciidoctorPdf
|
||||||
|
|
||||||
|
Test Application:
|
||||||
|
stage: test
|
||||||
|
script: ./gradlew check
|
||||||
|
|
||||||
|
sonarqube-check:
|
||||||
|
stage: analysis
|
||||||
|
variables:
|
||||||
|
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache
|
||||||
|
GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task
|
||||||
|
script: ./gradlew sonarqube
|
||||||
|
allow_failure: true
|
||||||
|
|
||||||
|
Publish Artifacts:
|
||||||
|
stage: publish
|
||||||
|
script: ./gradlew publish
|
||||||
|
|
||||||
@@ -1,36 +1,32 @@
|
|||||||
# kontor-quarkus
|
# kontor-quarkus Project
|
||||||
|
|
||||||
This project uses Quarkus, the Supersonic Subatomic Java Framework.
|
This project uses Quarkus, the Supersonic Subatomic Java Framework.
|
||||||
|
|
||||||
If you want to learn more about Quarkus, please visit its website: <https://quarkus.io/>.
|
If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ .
|
||||||
|
|
||||||
## Running the application in dev mode
|
## Running the application in dev mode
|
||||||
|
|
||||||
You can run your application in dev mode that enables live coding using:
|
You can run your application in dev mode that enables live coding using:
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
./gradlew quarkusDev
|
./gradlew quarkusDev
|
||||||
```
|
```
|
||||||
|
|
||||||
> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at <http://localhost:8080/q/dev/>.
|
> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at http://localhost:8080/q/dev/.
|
||||||
|
|
||||||
## Packaging and running the application
|
## Packaging and running the application
|
||||||
|
|
||||||
The application can be packaged using:
|
The application can be packaged using:
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
./gradlew build
|
./gradlew build
|
||||||
```
|
```
|
||||||
|
|
||||||
It produces the `quarkus-run.jar` file in the `build/quarkus-app/` directory.
|
It produces the `quarkus-run.jar` file in the `build/quarkus-app/` directory.
|
||||||
Be aware that it’s not an _über-jar_ as the dependencies are copied into the `build/quarkus-app/lib/` directory.
|
Be aware that it’s not an _über-jar_ as the dependencies are copied into the `build/quarkus-app/lib/` directory.
|
||||||
|
|
||||||
The application is now runnable using `java -jar build/quarkus-app/quarkus-run.jar`.
|
The application is now runnable using `java -jar build/quarkus-app/quarkus-run.jar`.
|
||||||
|
|
||||||
If you want to build an _über-jar_, execute the following command:
|
If you want to build an _über-jar_, execute the following command:
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
./gradlew build -Dquarkus.package.jar.type=uber-jar
|
./gradlew build -Dquarkus.package.type=uber-jar
|
||||||
```
|
```
|
||||||
|
|
||||||
The application, packaged as an _über-jar_, is now runnable using `java -jar build/*-runner.jar`.
|
The application, packaged as an _über-jar_, is now runnable using `java -jar build/*-runner.jar`.
|
||||||
@@ -38,31 +34,27 @@ The application, packaged as an _über-jar_, is now runnable using `java -jar bu
|
|||||||
## Creating a native executable
|
## Creating a native executable
|
||||||
|
|
||||||
You can create a native executable using:
|
You can create a native executable using:
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
./gradlew build -Dquarkus.native.enabled=true
|
./gradlew build -Dquarkus.package.type=native
|
||||||
```
|
```
|
||||||
|
|
||||||
Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
|
Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
./gradlew build -Dquarkus.native.enabled=true -Dquarkus.native.container-build=true
|
./gradlew build -Dquarkus.package.type=native -Dquarkus.native.container-build=true
|
||||||
```
|
```
|
||||||
|
|
||||||
You can then execute your native executable with: `./build/kontor-quarkus-1.0.0-SNAPSHOT-runner`
|
You can then execute your native executable with: `./build/kontor-quarkus-1.0.0-SNAPSHOT-runner`
|
||||||
|
|
||||||
If you want to learn more about building native executables, please consult <https://quarkus.io/guides/gradle-tooling>.
|
If you want to learn more about building native executables, please consult https://quarkus.io/guides/gradle-tooling.
|
||||||
|
|
||||||
## Related Guides
|
## Related Guides
|
||||||
|
|
||||||
- REST ([guide](https://quarkus.io/guides/rest)): A Jakarta REST implementation utilizing build time processing and Vert.x. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it.
|
- RESTEasy Reactive ([guide](https://quarkus.io/guides/resteasy-reactive)): A JAX-RS implementation utilizing build time processing and Vert.x. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it.
|
||||||
- REST Jackson ([guide](https://quarkus.io/guides/rest#json-serialisation)): Jackson serialization support for Quarkus REST. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it
|
|
||||||
- JDBC Driver - PostgreSQL ([guide](https://quarkus.io/guides/datasource)): Connect to the PostgreSQL database via JDBC
|
|
||||||
|
|
||||||
## Provided Code
|
## Provided Code
|
||||||
|
|
||||||
### REST
|
### RESTEasy Reactive
|
||||||
|
|
||||||
Easily start your REST Web Services
|
Easily start your Reactive RESTful Web Services
|
||||||
|
|
||||||
[Related guide section...](https://quarkus.io/guides/getting-started-reactive#reactive-jax-rs-resources)
|
[Related guide section...](https://quarkus.io/guides/getting-started-reactive#reactive-jax-rs-resources)
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'maven-publish'
|
||||||
|
id 'jacoco'
|
||||||
|
id 'jacoco-report-aggregation'
|
||||||
|
id 'pmd'
|
||||||
|
id 'checkstyle'
|
||||||
|
id 'jvm-test-suite'
|
||||||
|
id 'io.quarkus'
|
||||||
|
id "org.sonarqube" version "3.3"
|
||||||
|
alias(versionsLibs.plugins.asciidoctorConvert)
|
||||||
|
alias(versionsLibs.plugins.asciidoctorPdf)
|
||||||
|
alias(versionsLibs.plugins.asciidoctorGems)
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
ruby.gems()
|
||||||
|
maven {
|
||||||
|
url "https://nexus.thpeetz.de/nexus/content/groups/public/"
|
||||||
|
}
|
||||||
|
mavenCentral()
|
||||||
|
mavenLocal()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
|
||||||
|
implementation 'io.quarkus:quarkus-resteasy-reactive'
|
||||||
|
implementation 'io.quarkus:quarkus-resteasy-reactive-jackson'
|
||||||
|
implementation 'io.quarkus:quarkus-arc'
|
||||||
|
testImplementation 'io.quarkus:quarkus-junit5'
|
||||||
|
testImplementation 'io.rest-assured:rest-assured'
|
||||||
|
testImplementation("io.quarkus:quarkus-jacoco")
|
||||||
|
}
|
||||||
|
|
||||||
|
final BUILD_DATE = new Date().format('dd.MM.yyyy').toString()
|
||||||
|
def pdfFile = layout.buildDirectory.file("docs/asciidocPdf/" + project.name + ".pdf")
|
||||||
|
def pdfArtifact = artifacts.add('archives', pdfFile.get().asFile) {
|
||||||
|
type 'pdf'
|
||||||
|
builtBy asciidoctorPdf
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(GenerateModuleMetadata).configureEach {
|
||||||
|
suppressedValidationErrors.add('enforced-platform')
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
quarkusApp(MavenPublication){
|
||||||
|
from components.java
|
||||||
|
}
|
||||||
|
docs(MavenPublication) {
|
||||||
|
groupId = group + '.docs'
|
||||||
|
artifactId = project.name
|
||||||
|
artifact pdfArtifact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
name = 'nexusPeetz'
|
||||||
|
def releasesRepoUrl = "https://nexus.thpeetz.de/nexus/content/repositories/releases/"
|
||||||
|
def snapshotsRepoUrl = "https://nexus.thpeetz.de/nexus/content/repositories/snapshots/"
|
||||||
|
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
|
||||||
|
credentials(PasswordCredentials)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(GenerateModuleMetadata).configureEach {
|
||||||
|
suppressedValidationErrors.add('enforced-platform')
|
||||||
|
}
|
||||||
|
|
||||||
|
compileJava {
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
options.compilerArgs << '-parameters'
|
||||||
|
}
|
||||||
|
|
||||||
|
compileTestJava {
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
}
|
||||||
|
|
||||||
|
jacocoTestReport {
|
||||||
|
reports {
|
||||||
|
xml.required = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test.finalizedBy jacocoTestReport
|
||||||
|
|
||||||
|
|
||||||
|
pmd {
|
||||||
|
//ruleSetFiles = files("custom-pmd-ruleset.xml")
|
||||||
|
ruleSets = ["category/java/errorprone.xml", "category/java/bestpractices.xml"]
|
||||||
|
ignoreFailures = true
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType(Checkstyle) {
|
||||||
|
reports {
|
||||||
|
xml.required = true
|
||||||
|
html.required = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testing {
|
||||||
|
suites {
|
||||||
|
test {
|
||||||
|
useJUnitJupiter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pmdTest.dependsOn("compileQuarkusTestGeneratedSourcesJava")
|
||||||
|
checkstyleTest.dependsOn("compileQuarkusTestGeneratedSourcesJava")
|
||||||
|
jacocoTestReport.dependsOn("compileQuarkusGeneratedSourcesJava")
|
||||||
|
pmdMain.dependsOn("compileQuarkusGeneratedSourcesJava")
|
||||||
|
|
||||||
|
sonarqube {
|
||||||
|
properties {
|
||||||
|
property "sonar.host.url", "https://sonar.thpeetz.de"
|
||||||
|
property "sonar.qualitygate.wait", true
|
||||||
|
property "sonar.sourceEncoding", "UTF-8"
|
||||||
|
property "sonar.projectKey", "kontor_kontor-quarkus_AYQek8Mxz0hBjLSV8I8O"
|
||||||
|
property "sonar.login", "5ecd90dee57806857e07443a9b0efd3cd7774a81"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('sonarqube').configure {
|
||||||
|
dependsOn 'test', 'pmdMain', 'checkstyleMain', 'jacocoTestReport'
|
||||||
|
}
|
||||||
|
|
||||||
|
asciidoctorPdf {
|
||||||
|
baseDirFollowsSourceFile()
|
||||||
|
|
||||||
|
asciidoctorj {
|
||||||
|
attributes 'build-gradle': file('build.gradle'),
|
||||||
|
'endpoint-url': 'https://www.thpeetz.de',
|
||||||
|
'source-highlighter': 'rouge',
|
||||||
|
'imagesdir': './images',
|
||||||
|
'toc': 'left',
|
||||||
|
'toc-title': 'Inhaltsverzeichnis',
|
||||||
|
'revdate': BUILD_DATE,
|
||||||
|
'revnumber': { project.version.toString() },
|
||||||
|
'revremark': 'Entwurf',
|
||||||
|
'chapter-label': '',
|
||||||
|
'icons': 'font',
|
||||||
|
'idprefix': 'id_',
|
||||||
|
'idseparator': '-',
|
||||||
|
'docinfo1': ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper {
|
||||||
|
gradleVersion = "7.5"
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||||
|
<module name="Checker">
|
||||||
|
<module name="TreeWalker">
|
||||||
|
<module name="UnusedImports" />
|
||||||
|
<module name="AvoidStarImport" />
|
||||||
|
<module name="ConstantName" />
|
||||||
|
<module name="NeedBraces" />
|
||||||
|
<module name="RightCurly" />
|
||||||
|
<module name="ModifierOrder">
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
</module>
|
||||||
|
</module>
|
||||||
|
</module>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#Gradle properties
|
||||||
|
group=de.thpeetz.kontor
|
||||||
|
version=1.0.0-SNAPSHOT
|
||||||
|
|
||||||
|
quarkusPluginId=io.quarkus
|
||||||
|
quarkusPluginVersion=2.13.2.Final
|
||||||
|
quarkusPlatformGroupId=io.quarkus.platform
|
||||||
|
quarkusPlatformArtifactId=quarkus-bom
|
||||||
|
quarkusPlatformVersion=2.13.2.Final
|
||||||
BIN
Binary file not shown.
+1
-3
@@ -1,7 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||||
networkTimeout=10000
|
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
Vendored
+185
@@ -0,0 +1,185 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=`expr $i + 1`
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
0) set -- ;;
|
||||||
|
1) set -- "$args0" ;;
|
||||||
|
2) set -- "$args0" "$args1" ;;
|
||||||
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
Vendored
+104
@@ -0,0 +1,104 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
= Projektbeschreibung kontor-quarkus: Pflichtenheft und Projekthandbuch
|
||||||
|
:author: Thomas Peetz
|
||||||
|
:email: <thomas.peetz@thpeetz.de>
|
||||||
|
:doctype: article
|
||||||
|
:sectnums:
|
||||||
|
:sectnumlevels: 4
|
||||||
|
:toc:
|
||||||
|
:toclevels: 4
|
||||||
|
:table-caption!:
|
||||||
|
:counter: table-number: 0
|
||||||
|
|
||||||
|
//[title="Dokumenthistorie", caption="Tabelle {counter:table-number} ", id="Tabelle-{counter:table-number}", options="header"]
|
||||||
|
//[title="Dokumenthistorie", id="Table-{counter:table-number}", options="header", cols="4"]
|
||||||
|
[title="Dokumenthistorie", id="Table-{counter:table-number}", options="header"]
|
||||||
|
|===
|
||||||
|
| Version | Datum | Autor | Änderungsgrund / Bemerkungen
|
||||||
|
| 0.0.1 | 16.05.2022 | Thomas Peetz | Ersterstellung
|
||||||
|
|===
|
||||||
|
|
||||||
|
== Einführung
|
||||||
|
|
||||||
|
=== Zweck
|
||||||
|
|
||||||
|
=== Stakeholder des Systems
|
||||||
|
|
||||||
|
=== Systemumfang
|
||||||
|
|
||||||
|
==== Zielsetzung des Systems
|
||||||
|
|
||||||
|
=== Systemübersicht
|
||||||
|
|
||||||
|
==== Systemkontext
|
||||||
|
|
||||||
|
==== Systemarchitektur
|
||||||
|
|
||||||
|
==== Systemschnittstellen
|
||||||
|
|
||||||
|
===== Realisierte Schnittstellen
|
||||||
|
|
||||||
|
===== Verwendete Schnittstellen
|
||||||
|
|
||||||
|
==== Logisches Datenmodell
|
||||||
|
|
||||||
|
==== Einschränkungen
|
||||||
|
|
||||||
|
== Anforderungen der Domäne
|
||||||
|
|
||||||
|
=== Systemfunktionen
|
||||||
|
|
||||||
|
==== Anwendungsfälle
|
||||||
|
|
||||||
|
==== Akteure
|
||||||
|
|
||||||
|
==== Zielgruppen
|
||||||
|
|
||||||
|
=== Anforderungen
|
||||||
|
|
||||||
|
==== Anforderungen an externe Schnittstellen
|
||||||
|
|
||||||
|
==== Funktionale Anforderungen
|
||||||
|
|
||||||
|
==== Qualitätsanforderungen
|
||||||
|
|
||||||
|
==== Randbedingungen
|
||||||
|
|
||||||
|
==== Weitere Anforderungen
|
||||||
|
|
||||||
|
==== Wartungs- und Supportinformationen
|
||||||
|
|
||||||
|
=== Verifikation
|
||||||
|
|
||||||
|
[bibliography]
|
||||||
|
== Referenzen
|
||||||
|
|
||||||
|
[glossary]
|
||||||
|
== Glossar
|
||||||
|
|
||||||
|
== Verzeichnisse
|
||||||
|
|
||||||
|
=== Abbildungsverzeichnis
|
||||||
|
|
||||||
|
=== Tabellenverzeichnis
|
||||||
|
|
||||||
|
<<Table-1, Tabelle 1>> <<Table-1>>
|
||||||
+5
-9
@@ -14,9 +14,7 @@
|
|||||||
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus-jvm
|
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus-jvm
|
||||||
#
|
#
|
||||||
# If you want to include the debug port into your docker image
|
# If you want to include the debug port into your docker image
|
||||||
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
|
# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005
|
||||||
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
|
|
||||||
# when running the container
|
|
||||||
#
|
#
|
||||||
# Then run the container using :
|
# Then run the container using :
|
||||||
#
|
#
|
||||||
@@ -26,8 +24,7 @@
|
|||||||
# This scripts computes the command line to execute your Java application, and
|
# This scripts computes the command line to execute your Java application, and
|
||||||
# includes memory/GC tuning.
|
# includes memory/GC tuning.
|
||||||
# You can configure the behavior using the following environment properties:
|
# You can configure the behavior using the following environment properties:
|
||||||
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
|
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
|
||||||
# the default JVM options, use `JAVA_OPTS_APPEND` to append options
|
|
||||||
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
|
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
|
||||||
# in JAVA_OPTS (example: "-Dsome.property=foo")
|
# in JAVA_OPTS (example: "-Dsome.property=foo")
|
||||||
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
|
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
|
||||||
@@ -78,7 +75,7 @@
|
|||||||
# accessed directly. (example: "foo.example.com,bar.example.com")
|
# accessed directly. (example: "foo.example.com,bar.example.com")
|
||||||
#
|
#
|
||||||
###
|
###
|
||||||
FROM registry.access.redhat.com/ubi9/openjdk-21:1.21
|
FROM registry.access.redhat.com/ubi8/openjdk-11:1.14
|
||||||
|
|
||||||
ENV LANGUAGE='en_US:en'
|
ENV LANGUAGE='en_US:en'
|
||||||
|
|
||||||
@@ -91,8 +88,7 @@ COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/
|
|||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
USER 185
|
USER 185
|
||||||
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
|
ENV AB_JOLOKIA_OFF=""
|
||||||
|
ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
|
||||||
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
|
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
|
||||||
|
|
||||||
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
|
|
||||||
|
|
||||||
+6
-10
@@ -3,7 +3,7 @@
|
|||||||
#
|
#
|
||||||
# Before building the container image run:
|
# Before building the container image run:
|
||||||
#
|
#
|
||||||
# ./gradlew build -Dquarkus.package.jar.type=legacy-jar
|
# ./gradlew build -Dquarkus.package.type=legacy-jar
|
||||||
#
|
#
|
||||||
# Then, build the image with:
|
# Then, build the image with:
|
||||||
#
|
#
|
||||||
@@ -14,9 +14,7 @@
|
|||||||
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus-legacy-jar
|
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus-legacy-jar
|
||||||
#
|
#
|
||||||
# If you want to include the debug port into your docker image
|
# If you want to include the debug port into your docker image
|
||||||
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
|
# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005
|
||||||
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
|
|
||||||
# when running the container
|
|
||||||
#
|
#
|
||||||
# Then run the container using :
|
# Then run the container using :
|
||||||
#
|
#
|
||||||
@@ -26,8 +24,7 @@
|
|||||||
# This scripts computes the command line to execute your Java application, and
|
# This scripts computes the command line to execute your Java application, and
|
||||||
# includes memory/GC tuning.
|
# includes memory/GC tuning.
|
||||||
# You can configure the behavior using the following environment properties:
|
# You can configure the behavior using the following environment properties:
|
||||||
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
|
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
|
||||||
# the default JVM options, use `JAVA_OPTS_APPEND` to append options
|
|
||||||
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
|
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
|
||||||
# in JAVA_OPTS (example: "-Dsome.property=foo")
|
# in JAVA_OPTS (example: "-Dsome.property=foo")
|
||||||
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
|
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
|
||||||
@@ -78,7 +75,7 @@
|
|||||||
# accessed directly. (example: "foo.example.com,bar.example.com")
|
# accessed directly. (example: "foo.example.com,bar.example.com")
|
||||||
#
|
#
|
||||||
###
|
###
|
||||||
FROM registry.access.redhat.com/ubi9/openjdk-21:1.21
|
FROM registry.access.redhat.com/ubi8/openjdk-11:1.14
|
||||||
|
|
||||||
ENV LANGUAGE='en_US:en'
|
ENV LANGUAGE='en_US:en'
|
||||||
|
|
||||||
@@ -88,7 +85,6 @@ COPY build/*-runner.jar /deployments/quarkus-run.jar
|
|||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
USER 185
|
USER 185
|
||||||
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
|
ENV AB_JOLOKIA_OFF=""
|
||||||
|
ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
|
||||||
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
|
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
|
||||||
|
|
||||||
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
|
|
||||||
+4
-6
@@ -3,7 +3,7 @@
|
|||||||
#
|
#
|
||||||
# Before building the container image run:
|
# Before building the container image run:
|
||||||
#
|
#
|
||||||
# ./gradlew build -Dquarkus.native.enabled=true
|
# ./gradlew build -Dquarkus.package.type=native
|
||||||
#
|
#
|
||||||
# Then, build the image with:
|
# Then, build the image with:
|
||||||
#
|
#
|
||||||
@@ -13,17 +13,15 @@
|
|||||||
#
|
#
|
||||||
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus
|
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus
|
||||||
#
|
#
|
||||||
# The ` registry.access.redhat.com/ubi9/ubi-minimal:9.5` base image is based on UBI 9.
|
|
||||||
# To use UBI 8, switch to `quay.io/ubi8/ubi-minimal:8.10`.
|
|
||||||
###
|
###
|
||||||
FROM registry.access.redhat.com/ubi9/ubi-minimal:9.5
|
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
|
||||||
WORKDIR /work/
|
WORKDIR /work/
|
||||||
RUN chown 1001 /work \
|
RUN chown 1001 /work \
|
||||||
&& chmod "g+rwX" /work \
|
&& chmod "g+rwX" /work \
|
||||||
&& chown 1001:root /work
|
&& chown 1001:root /work
|
||||||
COPY --chown=1001:root --chmod=0755 build/*-runner /work/application
|
COPY --chown=1001:root build/*-runner /work/application
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
USER 1001
|
USER 1001
|
||||||
|
|
||||||
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
|
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
|
||||||
+4
-6
@@ -6,7 +6,7 @@
|
|||||||
#
|
#
|
||||||
# Before building the container image run:
|
# Before building the container image run:
|
||||||
#
|
#
|
||||||
# ./gradlew build -Dquarkus.native.enabled=true
|
# ./gradlew build -Dquarkus.package.type=native
|
||||||
#
|
#
|
||||||
# Then, build the image with:
|
# Then, build the image with:
|
||||||
#
|
#
|
||||||
@@ -16,17 +16,15 @@
|
|||||||
#
|
#
|
||||||
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus
|
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus
|
||||||
#
|
#
|
||||||
# The `quay.io/quarkus/ubi9-quarkus-micro-image:2.0` base image is based on UBI 9.
|
|
||||||
# To use UBI 8, switch to `quay.io/quarkus/quarkus-micro-image:2.0`.
|
|
||||||
###
|
###
|
||||||
FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0
|
FROM quay.io/quarkus/quarkus-micro-image:1.0
|
||||||
WORKDIR /work/
|
WORKDIR /work/
|
||||||
RUN chown 1001 /work \
|
RUN chown 1001 /work \
|
||||||
&& chmod "g+rwX" /work \
|
&& chmod "g+rwX" /work \
|
||||||
&& chown 1001:root /work
|
&& chown 1001:root /work
|
||||||
COPY --chown=1001:root --chmod=0755 build/*-runner /work/application
|
COPY --chown=1001:root build/*-runner /work/application
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
USER 1001
|
USER 1001
|
||||||
|
|
||||||
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
|
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package de.thpeetz.kontor.comics;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import de.thpeetz.kontor.comics.service.ArtistService;
|
||||||
|
import de.thpeetz.kontor.comics.service.ComicsService;
|
||||||
|
import de.thpeetz.kontor.comics.service.PublisherService;
|
||||||
|
|
||||||
|
@Path("/kontor/comics")
|
||||||
|
public class ComicsResource {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
transient ComicsService comicsService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
transient ArtistService artistService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
transient PublisherService publisherService;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.TEXT_HTML)
|
||||||
|
@Path("/")
|
||||||
|
public String showComicList() {
|
||||||
|
return comicsService.showComicList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.TEXT_HTML)
|
||||||
|
@Path("/artist")
|
||||||
|
public String showArtistList() {
|
||||||
|
return artistService.showArtistList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
@Path("/artist")
|
||||||
|
public String showArtistListJson() {
|
||||||
|
return artistService.showArtistList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.TEXT_HTML)
|
||||||
|
@Path("/artist/view/{artist_id}")
|
||||||
|
public String showArtist(String artist_id) {
|
||||||
|
return artistService.showArtist(artist_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.TEXT_HTML)
|
||||||
|
@Path("/artist/create")
|
||||||
|
public String showArtistCreation() {
|
||||||
|
return artistService.showArtistCreation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Produces(MediaType.TEXT_HTML)
|
||||||
|
@Path("/artist/create")
|
||||||
|
public String validateArtistDetails() {
|
||||||
|
return artistService.validateArtistDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.TEXT_HTML)
|
||||||
|
@Path("/publisher")
|
||||||
|
public String showPublisherList() {
|
||||||
|
return publisherService.showPublisherList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.TEXT_HTML)
|
||||||
|
@Path("/publisher/view/{publisher_id}")
|
||||||
|
public String showPublisher(String publisher_id) {
|
||||||
|
return publisherService.showPublisher(publisher_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.TEXT_HTML)
|
||||||
|
@Path("/publisher/create")
|
||||||
|
public String showPublisherCreation() {
|
||||||
|
return publisherService.showPublisherCreation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Produces(MediaType.TEXT_HTML)
|
||||||
|
@Path("/publisher/create")
|
||||||
|
public String validatePublisherDetails() {
|
||||||
|
return publisherService.validatePublisherDetails();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package de.thpeetz.kontor.comics.service;
|
||||||
|
|
||||||
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class ArtistService {
|
||||||
|
|
||||||
|
public String showArtistList() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String showArtist(String artist_id) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String showArtistCreation() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String validateArtistDetails() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package de.thpeetz.kontor.comics.service;
|
||||||
|
|
||||||
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class ComicsService {
|
||||||
|
|
||||||
|
public String showComicList() {
|
||||||
|
return "comics";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package de.thpeetz.kontor.comics.service;
|
||||||
|
|
||||||
|
import javax.enterprise.context.ApplicationScoped;
|
||||||
|
|
||||||
|
@ApplicationScoped
|
||||||
|
public class PublisherService {
|
||||||
|
|
||||||
|
public String showPublisherList() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String showPublisher(String publisher_id) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String showPublisherCreation() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String validatePublisherDetails() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
|||||||
|
# Your configuration properties
|
||||||
|
quarkus.http.test-port=8083
|
||||||
|
quarkus.http.test-ssl-port=8446
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package de.thpeetz.kontor.comics;
|
||||||
|
|
||||||
|
import io.quarkus.test.junit.QuarkusIntegrationTest;
|
||||||
|
|
||||||
|
@QuarkusIntegrationTest
|
||||||
|
public class ComicsResourceIT extends ComicsResourceTest {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package de.thpeetz.kontor.comics;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static io.restassured.RestAssured.given;
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
|
||||||
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
|
|
||||||
|
@QuarkusTest
|
||||||
|
class ComicsResourceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testComicsEndpoint() {
|
||||||
|
given().when().get("/kontor/comics")
|
||||||
|
.then()
|
||||||
|
.statusCode(200)
|
||||||
|
.body(is("comics"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -1,4 +1 @@
|
|||||||
.env
|
.env
|
||||||
.coverage
|
|
||||||
app.log
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
## ------------------------------- Builder Stage ------------------------------ ##
|
## ------------------------------- Builder Stage ------------------------------ ##
|
||||||
FROM python:3.13-bookworm AS builder
|
FROM python:3.13-bookworm AS builder
|
||||||
|
|
||||||
RUN apt-get update && apt-get install --no-install-recommends -y build-essential && \
|
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||||
|
build-essential libmariadb-dev && \
|
||||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Download the latest installer, install it and then remove it
|
# Download the latest installer, install it and then remove it
|
||||||
@@ -41,6 +42,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
COPY /src src
|
COPY /src src
|
||||||
COPY --from=builder /app/.venv .venv
|
COPY --from=builder /app/.venv .venv
|
||||||
|
COPY --from=builder /usr/lib/x86_64-linux-gnu/libmariadb.so.3 /usr/lib/x86_64-linux-gnu
|
||||||
|
|
||||||
# Set up environment variables for production
|
# Set up environment variables for production
|
||||||
ENV PATH="/app/.venv/bin:$PATH"
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
|
|||||||
+3
-3
@@ -4,11 +4,11 @@ clean:
|
|||||||
find . -name '*.py[co]' -delete
|
find . -name '*.py[co]' -delete
|
||||||
|
|
||||||
test:
|
test:
|
||||||
DB_SERVER=localhost uv run pytest -v --cov --cov-report=term --cov-report=html:coverage-report
|
DB_HOST=localhost uv run pytest -v --cov --cov-report=term --cov-report=html:coverage-report
|
||||||
|
|
||||||
docker: clean
|
docker: clean
|
||||||
docker build --target=production -t kontor-api:0.2.0-SNAPSHOT .
|
docker build --target=production -t kontor-api -t kontor-api:0.1.0-SNAPSHOT .
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
DB_SERVER=127.0.0.1 uv run fastapi dev src/main.py --port 8008
|
MARIADB_SERVER=localhost uv run fastapi dev src/main.py --port 8008
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ dependencies = [
|
|||||||
"beautifulsoup4>=4.13.4",
|
"beautifulsoup4>=4.13.4",
|
||||||
"fastapi[standard]>=0.115.12",
|
"fastapi[standard]>=0.115.12",
|
||||||
"httpx==0.24.1",
|
"httpx==0.24.1",
|
||||||
|
"mariadb>=1.1.12",
|
||||||
"sqlalchemy>=2.0.40",
|
"sqlalchemy>=2.0.40",
|
||||||
"platformdirs>=4.3.7",
|
"platformdirs>=4.3.7",
|
||||||
"pathlib>=1.0.1",
|
"pathlib>=1.0.1",
|
||||||
@@ -21,11 +22,4 @@ dependencies = [
|
|||||||
"python-jose>=3.4.0",
|
"python-jose>=3.4.0",
|
||||||
"python-multipart>=0.0.20",
|
"python-multipart>=0.0.20",
|
||||||
"natsort>=8.4.0",
|
"natsort>=8.4.0",
|
||||||
"psycopg2-binary>=2.9.10",
|
|
||||||
"pytest-cov>=6.1.1",
|
|
||||||
"databases[sqlite]>=0.9.0",
|
|
||||||
"pydantic[email]>=2.11.3",
|
|
||||||
"jinja2>=3.1.6",
|
|
||||||
"asyncpg>=0.30.0",
|
|
||||||
"bcrypt>=4.3.0",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from src.apis.version1 import comic, media, tysc, admin
|
from src.apis.version1 import comic, media, tysc
|
||||||
|
|
||||||
api_router = APIRouter(prefix="/api")
|
api_router = APIRouter(prefix="/api")
|
||||||
api_router.include_router(comic.router, prefix="/comics", tags=["comics"])
|
api_router.include_router(comic.router, prefix="/comics", tags=["comics"])
|
||||||
api_router.include_router(media.router, prefix="/media", tags=["media"])
|
api_router.include_router(media.router, prefix="/media", tags=["media"])
|
||||||
api_router.include_router(tysc.router, prefix="/tysc", tags=["tysc"])
|
api_router.include_router(tysc.router, prefix="/tysc", tags=["tysc"])
|
||||||
api_router.include_router(admin.router, prefix="/login", tags=["login"])
|
|
||||||
|
|||||||
@@ -1,13 +1,4 @@
|
|||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
from typing import Dict
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import HTTPException
|
|
||||||
from fastapi import Request
|
|
||||||
from fastapi import status
|
|
||||||
from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
|
|
||||||
from fastapi.security import OAuth2
|
|
||||||
from fastapi.security.utils import get_authorization_scheme_param
|
|
||||||
|
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
@@ -15,34 +6,3 @@ from sqlalchemy.orm import Session
|
|||||||
from src.db.session import get_db
|
from src.db.session import get_db
|
||||||
|
|
||||||
SessionDep = Annotated[Session, Depends(get_db)]
|
SessionDep = Annotated[Session, Depends(get_db)]
|
||||||
|
|
||||||
|
|
||||||
class OAuth2PasswordBearerWithCookie(OAuth2):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
tokenUrl: str,
|
|
||||||
scheme_name: Optional[str] = None,
|
|
||||||
scopes: Optional[Dict[str, str]] = None,
|
|
||||||
auto_error: bool = True,
|
|
||||||
):
|
|
||||||
if not scopes:
|
|
||||||
scopes = {}
|
|
||||||
flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes})
|
|
||||||
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
|
|
||||||
|
|
||||||
async def __call__(self, request: Request) -> Optional[str]:
|
|
||||||
authorization: str = request.cookies.get(
|
|
||||||
"access_token"
|
|
||||||
) # changed to accept access token from httpOnly Cookie
|
|
||||||
|
|
||||||
scheme, param = get_authorization_scheme_param(authorization)
|
|
||||||
if not authorization or scheme.lower() != "bearer":
|
|
||||||
if self.auto_error:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Not authenticated",
|
|
||||||
headers={"WWW-Authenticate": "Bearer"},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
return param
|
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
import logging
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
import bcrypt
|
|
||||||
from fastapi import APIRouter, HTTPException, status, Response, Depends
|
|
||||||
from fastapi.security import OAuth2PasswordRequestForm
|
|
||||||
from jose import jwt, JWTError
|
|
||||||
from src.apis.utils import SessionDep, OAuth2PasswordBearerWithCookie
|
|
||||||
from src.core.config import settings
|
|
||||||
from src.core.security import create_access_token
|
|
||||||
from src.db.models.admin import Profile
|
|
||||||
from src.db.repository.admin import get_profile
|
|
||||||
from src.schema.admin import Token
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
|
|
||||||
def authenticate_user(username: str, password: str, db: SessionDep) -> Profile | None:
|
|
||||||
user = get_profile(username=username, db=db)
|
|
||||||
print(user)
|
|
||||||
if not user:
|
|
||||||
return None
|
|
||||||
if bcrypt.checkpw(password.encode(), user.password.encode()):
|
|
||||||
print("User successful authenticated")
|
|
||||||
else:
|
|
||||||
logging.info("Authentication failed!")
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/token", response_model=Token)
|
|
||||||
def login_for_access_token(response: Response, db: SessionDep, form_data: OAuth2PasswordRequestForm = Depends()):
|
|
||||||
user = authenticate_user(form_data.username, form_data.password, db)
|
|
||||||
if not user:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Incorrect username or password",
|
|
||||||
)
|
|
||||||
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
|
||||||
access_token = create_access_token(
|
|
||||||
data={"sub": user.email}, expires_delta=access_token_expires
|
|
||||||
)
|
|
||||||
response.set_cookie(
|
|
||||||
key="access_token", value=f"Bearer {access_token}", httponly=True
|
|
||||||
)
|
|
||||||
return {"access_token": access_token, "token_type": "bearer"}
|
|
||||||
|
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearerWithCookie(tokenUrl="/api/login/token")
|
|
||||||
|
|
||||||
|
|
||||||
def get_current_user_from_token(db: SessionDep, token: str = Depends(oauth2_scheme)):
|
|
||||||
credentials_exception = HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Could not validate credentials",
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
payload = jwt.decode(
|
|
||||||
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
|
||||||
)
|
|
||||||
username: str = payload.get("sub")
|
|
||||||
print("username/email extracted is ", username)
|
|
||||||
if username is None:
|
|
||||||
raise credentials_exception
|
|
||||||
except JWTError:
|
|
||||||
raise credentials_exception
|
|
||||||
user = get_profile(username=username, db=db)
|
|
||||||
if user is None:
|
|
||||||
raise credentials_exception
|
|
||||||
return user
|
|
||||||
@@ -1,28 +1,31 @@
|
|||||||
from typing import List, AnyStr
|
from uuid import UUID
|
||||||
|
from typing import List
|
||||||
from fastapi import APIRouter, HTTPException, status
|
from fastapi import APIRouter, HTTPException, status
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
from src.apis.utils import SessionDep
|
from src.apis.utils import SessionDep
|
||||||
from src.db.repository.comics.artist import get_artist_details
|
|
||||||
from src.db.repository.comics.comic import list_comics, get_issue_details
|
|
||||||
from src.schema.comics.comic import ComicResponse, ComicDetailsResponse, get_comic_details, get_short_info
|
from src.schema.comics.comic import ComicResponse, ComicDetailsResponse, get_comic_details, get_short_info
|
||||||
from src.schema.comics.artist import ArtistCreation, ArtistDetailResponse, ArtistResponse
|
from src.schema.comics.artist import ArtistCreation, ArtistDetailResponse, ArtistResponse, get_artist_details
|
||||||
from src.db.models.comic import Comic, Artist, Issue
|
from src.db.models.comic import Comic, Artist
|
||||||
from src.schema.comics.issue import IssueDetailsResponse
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter(
|
||||||
|
prefix="/comic",
|
||||||
|
tags=["comics"],
|
||||||
|
responses={404: {"description": "Not found"}},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/comics")
|
@router.get("/comics")
|
||||||
def get_all_comics(db: SessionDep) -> List[ComicResponse]:
|
def get_all_comics(db: SessionDep) -> List[ComicResponse]:
|
||||||
results: List[ComicResponse] = []
|
results: List[ComicResponse] = []
|
||||||
comics = list_comics(db)
|
comics = db.scalars(select(Comic)).all()
|
||||||
for comic in comics:
|
for comic in comics:
|
||||||
response = get_short_info(comic)
|
response = get_short_info(comic)
|
||||||
results.append(response)
|
results.append(response)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@router.get("/comics/{comic_id}", response_model=ComicDetailsResponse)
|
@router.get("/comics/{comic_id}", response_model=ComicDetailsResponse)
|
||||||
def get_comic(comic_id: AnyStr, db: SessionDep) -> ComicDetailsResponse:
|
def get_comic(comic_id: UUID, db: SessionDep) -> ComicDetailsResponse:
|
||||||
comic = db.get(Comic, comic_id)
|
comic = db.get(Comic, comic_id)
|
||||||
if comic is None:
|
if comic is None:
|
||||||
raise HTTPException(status_code=404, detail="Comic could not be found")
|
raise HTTPException(status_code=404, detail="Comic could not be found")
|
||||||
@@ -38,7 +41,7 @@ def get_all_artists(db: SessionDep) -> List[ArtistResponse]:
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
@router.get("/artists/{artist_id}", response_model=ArtistDetailResponse)
|
@router.get("/artists/{artist_id}", response_model=ArtistDetailResponse)
|
||||||
def get_artist(artist_id: AnyStr, db: SessionDep) -> ArtistDetailResponse:
|
def get_artist(artist_id: UUID, db: SessionDep) -> ArtistDetailResponse:
|
||||||
artist = db.get(Artist, artist_id)
|
artist = db.get(Artist, artist_id)
|
||||||
if artist is None:
|
if artist is None:
|
||||||
raise HTTPException(status_code=404, detail="Artist could not be found")
|
raise HTTPException(status_code=404, detail="Artist could not be found")
|
||||||
@@ -57,11 +60,3 @@ def add_artist(db: SessionDep, artist_creation: ArtistCreation) -> ArtistRespons
|
|||||||
response = ArtistResponse(id=artist.id, name=artist.name)
|
response = ArtistResponse(id=artist.id, name=artist.name)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@router.get("/issues", response_model=List[IssueDetailsResponse])
|
|
||||||
def get_issues(db: SessionDep) -> List[IssueDetailsResponse]:
|
|
||||||
results: List[IssueDetailsResponse] = []
|
|
||||||
issues = db.query(Issue).all()
|
|
||||||
for issue in issues:
|
|
||||||
results.append(get_issue_details(issue))
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
from typing import List, AnyStr
|
from typing import List
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from fastapi import APIRouter, status, HTTPException, Depends
|
from fastapi import APIRouter, status, HTTPException
|
||||||
from sqlalchemy import select, Sequence
|
from sqlalchemy import select, Sequence
|
||||||
from src.core.log_conf import logger
|
|
||||||
from src.apis.utils import SessionDep
|
from src.apis.utils import SessionDep
|
||||||
from src.db.repository.media import create_new_mediafile
|
|
||||||
from src.schema.media.file import MediaFileResponse, Link, get_file_details, set_file
|
from src.schema.media.file import MediaFileResponse, Link, get_file_details, set_file
|
||||||
from src.db.models.media import MediaFile
|
from src.db.models.media import MediaFile
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter(
|
||||||
|
prefix="/media",
|
||||||
|
tags=["media"]
|
||||||
|
)
|
||||||
|
|
||||||
@router.get("/update-titles")
|
@router.get("/update-titles")
|
||||||
def update_titles(db: SessionDep) -> list[MediaFileResponse]:
|
def update_titles(db: SessionDep) -> list[MediaFileResponse]:
|
||||||
results: list[MediaFileResponse] = []
|
results: list[MediaFileResponse] = []
|
||||||
files = db.query(MediaFile).filter(MediaFile.review == True).all()
|
files = db.query(MediaFile).filter(MediaFile.review == 1).all()
|
||||||
for mediafile in files:
|
for mediafile in files:
|
||||||
mediafile.update_title()
|
mediafile.update_title()
|
||||||
db.add(mediafile)
|
db.add(mediafile)
|
||||||
@@ -24,14 +27,13 @@ def update_titles(db: SessionDep) -> list[MediaFileResponse]:
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/files", response_model=List[MediaFileResponse])
|
@router.get("/files", response_model=List[MediaFileResponse])
|
||||||
#def get_all_files(db: SessionDep, review: bool = False, download: bool = False, current_user: Profile = Depends(get_current_user_from_token)) -> List[MediaFileResponse]:
|
|
||||||
def get_all_files(db: SessionDep, review: bool = False, download: bool = False) -> List[MediaFileResponse]:
|
def get_all_files(db: SessionDep, review: bool = False, download: bool = False) -> List[MediaFileResponse]:
|
||||||
results: list[MediaFileResponse] = []
|
results: list[MediaFileResponse] = []
|
||||||
files: Sequence[MediaFile]
|
files: Sequence[MediaFile]
|
||||||
if review:
|
if review:
|
||||||
files = db.query(MediaFile).filter(MediaFile.review == True).all()
|
files = db.query(MediaFile).filter(MediaFile.review == 1).all()
|
||||||
elif download:
|
elif download:
|
||||||
files = db.query(MediaFile).filter(MediaFile.should_download == True).all()
|
files = db.query(MediaFile).filter(MediaFile.should_download == 1).all()
|
||||||
else:
|
else:
|
||||||
files = db.scalars(select(MediaFile)).all()
|
files = db.scalars(select(MediaFile)).all()
|
||||||
for mediafile in files:
|
for mediafile in files:
|
||||||
@@ -40,7 +42,7 @@ def get_all_files(db: SessionDep, review: bool = False, download: bool = False)
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
@router.get("/files/{file_id}", response_model=MediaFileResponse)
|
@router.get("/files/{file_id}", response_model=MediaFileResponse)
|
||||||
def get_file(file_id: AnyStr, db: SessionDep) -> MediaFileResponse:
|
def get_file(file_id: UUID, db: SessionDep) -> MediaFileResponse:
|
||||||
mediafile = db.get(MediaFile, file_id)
|
mediafile = db.get(MediaFile, file_id)
|
||||||
if not mediafile:
|
if not mediafile:
|
||||||
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
||||||
@@ -48,7 +50,7 @@ def get_file(file_id: AnyStr, db: SessionDep) -> MediaFileResponse:
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
@router.put("/files/{file_id}", response_model=MediaFileResponse)
|
@router.put("/files/{file_id}", response_model=MediaFileResponse)
|
||||||
def update_file(file_id: AnyStr, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse:
|
def update_file(file_id: UUID, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse:
|
||||||
mediaFile = db.get(MediaFile, file_id)
|
mediaFile = db.get(MediaFile, file_id)
|
||||||
if not mediaFile:
|
if not mediaFile:
|
||||||
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
||||||
@@ -60,9 +62,14 @@ def update_file(file_id: AnyStr, db: SessionDep, info: MediaFileResponse) -> Med
|
|||||||
|
|
||||||
@router.post("/files", status_code=status.HTTP_201_CREATED)
|
@router.post("/files", status_code=status.HTTP_201_CREATED)
|
||||||
def add_file(new_link: Link, db: SessionDep) -> MediaFileResponse:
|
def add_file(new_link: Link, db: SessionDep) -> MediaFileResponse:
|
||||||
logger.info(f"add url {new_link.url}")
|
print(new_link.url)
|
||||||
try:
|
try:
|
||||||
mediaFile: MediaFile = create_new_mediafile(new_link.url, db)
|
mediaFile: MediaFile = MediaFile()
|
||||||
|
setattr(mediaFile, "url", new_link.url)
|
||||||
|
setattr(mediaFile, "review", 1)
|
||||||
|
setattr(mediaFile, "should_download", 1)
|
||||||
|
db.add(mediaFile)
|
||||||
|
db.commit()
|
||||||
except:
|
except:
|
||||||
raise HTTPException(status_code=409, detail="Link duplicate")
|
raise HTTPException(status_code=409, detail="Link duplicate")
|
||||||
response = get_file_details(mediaFile)
|
response = get_file_details(mediaFile)
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ from src.apis.utils import SessionDep
|
|||||||
from src.schema.tysc.sport import SportResponse
|
from src.schema.tysc.sport import SportResponse
|
||||||
from src.db.models.tysc import Sport
|
from src.db.models.tysc import Sport
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter(
|
||||||
|
prefix="/tysc",
|
||||||
|
tags=["tysc"],
|
||||||
|
responses={404: {"description": "Not found"}},
|
||||||
|
)
|
||||||
|
|
||||||
@router.get("/sports")
|
@router.get("/sports")
|
||||||
def get_all_sports(db: SessionDep) -> List[SportResponse]:
|
def get_all_sports(db: SessionDep) -> List[SportResponse]:
|
||||||
|
|||||||
@@ -9,17 +9,15 @@ load_dotenv(dotenv_path=env_path)
|
|||||||
|
|
||||||
class Settings:
|
class Settings:
|
||||||
PROJECT_NAME: str = "Kontor"
|
PROJECT_NAME: str = "Kontor"
|
||||||
PROJECT_VERSION: str = "0.2.0"
|
PROJECT_VERSION: str = "0.1.0"
|
||||||
|
|
||||||
|
MARIADB_USER: str = os.getenv("MARIADB_USER", "kontor")
|
||||||
|
MARIADB_PASSWORD: str = os.getenv("MARIADB_PASSWORD", "kontor")
|
||||||
|
MARIADB_SERVER: str = os.getenv("MARIADB_SERVER", "mariadb")
|
||||||
|
MARIADB_PORT: str = os.getenv("MARIADB_PORT", 3306)
|
||||||
|
MARIADB_DB: str = os.getenv("MARIADB_DB", "kontor")
|
||||||
|
DATABASE_URL: str = f"mariadb+mariadbconnector://{MARIADB_USER}:{MARIADB_PASSWORD}@{MARIADB_SERVER}:{MARIADB_PORT}/{MARIADB_DB}"
|
||||||
|
|
||||||
DB_USER: str = os.getenv("DB_USER", "kontor")
|
|
||||||
DB_PASSWORD: str = os.getenv("DB_PASSWORD", "kontor")
|
|
||||||
DB_SERVER: str = os.getenv("DB_SERVER", "postgres")
|
|
||||||
DB_PORT: str = os.getenv("DB_PORT", 5432)
|
|
||||||
DB_DBNAME: str = os.getenv("DB_DBNAME", "kontor")
|
|
||||||
DATABASE_URL: str = f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_SERVER}:{DB_PORT}/{DB_DBNAME}"
|
|
||||||
SECRET_KEY: str = os.getenv("SECRET_KEY", "J6GOtcwC2NJI1l0VkHu20PacPFGTxpirBxWwynoHjsc=")
|
|
||||||
ALGORITHM = "HS256"
|
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES = 600 # in mins
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import logging
|
|
||||||
import logging.config
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
LOGGING_CONFIG: dict[str, Any] = {
|
|
||||||
"version": 1,
|
|
||||||
"disable_existing_loggers": False,
|
|
||||||
"formatters": {
|
|
||||||
"default": {
|
|
||||||
"()": "uvicorn.logging.DefaultFormatter",
|
|
||||||
"fmt": "%(asctime)s - %(name)s - %(levelprefix)s %(message)s"
|
|
||||||
},
|
|
||||||
"access": {
|
|
||||||
"()": "uvicorn.logging.AccessFormatter",
|
|
||||||
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
|
|
||||||
},
|
|
||||||
"access_file": {
|
|
||||||
"()": "uvicorn.logging.AccessFormatter",
|
|
||||||
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
|
|
||||||
"use_colors": False,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"handlers": {
|
|
||||||
"default": {
|
|
||||||
"formatter": "default",
|
|
||||||
"class": "logging.StreamHandler",
|
|
||||||
"stream": "ext://sys.stdout",
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"formatter": "access",
|
|
||||||
"class": "logging.StreamHandler",
|
|
||||||
"stream": "ext://sys.stderr",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"loggers": {
|
|
||||||
"root": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
|
||||||
"uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
|
||||||
"uvicorn.error": {"level": "INFO"},
|
|
||||||
"uvicorn.access": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
logging.config.dictConfig(LOGGING_CONFIG)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
from datetime import timedelta
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from src.core.config import settings
|
|
||||||
from jose import jwt
|
|
||||||
|
|
||||||
|
|
||||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
|
||||||
to_encode = data.copy()
|
|
||||||
if expires_delta:
|
|
||||||
expire = datetime.utcnow() + expires_delta
|
|
||||||
else:
|
|
||||||
expire = datetime.utcnow() + timedelta(
|
|
||||||
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
|
||||||
)
|
|
||||||
to_encode.update({"exp": expire})
|
|
||||||
encoded_jwt = jwt.encode(
|
|
||||||
to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM
|
|
||||||
)
|
|
||||||
return encoded_jwt
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean
|
from sqlalchemy import Column, ForeignKey, Integer, String
|
||||||
|
from sqlalchemy.dialects.mysql import BIT
|
||||||
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
||||||
|
|
||||||
from src.db.models.base import Base, BaseMixin
|
from src.db.models.base import Base, BaseMixin
|
||||||
@@ -8,12 +9,12 @@ from src.db.models.base import Base, BaseMixin
|
|||||||
|
|
||||||
class Profile(Base, BaseMixin):
|
class Profile(Base, BaseMixin):
|
||||||
__tablename__ = 'profile'
|
__tablename__ = 'profile'
|
||||||
first_name = Column(String)
|
first_name = Column(String(255))
|
||||||
last_name = Column(String)
|
last_name = Column(String(255))
|
||||||
user_name = Column(String, nullable=False)
|
user_name = Column(String(255), nullable=False)
|
||||||
email = Column(String)
|
email = Column(String(255))
|
||||||
password = Column(String)
|
password = Column(String(255))
|
||||||
enabled = Column(Boolean)
|
enabled = Column(BIT(1))
|
||||||
assignments = relationship("Assignment")
|
assignments = relationship("Assignment")
|
||||||
tokens = relationship("Token")
|
tokens = relationship("Token")
|
||||||
|
|
||||||
@@ -27,23 +28,20 @@ class Profile(Base, BaseMixin):
|
|||||||
full_name += self.last_name
|
full_name += self.last_name
|
||||||
return full_name
|
return full_name
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"Profile({self.id} {self.user_name}, {self.email})"
|
|
||||||
|
|
||||||
|
|
||||||
class Token(Base, BaseMixin):
|
class Token(Base, BaseMixin):
|
||||||
__tablename__ = "token"
|
__tablename__ = "token"
|
||||||
token = Column(String, nullable=False, unique=True)
|
token = Column(String(255), nullable=False, unique=True)
|
||||||
name = Column(String)
|
name = Column(String(255))
|
||||||
last_used_date: Mapped[datetime] = mapped_column()
|
last_used_date: Mapped[datetime] = mapped_column()
|
||||||
enabled = Column(Boolean)
|
enabled = Column(BIT(1))
|
||||||
profile_id = Column(String, ForeignKey("profile.id"), nullable=False)
|
profile_id = Column(String(255), ForeignKey("profile.id"), nullable=False)
|
||||||
profile = relationship("Profile", back_populates="tokens")
|
profile = relationship("Profile", back_populates="tokens")
|
||||||
|
|
||||||
|
|
||||||
class Permission(Base, BaseMixin):
|
class Permission(Base, BaseMixin):
|
||||||
__tablename__ = "permission"
|
__tablename__ = "permission"
|
||||||
name = Column(String, nullable=False)
|
name = Column(String(255), nullable=False)
|
||||||
assignments = relationship("Assignment")
|
assignments = relationship("Assignment")
|
||||||
|
|
||||||
|
|
||||||
@@ -55,14 +53,20 @@ class Assignment(Base, BaseMixin):
|
|||||||
permission = relationship("Permission", back_populates="assignments")
|
permission = relationship("Permission", back_populates="assignments")
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleData(Base, BaseMixin):
|
||||||
|
__tablename__ = "module_data"
|
||||||
|
module_name = Column(String(255), nullable=False)
|
||||||
|
import_data = Column(BIT(1))
|
||||||
|
|
||||||
|
|
||||||
class MailAccount(Base, BaseMixin):
|
class MailAccount(Base, BaseMixin):
|
||||||
__tablename__ = "mail_account"
|
__tablename__ = "mail_account"
|
||||||
host = Column(String)
|
host = Column(String(255))
|
||||||
port = Column(Integer)
|
port = Column(Integer)
|
||||||
protocol = Column(String)
|
protocol = Column(String(255))
|
||||||
user_name = Column(String)
|
user_name = Column(String(255))
|
||||||
password = Column(String)
|
password = Column(String(255))
|
||||||
start_tls = Column(Boolean)
|
start_tls = Column(BIT(1))
|
||||||
|
|
||||||
|
|
||||||
class Mail(Base, BaseMixin):
|
class Mail(Base, BaseMixin):
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import func, Column, String, Boolean
|
from sqlalchemy import func, Column, String
|
||||||
|
from sqlalchemy.dialects.mysql import BIT
|
||||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||||
|
|
||||||
|
|
||||||
@@ -10,8 +11,8 @@ class Base(DeclarativeBase):
|
|||||||
|
|
||||||
|
|
||||||
class BaseMixin:
|
class BaseMixin:
|
||||||
#id = Column(String, primary_key=True, default=uuid.uuid4)
|
id = Column(String(255), primary_key=True, default=uuid.uuid4())
|
||||||
id: Mapped[str] = mapped_column(primary_key=True, default=str(uuid.uuid4()))
|
# id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4())
|
||||||
# created_date = Column(DateTime)
|
# created_date = Column(DateTime)
|
||||||
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||||
# last_modified_date = Column(DateTime)
|
# last_modified_date = Column(DateTime)
|
||||||
@@ -21,10 +22,10 @@ class BaseMixin:
|
|||||||
|
|
||||||
|
|
||||||
class BaseVideoMixin:
|
class BaseVideoMixin:
|
||||||
cloud_link = Column(String)
|
cloud_link = Column(String(255))
|
||||||
file_name = Column(String)
|
file_name = Column(String(255))
|
||||||
path = Column(String)
|
path = Column(String(255))
|
||||||
review = Column(Boolean)
|
review = Column(BIT(1))
|
||||||
title = Column(String)
|
title = Column(String(255))
|
||||||
url = Column(String, unique=True)
|
url = Column(String(255), unique=True)
|
||||||
should_download = Column(Boolean)
|
should_download = Column(BIT(1))
|
||||||
|
|||||||
@@ -6,28 +6,28 @@ from src.db.models.base import Base, BaseMixin
|
|||||||
|
|
||||||
class Article(Base, BaseMixin):
|
class Article(Base, BaseMixin):
|
||||||
__tablename__ = 'article'
|
__tablename__ = 'article'
|
||||||
title = Column(String, unique=True)
|
title = Column(String(length=255), unique=True)
|
||||||
article_authors = relationship("ArticleAuthor")
|
article_authors = relationship("ArticleAuthor")
|
||||||
|
|
||||||
|
|
||||||
class Author(Base, BaseMixin):
|
class Author(Base, BaseMixin):
|
||||||
__tablename__ = 'author'
|
__tablename__ = 'author'
|
||||||
first_name = Column(String)
|
first_name = Column(String(255))
|
||||||
last_name = Column(String)
|
last_name = Column(String(255))
|
||||||
article_authors = relationship("ArticleAuthor")
|
article_authors = relationship("ArticleAuthor")
|
||||||
book_authors = relationship("BookAuthor")
|
book_authors = relationship("BookAuthor")
|
||||||
|
|
||||||
|
|
||||||
class BookshelfPublisher(Base, BaseMixin):
|
class BookshelfPublisher(Base, BaseMixin):
|
||||||
__tablename__ = 'bookshelf_publisher'
|
__tablename__ = 'bookshelf_publisher'
|
||||||
name = Column(String, unique=True)
|
name = Column(String(length=255), unique=True)
|
||||||
books = relationship("Book")
|
books = relationship("Book")
|
||||||
|
|
||||||
|
|
||||||
class Book(Base, BaseMixin):
|
class Book(Base, BaseMixin):
|
||||||
__tablename__ = 'book'
|
__tablename__ = 'book'
|
||||||
isbn = Column(String, unique=True)
|
isbn = Column(String(255), unique=True)
|
||||||
title = Column(String)
|
title = Column(String(255))
|
||||||
year = Column(Integer, nullable=False)
|
year = Column(Integer, nullable=False)
|
||||||
publisher_id = Column(String, ForeignKey('bookshelf_publisher.id'), nullable=False)
|
publisher_id = Column(String, ForeignKey('bookshelf_publisher.id'), nullable=False)
|
||||||
publisher = relationship('BookshelfPublisher', back_populates="books")
|
publisher = relationship('BookshelfPublisher', back_populates="books")
|
||||||
|
|||||||
@@ -1,24 +1,15 @@
|
|||||||
import uuid
|
from typing import Dict, List
|
||||||
from datetime import datetime
|
|
||||||
from typing import AnyStr, Dict, List, Optional, Any
|
|
||||||
from natsort import natsorted
|
from natsort import natsorted
|
||||||
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean, func
|
from sqlalchemy import Column, ForeignKey, Integer, String
|
||||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
from sqlalchemy.dialects.mysql import BIT
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from src.db.models.base import Base, BaseMixin
|
from src.db.models.base import Base, BaseMixin
|
||||||
|
|
||||||
|
|
||||||
class Publisher(Base):
|
class Publisher(Base, BaseMixin):
|
||||||
__tablename__ = "publisher"
|
__tablename__ = "publisher"
|
||||||
id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4)
|
name = Column(String(length=255), unique=True)
|
||||||
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
|
||||||
last_modified_date: Mapped[datetime] = mapped_column(default=func.now())
|
|
||||||
version: Mapped[int] = mapped_column(default=0)
|
|
||||||
name = Column(String, unique=True)
|
|
||||||
weblink = Column(String, nullable=True)
|
|
||||||
parent_publisher_id: Mapped[Optional[str]] = mapped_column(ForeignKey('publisher.id'))
|
|
||||||
parent_publisher: Mapped[Optional['Publisher']] = relationship("Publisher", back_populates="imprints", remote_side=[id])
|
|
||||||
imprints: Mapped[List['Publisher']] = relationship('Publisher', back_populates="parent_publisher")
|
|
||||||
comics = relationship("Comic")
|
comics = relationship("Comic")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@@ -30,12 +21,11 @@ class Publisher(Base):
|
|||||||
|
|
||||||
class Comic(Base, BaseMixin):
|
class Comic(Base, BaseMixin):
|
||||||
__tablename__ = 'comic'
|
__tablename__ = 'comic'
|
||||||
title = Column(String, unique=True)
|
title = Column(String(length=255), unique=True)
|
||||||
publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
|
publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
|
||||||
publisher = relationship("Publisher", back_populates="comics")
|
publisher = relationship("Publisher", back_populates="comics")
|
||||||
current_order = Column(Boolean)
|
current_order = Column(BIT(1))
|
||||||
completed = Column(Boolean)
|
completed = Column(BIT(1))
|
||||||
weblink = Column(String, nullable=True)
|
|
||||||
issues = relationship("Issue", order_by="Issue.issue_number")
|
issues = relationship("Issue", order_by="Issue.issue_number")
|
||||||
story_arcs = relationship("StoryArc")
|
story_arcs = relationship("StoryArc")
|
||||||
trade_paperbacks = relationship("TradePaperback")
|
trade_paperbacks = relationship("TradePaperback")
|
||||||
@@ -48,10 +38,10 @@ class Comic(Base, BaseMixin):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f'{self.title}({self.id})'
|
return f'{self.title}({self.id})'
|
||||||
|
|
||||||
def get_artists(self) -> Dict[Any, List[Any]]:
|
def get_artists(self) -> Dict[str, List[str]]:
|
||||||
works: Dict[Any, List[Any]] = {}
|
works: Dict[str, List[str]] = {}
|
||||||
for work in self.comic_works:
|
for work in self.comic_works:
|
||||||
work_type = work.work_type
|
work_type = work.work_type.name
|
||||||
artist = work.artist
|
artist = work.artist
|
||||||
if work_type in works:
|
if work_type in works:
|
||||||
works[work_type].append(artist)
|
works[work_type].append(artist)
|
||||||
@@ -66,16 +56,15 @@ class Comic(Base, BaseMixin):
|
|||||||
|
|
||||||
class Volume(Base, BaseMixin):
|
class Volume(Base, BaseMixin):
|
||||||
__tablename__ = "volume"
|
__tablename__ = "volume"
|
||||||
name = Column(String, nullable=False)
|
name = Column(String(length=255), nullable=False)
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||||
comic = relationship("Comic", back_populates="volumes")
|
comic = relationship("Comic", back_populates="volumes")
|
||||||
story_arcs = relationship("StoryArc")
|
|
||||||
issues = relationship("Issue")
|
issues = relationship("Issue")
|
||||||
|
|
||||||
|
|
||||||
class TradePaperback(Base, BaseMixin):
|
class TradePaperback(Base, BaseMixin):
|
||||||
__tablename__ = "trade_paperback"
|
__tablename__ = "trade_paperback"
|
||||||
name = Column(String, nullable=False)
|
name = Column(String(length=255), nullable=False)
|
||||||
issue_start = Column(Integer)
|
issue_start = Column(Integer)
|
||||||
issue_end = Column(Integer)
|
issue_end = Column(Integer)
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||||
@@ -84,58 +73,31 @@ class TradePaperback(Base, BaseMixin):
|
|||||||
|
|
||||||
class StoryArc(Base, BaseMixin):
|
class StoryArc(Base, BaseMixin):
|
||||||
__tablename__ = "story_arc"
|
__tablename__ = "story_arc"
|
||||||
name = Column(String, nullable=False)
|
name = Column(String(length=255), nullable=False)
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||||
comic = relationship("Comic", back_populates="story_arcs")
|
comic = relationship("Comic", back_populates="story_arcs")
|
||||||
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
|
||||||
volume = relationship("Volume", back_populates="story_arcs")
|
|
||||||
issues = relationship("Issue")
|
|
||||||
|
|
||||||
|
|
||||||
class Issue(Base, BaseMixin):
|
class Issue(Base, BaseMixin):
|
||||||
__tablename__ = "issue"
|
__tablename__ = "issue"
|
||||||
issue_number = Column(String)
|
issue_number = Column(String(255))
|
||||||
title = Column(String, nullable=True)
|
in_stock = Column(BIT(1))
|
||||||
published_on: Mapped[datetime] = mapped_column(nullable=True)
|
is_read = Column(BIT(1))
|
||||||
in_stock = Column(Boolean)
|
|
||||||
is_read = Column(Boolean)
|
|
||||||
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
|
||||||
comic = relationship("Comic", back_populates="issues")
|
comic = relationship("Comic", back_populates="issues")
|
||||||
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
volume_id = Column(String, ForeignKey("volume.id"), nullable=True)
|
||||||
volume = relationship("Volume", back_populates="issues")
|
volume = relationship("Volume", back_populates="issues")
|
||||||
story_arc_id = Column(String, ForeignKey("story_arc.id"), nullable=True)
|
|
||||||
story_arc = relationship("StoryArc", back_populates="issues")
|
|
||||||
issue_works = relationship("IssueWork")
|
|
||||||
|
|
||||||
def get_full_title(self) -> AnyStr:
|
|
||||||
full_title: AnyStr = self.issue_number
|
|
||||||
if self.title:
|
|
||||||
full_title += ": " + self.title
|
|
||||||
return full_title
|
|
||||||
|
|
||||||
def get_artists(self) -> Dict[Any, List[Any]]:
|
|
||||||
works: Dict[Any, List[Any]] = {}
|
|
||||||
for work in self.issue_works:
|
|
||||||
work_type = work.work_type
|
|
||||||
artist = work.artist
|
|
||||||
if work_type in works:
|
|
||||||
works[work_type].append(artist)
|
|
||||||
else:
|
|
||||||
works[work_type] = [artist]
|
|
||||||
return works
|
|
||||||
|
|
||||||
|
|
||||||
class Artist(Base, BaseMixin):
|
class Artist(Base, BaseMixin):
|
||||||
__tablename__ = "artist"
|
__tablename__ = "artist"
|
||||||
name = Column(String, nullable=False)
|
name = Column(String(length=255), nullable=False)
|
||||||
weblink = Column(String, nullable=True)
|
|
||||||
comic_works = relationship("ComicWork")
|
comic_works = relationship("ComicWork")
|
||||||
issue_works = relationship("IssueWork")
|
|
||||||
|
|
||||||
def get_comics(self) -> Dict[Any, List[Comic]]:
|
def get_comics(self) -> Dict[str, List[str]]:
|
||||||
works: Dict[Any, List[Comic]] = {}
|
works: Dict[str, List[str]] = {}
|
||||||
for work in self.comic_works:
|
for work in self.comic_works:
|
||||||
work_type = work.work_type
|
work_type = work.work_type.name
|
||||||
comic = work.comic
|
comic = work.comic
|
||||||
if work_type in works:
|
if work_type in works:
|
||||||
works[work_type].append(comic)
|
works[work_type].append(comic)
|
||||||
@@ -146,20 +108,8 @@ class Artist(Base, BaseMixin):
|
|||||||
|
|
||||||
class WorkType(Base, BaseMixin):
|
class WorkType(Base, BaseMixin):
|
||||||
__tablename__ = "worktype"
|
__tablename__ = "worktype"
|
||||||
name = Column(String, nullable=False, unique=True)
|
name = Column(String(length=255), nullable=False, unique=True)
|
||||||
comic_works = relationship("ComicWork")
|
comic_works = relationship("ComicWork")
|
||||||
issue_works = relationship("IssueWork")
|
|
||||||
|
|
||||||
def get_artists(self) -> Dict[str, List[str]]:
|
|
||||||
works: Dict[str, List[str]] = {}
|
|
||||||
for work in self.comic_works:
|
|
||||||
comic = work.comic.title
|
|
||||||
artist = work.artist
|
|
||||||
if comic in works:
|
|
||||||
works[comic].append(artist)
|
|
||||||
else:
|
|
||||||
works[comic] = [artist]
|
|
||||||
return works
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f'Worktype({self.id} {self.version} {self.name} {len(self.comic_works)})'
|
return f'Worktype({self.id} {self.version} {self.name} {len(self.comic_works)})'
|
||||||
@@ -176,14 +126,3 @@ class ComicWork(Base, BaseMixin):
|
|||||||
artist = relationship("Artist", back_populates="comic_works")
|
artist = relationship("Artist", back_populates="comic_works")
|
||||||
work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
|
work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
|
||||||
work_type = relationship("WorkType", back_populates="comic_works")
|
work_type = relationship("WorkType", back_populates="comic_works")
|
||||||
|
|
||||||
|
|
||||||
class IssueWork(Base, BaseMixin):
|
|
||||||
__tablename__ = "issue_work"
|
|
||||||
issue_id = Column(String, ForeignKey("issue.id"), nullable=False)
|
|
||||||
issue = relationship("Issue", back_populates="issue_works")
|
|
||||||
artist_id = Column(String, ForeignKey("artist.id"), nullable=False)
|
|
||||||
artist = relationship("Artist", back_populates="issue_works")
|
|
||||||
work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
|
|
||||||
work_type = relationship("WorkType", back_populates="issue_works")
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, List
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
@@ -12,7 +13,7 @@ from sqlalchemy.orm import sessionmaker
|
|||||||
from src.db.models.tysc import Card, CardSet, Rooster, Team, FieldPosition, Player, Vendor, Sport
|
from src.db.models.tysc import Card, CardSet, Rooster, Team, FieldPosition, Player, Vendor, Sport
|
||||||
from src.db.models.comic import Issue, TradePaperback, StoryArc, Volume, ComicWork, Artist, Comic, Publisher, WorkType
|
from src.db.models.comic import Issue, TradePaperback, StoryArc, Volume, ComicWork, Artist, Comic, Publisher, WorkType
|
||||||
from src.db.models.bookshelf import ArticleAuthor, BookAuthor, BookshelfPublisher, Article, Book, Author
|
from src.db.models.bookshelf import ArticleAuthor, BookAuthor, BookshelfPublisher, Article, Book, Author
|
||||||
from src.db.models.admin import Mail, MailAccount, ModuleData, Token, Assignment, Permission, Profile
|
from src.db.models.admin import Mail, MailAccount, ModuleData, Role, User, Token, AuthorizationMatrix
|
||||||
from src.db.models.metadata import MetaDataTable, MetaDataColumn
|
from src.db.models.metadata import MetaDataTable, MetaDataColumn
|
||||||
from src.db.models.media import MediaVideo, MediaArticle, MediaFile, MediaActor, MediaActorFile
|
from src.db.models.media import MediaVideo, MediaArticle, MediaFile, MediaActor, MediaActorFile
|
||||||
|
|
||||||
@@ -78,10 +79,10 @@ class KontorDB:
|
|||||||
self.registry[MediaVideo.__tablename__] = MediaVideo
|
self.registry[MediaVideo.__tablename__] = MediaVideo
|
||||||
self.registry[MetaDataColumn.__tablename__] = MetaDataColumn
|
self.registry[MetaDataColumn.__tablename__] = MetaDataColumn
|
||||||
self.registry[MetaDataTable.__tablename__] = MetaDataTable
|
self.registry[MetaDataTable.__tablename__] = MetaDataTable
|
||||||
self.registry[Assignment.__tablename__] = Assignment
|
self.registry[AuthorizationMatrix.__tablename__] = AuthorizationMatrix
|
||||||
self.registry[Token.__tablename__] = Token
|
self.registry[Token.__tablename__] = Token
|
||||||
self.registry[Profile.__tablename__] = Profile
|
self.registry[User.__tablename__] = User
|
||||||
self.registry[Permission.__tablename__] = Permission
|
self.registry[Role.__tablename__] = Role
|
||||||
self.registry[ModuleData.__tablename__] = ModuleData
|
self.registry[ModuleData.__tablename__] = ModuleData
|
||||||
self.registry[MailAccount.__tablename__] = MailAccount
|
self.registry[MailAccount.__tablename__] = MailAccount
|
||||||
self.registry[Mail.__tablename__] = Mail
|
self.registry[Mail.__tablename__] = Mail
|
||||||
@@ -359,7 +360,7 @@ class KontorDB:
|
|||||||
update_list[link.id] = link.title
|
update_list[link.id] = link.title
|
||||||
return update_list
|
return update_list
|
||||||
|
|
||||||
def get_download_list(self) -> List[str]:
|
def get_download_list(self) -> list[uuid.UUID]:
|
||||||
download_list = []
|
download_list = []
|
||||||
__session__ = sessionmaker(self.engine)
|
__session__ = sessionmaker(self.engine)
|
||||||
_filter = { 'should_download': True}
|
_filter = { 'should_download': True}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ from pathlib import Path
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from sqlalchemy import Column, String, ForeignKey, Boolean
|
from sqlalchemy import Column, String, ForeignKey
|
||||||
|
from sqlalchemy.dialects.mysql import BIT
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from src.db.models.base import Base, BaseMixin, BaseVideoMixin
|
from src.db.models.base import Base, BaseMixin, BaseVideoMixin
|
||||||
@@ -29,10 +30,10 @@ class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
|||||||
soup = BeautifulSoup(r.content, "html.parser")
|
soup = BeautifulSoup(r.content, "html.parser")
|
||||||
title = soup.title.string
|
title = soup.title.string
|
||||||
self.title = title
|
self.title = title
|
||||||
self.review = False
|
self.review = 0
|
||||||
except:
|
except:
|
||||||
self.title = None
|
self.title = None
|
||||||
self.review = True
|
self.review = 1
|
||||||
self.last_modified_date = datetime.now()
|
self.last_modified_date = datetime.now()
|
||||||
|
|
||||||
def download_file(self, download_dir: str, dl_tool: str):
|
def download_file(self, download_dir: str, dl_tool: str):
|
||||||
@@ -44,12 +45,12 @@ class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
|||||||
lines_list = output.splitlines()
|
lines_list = output.splitlines()
|
||||||
file_name = self.__parse_output__(lines_list)
|
file_name = self.__parse_output__(lines_list)
|
||||||
if file_name is None:
|
if file_name is None:
|
||||||
self.review = True
|
self.review = 1
|
||||||
self.should_download = True
|
self.should_download = 1
|
||||||
self.file_name = None
|
self.file_name = None
|
||||||
else:
|
else:
|
||||||
download_file = Path(file_name)
|
download_file = Path(file_name)
|
||||||
self.should_download = False
|
self.should_download = 0
|
||||||
self.file_name = download_file.name
|
self.file_name = download_file.name
|
||||||
self.cloud_link = str(download_file.absolute())
|
self.cloud_link = str(download_file.absolute())
|
||||||
self.last_modified_date = datetime.now()
|
self.last_modified_date = datetime.now()
|
||||||
@@ -70,42 +71,31 @@ class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
|||||||
|
|
||||||
class MediaActor(Base, BaseMixin):
|
class MediaActor(Base, BaseMixin):
|
||||||
__tablename__ = 'media_actor'
|
__tablename__ = 'media_actor'
|
||||||
name = Column(String)
|
name = Column(String(255))
|
||||||
media_actor_files = relationship("MediaActorFile")
|
media_actor_files = relationship("MediaActorFile")
|
||||||
|
|
||||||
|
|
||||||
class MediaActorFile(Base, BaseMixin):
|
class MediaActorFile(Base, BaseMixin):
|
||||||
__tablename__ = 'media_actor_file'
|
__tablename__ = 'media_actor_file'
|
||||||
media_actor_id = Column(String, ForeignKey("media_actor.id"), nullable=False)
|
media_actor_id = Column(String(255), ForeignKey("media_actor.id"), nullable=False)
|
||||||
media_actor = relationship("MediaActor", back_populates="media_actor_files")
|
media_actor = relationship("MediaActor", back_populates="media_actor_files")
|
||||||
media_file_id = Column(String, ForeignKey("media_file.id"), nullable=True)
|
media_file_id = Column(String(255), ForeignKey("media_file.id"), nullable=True)
|
||||||
media_file = relationship("MediaFile", back_populates="media_actor_files")
|
media_file = relationship("MediaFile", back_populates="media_actor_files")
|
||||||
|
|
||||||
|
|
||||||
class MediaArticle(Base, BaseMixin):
|
class MediaArticle(Base, BaseMixin):
|
||||||
__tablename__ = 'media_article'
|
__tablename__ = 'media_article'
|
||||||
review = Column(Boolean)
|
review = Column(BIT(1))
|
||||||
title = Column(String)
|
title = Column(String(255))
|
||||||
url = Column(String, unique=True)
|
url = Column(String(255), unique=True)
|
||||||
|
|
||||||
|
|
||||||
class MediaVideo(Base, BaseMixin):
|
class MediaVideo(Base, BaseMixin):
|
||||||
__tablename__ = 'media_video'
|
__tablename__ = 'media_video'
|
||||||
cloud_link = Column(String)
|
cloud_link = Column(String(255))
|
||||||
file_name = Column(String)
|
file_name = Column(String(255))
|
||||||
path = Column(String)
|
path = Column(String(255))
|
||||||
review = Column(Boolean)
|
review = Column(BIT(1))
|
||||||
title = Column(String)
|
title = Column(String(255))
|
||||||
url = Column(String, unique=True)
|
url = Column(String(255), unique=True)
|
||||||
should_download = Column(Boolean)
|
should_download = Column(BIT(1))
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'MediaFile({self.id} {self.title} {self.url})'
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if self.title is None:
|
|
||||||
return f'{self.url}({self.id})'
|
|
||||||
else:
|
|
||||||
return f'{self.title}({self.id})'
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
from sqlalchemy import Column, String, ForeignKey, Integer
|
||||||
|
from sqlalchemy.dialects.mysql import BIT
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from src.db.models.base import Base, BaseMixin
|
||||||
|
|
||||||
|
|
||||||
|
class MetaDataTable(Base, BaseMixin):
|
||||||
|
__tablename__ = 'meta_data_table'
|
||||||
|
table_name = Column(String(255), unique=True)
|
||||||
|
table_columns = relationship("MetaDataColumn")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'MetaDataTable({self.id} {self.table_name})'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.table_name}({self.id})'
|
||||||
|
|
||||||
|
|
||||||
|
class MetaDataColumn(Base, BaseMixin):
|
||||||
|
__tablename__ = 'meta_data_column'
|
||||||
|
column_name = Column(String(255), nullable=False)
|
||||||
|
column_sync_name = Column(String(255))
|
||||||
|
column_type = Column(String(255))
|
||||||
|
column_modifier = Column(String(255), nullable=True)
|
||||||
|
column_order = Column(Integer)
|
||||||
|
table_id = Column(String, ForeignKey('meta_data_table.id'))
|
||||||
|
table = relationship("MetaDataTable", back_populates="table_columns")
|
||||||
|
column_label = Column(String(255))
|
||||||
|
filter_label = Column(String(255))
|
||||||
|
is_shown = Column(BIT(1))
|
||||||
|
show_filter = Column(BIT(1))
|
||||||
|
ref_column = Column(String, nullable=True)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self.column_name is None:
|
||||||
|
return f'MetaDataColumn({self.id} {self.table.table_name}.__)'
|
||||||
|
else:
|
||||||
|
return f'MetaDataColumn({self.id} {self.table.table_name}.{self.column_name})'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.column_name}({self.id})'
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Boolean
|
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
|
||||||
|
from sqlalchemy.dialects.mysql import BIT
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
from src.db.models.base import Base, BaseMixin
|
from src.db.models.base import Base, BaseMixin
|
||||||
@@ -9,15 +10,15 @@ class Sport(Base, BaseMixin):
|
|||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint("name"),
|
UniqueConstraint("name"),
|
||||||
)
|
)
|
||||||
name = Column(String, nullable=False, index=True, unique=True)
|
name = Column(String(255), nullable=False, index=True, unique=True)
|
||||||
teams = relationship("Team")
|
teams = relationship("Team")
|
||||||
positions = relationship("FieldPosition")
|
positions = relationship("FieldPosition")
|
||||||
|
|
||||||
|
|
||||||
class Team(Base, BaseMixin):
|
class Team(Base, BaseMixin):
|
||||||
__tablename__ = "team"
|
__tablename__ = "team"
|
||||||
name = Column(String, nullable=False, index=True, unique=True)
|
name = Column(String(255), nullable=False, index=True, unique=True)
|
||||||
short_name = Column(String, nullable=False, )
|
short_name = Column(String(255), nullable=False, )
|
||||||
sport_id = Column(String, ForeignKey("sport.id"), nullable=False)
|
sport_id = Column(String, ForeignKey("sport.id"), nullable=False)
|
||||||
sport = relationship("Sport", back_populates="teams")
|
sport = relationship("Sport", back_populates="teams")
|
||||||
roosters = relationship("Rooster")
|
roosters = relationship("Rooster")
|
||||||
@@ -29,8 +30,8 @@ class FieldPosition(Base, BaseMixin):
|
|||||||
UniqueConstraint("name", "sport_id"),
|
UniqueConstraint("name", "sport_id"),
|
||||||
UniqueConstraint("short_name", "sport_id"),
|
UniqueConstraint("short_name", "sport_id"),
|
||||||
)
|
)
|
||||||
name = Column(String, nullable=False, index=True)
|
name = Column(String(255), nullable=False, index=True)
|
||||||
short_name = Column(String, nullable=False)
|
short_name = Column(String(255), nullable=False)
|
||||||
sport_id = Column(String, ForeignKey("sport.id"), nullable=False, index=True)
|
sport_id = Column(String, ForeignKey("sport.id"), nullable=False, index=True)
|
||||||
sport = relationship("Sport", back_populates="positions")
|
sport = relationship("Sport", back_populates="positions")
|
||||||
roosters = relationship("Rooster")
|
roosters = relationship("Rooster")
|
||||||
@@ -41,8 +42,8 @@ class Player(Base, BaseMixin):
|
|||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint("first_name", "last_name"),
|
UniqueConstraint("first_name", "last_name"),
|
||||||
)
|
)
|
||||||
first_name = Column(String, nullable=False, index=True)
|
first_name = Column(String(255), nullable=False, index=True)
|
||||||
last_name = Column(String, nullable=False, index=True)
|
last_name = Column(String(255), nullable=False, index=True)
|
||||||
roosters = relationship("Rooster")
|
roosters = relationship("Rooster")
|
||||||
|
|
||||||
def get_full_name(self) -> str:
|
def get_full_name(self) -> str:
|
||||||
@@ -66,7 +67,7 @@ class Rooster(Base, BaseMixin):
|
|||||||
|
|
||||||
class Vendor(Base, BaseMixin):
|
class Vendor(Base, BaseMixin):
|
||||||
__tablename__ = "vendor"
|
__tablename__ = "vendor"
|
||||||
name = Column(String, nullable=False, unique=True, index=True)
|
name = Column(String(255), nullable=False, unique=True, index=True)
|
||||||
card_sets = relationship("CardSet")
|
card_sets = relationship("CardSet")
|
||||||
cards = relationship("Card")
|
cards = relationship("Card")
|
||||||
|
|
||||||
@@ -76,9 +77,9 @@ class CardSet(Base, BaseMixin):
|
|||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
UniqueConstraint("name", "vendor_id"),
|
UniqueConstraint("name", "vendor_id"),
|
||||||
)
|
)
|
||||||
name = Column(String, index=True)
|
name = Column(String(255), index=True)
|
||||||
parallel_set = Column(Boolean)
|
parallel_set = Column(BIT(1))
|
||||||
insert_set = Column(Boolean)
|
insert_set = Column(BIT(1))
|
||||||
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False, index=True)
|
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False, index=True)
|
||||||
vendor = relationship("Vendor", back_populates="card_sets")
|
vendor = relationship("Vendor", back_populates="card_sets")
|
||||||
cards = relationship("Card")
|
cards = relationship("Card")
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
from typing import AnyStr
|
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from src.db.models.admin import Profile
|
|
||||||
|
|
||||||
|
|
||||||
def get_profile(username: AnyStr, db: Session):
|
|
||||||
profile = db.query(Profile).filter(Profile.email == username).first()
|
|
||||||
return profile
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
from src.db.models.comic import Artist
|
|
||||||
from src.schema.comics.artist import ArtistDetailResponse
|
|
||||||
|
|
||||||
|
|
||||||
def get_artist_details(artist: Artist) -> ArtistDetailResponse:
|
|
||||||
works = {}
|
|
||||||
for work in artist.comic_works:
|
|
||||||
work_type = work.work_type.name
|
|
||||||
comic_title = work.comic.title
|
|
||||||
if work_type in works:
|
|
||||||
works[work_type].append(comic_title)
|
|
||||||
else:
|
|
||||||
works[work_type] = [comic_title]
|
|
||||||
response = ArtistDetailResponse(
|
|
||||||
id=artist.id,
|
|
||||||
name=artist.name,
|
|
||||||
works=works
|
|
||||||
)
|
|
||||||
return response
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
from typing import List, Type, AnyStr
|
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from src.core.log_conf import logger
|
|
||||||
from src.db.models.comic import Comic, Issue
|
|
||||||
from src.schema.comics.comic import ComicSchema
|
|
||||||
from src.schema.comics.issue import IssueDetailsResponse
|
|
||||||
|
|
||||||
|
|
||||||
def list_comics(db: Session) -> List[Type[Comic]]:
|
|
||||||
comics = db.query(Comic).all()
|
|
||||||
return comics
|
|
||||||
|
|
||||||
|
|
||||||
def get_issue_details(issue: Issue) -> IssueDetailsResponse:
|
|
||||||
response = IssueDetailsResponse(
|
|
||||||
id=issue.id,
|
|
||||||
issue_number=issue.issue_number,
|
|
||||||
in_stock=issue.in_stock,
|
|
||||||
is_read=issue.is_read,
|
|
||||||
comic_id=issue.comic_id,
|
|
||||||
volume_id=issue.volume_id
|
|
||||||
)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def update_comic(comic: ComicSchema, comic_id: AnyStr, db: Session) -> type[Comic] | None:
|
|
||||||
logger.info(f"update_comic: {comic} with {comic_id}")
|
|
||||||
comic = db.get(Comic, comic_id)
|
|
||||||
return comic
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import uuid
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import AnyStr
|
|
||||||
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
|
|
||||||
from src.core.log_conf import logger
|
|
||||||
from src.db.models.comic import WorkType
|
|
||||||
from src.schema.comics.worktype import AddWorkType
|
|
||||||
|
|
||||||
|
|
||||||
def create_new_worktype(work: AddWorkType, db: Session) -> WorkType:
|
|
||||||
worktype = WorkType()
|
|
||||||
worktype.id = str(uuid.uuid4())
|
|
||||||
worktype.created_date = datetime.now()
|
|
||||||
worktype.last_modified_date = datetime.now()
|
|
||||||
worktype.name = work.worktype
|
|
||||||
db.add(worktype)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(worktype)
|
|
||||||
logger.info(f"create_new_worktype: {worktype}")
|
|
||||||
return worktype
|
|
||||||
|
|
||||||
|
|
||||||
def update_worktype(work: AddWorkType, worktype_id: AnyStr, db: Session) -> WorkType:
|
|
||||||
logger.info("update worktype")
|
|
||||||
worktype = db.get(WorkType, worktype_id)
|
|
||||||
worktype.name = work.worktype
|
|
||||||
db.add(worktype)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(worktype)
|
|
||||||
return worktype
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
from sqlalchemy.orm import Session
|
|
||||||
from typing import AnyStr
|
|
||||||
import uuid
|
|
||||||
from datetime import datetime
|
|
||||||
from src.core.log_conf import logger
|
|
||||||
from src.db.models.media import MediaFile, MediaVideo
|
|
||||||
from src.webapps.media.forms import AddLinkForm
|
|
||||||
|
|
||||||
|
|
||||||
def create_new_video(video: AddLinkForm, db: Session) -> MediaVideo:
|
|
||||||
print(video.url)
|
|
||||||
media_video = MediaVideo()
|
|
||||||
media_video.id = str(uuid.uuid4())
|
|
||||||
media_video.url = video.url
|
|
||||||
media_video.created_date = datetime.now()
|
|
||||||
media_video.last_modified_date = datetime.now()
|
|
||||||
media_video.review = True
|
|
||||||
media_video.should_download = True
|
|
||||||
db.add(media_video)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(media_video)
|
|
||||||
print(media_video)
|
|
||||||
return media_video
|
|
||||||
|
|
||||||
def create_new_mediafile(link: AnyStr, db: Session) -> MediaFile:
|
|
||||||
logger.info("create MediaFile with url {link}")
|
|
||||||
media_file: MediaFile = MediaFile()
|
|
||||||
media_file.id = str(uuid.uuid4())
|
|
||||||
media_file.url = link
|
|
||||||
media_file.created_date = datetime.now()
|
|
||||||
media_file.last_modified_date = datetime.now()
|
|
||||||
media_file.version = 0
|
|
||||||
media_file.review = True
|
|
||||||
media_file.should_download = True
|
|
||||||
db.add(media_file)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(media_file)
|
|
||||||
logger.info(f"created {media_file}")
|
|
||||||
return media_file
|
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Generator
|
from typing import Generator, Annotated
|
||||||
|
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker, Session
|
||||||
|
|
||||||
from src.core.config import settings
|
from src.core.config import settings
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import databases
|
|
||||||
from src.db.session import SQLALCHEMY_DATABASE_URL
|
|
||||||
|
|
||||||
|
|
||||||
async def check_db_connected():
|
|
||||||
try:
|
|
||||||
if not str(SQLALCHEMY_DATABASE_URL).__contains__("sqlite"):
|
|
||||||
database = databases.Database(SQLALCHEMY_DATABASE_URL)
|
|
||||||
if not database.is_connected:
|
|
||||||
await database.connect()
|
|
||||||
await database.execute("SELECT 1")
|
|
||||||
print("Database is connected (^_^)")
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
"Looks like db is missing or is there is some problem in connection,see below traceback"
|
|
||||||
)
|
|
||||||
raise e
|
|
||||||
|
|
||||||
|
|
||||||
async def check_db_disconnected():
|
|
||||||
try:
|
|
||||||
if not str(SQLALCHEMY_DATABASE_URL).__contains__("sqlite"):
|
|
||||||
database = databases.Database(SQLALCHEMY_DATABASE_URL)
|
|
||||||
if database.is_connected:
|
|
||||||
await database.disconnect()
|
|
||||||
print("Database is Disconnected (-_-) zZZ")
|
|
||||||
except Exception as e:
|
|
||||||
raise e
|
|
||||||
+5
-17
@@ -1,25 +1,12 @@
|
|||||||
import logging
|
|
||||||
import logging.config
|
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from src.apis.base import api_router
|
from src.apis.base import api_router
|
||||||
from src.core.log_conf import LOGGING_CONFIG, logger
|
|
||||||
from src.db.session import engine
|
from src.db.session import engine
|
||||||
from src.db.utils import check_db_connected, check_db_disconnected
|
|
||||||
from src.webapps.base import api_router as web_app_router
|
from src.webapps.base import api_router as web_app_router
|
||||||
from src.core.config import settings
|
from src.core.config import settings
|
||||||
from src.db.models.base import Base
|
from src.db.models.base import Base
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def lifespan(app: FastAPI):
|
|
||||||
await check_db_connected()
|
|
||||||
yield
|
|
||||||
await check_db_disconnected()
|
|
||||||
|
|
||||||
def include_router(app: FastAPI):
|
def include_router(app: FastAPI):
|
||||||
app.include_router(api_router)
|
app.include_router(api_router)
|
||||||
app.include_router(web_app_router)
|
app.include_router(web_app_router)
|
||||||
@@ -30,12 +17,13 @@ def configure_static(app: FastAPI):
|
|||||||
def create_tables():
|
def create_tables():
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
def start_application(log):
|
def start_application():
|
||||||
log.info(f"using database: {settings.DATABASE_URL}")
|
app = FastAPI(title=settings.PROJECT_NAME, version=settings.PROJECT_VERSION)
|
||||||
app = FastAPI(title=settings.PROJECT_NAME, version=settings.PROJECT_VERSION, lifespan=lifespan)
|
|
||||||
include_router(app)
|
include_router(app)
|
||||||
configure_static(app)
|
configure_static(app)
|
||||||
create_tables()
|
create_tables()
|
||||||
return app
|
return app
|
||||||
|
|
||||||
kontor = start_application(logger)
|
|
||||||
|
kontor = start_application()
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class Token(BaseModel):
|
|
||||||
access_token: str
|
|
||||||
token_type: str
|
|
||||||
@@ -1,19 +1,36 @@
|
|||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from src.db.models.comic import Artist
|
||||||
|
|
||||||
|
|
||||||
class ArtistCreation(BaseModel):
|
class ArtistCreation(BaseModel):
|
||||||
id: str
|
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
class ArtistResponse(BaseModel):
|
class ArtistResponse(BaseModel):
|
||||||
id: str
|
id: UUID
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
class ArtistDetailResponse(BaseModel):
|
class ArtistDetailResponse(BaseModel):
|
||||||
id: str
|
id: UUID
|
||||||
name: str
|
name: str
|
||||||
weblink: str
|
|
||||||
works: Dict[str, List[str]]
|
works: Dict[str, List[str]]
|
||||||
|
|
||||||
|
def get_artist_details(artist: Artist) -> ArtistDetailResponse:
|
||||||
|
works = {}
|
||||||
|
for work in artist.comic_works:
|
||||||
|
work_type = work.work_type.name
|
||||||
|
comic_title = work.comic.title
|
||||||
|
if work_type in works:
|
||||||
|
works[work_type].append(comic_title)
|
||||||
|
else:
|
||||||
|
works[work_type] = [comic_title]
|
||||||
|
response = ArtistDetailResponse(
|
||||||
|
id=artist.id,
|
||||||
|
name=artist.name,
|
||||||
|
works=works
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,26 @@
|
|||||||
from typing import List, Dict, Optional
|
from typing import List, Dict
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from pydantic import BaseModel, AnyUrl
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from src.db.models.comic import Comic
|
from src.db.models.comic import Comic
|
||||||
|
|
||||||
|
|
||||||
class ComicResponse(BaseModel):
|
class ComicResponse(BaseModel):
|
||||||
id: str
|
id: UUID
|
||||||
title: str
|
title: str
|
||||||
completed: bool
|
completed: bool
|
||||||
|
|
||||||
class ComicDetailsResponse(BaseModel):
|
class ComicDetailsResponse(BaseModel):
|
||||||
id: str
|
id: UUID
|
||||||
created: str
|
created: str
|
||||||
title: str
|
title: str
|
||||||
completed : bool
|
completed : bool
|
||||||
current_order : bool
|
current_order : bool
|
||||||
weblink: str
|
|
||||||
publisher: str
|
publisher: str
|
||||||
volumes: List[str]
|
volumes: List[str]
|
||||||
works: Dict[str, List[str]]
|
works: Dict[str, List[str]]
|
||||||
|
|
||||||
|
|
||||||
class ComicSchema(BaseModel):
|
|
||||||
id: str
|
|
||||||
title: str
|
|
||||||
weblink: Optional[AnyUrl]
|
|
||||||
completed: Optional[bool]
|
|
||||||
current_order: Optional[bool]
|
|
||||||
|
|
||||||
|
|
||||||
def get_short_info(comic: Comic) -> ComicResponse:
|
def get_short_info(comic: Comic) -> ComicResponse:
|
||||||
response = ComicResponse(
|
response = ComicResponse(
|
||||||
id=comic.id,
|
id=comic.id,
|
||||||
@@ -55,11 +46,11 @@ def get_comic_details(comic: Comic) -> ComicDetailsResponse | None:
|
|||||||
id=comic.id,
|
id=comic.id,
|
||||||
created=str(comic.created_date),
|
created=str(comic.created_date),
|
||||||
title=comic.title,
|
title=comic.title,
|
||||||
completed=comic.completed,
|
completed=(comic.completed == 1),
|
||||||
current_order=comic.current_order,
|
current_order=(comic.current_order == 1),
|
||||||
weblink=comic.weblink,
|
|
||||||
publisher=comic.publisher.name,
|
publisher=comic.publisher.name,
|
||||||
volumes=volumes,
|
volumes=volumes,
|
||||||
works=works
|
works=works
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class IssueDetailsResponse(BaseModel):
|
|
||||||
id: str
|
|
||||||
issue_number: str
|
|
||||||
in_stock: bool
|
|
||||||
is_read: bool
|
|
||||||
comic_id: str
|
|
||||||
volume_id: str | None
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
class AddWorkType(BaseModel):
|
|
||||||
worktype: str
|
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from src.db.models.media import MediaFile
|
from src.db.models.media import MediaFile
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class MediaFileResponse(BaseModel):
|
class MediaFileResponse(BaseModel):
|
||||||
id: str
|
id: UUID
|
||||||
title: str | None = None
|
title: str | None = None
|
||||||
file_name: str | None = None
|
file_name: str | None = None
|
||||||
cloud_link: str | None = None
|
cloud_link: str | None = None
|
||||||
@@ -22,8 +23,8 @@ def get_file_details(mediafile: MediaFile) -> MediaFileResponse | None:
|
|||||||
file_name=mediafile.file_name,
|
file_name=mediafile.file_name,
|
||||||
cloud_link=mediafile.cloud_link,
|
cloud_link=mediafile.cloud_link,
|
||||||
url=str(mediafile.url),
|
url=str(mediafile.url),
|
||||||
review=mediafile.review,
|
review=(mediafile.review == 1),
|
||||||
should_download=mediafile.should_download)
|
should_download=(mediafile.should_download == 1))
|
||||||
#print(f"id: {mediafile.id}: review: {response.review} <- {mediafile.review}")
|
#print(f"id: {mediafile.id}: review: {response.review} <- {mediafile.review}")
|
||||||
#print(f"id: {mediafile.id}: download: {response.should_download} <- {mediafile.should_download}")
|
#print(f"id: {mediafile.id}: download: {response.should_download} <- {mediafile.should_download}")
|
||||||
return response
|
return response
|
||||||
@@ -34,5 +35,11 @@ def set_file(model: MediaFileResponse, mediafile: MediaFile) -> None:
|
|||||||
mediafile.url = model.url
|
mediafile.url = model.url
|
||||||
mediafile.title = model.title
|
mediafile.title = model.title
|
||||||
mediafile.last_modified_date = datetime.now()
|
mediafile.last_modified_date = datetime.now()
|
||||||
mediafile.review = model.review
|
if model.review:
|
||||||
mediafile.should_download = model.should_download
|
mediafile.review = 1
|
||||||
|
else:
|
||||||
|
mediafile.review = 0
|
||||||
|
if model.should_download:
|
||||||
|
mediafile.should_download = 1
|
||||||
|
else:
|
||||||
|
mediafile.should_download = 0
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class AddLink(BaseModel):
|
|
||||||
url: str
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from typing import AnyStr
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class SportResponse(BaseModel):
|
class SportResponse(BaseModel):
|
||||||
id: AnyStr
|
id: UUID
|
||||||
name: str
|
name: str
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Permissions</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% with msg=msg %}
|
|
||||||
{% include "components/alerts.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h1 class="display-5">Permissions..</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-hover">
|
|
||||||
<thead><tr>
|
|
||||||
<th scope="col">Name</th>
|
|
||||||
<th colspan="2">Actions</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for permission in permissions %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row"><a href="/admins/permissions/{{permission.id}}">{{permission.name}}</a></th>
|
|
||||||
<td><a href="/admin/permission/edit/{{permission.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
|
||||||
<td><a href="/admin/permission/delete/{{permission.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div>
|
|
||||||
<a href="/admin/permission/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add Permission</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Profiles</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% with msg=msg %}
|
|
||||||
{% include "components/alerts.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h1 class="display-5">Profiles..</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-hover">
|
|
||||||
<thead><tr>
|
|
||||||
<th scope="col">Username</th>
|
|
||||||
<th scope="col">First Name</th>
|
|
||||||
<th scope="col">Last Name</th>
|
|
||||||
<th colspan="2">Actions</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for profile in profiles %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row"><a href="/admins/profiles/{{profile.id}}">{{profile.user_name}}</a></th>
|
|
||||||
<th scope="row">{{profile.first_name}}</th>
|
|
||||||
<th scope="row">{{profile.last_name}}</th>
|
|
||||||
<th scope="row">{{profile.email}}</th>
|
|
||||||
<td><a href="/admin/profile/edit/{{profile.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
|
||||||
<td><a href="/admin/profile/delete/{{profile.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div>
|
|
||||||
<a href="/admin/profile/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add Profile</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Login</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<h5 class="display-5">Login to Kontor</h5>
|
|
||||||
<div class="text-danger font-weight-bold">
|
|
||||||
{% for error in errors %}
|
|
||||||
<li>{{error}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<div class="text-success font-weight-bold">
|
|
||||||
{% if msg %}
|
|
||||||
<div class="badge bg-success text-wrap font-weight-bold" style="font-size: large;">
|
|
||||||
{{msg}}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row my-5">
|
|
||||||
<form method="POST">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label>Email</label>
|
|
||||||
<input type="text" required placeholder="Your email" name="email" value="{{email}}" class="form-control">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label>Password</label>
|
|
||||||
<input type="password" required placeholder="Choose a secure password" value="{{password}}" name="password" class="form-control">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -20,16 +20,12 @@
|
|||||||
<th scope="row">Artist Name</th>
|
<th scope="row">Artist Name</th>
|
||||||
<td colspan="2">{{artist.name}}</td>
|
<td colspan="2">{{artist.name}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th scope="row">Link</th>
|
|
||||||
<td colspan="2">{{artist.weblink}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Works</th>
|
<th scope="row">Works</th>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
{% for work in artist.get_comics() %}
|
{% for work in artist.get_comics() %}
|
||||||
<p>
|
<p>
|
||||||
<a href="/comic/worktypes/{{work.id}}">{{work.name}}</a>
|
{{work}}:
|
||||||
<ul>
|
<ul>
|
||||||
{% for comic in artist.get_comics()[work] %}
|
{% for comic in artist.get_comics()[work] %}
|
||||||
<li><a href="/comic/comics/{{comic.id}}">{{comic.title}}</a></li>
|
<li><a href="/comic/comics/{{comic.id}}">{{comic.title}}</a></li>
|
||||||
@@ -47,20 +43,8 @@
|
|||||||
<th scope="row">Data Modified</th>
|
<th scope="row">Data Modified</th>
|
||||||
<td colspan="2">{{artist.last_modified_date}}</td>
|
<td colspan="2">{{artist.last_modified_date}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Version</th>
|
|
||||||
<td colspan="2">{{artist.version}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<a href="/comic/artists" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
|
||||||
<a href="/comic/artist/edit/{{artist.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
|
||||||
<a href="/comic/artist/delete/{{artist.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Edit Artist</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="text-danger font-weight-bold">
|
|
||||||
{% for error in errors %}
|
|
||||||
<li>{{error}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row my-5">
|
|
||||||
<h3 class="text-center display-4">Edit an Artist entry</h3>
|
|
||||||
<form method="POST">
|
|
||||||
<div class="mb-3">
|
|
||||||
<input type="text" required class="form-control" name="artist.name" value="{{artist_name}}" placeholder="Artist name here">
|
|
||||||
<input type="text" required class="form-control" name="artist.link" value="{{artist_link}}" placeholder="Web link for artist here">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
|
||||||
<button type="cancel" class="btn btn-primary">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
{% for artist in artists %}
|
{% for artist in artists %}
|
||||||
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
||||||
{% with obj=artist %}
|
{% with obj=artist %}
|
||||||
{% include "comic/artist_cards.html" %}
|
{% include "components/artist_cards.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% if loop.index %3 %}
|
{% if loop.index %3 %}
|
||||||
|
|||||||
@@ -27,17 +27,12 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th scope="row">Link</th>
|
|
||||||
<td colspan="2">{{comic.weblink}}</td>
|
|
||||||
</tr>
|
|
||||||
{% if comic.get_artists()|length > 0 %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Works</th>
|
<th scope="row">Works</th>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
{% for work in comic.get_artists() %}
|
{% for work in comic.get_artists() %}
|
||||||
<p>
|
<p>
|
||||||
<a href="/comic/worktypes/{{work.id}}">{{work.name}}</a>
|
{{work}}:
|
||||||
<ul>
|
<ul>
|
||||||
{% for artist in comic.get_artists()[work] %}
|
{% for artist in comic.get_artists()[work] %}
|
||||||
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
|
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
|
||||||
@@ -47,29 +42,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
|
||||||
{% if comic.volumes|length > 0 %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Volumes</th>
|
|
||||||
<td colspan="2">
|
|
||||||
<ul>
|
|
||||||
{% for volume in comic.volumes %}
|
|
||||||
<li><a href="/comic/volumes/{{volume.id}}">{{volume.name}}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Issues</th>
|
|
||||||
<td colspan="2">
|
|
||||||
<ul>
|
|
||||||
{% for issue in comic.sorted_issues() %}
|
|
||||||
<li><a href="/comic/issues/{{issue.id}}">{{issue.get_full_title()}}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Data Created</th>
|
<th scope="row">Data Created</th>
|
||||||
<td colspan="2">{{comic.created_date}}</td>
|
<td colspan="2">{{comic.created_date}}</td>
|
||||||
@@ -82,15 +54,18 @@
|
|||||||
<th scope="row">Data Version</th>
|
<th scope="row">Data Version</th>
|
||||||
<td colspan="2">{{comic.version}}</td>
|
<td colspan="2">{{comic.version}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Issues</th>
|
||||||
|
<td colspan="2">
|
||||||
|
<ul>
|
||||||
|
{% for issue in comic.sorted_issues() %}
|
||||||
|
<li><a href="comic/issues/{{issue.id}}">{{issue.issue_number}}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<a href="/comic/comics" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
|
||||||
<a href="/comic/comic/edit/{{comic.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
|
||||||
<a href="/comic/comic/delete/{{comic.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Edit Comic</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="text-danger font-weight-bold">
|
|
||||||
{% for error in errors %}
|
|
||||||
<li>{{error}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row my-5">
|
|
||||||
<h3 class="text-center display-4">Edit an Comic entry</h3>
|
|
||||||
<form class="form-horizontal" method="POST">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label col-sm-2" for="title">Title</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" id="title" name="title" value="{{comic_title}}" placeholder="Comic title here">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label col-sm-2" for="weblink">Link</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<input type="text" class="form-control" id="weblink" name="weblink" value="{{comic_weblink}}" placeholder="Web link for comic here">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-offset-2 col-sm-10">
|
|
||||||
<div class="checkbox">
|
|
||||||
<label><input type="checkbox" id="completed" name="completed" value="{{comic_completed}}"> Completed</label>
|
|
||||||
</div
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-offset-2 col-sm-10">
|
|
||||||
<div class="checkbox">
|
|
||||||
<label><input type="checkbox" id="current_order" name="current_order" value="{{comic_current_order}}"> Current Order</label>
|
|
||||||
</div
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-offset-2 col-sm-10">
|
|
||||||
<button type="submit" class="btn btn-primary" name="action" value="submit">Submit</button>
|
|
||||||
<button type="cancel" class="btn btn-primary" name="action" value="cancel">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -9,40 +9,23 @@
|
|||||||
{% include "components/alerts.html" %}
|
{% include "components/alerts.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<form class="d-flex" action="/comic/comics/">
|
<div class="col">
|
||||||
<input class="form-control me-2" name="query" id="autocomplete" type="search" placeholder="Search" aria-label="Search">
|
<h1 class="display-5">Find Jobs..</h1>
|
||||||
Completed<input type="checkbox" name="completed" {% if request.query_params.get("completed")=="on" %}checked{% endif %} aria-label="Completed">
|
|
||||||
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 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 %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row"><a href="/comic/comics/{{comic.id}}">{{comic.title}}</a></th>
|
|
||||||
<td><a href="/comic/publishers/{{comic.publisher.id}}">{{comic.publisher.name}}</a></td>
|
|
||||||
<td>{% with check=comic.completed %}{% include "components/check.html" %}{% endwith %}</td>
|
|
||||||
<td><a href="/comic/comics/edit/{{comic.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
|
||||||
<td><a href="/comic/comics/delete/{{comic.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div>
|
|
||||||
<a href="/comic/comic/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add Comic</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{% for comic in comics %}
|
||||||
|
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
||||||
|
{% with obj=comic %}
|
||||||
|
{% include "components/comic_cards.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% if loop.index %3 %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
</div></div><br><div class="row">
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Issue Detail</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h1 class="display-5">Issue Detail</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-striped table-hover">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Issue Number</th>
|
|
||||||
<td colspan="2">{{issue.issue_number}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Full Title</th>
|
|
||||||
<td colspan="2">{{issue.title}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Published</th>
|
|
||||||
<td colspan="2">{{issue.published_on}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Auf Lager</th>
|
|
||||||
<td colspan="2">
|
|
||||||
{% with check=issue.in_stock %}
|
|
||||||
{% include "components/check.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Gelesen</th>
|
|
||||||
<td colspan="2">
|
|
||||||
{% with check=issue.is_read %}
|
|
||||||
{% include "components/check.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Comic</th>
|
|
||||||
<td colspan="2">
|
|
||||||
<a href="/comic/comics/{{issue.comic_id}}">{{issue.comic.title}}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% if issue.volume %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Volume</th>
|
|
||||||
<td colspan="2">
|
|
||||||
<a href="/comic/comics/{{issue.volume_id}}">{{issue.volume.name}}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Works</th>
|
|
||||||
<td colspan="2">
|
|
||||||
{% for work in issue.get_artists() %}
|
|
||||||
<p>
|
|
||||||
<a href="/comic/worktypes/{{work.id}}">{{work.name}}</a>
|
|
||||||
<ul>
|
|
||||||
{% for artist in issue.get_artists()[work] %}
|
|
||||||
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Created</th>
|
|
||||||
<td colspan="2">{{issue.created_date}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Modified</th>
|
|
||||||
<td colspan="2">{{issue.last_modified_date}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Version</th>
|
|
||||||
<td colspan="2">{{issue.version}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -20,24 +20,6 @@
|
|||||||
<th scope="row">Publisher Name</th>
|
<th scope="row">Publisher Name</th>
|
||||||
<td colspan="2">{{publisher.name}}</td>
|
<td colspan="2">{{publisher.name}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if publisher.parent_publisher_id %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Parent Company</th>
|
|
||||||
<td colspan="2"><a href="/comic/publishers/{{publisher.parent_publisher_id}}">{{publisher.parent_publisher.name}}</a></td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% if publisher.imprints|length > 0 %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Imprints</th>
|
|
||||||
<td colspan="2">
|
|
||||||
<ul>
|
|
||||||
{% for imprint in publisher.imprints %}
|
|
||||||
<li><a href="/comic/publishers/{{imprint.id}}">{{imprint.name}}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Comics</th>
|
<th scope="row">Comics</th>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
@@ -59,12 +41,5 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<a href="/comic/publishers" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
|
||||||
<a href="/comic/publisher/edit/{{publisher.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
|
||||||
<a href="/comic/publisher/delete/{{publisher.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
{% for publisher in publishers %}
|
{% for publisher in publishers %}
|
||||||
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
||||||
{% with obj=publisher %}
|
{% with obj=publisher %}
|
||||||
{% include "comic/publisher_cards.html" %}
|
{% include "components/publisher_cards.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% if loop.index %3 %}
|
{% if loop.index %3 %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>WorkType Detail</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-striped table-hover">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">WorkType Name</th>
|
|
||||||
<td colspan="2">{{worktype.name}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Works</th>
|
|
||||||
<td colspan="2">
|
|
||||||
{% for comic in worktype.get_artists() %}
|
|
||||||
<p>
|
|
||||||
{{comic}}:
|
|
||||||
<ul>
|
|
||||||
{% for artist in worktype.get_artists()[comic] %}
|
|
||||||
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</p>
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">ID</th>
|
|
||||||
<td colspan="2">{{worktype.id}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Created</th>
|
|
||||||
<td colspan="2">{{worktype.created_date}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Modified</th>
|
|
||||||
<td colspan="2">{{worktype.last_modified_date}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Version</th>
|
|
||||||
<td colspan="2">{{worktype.version}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<a href="/comic/worktypes" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
|
||||||
<a href="/comic/worktype/edit/{{worktype.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
|
||||||
<a href="/comic/worktype/delete/{{worktype.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Edit WorkType</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="text-danger font-weight-bold">
|
|
||||||
{% for error in errors %}
|
|
||||||
<li>{{error}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row my-5">
|
|
||||||
<h3 class="text-center display-4">Add a WorkType</h3>
|
|
||||||
<form method="POST">
|
|
||||||
<div class="mb-3">
|
|
||||||
<input type="text" required class="form-control" name="worktype" value="{{worktype}}" placeholder="WorkType here">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>WorkTypes</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% with msg=msg %}
|
|
||||||
{% include "components/alerts.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
<div class="container">
|
|
||||||
<table class="table table-hover">
|
|
||||||
<thead><tr>
|
|
||||||
<th scope="col">Name</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for worktype in worktypes %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row"><a href="/comic/worktypes/{{worktype.id}}">{{worktype.name}}</a></th>
|
|
||||||
<td><a href="/comic/worktype/edit/{{worktype.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
|
||||||
<td><a href="/comic/worktype/delete/{{worktype.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<a href="/comic/worktype/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add WorkType</a>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
-1
@@ -1,7 +1,6 @@
|
|||||||
<div class="card shadow p-3 mb-2 bg-body rounded" style="width: 18rem;">
|
<div class="card shadow p-3 mb-2 bg-body rounded" style="width: 18rem;">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">{{obj.name}}</h5>
|
<h5 class="card-title">{{obj.name}}</h5>
|
||||||
<p class="card-text">Link : {{obj.weblink}}</p>
|
|
||||||
<a href="/comic/artists/{{obj.id}}" class="btn btn-primary">Read more</a>
|
<a href="/comic/artists/{{obj.id}}" class="btn btn-primary">Read more</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{% if check %}
|
{% if check == 1 %}
|
||||||
<img src="{{ url_for('static', path='images/tick.png') }}" alt="" width="24" height="24">
|
<img src="{{ url_for('static', path='images/tick.png') }}" alt="" width="24" height="24">
|
||||||
{% else %}
|
{% else %}
|
||||||
<img src="{{ url_for('static', path='images/cross.png') }}" alt="" width="24" height="24">
|
<img src="{{ url_for('static', path='images/cross.png') }}" alt="" width="24" height="24">
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light px-5">
|
<nav class="navbar navbar-expand-lg navbar-light bg-light px-5">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="#">Kontor</a>
|
<a class="navbar-brand" href="#">
|
||||||
|
<img src="{{ url_for('static', path='images/logo.png') }}" alt="" width="30" height="24">
|
||||||
|
</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
@@ -16,7 +18,7 @@
|
|||||||
<li><a class="dropdown-item" href="/comic/artists/">Artists</a></li>
|
<li><a class="dropdown-item" href="/comic/artists/">Artists</a></li>
|
||||||
<li><a class="dropdown-item" href="/comic/publishers/">Publishers</a></li>
|
<li><a class="dropdown-item" href="/comic/publishers/">Publishers</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="/comic/worktypes">WorkTypes</a></li>
|
<li><a class="dropdown-item" href="#">Something else here</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
@@ -24,7 +26,6 @@
|
|||||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<li><a class="dropdown-item" href="/media/files/">MediaFiles</a></li>
|
<li><a class="dropdown-item" href="/media/files/">MediaFiles</a></li>
|
||||||
<li><a class="dropdown-item" href="/media/actors/">MediaActors</a></li>
|
<li><a class="dropdown-item" href="/media/actors/">MediaActors</a></li>
|
||||||
<li><a class="dropdown-item" href="/media/videos/">MediaVideos</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
@@ -41,19 +42,18 @@
|
|||||||
<li><a class="dropdown-item" href="/register/">Signup</a></li>
|
<li><a class="dropdown-item" href="/register/">Signup</a></li>
|
||||||
<li><a class="dropdown-item" href="/login/">Login</a></li>
|
<li><a class="dropdown-item" href="/login/">Login</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li><a class="dropdown-item" href="/admin/profiles">Profiles</a></li>
|
<li><a class="dropdown-item" href="#">Something else here</a></li>
|
||||||
<li><a class="dropdown-item" href="/admin/permissions">Permissions</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav ml-auto mb-2 mb-lg-0">
|
<ul class="navbar-nav ml-auto mb-2 mb-lg-0">
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
Media
|
Jobs
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
<li><a class="dropdown-item" href="/media/add-link/">Add Link</a></li>
|
<li><a class="dropdown-item" href="/comics/">Comics</a></li>
|
||||||
<li><a class="dropdown-item" href="/media/add-file">Add MediaFile</a></li>
|
<li><a class="dropdown-item" href="/comics/">Media</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -41,12 +41,5 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<a href="/media/actors" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
|
||||||
<a href="/media/actor/edit/{{actor.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
|
||||||
<a href="/media/actor/delete/{{actor.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
{% for actor in actors %}
|
{% for actor in actors %}
|
||||||
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
||||||
{% with obj=actor %}
|
{% with obj=actor %}
|
||||||
{% include "media/actor_cards.html" %}
|
{% include "components/actor_cards.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% if loop.index %3 %}
|
{% if loop.index %3 %}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Add a Video Link</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="text-danger font-weight-bold">
|
|
||||||
{% for error in errors %}
|
|
||||||
<li>{{error}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row my-5">
|
|
||||||
<h3 class="text-center display-4">Add a Video Link</h3>
|
|
||||||
<form method="POST">
|
|
||||||
<div class="mb-3">
|
|
||||||
<input type="text" required class="form-control" name="url" value="{{url}}" placeholder="Video Link here">
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">Submit</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
@@ -61,12 +61,5 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
<div>
|
|
||||||
<a href="/media/files" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
|
||||||
<a href="/media/file/edit/{{mediafile.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
|
||||||
<a href="/media/file/delete/{{mediafile.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -9,31 +9,21 @@
|
|||||||
{% include "components/alerts.html" %}
|
{% include "components/alerts.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<table class="table table-hover">
|
||||||
<form class="d-flex" action="/media/files/">
|
<thead><tr>
|
||||||
<input class="form-control me-2" name="query" id="autocomplete" type="search" placeholder="Search" aria-label="Search">
|
<th scope="col">Titel</th>
|
||||||
Review<input type="checkbox" name="review" aria-label="Review">
|
<th scope="col">URL</th>
|
||||||
Download<input type="checkbox" name="download" aria-label="Download">
|
<th scope="col">Cloudlink</th>
|
||||||
<button class="btn btn-outline-success" type="submit">Search</button>
|
</tr></thead>
|
||||||
</form>
|
<tbody>
|
||||||
</div>
|
{% for mediafile in mediafiles %}
|
||||||
<div class="row">
|
<tr>
|
||||||
<table class="table table-hover">
|
<th scope="row"><a href="/media/files/{{mediafile.id}}">{{mediafile.title}}</a></th>
|
||||||
<thead><tr>
|
<td>{{mediafile.url}}</td>
|
||||||
<th scope="col">Titel</th>
|
<td>{{mediafile.cloud_link}}</td>
|
||||||
<th scope="col">Review</th>
|
</tr>
|
||||||
<th scope="col">Download</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for mediafile in mediafiles %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row"><a href="/media/files/{{mediafile.id}}">{{mediafile.title}}</a></th>
|
|
||||||
<td>{% with check=mediafile.review %}{% include "components/check.html" %}{% endwith %}</td>
|
|
||||||
<td>{% with check=mediafile.should_download %}{% include "components/check.html" %}{% endwith %}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>MediaVideo Detail</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<h1 class="display-5">MediaVideo Detail</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-striped table-hover">
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">MediaVideo Title</th>
|
|
||||||
<td colspan="2">{{mediavideo.title}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">MediaVideo URL</th>
|
|
||||||
<td colspan="2">{{mediavideo.url}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">MediaVideo Cloudlink</th>
|
|
||||||
<td colspan="2">{{mediavideo.cloud_link}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">MediaVideo Download?</th>
|
|
||||||
<td colspan="2">{{mediavideo.should_download}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">MediaVideo Review?</th>
|
|
||||||
<td colspan="2">{{mediavideo.review}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Created</th>
|
|
||||||
<td colspan="2">{{mediavideo.created_date}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Modified</th>
|
|
||||||
<td colspan="2">{{mediavideo.last_modified_date}}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Data Version</th>
|
|
||||||
<td colspan="2">{{mediavideo.version}}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{% extends "shared/base.html" %}
|
|
||||||
|
|
||||||
{% block title %}
|
|
||||||
<title>Video Links</title>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
{% with msg=msg %}
|
|
||||||
{% include "components/alerts.html" %}
|
|
||||||
{% endwith %}
|
|
||||||
<div class="container">
|
|
||||||
<table class="table table-hover">
|
|
||||||
<thead><tr>
|
|
||||||
<th scope="col">Titel</th>
|
|
||||||
<th scope="col">Review</th>
|
|
||||||
<th scope="col">Download</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody>
|
|
||||||
{% for mediavideo in mediavideos %}
|
|
||||||
<tr>
|
|
||||||
<th scope="row"><a href="/media/videos/{{mediavideo.id}}">{{mediavideo.title}}</a></th>
|
|
||||||
<td>{% with check=mediavideo.review %}{% include "components/check.html" %}{% endwith %}</td>
|
|
||||||
<td>{% with check=mediavideo.should_download %}{% include "components/check.html" %}{% endwith %}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de-DE">
|
<html lang="en-us">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
@@ -21,5 +21,6 @@
|
|||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
from typing import AnyStr
|
|
||||||
from fastapi import APIRouter, Request
|
|
||||||
from fastapi.security.utils import get_authorization_scheme_param
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
|
|
||||||
from src.apis.utils import SessionDep
|
|
||||||
from src.apis.version1.admin import get_current_user_from_token
|
|
||||||
from src.db.models.admin import Permission, Profile
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="src/templates")
|
|
||||||
router = APIRouter(include_in_schema=False, prefix="/admin")
|
|
||||||
|
|
||||||
@router.get("/profiles")
|
|
||||||
def get_profiles(db: SessionDep, request: Request, msg: str | None = None):
|
|
||||||
profiles = db.query(Profile).all()
|
|
||||||
return templates.TemplateResponse("admin/profiles.html", {"request": request, "msg": msg, "profiles": profiles})
|
|
||||||
|
|
||||||
@router.get("/profiles/{profile_id}")
|
|
||||||
def comic_details(profile_id: AnyStr, request: Request, db: SessionDep):
|
|
||||||
profile = db.get(Profile, profile_id)
|
|
||||||
return templates.TemplateResponse("admin/profile_detail.html", {"request": request, "profile":profile})
|
|
||||||
|
|
||||||
@router.get("/permissions")
|
|
||||||
def get_permissions(db: SessionDep, request: Request, msg: str | None = None):
|
|
||||||
permissions = db.query(Permission).all()
|
|
||||||
return templates.TemplateResponse("admin/permissions.html", {"request": request, "msg": msg, "permissions": permissions})
|
|
||||||
|
|
||||||
@router.get("/permissions/{permission_id}")
|
|
||||||
def artist_detail(permission_id: AnyStr, request: Request, db: SessionDep):
|
|
||||||
permission= db.get(Permission, str(permission_id))
|
|
||||||
return templates.TemplateResponse("comic/permission_detail.html", {"request": request, "permission": permission})
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
from typing import List
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import Request
|
|
||||||
|
|
||||||
|
|
||||||
class LoginForm:
|
|
||||||
def __init__(self, request: Request):
|
|
||||||
self.request: Request = request
|
|
||||||
self.errors: List = []
|
|
||||||
self.username: Optional[str] = None
|
|
||||||
self.password: Optional[str] = None
|
|
||||||
|
|
||||||
async def load_data(self):
|
|
||||||
form = await self.request.form()
|
|
||||||
# since auth works on username field we are considering email as username
|
|
||||||
self.username = form.get("email")
|
|
||||||
self.password = form.get("password")
|
|
||||||
|
|
||||||
async def is_valid(self):
|
|
||||||
if not self.username or not (self.username.__contains__("@")):
|
|
||||||
self.errors.append("Email is required")
|
|
||||||
if not self.password or not len(self.password) >= 4:
|
|
||||||
self.errors.append("A valid password is required")
|
|
||||||
if not self.errors:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
from src.apis.version1.admin import login_for_access_token
|
|
||||||
from src.db.session import get_db
|
|
||||||
from fastapi import APIRouter
|
|
||||||
from fastapi import Depends
|
|
||||||
from fastapi import HTTPException
|
|
||||||
from fastapi import Request
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from sqlalchemy.orm import Session
|
|
||||||
from src.webapps.auth.forms import LoginForm
|
|
||||||
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="src/templates")
|
|
||||||
router = APIRouter(include_in_schema=False)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/login/")
|
|
||||||
def login(request: Request):
|
|
||||||
return templates.TemplateResponse("auth/login.html", {"request": request})
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/login/")
|
|
||||||
async def login(request: Request, db: Session = Depends(get_db)):
|
|
||||||
form = LoginForm(request)
|
|
||||||
await form.load_data()
|
|
||||||
if await form.is_valid():
|
|
||||||
try:
|
|
||||||
form.__dict__.update(msg="Login Successful :)")
|
|
||||||
response = templates.TemplateResponse("auth/login.html", form.__dict__)
|
|
||||||
login_for_access_token(response=response, form_data=form, db=db)
|
|
||||||
return response
|
|
||||||
except HTTPException:
|
|
||||||
form.__dict__.update(msg="")
|
|
||||||
form.__dict__.get("errors").append("Incorrect Email or Password")
|
|
||||||
return templates.TemplateResponse("auth/login.html", form.__dict__)
|
|
||||||
return templates.TemplateResponse("auth/login.html", form.__dict__)
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user