Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7eae6e07f4 | |||
| b26b5ecc9c |
@@ -1,4 +1,3 @@
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
@@ -36,4 +35,4 @@ secrets:
|
||||
networks:
|
||||
database:
|
||||
name: database
|
||||
|
||||
external: true
|
||||
|
||||
+69
-25
@@ -42,7 +42,75 @@ services:
|
||||
image: kontor-api:0.2.0-SNAPSHOT
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://kontor-api:8800/health"]
|
||||
test: ["CMD", "curl", "-f", "http://kontor-api:8500/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
networks:
|
||||
- database
|
||||
- integration
|
||||
- frontend
|
||||
ports:
|
||||
- 8500:8500
|
||||
volumes:
|
||||
- images-data:/data/images
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
kontor-fiber:
|
||||
build:
|
||||
context: ./kontor-fiber
|
||||
dockerfile: Dockerfile
|
||||
tags:
|
||||
- kontor-fiber:0.2.0-SNAPSHOT
|
||||
image: kontor-fiber:0.2.0-SNAPSHOT
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://kontor-fiber:8600/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
networks:
|
||||
- database
|
||||
- integration
|
||||
- frontend
|
||||
ports:
|
||||
- 8600:8600
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
kontor-echo:
|
||||
build:
|
||||
context: ./kontor-echo
|
||||
dockerfile: Dockerfile
|
||||
tags:
|
||||
- kontor-echo:0.2.0-SNAPSHOT
|
||||
image: kontor-echo:0.2.0-SNAPSHOT
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://kontor-echo:8700/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
networks:
|
||||
- database
|
||||
- integration
|
||||
- frontend
|
||||
ports:
|
||||
- 8700:8700
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
kontor-quarkus:
|
||||
build:
|
||||
context: ./kontor-quarkus
|
||||
dockerfile: Dockerfile
|
||||
tags:
|
||||
- kontor-quarkus:0.2.0-SNAPSHOT
|
||||
image: kontor-quarkus:0.2.0-SNAPSHOT
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://kontor-quarkus:8800/q/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
@@ -52,30 +120,6 @@ services:
|
||||
- frontend
|
||||
ports:
|
||||
- 8800:8800
|
||||
volumes:
|
||||
- images-data:/data/images
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
kontor-api-go:
|
||||
build:
|
||||
context: ./kontor-api-go
|
||||
dockerfile: Dockerfile
|
||||
tags:
|
||||
- kontor-api-go:0.2.0-SNAPSHOT
|
||||
image: kontor-api-go:0.2.0-SNAPSHOT
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://kontor-api-go:8900/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
networks:
|
||||
- database
|
||||
- integration
|
||||
- frontend
|
||||
ports:
|
||||
- 8900:8900
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"kontor-api-go/pkg/schema"
|
||||
"log"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func SetupComicRoutes(api fiber.Router) {
|
||||
comics := api.Group("/comics")
|
||||
comics.Get("/comics", GetAllComics)
|
||||
}
|
||||
|
||||
func GetAllComics(c *fiber.Ctx) error {
|
||||
var comics []schema.Comic
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = schema.GetDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.NewSelect().Model(&comics).Relation("Publisher").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return c.JSON(comics)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type Publisher struct {
|
||||
bun.BaseModel `bun:"table:publisher"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name"`
|
||||
WebLink string `bun:"weblink"`
|
||||
|
||||
ParentPublisherID *string `bun:"parent_publisher_id"`
|
||||
ParentPublisher *Publisher `bun:"rel:belongs-to,join:parent_publisher_id=id"`
|
||||
Imprints []Publisher `bun:"rel:has-many,join:id=parent_publisher_id"`
|
||||
Comics []Comic `bun:"rel:has-many,join:id=publisher_id"`
|
||||
}
|
||||
|
||||
type Comic struct {
|
||||
bun.BaseModel `bun:"table:comic"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Title string `bun:"title,unique:title,notnull"`
|
||||
CurrentOrder bool `bun:"current_order"`
|
||||
Completed bool `bun:"completed"`
|
||||
WebLink string `bun:"weblink"`
|
||||
|
||||
PublisherID *string `bun:"publisher_id"`
|
||||
Publisher *Publisher `bun:"rel:belongs-to,join:publisher_id=id"`
|
||||
|
||||
// Issues []Issue `bun:"rel:has-many,join:id=comic_id"`
|
||||
// StoryArcs []StoryArc `bun:"rel:has-many,join:id=comic_id"`
|
||||
// TradePaperbacks []TradePaperback `bun:"rel:has-many,join:id=comic_id"`
|
||||
// Volumes []Volume `bun:"rel:has-many,join:id=comic_id"`
|
||||
// ComicWorks []ComicWork `bun:"rel:has-many,join:id=comic_id"`
|
||||
}
|
||||
@@ -51,5 +51,5 @@ ENV PATH="/app/.venv/bin:$PATH"
|
||||
EXPOSE $PORT
|
||||
|
||||
# Start the application with Uvicorn in production mode, using environment variable references
|
||||
CMD ["uvicorn", "src.main:kontor", "--log-level", "info", "--host", "0.0.0.0" , "--port", "8800"]
|
||||
CMD ["uvicorn", "src.main:kontor", "--log-level", "info", "--host", "0.0.0.0" , "--port", "8500"]
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from datetime import timedelta
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status, Depends, Response
|
||||
from fastapi import APIRouter, Body, HTTPException, status, Depends, Response
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
|
||||
from src.core.config import settings
|
||||
@@ -31,7 +31,7 @@ def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depen
|
||||
|
||||
# @router.post("/token-cookie", response_model=Token)
|
||||
def login_for_token_cookie(response: Response, form_data: LoginForm = Depends()):
|
||||
user = authenticate_user(form_data.username, form_data.password)
|
||||
user = authenticate_user(form_data.username, form_data.password) # type: ignore
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from pydantic import BaseModel
|
||||
|
||||
from src.core.config import settings
|
||||
from src.core.log_conf import logger
|
||||
from src.core.security import authenticate_user, create_access_token
|
||||
from src.schema.admin import Token
|
||||
|
||||
login_router = APIRouter()
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
email: str | None = None
|
||||
password: str | None = None
|
||||
|
||||
|
||||
@login_router.post(
|
||||
"/login",
|
||||
tags=["login"],
|
||||
summary="Login and get token",
|
||||
response_description="Return HTTP status code 200 (OK)",
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
def login(request: LoginRequest) -> Token:
|
||||
logger.info(f"login with {request.email}")
|
||||
user = authenticate_user(request.email, request.password) # type: ignore
|
||||
scopes = ["admin", "read"]
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_access_token(
|
||||
data={"sub": user.email, "scope": " ".join(scopes)},
|
||||
expires_delta=access_token_expires,
|
||||
)
|
||||
return Token(access_token=access_token, token_type="bearer")
|
||||
@@ -1,7 +1,7 @@
|
||||
from fastapi import APIRouter, status, HTTPException
|
||||
from sqlalchemy import select
|
||||
from src.core.log_conf import logger
|
||||
from src.db.repository.media import create_new_mediaactor
|
||||
from src.db.repository.media import create_new_mediaactor, delete_mediaactor
|
||||
from src.db.session import SessionDep
|
||||
from src.schema.media.actor import Actor, MediaActorResponse, get_actor_details
|
||||
from src.db.models.media import MediaActor
|
||||
@@ -10,7 +10,7 @@ router = APIRouter()
|
||||
|
||||
@router.get("/actors", response_model=list[MediaActorResponse])
|
||||
# def get_all_files(db: SessionDep, review: bool = False, download: bool = False, current_user: Profile = Depends(get_current_user_from_token)) -> List[MediaFileResponse]:
|
||||
def get_all_actors(db: SessionDep, review: bool = False, download: bool = False) -> list[MediaActorResponse]:
|
||||
def get_all_actors(db: SessionDep, review: bool = False, download: bool = False) -> list[MediaActorResponse]: # type: ignore
|
||||
results: list[MediaActorResponse] = []
|
||||
actors = db.scalars(select(MediaActor)).all()
|
||||
for mediaactor in actors:
|
||||
@@ -19,15 +19,26 @@ def get_all_actors(db: SessionDep, review: bool = False, download: bool = False)
|
||||
return results
|
||||
|
||||
@router.get("/actors/{actor_id}", response_model=MediaActorResponse)
|
||||
def get_actor(actor_id: str, db: SessionDep) -> MediaActorResponse:
|
||||
def get_actor(actor_id: str, db: SessionDep) -> MediaActorResponse: # type: ignore
|
||||
media_actor = db.get(MediaActor, actor_id)
|
||||
if not media_actor:
|
||||
raise HTTPException(status_code=404, detail="MediaActor could not be found")
|
||||
response = get_actor_details(media_actor)
|
||||
return response
|
||||
|
||||
@router.delete("/actors/{actor_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_actor(actor_id: str, db: SessionDep): # type: ignore
|
||||
media_actor = db.get(MediaActor, actor_id)
|
||||
if not media_actor:
|
||||
raise HTTPException(status_code=404, detail="MediaActor could not be found")
|
||||
logger.info(f"delete MediaActor: {actor_id}")
|
||||
actor_files = media_actor.media_actor_files
|
||||
logger.info(f"MediaActorFiles links {len(actor_files)}")
|
||||
if len(actor_files) == 0:
|
||||
delete_mediaactor(db, media_actor.id)
|
||||
|
||||
@router.post("/actors", status_code=status.HTTP_201_CREATED)
|
||||
def add_actor(new_actor: Actor, db: SessionDep) -> MediaActorResponse:
|
||||
def add_actor(new_actor: Actor, db: SessionDep) -> MediaActorResponse: # type: ignore
|
||||
logger.info(f"add actor {new_actor.url}")
|
||||
try:
|
||||
mediaActor: MediaActor = create_new_mediaactor(new_actor, db)
|
||||
|
||||
@@ -8,7 +8,7 @@ from src.schema.media.actorfile import MediaActorFileResponse, get_actorfile_det
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/actorfiles", response_model=list[MediaActorFileResponse])
|
||||
def get_all_actorfiles(db: SessionDep) -> list[MediaActorFileResponse]:
|
||||
def get_all_actorfiles(db: SessionDep) -> list[MediaActorFileResponse]: # type: ignore
|
||||
results: list[MediaActorFileResponse] = []
|
||||
actorfiles = db.scalars(select(MediaActorFile)).all()
|
||||
for mediaactorfile in actorfiles:
|
||||
@@ -17,7 +17,7 @@ def get_all_actorfiles(db: SessionDep) -> list[MediaActorFileResponse]:
|
||||
return results
|
||||
|
||||
@router.get("/actorfiles/{actorfile_id}", response_model=MediaActorFileResponse)
|
||||
def get_actorfile(actorfile_id: str, db: SessionDep) -> MediaActorFileResponse:
|
||||
def get_actorfile(actorfile_id: str, db: SessionDep) -> MediaActorFileResponse: # type: ignore
|
||||
media_actorfile = db.get(MediaActorFile, actorfile_id)
|
||||
if not media_actorfile:
|
||||
raise HTTPException(status_code=404, detail="MediaActor could not be found")
|
||||
@@ -25,7 +25,7 @@ def get_actorfile(actorfile_id: str, db: SessionDep) -> MediaActorFileResponse:
|
||||
return response
|
||||
|
||||
@router.delete("/actorfiles/{actorfile_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_actorfile(actorfile_id: str, db: SessionDep):
|
||||
def delete_actorfile(actorfile_id: str, db: SessionDep): # type: ignore
|
||||
media_actorfile = db.get(MediaActorFile, actorfile_id)
|
||||
if not media_actorfile:
|
||||
raise HTTPException(status_code=404, detail="MediaActor could not be found")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from fastapi import APIRouter, status, HTTPException, Depends
|
||||
from sqlalchemy import select, Sequence
|
||||
from src.core.log_conf import logger
|
||||
from src.db.repository.media import create_new_mediaactorfile, create_new_mediafile
|
||||
from src.db.repository.media import create_new_mediaactorfile, create_new_mediafile, delete_mediafile
|
||||
from src.db.session import SessionDep
|
||||
from src.schema.media.actor import MediaActorResponse
|
||||
from src.schema.media.actorfile import MediaActorFileResponse
|
||||
@@ -11,7 +11,7 @@ from src.db.models.media import MediaFile
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/update-titles")
|
||||
def update_titles(db: SessionDep) -> list[MediaFileResponse]:
|
||||
def update_titles(db: SessionDep) -> list[MediaFileResponse]: # type: ignore
|
||||
results: list[MediaFileResponse] = []
|
||||
files = db.query(MediaFile).filter(MediaFile.review == True).all()
|
||||
for mediafile in files:
|
||||
@@ -25,7 +25,7 @@ def update_titles(db: SessionDep) -> 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]: # type: ignore
|
||||
results: list[MediaFileResponse] = []
|
||||
files: Sequence[MediaFile]
|
||||
if review:
|
||||
@@ -40,15 +40,26 @@ def get_all_files(db: SessionDep, review: bool = False, download: bool = False)
|
||||
return results
|
||||
|
||||
@router.get("/files/{file_id}", response_model=MediaFileResponse)
|
||||
def get_file(file_id: str, db: SessionDep) -> MediaFileResponse:
|
||||
def get_file(file_id: str, db: SessionDep) -> MediaFileResponse: # type: ignore
|
||||
mediafile = db.get(MediaFile, file_id)
|
||||
if not mediafile:
|
||||
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
||||
response = get_file_details(mediafile)
|
||||
return response
|
||||
|
||||
@router.delete("/files/{file_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_file(file_id: str, db: SessionDep): # type: ignore
|
||||
mediafile = db.get(MediaFile, file_id)
|
||||
if not mediafile:
|
||||
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
||||
logger.info(f"delete MediaFile: {file_id}")
|
||||
actor_files = mediafile.media_actor_files
|
||||
logger.info(f"MediaActorFiles links {len(actor_files)}")
|
||||
if len(actor_files) == 0:
|
||||
delete_mediafile(db, mediafile.id)
|
||||
|
||||
@router.get("/files/{file_id}/actors", response_model=list[MediaActorResponse])
|
||||
def get_file_actors(file_id: str, db: SessionDep) -> list[MediaActorResponse]:
|
||||
def get_file_actors(file_id: str, db: SessionDep) -> list[MediaActorResponse]: # type: ignore
|
||||
mediafile = db.get(MediaFile, file_id)
|
||||
if not mediafile:
|
||||
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
||||
@@ -61,7 +72,7 @@ def get_file_actors(file_id: str, db: SessionDep) -> list[MediaActorResponse]:
|
||||
return results
|
||||
|
||||
@router.put("/files/{file_id}/actors", response_model=list[MediaActorFileResponse])
|
||||
def update_file_actors(file_id: str, db: SessionDep, actors: list[MediaActorResponse]) -> list[MediaActorFileResponse]:
|
||||
def update_file_actors(file_id: str, db: SessionDep, actors: list[MediaActorResponse]) -> list[MediaActorFileResponse]: # type: ignore
|
||||
mediafile = db.get(MediaFile, file_id)
|
||||
if not mediafile:
|
||||
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
||||
@@ -85,7 +96,7 @@ def update_file_actors(file_id: str, db: SessionDep, actors: list[MediaActorResp
|
||||
return results
|
||||
|
||||
@router.put("/files/{file_id}", response_model=MediaFileResponse)
|
||||
def update_file(file_id: str, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse:
|
||||
def update_file(file_id: str, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse: # type: ignore
|
||||
mediaFile = db.get(MediaFile, file_id)
|
||||
if not mediaFile:
|
||||
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
||||
@@ -99,7 +110,7 @@ def update_file(file_id: str, db: SessionDep, info: MediaFileResponse) -> MediaF
|
||||
return response
|
||||
|
||||
@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: # type: ignore
|
||||
logger.info(f"add url {new_link.url}")
|
||||
try:
|
||||
mediaFile: MediaFile = create_new_mediafile(new_link.url, db)
|
||||
|
||||
@@ -14,7 +14,7 @@ class Settings:
|
||||
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_PORT: int = int(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=")
|
||||
|
||||
@@ -8,15 +8,15 @@ LOGGING_CONFIG: dict[str, Any] = {
|
||||
"formatters": {
|
||||
"default": {
|
||||
"()": "uvicorn.logging.DefaultFormatter",
|
||||
"fmt": "%(asctime)s - %(name)s - %(levelprefix)s %(message)s"
|
||||
"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
|
||||
"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
|
||||
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
|
||||
"use_colors": False,
|
||||
},
|
||||
},
|
||||
@@ -34,12 +34,16 @@ LOGGING_CONFIG: dict[str, Any] = {
|
||||
},
|
||||
"loggers": {
|
||||
"root": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
||||
"kontor": {"handlers": ["default"], "level": "INFO", "propagate": True},
|
||||
"kontor": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
||||
"uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
||||
"uvicorn.error": {"level": "INFO"},
|
||||
"uvicorn.access": {"handlers": ["default"], "level": "WARNING", "propagate": False},
|
||||
"uvicorn.access": {
|
||||
"handlers": ["default"],
|
||||
"level": "WARNING",
|
||||
"propagate": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
logging.config.dictConfig(LOGGING_CONFIG)
|
||||
logger = logging.getLogger('kontor')
|
||||
logger = logging.getLogger("kontor")
|
||||
|
||||
@@ -1,29 +1,21 @@
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from datetime import timedelta
|
||||
from typing import Optional, Annotated, List
|
||||
from typing import Dict
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import HTTPException, Security
|
||||
from fastapi import Request
|
||||
from fastapi import status
|
||||
from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
|
||||
from fastapi.security.utils import get_authorization_scheme_param
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Annotated, Dict, List, Optional
|
||||
|
||||
import bcrypt
|
||||
from fastapi import Depends
|
||||
from fastapi.security import SecurityScopes, OAuth2PasswordBearer, OAuth2
|
||||
from fastapi import Depends, HTTPException, Request, Security, status
|
||||
from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel
|
||||
from fastapi.security import OAuth2, OAuth2PasswordBearer, SecurityScopes
|
||||
from fastapi.security.utils import get_authorization_scheme_param
|
||||
from jose import JWTError, jwt
|
||||
from pydantic import ValidationError
|
||||
|
||||
from src.core.config import settings
|
||||
from jose import jwt, JWTError
|
||||
|
||||
from src.core.log_conf import logger
|
||||
from src.db.models.admin import Profile
|
||||
from src.db.repository.admin import get_profile
|
||||
from src.db.session import SessionLocal
|
||||
from src.schema.admin import TokenData, ProfileModel
|
||||
from src.schema.admin import ProfileModel, TokenData
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(
|
||||
tokenUrl="/api/login/token",
|
||||
@@ -33,19 +25,19 @@ oauth2_scheme = OAuth2PasswordBearer(
|
||||
|
||||
class OAuth2PasswordBearerWithCookie(OAuth2):
|
||||
def __init__(
|
||||
self,
|
||||
tokenUrl: str,
|
||||
scheme_name: Optional[str] = None,
|
||||
scopes: Optional[Dict[str, str]] = None,
|
||||
auto_error: bool = True,
|
||||
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})
|
||||
flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes}) # type: ignore
|
||||
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
|
||||
|
||||
async def __call__(self, request: Request) -> Optional[str]:
|
||||
authorization: str = request.cookies.get("access_token") # changed to accept access token from httpOnly Cookie
|
||||
authorization: str = request.cookies.get("access_token") # type: ignore # changed to accept access token from httpOnly Cookie
|
||||
|
||||
scheme, param = get_authorization_scheme_param(authorization)
|
||||
if not authorization or scheme.lower() != "bearer":
|
||||
@@ -63,7 +55,7 @@ class OAuth2PasswordBearerWithCookie(OAuth2):
|
||||
def authenticate_user(username: str, password: str) -> Optional[Profile]:
|
||||
with SessionLocal() as db:
|
||||
user = get_profile(username=username, db=db)
|
||||
logger.info(user)
|
||||
logger.debug(user)
|
||||
if not user:
|
||||
return None
|
||||
if bcrypt.checkpw(password.encode(), user.password.encode()):
|
||||
@@ -88,7 +80,9 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
async def get_current_user(security_scopes: SecurityScopes, token: Annotated[str, Depends(oauth2_scheme)]):
|
||||
async def get_current_user(
|
||||
security_scopes: SecurityScopes, token: Annotated[str, Depends(oauth2_scheme)]
|
||||
):
|
||||
if security_scopes.scopes:
|
||||
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
|
||||
else:
|
||||
@@ -99,8 +93,10 @@ async def get_current_user(security_scopes: SecurityScopes, token: Annotated[str
|
||||
headers={"WWW-Authenticate": authenticate_value},
|
||||
)
|
||||
try:
|
||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
||||
username: str = payload.get("sub")
|
||||
payload = jwt.decode(
|
||||
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
||||
)
|
||||
username: str = payload.get("sub") # type: ignore
|
||||
logger.info("username/email extracted is ", username)
|
||||
if username is None:
|
||||
raise credentials_exception
|
||||
@@ -110,7 +106,7 @@ async def get_current_user(security_scopes: SecurityScopes, token: Annotated[str
|
||||
except (JWTError, ValidationError):
|
||||
raise credentials_exception
|
||||
with SessionLocal() as db:
|
||||
user = get_profile(username=token_data.username, db=db)
|
||||
user = get_profile(username=token_data.username, db=db) # type: ignore
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
for scope in security_scopes.scopes:
|
||||
@@ -124,17 +120,20 @@ async def get_current_user(security_scopes: SecurityScopes, token: Annotated[str
|
||||
|
||||
|
||||
async def get_current_active_user(
|
||||
current_user: Annotated[Profile, Security(get_current_user, scopes=["me"])],
|
||||
current_user: Annotated[Profile, Security(get_current_user, scopes=["me"])],
|
||||
) -> ProfileModel:
|
||||
if not current_user.enabled:
|
||||
if not current_user.enabled: # type: ignore
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
user_model = ProfileModel(username=current_user.user_name, email=current_user.email,
|
||||
first_name=current_user.first_name, last_name=current_user.last_name,
|
||||
active=current_user.enabled)
|
||||
user_model = ProfileModel(
|
||||
username=current_user.user_name,
|
||||
email=current_user.email, # type: ignore
|
||||
first_name=current_user.first_name,
|
||||
last_name=current_user.last_name, # type: ignore
|
||||
active=current_user.enabled,
|
||||
) # type: ignore
|
||||
return user_model
|
||||
|
||||
|
||||
|
||||
def get_current_user_from_token(token: str = Depends(oauth2_scheme)):
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@@ -144,7 +143,7 @@ def get_current_user_from_token(token: str = Depends(oauth2_scheme)):
|
||||
payload = jwt.decode(
|
||||
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
||||
)
|
||||
username: str = payload.get("sub")
|
||||
username: str = payload.get("sub") # type: ignore
|
||||
logger.info("username/email extracted is ", username)
|
||||
if username is None:
|
||||
raise credentials_exception
|
||||
|
||||
@@ -38,6 +38,12 @@ def create_new_mediafile(link: str, db: Session) -> MediaFile:
|
||||
logger.info(f"created {media_file}")
|
||||
return media_file
|
||||
|
||||
def delete_mediafile(db: Session, media_file_id: str):
|
||||
logger.info(f"delete MediaFile with id {media_file_id}")
|
||||
media_file = db.get(MediaFile, media_file_id)
|
||||
db.delete(media_file)
|
||||
db.commit()
|
||||
|
||||
def create_new_mediaactor(new_actor: Actor, db: Session) -> MediaActor:
|
||||
logger.info(f"create MediaActor with url {new_actor.url}")
|
||||
media_actor: MediaActor = MediaActor()
|
||||
@@ -53,6 +59,12 @@ def create_new_mediaactor(new_actor: Actor, db: Session) -> MediaActor:
|
||||
logger.info(f"created {media_actor}")
|
||||
return media_actor
|
||||
|
||||
def delete_mediaactor(db: Session, actor_id: str):
|
||||
logger.info(f"delete MediaActor with id {actor_id}")
|
||||
media_actor = db.get(MediaActor, actor_id)
|
||||
db.delete(media_actor)
|
||||
db.commit()
|
||||
|
||||
def create_new_mediaactorfile(db: Session, actor_id: str, file_id: str) -> MediaActorFile:
|
||||
logger.info(f"create MediaActorFile with actor {actor_id} and file {file_id}")
|
||||
media_actor_file: MediaActorFile = MediaActorFile()
|
||||
|
||||
+18
-8
@@ -1,17 +1,18 @@
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from src.apis.base import api_router
|
||||
from src.apis.version1.healthcheck import health_router
|
||||
from src.apis.version1.login import login_router
|
||||
from src.core.config import settings
|
||||
from src.core.log_conf import logger
|
||||
from src.db.models.base import Base
|
||||
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.core.config import settings
|
||||
from src.db.models.base import Base
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -20,33 +21,42 @@ async def lifespan(app: FastAPI):
|
||||
yield
|
||||
await check_db_disconnected()
|
||||
|
||||
|
||||
def include_router(app: FastAPI):
|
||||
app.include_router(api_router)
|
||||
app.include_router(web_app_router)
|
||||
app.include_router(health_router)
|
||||
app.include_router(login_router)
|
||||
|
||||
|
||||
def configure_static(app: FastAPI):
|
||||
app.mount("/static", StaticFiles(directory="src/static"), name="static")
|
||||
|
||||
|
||||
def add_middle_ware(app: FastAPI):
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=['*'],
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=['*'],
|
||||
allow_headers=['*'],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
def create_tables():
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
|
||||
def start_application(log):
|
||||
log.info(f"using database: {settings.DATABASE_URL}")
|
||||
app = FastAPI(title=settings.PROJECT_NAME, version=settings.PROJECT_VERSION, lifespan=lifespan)
|
||||
app = FastAPI(
|
||||
title=settings.PROJECT_NAME, version=settings.PROJECT_VERSION, lifespan=lifespan
|
||||
)
|
||||
include_router(app)
|
||||
configure_static(app)
|
||||
add_middle_ware(app)
|
||||
create_tables()
|
||||
return app
|
||||
|
||||
|
||||
kontor = start_application(logger)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"kontor-api-echo/pkg/handler"
|
||||
"kontor-api-echo/pkg/schema"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
echojwt "github.com/labstack/echo-jwt/v4"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetOutput(os.Stdout)
|
||||
log.Println("Kontor started")
|
||||
|
||||
if _, err := schema.GetDatabase(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
e := echo.New()
|
||||
e.GET("/health", handler.GetHealth)
|
||||
e.POST("/login", handler.Login)
|
||||
|
||||
skipper := func(c echo.Context) bool {
|
||||
// Skip health check endpoint
|
||||
return c.Request().URL.Path == "/health"
|
||||
}
|
||||
|
||||
e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
|
||||
LogStatus: true,
|
||||
LogURI: true,
|
||||
LogError: true,
|
||||
HandleError: true,
|
||||
Skipper: skipper,
|
||||
LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
|
||||
if v.Error == nil {
|
||||
log.Printf("REQUEST: uri: %v, status: %v\n", v.URI, v.Status)
|
||||
} else {
|
||||
log.Printf("REQUEST-ERROR: uri: %v, status: %v, err: %v\n", v.URI, v.Status, v.Error.Error())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}))
|
||||
|
||||
group := e.Group("/api")
|
||||
group.Use(echojwt.WithConfig(echojwt.Config{SigningKey: []byte("secret")}))
|
||||
handler.SetupComicRoutes(group)
|
||||
handler.SetupMediaRoutes(group)
|
||||
|
||||
e.Logger.Fatal(e.Start(":8700"))
|
||||
log.Println("Kontor finished")
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
module kontor-api-echo
|
||||
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/labstack/echo-jwt/v4 v4.4.0 // indirect
|
||||
github.com/labstack/echo/v4 v4.15.0 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/uptrace/bun v1.2.16 // indirect
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.16 // indirect
|
||||
github.com/uptrace/bun/driver/pgdriver v1.2.16 // indirect
|
||||
github.com/uptrace/bun/extra/bundebug v1.2.16 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
mellium.im/sasl v0.3.2 // indirect
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/labstack/echo-jwt/v4 v4.4.0 h1:nrXaEnJupfc2R4XChcLRDyghhMZup77F8nIzHnBK19U=
|
||||
github.com/labstack/echo-jwt/v4 v4.4.0/go.mod h1:kYXWgWms9iFqI3ldR+HAEj/Zfg5rZtR7ePOgktG4Hjg=
|
||||
github.com/labstack/echo/v4 v4.15.0 h1:hoRTKWcnR5STXZFe9BmYun9AMTNeSbjHi2vtDuADJ24=
|
||||
github.com/labstack/echo/v4 v4.15.0/go.mod h1:xmw1clThob0BSVRX1CRQkGQ/vjwcpOMjQZSZa9fKA/c=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
github.com/uptrace/bun v1.2.16 h1:QlObi6ZIK5Ao7kAALnh91HWYNZUBbVwye52fmlQM9kc=
|
||||
github.com/uptrace/bun v1.2.16/go.mod h1:jMoNg2n56ckaawi/O/J92BHaECmrz6IRjuMWqlMaMTM=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.16 h1:KFNZ0LxAyczKNfK/IJWMyaleO6eI9/Z5tUv3DE1NVL4=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.16/go.mod h1:IJdMeV4sLfh0LDUZl7TIxLI0LipF1vwTK3hBC7p5qLo=
|
||||
github.com/uptrace/bun/driver/pgdriver v1.2.16 h1:b1kpXKUxtTSGYow5Vlsb+dKV3z0R7aSAJNfMfKp61ZU=
|
||||
github.com/uptrace/bun/driver/pgdriver v1.2.16/go.mod h1:H6lUZ9CBfp1X5Vq62YGSV7q96/v94ja9AYFjKvdoTk0=
|
||||
github.com/uptrace/bun/extra/bundebug v1.2.16 h1:3OXAfHTU4ydu2+4j05oB1BxPx6+ypdWIVzTugl/7zl0=
|
||||
github.com/uptrace/bun/extra/bundebug v1.2.16/go.mod h1:vk6R/1i67/S2RvUI5AH/m3P5e67mOkfDCmmCsAPUumo=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0=
|
||||
mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY=
|
||||
@@ -0,0 +1,78 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"kontor-api-echo/pkg/schema"
|
||||
"kontor-api-echo/pkg/utils"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type jwtCustomClaims struct {
|
||||
Name string `json:"name"`
|
||||
Admin bool `json:"admin"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func Login(c echo.Context) error {
|
||||
// user := c.FormValue("email")
|
||||
// pass := c.FormValue("password")
|
||||
loginRequest := new(LoginRequest)
|
||||
if err := c.Bind(loginRequest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
email := loginRequest.Email
|
||||
password := loginRequest.Password
|
||||
|
||||
var profile schema.Profile
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, _ = schema.GetDatabase()
|
||||
err = db.NewSelect().Model(&profile).Where("email = ?", email).Scan(ctx)
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
if !utils.ComparePassword(profile.Password, password) {
|
||||
return echo.ErrUnauthorized
|
||||
}
|
||||
|
||||
// Set custom claims
|
||||
claims := &jwtCustomClaims{
|
||||
"Jon Snow",
|
||||
true,
|
||||
jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 72)),
|
||||
},
|
||||
}
|
||||
|
||||
// Create token with claims
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
// Generate encoded token and send it as response.
|
||||
t, err := token.SignedString([]byte("secret"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, echo.Map{"access_token": t, "token_type": "bearer"})
|
||||
}
|
||||
|
||||
func restricted(c echo.Context) error {
|
||||
user := c.Get("user").(*jwt.Token)
|
||||
claims := user.Claims.(*jwtCustomClaims)
|
||||
name := claims.Name
|
||||
return c.String(http.StatusOK, "Welcome "+name+"!")
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"kontor-api-echo/pkg/schema"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func SetupComicRoutes(api *echo.Group) {
|
||||
comics := api.Group("/comics")
|
||||
comics.GET("/comics", GetAllComics)
|
||||
comics.GET("/publishers", GetAllPublishers)
|
||||
comics.GET("/comicworks", GetAllComicWorks)
|
||||
}
|
||||
|
||||
func GetAllComics(c echo.Context) error {
|
||||
var comics []schema.Comic
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
fmt.Printf("REQUEST: uri: %v, status: %v\n", c.Request().URL, c.Response().Status)
|
||||
log.Printf("REQUEST: uri: %v, status: %v\n", c.Request().URL, c.Response().Status)
|
||||
db, err = schema.GetDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.NewSelect().Model(&comics).Relation("Publisher").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return echo.ErrInternalServerError
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, comics)
|
||||
}
|
||||
|
||||
func GetAllPublishers(c echo.Context) error {
|
||||
var publishers []schema.Publisher
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = schema.GetDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.NewSelect().Model(&publishers).Relation("ParentPublisher").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return echo.ErrInternalServerError
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, publishers)
|
||||
}
|
||||
|
||||
func GetAllComicWorks(c echo.Context) error {
|
||||
var comic_works []schema.ComicWork
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = schema.GetDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.NewSelect().Model(&comic_works).Relation("Comic").Relation("Artist").Relation("WorkType").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return echo.ErrInternalServerError
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, comic_works)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func GetHealth(c echo.Context) error {
|
||||
status := new(Status)
|
||||
status.Status = "ok"
|
||||
return c.JSON(http.StatusOK, status)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"kontor-api-echo/pkg/schema"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func SetupMediaRoutes(api *echo.Group) {
|
||||
media := api.Group("/media")
|
||||
media.GET("/files", GetAllFiles)
|
||||
media.POST("/files", AddFile)
|
||||
}
|
||||
|
||||
func GetAllFiles(c echo.Context) error {
|
||||
var files []schema.MediaFile
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = schema.GetDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = db.NewSelect().Model(&files).Relation("MediaActorFiles").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return echo.ErrInternalServerError
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, files)
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func AddFile(c echo.Context) error {
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
link := new(Link)
|
||||
if err = c.Bind(link); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("URL %s has been sent", link)
|
||||
|
||||
db, err = schema.GetDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
id := uuid.NewString()
|
||||
timestamp := time.Now()
|
||||
mediafile := &schema.MediaFile{ID: id, CreatedAt: timestamp, UpdatedAt: timestamp, WebLink: fmt.Sprintf("%s", link), Version: 1, ShouldDownload: true, Review: true}
|
||||
_, err = db.NewInsert().Model(mediafile).Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.String(http.StatusCreated, "Link created")
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type Publisher struct {
|
||||
bun.BaseModel `bun:"table:publisher"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name"`
|
||||
WebLink string `bun:"weblink"`
|
||||
|
||||
ParentPublisherID *string `bun:"parent_publisher_id"`
|
||||
ParentPublisher *Publisher `bun:"rel:belongs-to,join:parent_publisher_id=id"`
|
||||
Imprints []Publisher `bun:"rel:has-many,join:id=parent_publisher_id"`
|
||||
Comics []Comic `bun:"rel:has-many,join:id=publisher_id"`
|
||||
}
|
||||
|
||||
type Comic struct {
|
||||
bun.BaseModel `bun:"table:comic"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Title string `bun:"title,unique:title,notnull"`
|
||||
CurrentOrder bool `bun:"current_order"`
|
||||
Completed bool `bun:"completed"`
|
||||
WebLink string `bun:"weblink"`
|
||||
|
||||
PublisherID *string `bun:"publisher_id"`
|
||||
Publisher *Publisher `bun:"rel:belongs-to,join:publisher_id=id"`
|
||||
Issues []Issue `bun:"rel:has-many,join:id=comic_id"`
|
||||
StoryArcs []StoryArc `bun:"rel:has-many,join:id=comic_id"`
|
||||
TradePaperbacks []TradePaperback `bun:"rel:has-many,join:id=comic_id"`
|
||||
Volumes []Volume `bun:"rel:has-many,join:id=comic_id"`
|
||||
ComicWorks []ComicWork `bun:"rel:has-many,join:id=comic_id"`
|
||||
}
|
||||
|
||||
type Artist struct {
|
||||
bun.BaseModel `bun:"table:artist"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:title,notnull"`
|
||||
WebLink string `bun:"weblink"`
|
||||
}
|
||||
|
||||
type Issue struct {
|
||||
bun.BaseModel `bun:"table:issue"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
InStock bool `bun:"in_stock"`
|
||||
IsRead bool `bun:"is_read"`
|
||||
IssueNumber string `bu:"issue_number"`
|
||||
Title string `bun:"title"`
|
||||
PublishedOn time.Time `bun:"published_on"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
StoryArcID *string `bun:"story_arc_id"`
|
||||
StoryArc *StoryArc `bun:"rel:belongs-to,join:story_arc_id=id"`
|
||||
VolumeID *string `bun:"volume_id"`
|
||||
Volume *Volume `bun:"rel:belongs-to,join:volume_id=id"`
|
||||
}
|
||||
|
||||
type StoryArc struct {
|
||||
bun.BaseModel `bun:"table:story_arc"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name,notnull"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
VolumeID *string `bun:"volume_id"`
|
||||
Volume *Volume `bun:"rel:belongs-to,join:volume_id=id"`
|
||||
}
|
||||
|
||||
type TradePaperback struct {
|
||||
bun.BaseModel `bun:"table:trade_paperback"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name,notnull"`
|
||||
IssueStart int `bun:"issue_start"`
|
||||
IssueEnd int `bun:"issue_end"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
}
|
||||
|
||||
type Volume struct {
|
||||
bun.BaseModel `bun:"table:volume"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name,notnull"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
}
|
||||
|
||||
type WorkType struct {
|
||||
bun.BaseModel `bun:"table:worktype"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name,notnull"`
|
||||
}
|
||||
|
||||
type ComicWork struct {
|
||||
bun.BaseModel `bun:"table:comic_work"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
ArtistID *string `bun:"artist_id"`
|
||||
Artist *Artist `bun:"rel:belongs-to,join:artist_id=id"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
WorkTypeID *string `bun:"work_type_id"`
|
||||
WorkType *WorkType `bun:"rel:belongs-to,join:work_type_id=id"`
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"database/sql"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
|
||||
"github.com/uptrace/bun/dialect/pgdialect"
|
||||
"github.com/uptrace/bun/driver/pgdriver"
|
||||
)
|
||||
|
||||
func GetTestDatabase() (*bun.DB, error) {
|
||||
var err error
|
||||
|
||||
dsn := "postgres://kontor:kontor@localhost:5432/kontor?sslmode=disable"
|
||||
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))
|
||||
|
||||
sqldb.SetMaxOpenConns(4)
|
||||
sqldb.SetMaxIdleConns(4)
|
||||
|
||||
DB := bun.NewDB(sqldb, pgdialect.New())
|
||||
// DB.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true)))
|
||||
|
||||
if err = DB.Ping(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Println("Returned Database Connection")
|
||||
return DB, nil
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log.Println("Setup Test")
|
||||
exitCode := m.Run()
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func TestSelectComics(t *testing.T) {
|
||||
var comics []Comic
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.NewSelect().Model(&comics).Relation("Publisher").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 168, len(comics))
|
||||
}
|
||||
|
||||
func TestSelectPublishers(t *testing.T) {
|
||||
var publishers []Publisher
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.NewSelect().Model(&publishers).Relation("ParentPublisher").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 19, len(publishers))
|
||||
}
|
||||
|
||||
func TestSelectWorkTypes(t *testing.T) {
|
||||
var comic_works []ComicWork
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.NewSelect().Model(&comic_works).Relation("Comic").Relation("Artist").Relation("WorkType").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 59, len(comic_works))
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect/pgdialect"
|
||||
"github.com/uptrace/bun/driver/pgdriver"
|
||||
)
|
||||
|
||||
var DB *bun.DB
|
||||
|
||||
func GetDatabase() (*bun.DB, error) {
|
||||
var err error
|
||||
|
||||
dsn := "postgres://kontor:kontor@postgres:5432/kontor?sslmode=disable"
|
||||
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))
|
||||
|
||||
sqldb.SetMaxOpenConns(4)
|
||||
sqldb.SetMaxIdleConns(4)
|
||||
|
||||
DB := bun.NewDB(sqldb, pgdialect.New())
|
||||
|
||||
if err = DB.Ping(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Println("Returned Database Connection")
|
||||
return DB, nil
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type Sport struct {
|
||||
bun.BaseModel `bun:"table:sport"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Teams []Team `bun:"rel:has-many,join:id=sport_id"`
|
||||
Positions []FieldPosition `bun:"rel:has-many,join:id=sport_id"`
|
||||
}
|
||||
|
||||
type Team struct {
|
||||
bun.BaseModel `bun:"table:team"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name"`
|
||||
Shortname string `bun:"short_name"`
|
||||
Roosters []Rooster `bun:"rel:has-many,join:id=team_id"`
|
||||
SportID *string `bun:"sport_id"`
|
||||
Sport *Sport `bun:"rel:belongs-to,join:sport_id=id"`
|
||||
}
|
||||
|
||||
type FieldPosition struct {
|
||||
bun.BaseModel `bun:"table:field_position"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name"`
|
||||
Shortname string `bun:"short_name"`
|
||||
SportID *string `bun:"sport_id"`
|
||||
Sport *Sport `bun:"rel:belongs-to,join:sport_id=id"`
|
||||
Roosters []Rooster `bun:"rel:has-many,join:id=position_id"`
|
||||
}
|
||||
|
||||
type Player struct {
|
||||
bun.BaseModel `bun:"table:player"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Firstname string `bun:"first_name"`
|
||||
Lastname string `bun:"last_name"`
|
||||
Roosters []Rooster `bun:"rel:has-many,join:id=player_id"`
|
||||
}
|
||||
|
||||
type Rooster struct {
|
||||
bun.BaseModel `bun:"table:rooster"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Year int `bun:"year"`
|
||||
TeamID *string `bun:"team_id"`
|
||||
Team *Team `bun:"rel:belongs-to,join:team_id=id"`
|
||||
PlayerID *string `bun:"player_id"`
|
||||
Player *Player `bun:"rel:belongs-to,join:player_id=id"`
|
||||
PositionID *string `bun:"position_id"`
|
||||
Position *FieldPosition `bun:"rel:belongs-to,join:position_id=id"`
|
||||
}
|
||||
|
||||
type CardSet struct {
|
||||
bun.BaseModel `bun:"table:card_set"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name"`
|
||||
ParallelSet bool `bun:"parallel_set"`
|
||||
InsertSet bool `bun:"insert_set"`
|
||||
VendorID *string `bun:"vendor_id"`
|
||||
Vendor *Vendor `bun:"rel:belongs-to,join:vendor_id=id"`
|
||||
}
|
||||
|
||||
type Vendor struct {
|
||||
bun.BaseModel `bun:"table:vendor"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name"`
|
||||
CardSets []CardSet `bun:"rel:has-many,join:id=vendor_id"`
|
||||
}
|
||||
|
||||
type Card struct {
|
||||
bun.BaseModel `bun:"table:card"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
CardNumber int `bun:"card_number"`
|
||||
Year int `bun:"year"`
|
||||
VendorID *string `bun:"vendor_id"`
|
||||
Vendor *Vendor `bun:"rel:belongs-to,join:vendor_id=id"`
|
||||
CardSetID *string `bun:"card_set_id"`
|
||||
CardSet *CardSet `bun:"rel:belongs-to,join:card_set_id=id"`
|
||||
RoosterID *string `bun:"rooster_id"`
|
||||
Rooster *Rooster `bun:"rel:belongs-to,join:rooster_id=id"`
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func TestSelectSports(t *testing.T) {
|
||||
var sports []Sport
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.NewSelect().Model(&sports).Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 4, len(sports))
|
||||
}
|
||||
|
||||
func TestSelectTeams(t *testing.T) {
|
||||
var teams []Team
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.NewSelect().Model(&teams).Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 122, len(teams))
|
||||
}
|
||||
|
||||
func TestSelectFieldPositions(t *testing.T) {
|
||||
var positions []FieldPosition
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.NewSelect().Model(&positions).Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 44, len(positions))
|
||||
}
|
||||
|
||||
func TestSelectPlayers(t *testing.T) {
|
||||
var players []Player
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.NewSelect().Model(&players).Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 38, len(players))
|
||||
}
|
||||
|
||||
func TestSelectRoosters(t *testing.T) {
|
||||
var roosters []Rooster
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.NewSelect().Model(&roosters).Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 11, len(roosters))
|
||||
}
|
||||
|
||||
func TestSelectVendors(t *testing.T) {
|
||||
var vendors []Vendor
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.NewSelect().Model(&vendors).Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 9, len(vendors))
|
||||
}
|
||||
|
||||
func TestSelectCardSets(t *testing.T) {
|
||||
var cardSets []CardSet
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.NewSelect().Model(&cardSets).Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 15, len(cardSets))
|
||||
}
|
||||
|
||||
func TestSelectCards(t *testing.T) {
|
||||
var cards []Card
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.NewSelect().Model(&cards).Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 10, len(cards))
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"kontor-api-go/pkg/schema"
|
||||
"kontor-api-echo/pkg/schema"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
@@ -0,0 +1,28 @@
|
||||
# ---------- Stage 1: Build ----------
|
||||
FROM golang:1.25-alpine AS builder
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
# Ensure a portable, static-ish binary
|
||||
ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64
|
||||
# Copy and download dependencies
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
# Copy the source code
|
||||
COPY . .
|
||||
# Build the Go application (strip debug info for smaller size)
|
||||
RUN go build -trimpath -ldflags="-s -w" -o kontor cmd/kontor/main.go
|
||||
|
||||
# ---------- Stage 2: Final ----------
|
||||
FROM alpine:latest
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
# Install runtime dependencies you actually need
|
||||
RUN apk add --no-cache ca-certificates tzdata curl
|
||||
# Create non-root user for security
|
||||
RUN addgroup -S kontor && adduser -S -G kontor -H -s /sbin/nologin kontor
|
||||
# Copy the binary and set ownership
|
||||
COPY --from=builder --chown=kontor:kontor /app/kontor /app/kontor
|
||||
# Run as non-root user
|
||||
USER kontor
|
||||
# Set the entrypoint command
|
||||
ENTRYPOINT ["/app/kontor"]
|
||||
@@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"kontor-api-go/pkg/handler"
|
||||
"kontor-api-go/pkg/schema"
|
||||
"kontor-api-fiber/pkg/handler"
|
||||
"kontor-api-fiber/pkg/schema"
|
||||
"log"
|
||||
|
||||
jwtware "github.com/gofiber/contrib/jwt"
|
||||
@@ -23,17 +23,19 @@ func main() {
|
||||
// SigningKey: jwtware.SigningKey{Key: []byte("secret")},
|
||||
// }))
|
||||
|
||||
// app.Use(logger.New())
|
||||
//app.Use(logger.New())
|
||||
|
||||
app.Get("/health", handler.GetHealth)
|
||||
app.Post("/login", handler.Login)
|
||||
|
||||
api := app.Group("/api", logger.New(), jwtware.New(jwtware.Config{
|
||||
SigningKey: jwtware.SigningKey{Key: []byte("secret")},
|
||||
}))
|
||||
api.Use(logger.New())
|
||||
handler.SetupComicRoutes(api)
|
||||
handler.SetupMediaRoutes(api)
|
||||
// Listen on port 8900
|
||||
app.Listen(":8900")
|
||||
app.Listen(":8600")
|
||||
|
||||
log.Println("Kontor finished")
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
module kontor-api-go
|
||||
module kontor-api-fiber
|
||||
|
||||
go 1.24.2
|
||||
|
||||
@@ -7,6 +7,8 @@ require (
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/gofiber/contrib/jwt v1.1.2 // indirect
|
||||
github.com/gofiber/fiber/v2 v2.52.10 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
@@ -17,12 +19,15 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/uptrace/bun v1.2.16 // indirect
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.16 // indirect
|
||||
github.com/uptrace/bun/driver/pgdriver v1.2.16 // indirect
|
||||
github.com/uptrace/bun/extra/bundebug v1.2.16 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.68.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
@@ -31,6 +36,7 @@ require (
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
mellium.im/sasl v0.3.2 // indirect
|
||||
)
|
||||
@@ -7,6 +7,10 @@ github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfa
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
|
||||
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/gofiber/contrib/jwt v1.1.2 h1:GmWnOqT4A15EkA8IPXwSpvNUXZR4u5SMj+geBmyLAjs=
|
||||
github.com/gofiber/contrib/jwt v1.1.2/go.mod h1:CpIwrkUQ3Q6IP8y9n3f0wP9bOnSKx39EDp2fBVgMFVk=
|
||||
github.com/gofiber/fiber v1.14.6 h1:QRUPvPmr8ijQuGo1MgupHBn8E+wW0IKqiOvIZPtV70o=
|
||||
@@ -38,10 +42,14 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
github.com/uptrace/bun v1.2.16 h1:QlObi6ZIK5Ao7kAALnh91HWYNZUBbVwye52fmlQM9kc=
|
||||
@@ -50,6 +58,8 @@ github.com/uptrace/bun/dialect/pgdialect v1.2.16 h1:KFNZ0LxAyczKNfK/IJWMyaleO6eI
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.16/go.mod h1:IJdMeV4sLfh0LDUZl7TIxLI0LipF1vwTK3hBC7p5qLo=
|
||||
github.com/uptrace/bun/driver/pgdriver v1.2.16 h1:b1kpXKUxtTSGYow5Vlsb+dKV3z0R7aSAJNfMfKp61ZU=
|
||||
github.com/uptrace/bun/driver/pgdriver v1.2.16/go.mod h1:H6lUZ9CBfp1X5Vq62YGSV7q96/v94ja9AYFjKvdoTk0=
|
||||
github.com/uptrace/bun/extra/bundebug v1.2.16 h1:3OXAfHTU4ydu2+4j05oB1BxPx6+ypdWIVzTugl/7zl0=
|
||||
github.com/uptrace/bun/extra/bundebug v1.2.16/go.mod h1:vk6R/1i67/S2RvUI5AH/m3P5e67mOkfDCmmCsAPUumo=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA=
|
||||
@@ -78,6 +88,11 @@ golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0=
|
||||
mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY=
|
||||
@@ -2,8 +2,8 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"kontor-api-go/pkg/schema"
|
||||
"kontor-api-go/pkg/utils"
|
||||
"kontor-api-fiber/pkg/schema"
|
||||
"kontor-api-fiber/pkg/utils"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
@@ -0,0 +1,77 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"kontor-api-fiber/pkg/schema"
|
||||
"log"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func SetupComicRoutes(api fiber.Router) {
|
||||
comics := api.Group("/comics")
|
||||
comics.Get("/comics", GetAllComics)
|
||||
comics.Get("/publishers", GetAllPublishers)
|
||||
comics.Get("/comicworks", GetAllComicWorks)
|
||||
}
|
||||
|
||||
func GetAllComics(c *fiber.Ctx) error {
|
||||
var comics []schema.Comic
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = schema.GetDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.NewSelect().Model(&comics).Relation("Publisher").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return c.JSON(comics)
|
||||
}
|
||||
|
||||
func GetAllPublishers(c *fiber.Ctx) error {
|
||||
var publishers []schema.Publisher
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = schema.GetDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.NewSelect().Model(&publishers).Relation("ParentPublisher").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return c.JSON(publishers)
|
||||
}
|
||||
|
||||
func GetAllComicWorks(c *fiber.Ctx) error {
|
||||
var comic_works []schema.ComicWork
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = schema.GetDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.NewSelect().Model(&comic_works).Relation("Comic").Relation("Artist").Relation("WorkType").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return c.JSON(comic_works)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"kontor-api-go/pkg/schema"
|
||||
"kontor-api-fiber/pkg/schema"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type Profile struct {
|
||||
bun.BaseModel `bun:"table:profile"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
FirstName string `bun:"first_name"`
|
||||
LastName string `bun:"last_name"`
|
||||
UserName string `bun:"user_name,unique:user_name"`
|
||||
Email string `bun:"email"`
|
||||
Password string `bun:"password"`
|
||||
Enabled bool `bun:"enabled"`
|
||||
Assignments []Assignment `bun:"rel:has-many,join:id=profile_id"`
|
||||
Tokens []Token `bun:"rel:has-many,join:id=profile_id"`
|
||||
}
|
||||
|
||||
type Permission struct {
|
||||
bun.BaseModel `bun:"table:permission"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name"`
|
||||
Assignments []Assignment `bun:"rel:has-many,join:id=permission_id"`
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
bun.BaseModel `bun:"table:token"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name"`
|
||||
LastUsedAt time.Time `bun:"last_used_date,nullzero,notnull,default:current_timestamp"`
|
||||
Enabled bool `bun:"enabled,default:true"`
|
||||
ProfileID *string `bun:"profile_id"`
|
||||
Profile *Profile `bun:"rel:belongs-to,join:profile_id=id"`
|
||||
}
|
||||
|
||||
type Assignment struct {
|
||||
bun.BaseModel `bun:"table:assignment"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
ProfileID *string `bun:"profile_id"`
|
||||
Profile *Profile `bun:"rel:belongs-to,join:profile_id=id"`
|
||||
PermissionID *string `bun:"permission_id"`
|
||||
Permission *Permission `bun:"rel:belongs-to,join:permission_id=id"`
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type Publisher struct {
|
||||
bun.BaseModel `bun:"table:publisher"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name"`
|
||||
WebLink string `bun:"weblink"`
|
||||
|
||||
ParentPublisherID *string `bun:"parent_publisher_id"`
|
||||
ParentPublisher *Publisher `bun:"rel:belongs-to,join:parent_publisher_id=id"`
|
||||
Imprints []Publisher `bun:"rel:has-many,join:id=parent_publisher_id"`
|
||||
Comics []Comic `bun:"rel:has-many,join:id=publisher_id"`
|
||||
}
|
||||
|
||||
type Comic struct {
|
||||
bun.BaseModel `bun:"table:comic"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Title string `bun:"title,unique:title,notnull"`
|
||||
CurrentOrder bool `bun:"current_order"`
|
||||
Completed bool `bun:"completed"`
|
||||
WebLink string `bun:"weblink"`
|
||||
|
||||
PublisherID *string `bun:"publisher_id"`
|
||||
Publisher *Publisher `bun:"rel:belongs-to,join:publisher_id=id"`
|
||||
Issues []Issue `bun:"rel:has-many,join:id=comic_id"`
|
||||
StoryArcs []StoryArc `bun:"rel:has-many,join:id=comic_id"`
|
||||
TradePaperbacks []TradePaperback `bun:"rel:has-many,join:id=comic_id"`
|
||||
Volumes []Volume `bun:"rel:has-many,join:id=comic_id"`
|
||||
ComicWorks []ComicWork `bun:"rel:has-many,join:id=comic_id"`
|
||||
}
|
||||
|
||||
type Artist struct {
|
||||
bun.BaseModel `bun:"table:artist"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:title,notnull"`
|
||||
WebLink string `bun:"weblink"`
|
||||
}
|
||||
|
||||
type Issue struct {
|
||||
bun.BaseModel `bun:"table:issue"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
InStock bool `bun:"in_stock"`
|
||||
IsRead bool `bun:"is_read"`
|
||||
IssueNumber string `bu:"issue_number"`
|
||||
Title string `bun:"title"`
|
||||
PublishedOn time.Time `bun:"published_on"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
StoryArcID *string `bun:"story_arc_id"`
|
||||
StoryArc *StoryArc `bun:"rel:belongs-to,join:story_arc_id=id"`
|
||||
VolumeID *string `bun:"volume_id"`
|
||||
Volume *Volume `bun:"rel:belongs-to,join:volume_id=id"`
|
||||
}
|
||||
|
||||
type StoryArc struct {
|
||||
bun.BaseModel `bun:"table:story_arc"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name,notnull"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
VolumeID *string `bun:"volume_id"`
|
||||
Volume *Volume `bun:"rel:belongs-to,join:volume_id=id"`
|
||||
}
|
||||
|
||||
type TradePaperback struct {
|
||||
bun.BaseModel `bun:"table:trade_paperback"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name,notnull"`
|
||||
IssueStart int `bun:"issue_start"`
|
||||
IssueEnd int `bun:"issue_end"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
}
|
||||
|
||||
type Volume struct {
|
||||
bun.BaseModel `bun:"table:volume"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name,notnull"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
}
|
||||
|
||||
type WorkType struct {
|
||||
bun.BaseModel `bun:"table:worktype"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name,notnull"`
|
||||
}
|
||||
|
||||
type ComicWork struct {
|
||||
bun.BaseModel `bun:"table:comic_work"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
ArtistID *string `bun:"artist_id"`
|
||||
Artist *Artist `bun:"rel:belongs-to,join:artist_id=id"`
|
||||
ComicID *string `bun:"comic_id"`
|
||||
Comic *Comic `bun:"rel:belongs-to,join:comic_id=id"`
|
||||
WorkTypeID *string `bun:"work_type_id"`
|
||||
WorkType *WorkType `bun:"rel:belongs-to,join:work_type_id=id"`
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"database/sql"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/extra/bundebug"
|
||||
|
||||
"github.com/uptrace/bun/dialect/pgdialect"
|
||||
"github.com/uptrace/bun/driver/pgdriver"
|
||||
)
|
||||
|
||||
func GetTestDatabase() (*bun.DB, error) {
|
||||
var err error
|
||||
|
||||
dsn := "postgres://kontor:kontor@localhost:5432/kontor?sslmode=disable"
|
||||
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))
|
||||
|
||||
sqldb.SetMaxOpenConns(4)
|
||||
sqldb.SetMaxIdleConns(4)
|
||||
|
||||
DB := bun.NewDB(sqldb, pgdialect.New())
|
||||
DB.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true)))
|
||||
|
||||
if err = DB.Ping(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Println("Returned Database Connection")
|
||||
return DB, nil
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log.Println("Setup Test")
|
||||
exitCode := m.Run()
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func TestSelectComics(t *testing.T) {
|
||||
var comics []Comic
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.NewSelect().Model(&comics).Relation("Publisher").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 168, len(comics))
|
||||
}
|
||||
|
||||
func TestSelectPublishers(t *testing.T) {
|
||||
var publishers []Publisher
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.NewSelect().Model(&publishers).Relation("ParentPublisher").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 19, len(publishers))
|
||||
}
|
||||
|
||||
func TestSelectWorkTypes(t *testing.T) {
|
||||
var comic_works []ComicWork
|
||||
var err error
|
||||
var db *bun.DB
|
||||
ctx := context.Background()
|
||||
|
||||
db, err = GetTestDatabase()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.NewSelect().Model(&comic_works).Relation("Comic").Relation("Artist").Relation("WorkType").Scan(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 59, len(comic_works))
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type MediaFile struct {
|
||||
bun.BaseModel `bun:"table:media_file"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
CloudLink string `bun:"cloud_link"`
|
||||
FileName string `bun:"file_name"`
|
||||
Path string `bun:"path"`
|
||||
Review bool `bun:"review"`
|
||||
Title string `bun:"title"`
|
||||
WebLink string `bun:"url,unique:url"`
|
||||
ShouldDownload bool `bun:"should_download"`
|
||||
|
||||
MediaActorFiles []MediaActorFile `bun:"rel:has-many,join:id=media_file_id"`
|
||||
}
|
||||
|
||||
type MediaActor struct {
|
||||
bun.BaseModel `bun:"table:media_actor"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Name string `bun:"name,unique:name"`
|
||||
WebLink string `bun:"url,unique:url"`
|
||||
|
||||
MediaActorFiles []MediaActorFile `bun:"rel:has-many,join:id=media_actor_id"`
|
||||
}
|
||||
|
||||
type MediaActorFile struct {
|
||||
bun.BaseModel `bun:"table:media_actor_file"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
MediaActorID *string `bun:"media_actor_id"`
|
||||
MediaActor *MediaActor `bun:"rel:belongs-to,join:media_actor_id=id"`
|
||||
MediaFileID *string `bun:"media_file_id"`
|
||||
MediaFile *MediaFile `bun:"rel:belongs-to,join:media_file_id=id"`
|
||||
}
|
||||
|
||||
type MediaArticle struct {
|
||||
bun.BaseModel `bun:"table:media_article"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
Review bool `bun:"review"`
|
||||
Title string `bun:"title"`
|
||||
WebLink string `bun:"url,unique:url"`
|
||||
}
|
||||
|
||||
type MediaVideo struct {
|
||||
bun.BaseModel `bun:"table:media_article"`
|
||||
|
||||
ID string `bun:"id,pk"`
|
||||
CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"`
|
||||
Version int `bun:"version,default:0"`
|
||||
CloudLink string `bun:"cloud_link"`
|
||||
FileName string `bun:"file_name"`
|
||||
Path string `bun:"path"`
|
||||
Review bool `bun:"review"`
|
||||
Title string `bun:"title"`
|
||||
WebLink string `bun:"url,unique:url"`
|
||||
ShouldDownload bool `bun:"should_download"`
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package utils
|
||||
|
||||
import "golang.org/x/crypto/bcrypt"
|
||||
|
||||
func ComparePassword(hashedPassword, password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"kontor-api-fiber/pkg/schema"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
func GenerateToken(user schema.Profile) (string, error) {
|
||||
// Create the Claims
|
||||
claims := jwt.MapClaims{
|
||||
"name": user.FirstName + ", " + user.LastName,
|
||||
"admin": true,
|
||||
"exp": time.Now().Add(time.Hour * 72).Unix(),
|
||||
}
|
||||
|
||||
// Create token
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
// Generate encoded token and send it as response.
|
||||
t, err := token.SignedString([]byte("secret"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func VerifyToken(tokenString string) (bool, error) {
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte("secret"), nil
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return token.Valid, nil
|
||||
}
|
||||
+17
-87
@@ -1,10 +1,5 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
//id 'application'
|
||||
id 'jacoco'
|
||||
id 'test-report-aggregation'
|
||||
id 'jacoco-report-aggregation'
|
||||
alias(libs.plugins.lombok)
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -12,89 +7,27 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation libs.javalin.bundle
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
implementation libs.postgresql
|
||||
implementation libs.hibernate
|
||||
annotationProcessor libs.hibernate.modelgen
|
||||
testImplementation( platform( libs.junit.bom.get().toString() ))
|
||||
testImplementation "org.junit.jupiter:junit-jupiter"
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
implementation libs.javalin // Pulling in Javalin
|
||||
implementation libs.jackson // For JSON serialization of persons
|
||||
implementation libs.slf4j // To see some Javalin logging
|
||||
compileOnly libs.lombok
|
||||
annotationProcessor libs.lombok
|
||||
|
||||
implementation libs.postgresql
|
||||
implementation libs.hibernate
|
||||
implementation libs.hibernate.validator
|
||||
implementation libs.hypersistence
|
||||
implementation libs.validation.api
|
||||
annotationProcessor libs.hibernate.jpamodelgen
|
||||
implementation libs.el
|
||||
annotationProcessor libs.openapi.annotation
|
||||
implementation libs.javalin.openapi
|
||||
implementation libs.javalin.swagger
|
||||
implementation libs.javalin.redoc
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testImplementation "org.junit.jupiter:junit-jupiter"
|
||||
}
|
||||
|
||||
testing {
|
||||
suites {
|
||||
configureEach {
|
||||
useJUnitJupiter()
|
||||
dependencies {
|
||||
implementation project()
|
||||
implementation libs.hsqldb
|
||||
implementation libs.sqlite.jdbc
|
||||
runtimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
}
|
||||
test(JvmTestSuite) {
|
||||
targets {
|
||||
all {
|
||||
testTask.configure {
|
||||
reports {
|
||||
junitXml {
|
||||
outputPerTestCase = true // defaults to false
|
||||
mergeReruns = true // defaults to false
|
||||
}
|
||||
}
|
||||
finalizedBy(jacocoTestReport)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
integrationTest(JvmTestSuite) {
|
||||
targets {
|
||||
all {
|
||||
testTask.configure {
|
||||
shouldRunAfter(test)
|
||||
finalizedBy(jacocoTestReport)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('check') {
|
||||
dependsOn(testing.suites.integrationTest)
|
||||
dependsOn(testing.suites.test)
|
||||
dependsOn tasks.named('testAggregateTestReport', TestReport)
|
||||
dependsOn tasks.named('integrationTestAggregateTestReport', TestReport)
|
||||
}
|
||||
|
||||
|
||||
jacocoTestReport {
|
||||
dependsOn test, integrationTest
|
||||
reports {
|
||||
xml.required = true
|
||||
csv.required = false
|
||||
}
|
||||
}
|
||||
|
||||
reporting {
|
||||
reports {
|
||||
testAggregateTestReport(AggregateTestReport) {
|
||||
}
|
||||
integrationTestAggregateTestReport(AggregateTestReport) {
|
||||
}
|
||||
integrationTestCodeCoverageReport(JacocoCoverageReport) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// application {
|
||||
// mainClass = 'de.thpeetz.kontor.api.Main'
|
||||
// }
|
||||
|
||||
task fatJar(type: Jar) {
|
||||
manifest {
|
||||
attributes 'Main-Class': 'de.thpeetz.kontor.Main'
|
||||
@@ -105,9 +38,6 @@ task fatJar(type: Jar) {
|
||||
}
|
||||
|
||||
build.dependsOn fatJar
|
||||
// distZip.dependsOn fatJar
|
||||
// distTar.dependsOn fatJar
|
||||
// startScripts.dependsOn fatJar
|
||||
|
||||
wrapper {
|
||||
gradleVersion = "9.2.1"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
description='Kontor with Spring Boot'
|
||||
description='Kontor with Javalin'
|
||||
version=0.2.0-SNAPSHOT
|
||||
group=de.thpeetz
|
||||
org.gradle.warning.mode=none
|
||||
|
||||
|
||||
@@ -1,32 +1,40 @@
|
||||
[versions]
|
||||
gradle = "8.6"
|
||||
hibernate = "6.4.4.Final"
|
||||
hsqldb = "2.7.1"
|
||||
jackson = "2.16.1"
|
||||
javalin = "6.7.0"
|
||||
junit = "5.8.2"
|
||||
lombok = "8.6"
|
||||
postgresql = "42.6.2"
|
||||
#junit = "5.8.2"
|
||||
junit = "5.10.3"
|
||||
#slf4j = "1.7.36"
|
||||
slf4j = "2.0.16"
|
||||
sonarqube = "3.3"
|
||||
spotbugs = "6.0.7"
|
||||
hsqldb = "2.7.1"
|
||||
sqlite = "3.25.2"
|
||||
jackson = "2.16.1"
|
||||
#javalin = "4.6.4"
|
||||
javalin = "6.7.0"
|
||||
lombok = "1.18.34"
|
||||
postgresql = "42.7.3"
|
||||
hibernate = "7.0.5.Final"
|
||||
validation = "2.0.1.Final"
|
||||
hypersistence = "3.14.1"
|
||||
el = "4.0.1"
|
||||
openapi = "6.7.0-3"
|
||||
|
||||
[libraries]
|
||||
hibernate = { module = "org.hibernate.orm:hibernate-core", version.ref = "hibernate" }
|
||||
hibernate-modelgen = { module = "org.hibernate.orm:hibernate-jpamodelgen", version.ref = "hibernate" }
|
||||
hsqldb = { module = "org.hsqldb:hsqldb", version.ref = "hsqldb" }
|
||||
jackson = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
|
||||
javalin = { module = "io.javalin:javalin", version.ref = "javalin" }
|
||||
javalin-bundle = { module = "io.javalin:javalin-bundle", version.ref = "javalin" }
|
||||
junit = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
|
||||
junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
|
||||
slf4j = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
|
||||
hsqldb = { module = "org.hsqldb:hsqldb", version.ref = "hsqldb" }
|
||||
jackson = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
|
||||
sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
|
||||
javalin = { module = "io.javalin:javalin", version.ref = "javalin" }
|
||||
javalin-bundle = { module = "io.javalin:javalin-bundle", version.ref = "javalin" }
|
||||
lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" }
|
||||
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
|
||||
slf4j = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
|
||||
sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
|
||||
|
||||
[plugins]
|
||||
spotbugs = { id = "com.github.spotbugs", version.ref = "spotbugs" }
|
||||
sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" }
|
||||
lombok = { id = "io.freefair.lombok", version.ref = "lombok" }
|
||||
hibernate = { module = "org.hibernate.orm:hibernate-core", version.ref = "hibernate" }
|
||||
hibernate-jpamodelgen = { module = "org.hibernate.orm:hibernate-jpamodelgen", version.ref = "hibernate" }
|
||||
hibernate-validator = { module = "org.hibernate:hibernate-validator", version.ref = "hibernate" }
|
||||
validation-api = { module = "javax.validation:validation-api", version.ref = "validation" }
|
||||
el = { module = "org.glassfish:jakarta.el", version.ref = "el" }
|
||||
hypersistence = { module = "io.hypersistence:hypersistence-utils-hibernate-70", version.ref = "hypersistence" }
|
||||
javalin-openapi = { module = "io.javalin.community.openapi:javalin-openapi-plugin", version.ref = "openapi" }
|
||||
javalin-swagger = { module = "io.javalin.community.openapi:javalin-swagger-plugin", version.ref = "openapi" }
|
||||
javalin-redoc = { module = "io.javalin.community.openapi:javalin-redoc-plugin", version.ref = "openapi" }
|
||||
openapi-annotation = { module = "io.javalin.community.openapi:openapi-annotation-processor", version.ref = "openapi" }
|
||||
@@ -1,26 +0,0 @@
|
||||
package de.thpeetz.kontor;
|
||||
|
||||
import io.javalin.Javalin;
|
||||
|
||||
import static io.javalin.apibuilder.ApiBuilder.get;
|
||||
import static io.javalin.apibuilder.ApiBuilder.path;
|
||||
import java.util.HashMap;
|
||||
|
||||
import de.thpeetz.kontor.web.PersonHandler;
|
||||
|
||||
public class JavalinApp {
|
||||
|
||||
public static Javalin create() {
|
||||
return Javalin.create((var config) -> config.router.apiBuilder(() -> {
|
||||
|
||||
path("/health", () -> get(ctx -> {
|
||||
HashMap<String, String> status = new HashMap<>();
|
||||
status.put("status", "ok");
|
||||
ctx.json(status);
|
||||
}));
|
||||
path("/persons", () -> {
|
||||
get(PersonHandler.listAll);
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,89 @@
|
||||
package de.thpeetz.kontor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Main {
|
||||
import de.thpeetz.kontor.infrastructure.AppHibernateSessionFactory;
|
||||
import de.thpeetz.kontor.models.comics.Comic;
|
||||
import de.thpeetz.kontor.models.media.MediaFile;
|
||||
import io.javalin.Javalin;
|
||||
import io.javalin.config.Key;
|
||||
//import jakarta.persistence.EntityManager;
|
||||
|
||||
public class Main {
|
||||
private static Logger logger = LoggerFactory.getLogger(Main.class);
|
||||
private static short port = 8400;
|
||||
// private static Key<EntityManagerFactory> emf = new
|
||||
// Key<EntityManagerFactory>("entityManagerFactory");
|
||||
private static Key<SessionFactory> sf = new Key<>("SessionFactory");
|
||||
|
||||
public static void main(String[] args) {
|
||||
JavalinApp.create().start(port);
|
||||
logger.info("API's alive for real :-)))");
|
||||
Javalin app = Javalin.create(config -> {
|
||||
config.requestLogger.http((ctx, ms) -> {
|
||||
var path = ctx.path();
|
||||
if (!path.equals("/health")) {
|
||||
logger.info(ctx.path());
|
||||
}
|
||||
});
|
||||
config.appData(sf, AppHibernateSessionFactory.getSessionFactory());
|
||||
});
|
||||
app.get("/", ctx -> ctx.json("Ok"));
|
||||
app.get("/health", ctx -> {
|
||||
HashMap<String, String> status = new HashMap<>();
|
||||
status.put("status", "ok");
|
||||
ctx.json(status);
|
||||
});
|
||||
app.get("/api/v1/comics", ctx -> {
|
||||
var result = new LinkedList<Comic>();
|
||||
ctx.appData(sf).inStatelessTransaction(session -> {
|
||||
session.createSelectionQuery("from Comic", Comic.class).getResultList().forEach(comic -> {
|
||||
result.add(comic);
|
||||
});
|
||||
});
|
||||
// var em = ctx.appData(emf).createEntityManager();
|
||||
// logger.info(em.toString());
|
||||
// EntityManager entityManager = ctx.attribute("entityManager");
|
||||
// entityManager.createQuery("select * from Comic",
|
||||
// Comic.class).getResultList().forEach(comic -> {
|
||||
// result.add(comic);
|
||||
// });
|
||||
ctx.json(result);
|
||||
});
|
||||
app.get("/api/v1/media/files", ctx -> {
|
||||
var result = new LinkedList<>();
|
||||
// ctx.appData(sf).inStatelessTransaction(session -> {
|
||||
// var files = session.createSelectionQuery("from MediaFile",
|
||||
// MediaFile.class).getResultList();
|
||||
// result.addAll(files);
|
||||
// });
|
||||
var list = ctx.appData(sf).openStatelessSession().createSelectionQuery("from MediaFile", MediaFile.class)
|
||||
.getResultList();
|
||||
result.addAll(list);
|
||||
ctx.json(result);
|
||||
});
|
||||
// app.before(ctx -> {
|
||||
// ctx.attribute("configuration", AppHibernateConfig.configuration());
|
||||
// ctx.attribute("sessionFactory",
|
||||
// AppHibernateSessionFactory.getSessionFactory());
|
||||
// });
|
||||
app.after(ctx -> {
|
||||
// SessionFactory sessionFactory = ctx.attribute("sessionFactory");
|
||||
// if (sessionFactory != null) {
|
||||
// logger.info("close SessionFactory");
|
||||
// sessionFactory.close();
|
||||
// }
|
||||
// EntityManager entityManager = ctx.attribute("entityManager");
|
||||
// if (entityManager != null) {
|
||||
// logger.info("close EntityManager");
|
||||
// entityManager.close();
|
||||
// }
|
||||
});
|
||||
app.start(port);
|
||||
|
||||
logger.info("API's alive for real :-)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.thpeetz.kontor.infrastructure;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.hibernate.StatelessSession;
|
||||
|
||||
public class AppHibernate {
|
||||
|
||||
public static void inTransaction(Consumer<StatelessSession> consumer) {
|
||||
AppHibernateSessionFactory.getSessionFactory().inStatelessTransaction(consumer);
|
||||
}
|
||||
|
||||
public static <R> R fromTransaction(Function<StatelessSession, R> function) {
|
||||
return AppHibernateSessionFactory.getSessionFactory().fromStatelessTransaction(function);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package de.thpeetz.kontor.infrastructure;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.tool.schema.Action;
|
||||
|
||||
import de.thpeetz.kontor.models.comics.Artist;
|
||||
import de.thpeetz.kontor.models.comics.Comic;
|
||||
import de.thpeetz.kontor.models.comics.ComicWork;
|
||||
import de.thpeetz.kontor.models.comics.Issue;
|
||||
import de.thpeetz.kontor.models.comics.IssueWork;
|
||||
import de.thpeetz.kontor.models.comics.Publisher;
|
||||
import de.thpeetz.kontor.models.comics.StoryArc;
|
||||
import de.thpeetz.kontor.models.comics.TradePaperback;
|
||||
import de.thpeetz.kontor.models.comics.Volume;
|
||||
import de.thpeetz.kontor.models.comics.Worktype;
|
||||
import de.thpeetz.kontor.models.media.MediaActor;
|
||||
import de.thpeetz.kontor.models.media.MediaActorFile;
|
||||
import de.thpeetz.kontor.models.media.MediaArticle;
|
||||
import de.thpeetz.kontor.models.media.MediaFile;
|
||||
import de.thpeetz.kontor.models.media.MediaVideo;
|
||||
|
||||
public class AppHibernateConfig {
|
||||
public static Configuration configuration() {
|
||||
var configuration = new Configuration();
|
||||
var settings = new Properties();
|
||||
settings.put(AvailableSettings.JAKARTA_JDBC_DRIVER, "org.postgresql.Driver");
|
||||
settings.put(AvailableSettings.JAKARTA_JDBC_URL, "jdbc:postgresql://postgres:5432/kontor");
|
||||
settings.put(AvailableSettings.JAKARTA_JDBC_USER, "kontor");
|
||||
settings.put(AvailableSettings.JAKARTA_JDBC_PASSWORD, "kontor");
|
||||
settings.put(AvailableSettings.HIGHLIGHT_SQL, true);
|
||||
settings.put(AvailableSettings.HBM2DDL_AUTO, Action.ACTION_UPDATE);
|
||||
|
||||
configuration.setProperties(settings);
|
||||
configuration.addAnnotatedClass(MediaFile.class);
|
||||
configuration.addAnnotatedClass(MediaActorFile.class);
|
||||
configuration.addAnnotatedClass(MediaActor.class);
|
||||
configuration.addAnnotatedClass(MediaArticle.class);
|
||||
configuration.addAnnotatedClass(MediaVideo.class);
|
||||
configuration.addAnnotatedClass(Comic.class);
|
||||
configuration.addAnnotatedClass(Publisher.class);
|
||||
configuration.addAnnotatedClass(Artist.class);
|
||||
configuration.addAnnotatedClass(ComicWork.class);
|
||||
configuration.addAnnotatedClass(Issue.class);
|
||||
configuration.addAnnotatedClass(IssueWork.class);
|
||||
configuration.addAnnotatedClass(StoryArc.class);
|
||||
configuration.addAnnotatedClass(TradePaperback.class);
|
||||
configuration.addAnnotatedClass(Volume.class);
|
||||
configuration.addAnnotatedClass(Worktype.class);
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package de.thpeetz.kontor.infrastructure;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.hibernate.SessionFactory;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistry;
|
||||
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AppHibernateSessionFactory {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(AppHibernateSessionFactory.class);
|
||||
|
||||
private static SessionFactory sessionFactory;
|
||||
|
||||
public static SessionFactory getSessionFactory() {
|
||||
if (Objects.isNull(sessionFactory)) {
|
||||
var configuration = AppHibernateConfig.configuration();
|
||||
final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
|
||||
.applySettings(configuration.getProperties())
|
||||
.build();
|
||||
try {
|
||||
sessionFactory = configuration.buildSessionFactory(registry);
|
||||
logger.info("SessionFactory created");
|
||||
} catch (Throwable ex) {
|
||||
logger.error("Failed to create session factory", ex);
|
||||
StandardServiceRegistryBuilder.destroy(registry);
|
||||
}
|
||||
}
|
||||
return sessionFactory;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package de.thpeetz.kontor.models;
|
||||
|
||||
public class Person {
|
||||
private String name;
|
||||
private int age;
|
||||
|
||||
public Person(String name, int age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package de.thpeetz.kontor.models.comics;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table
|
||||
@Getter
|
||||
@Setter
|
||||
public class Artist {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@Column(unique = true, nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(nullable = true)
|
||||
private String weblink;
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "artist", cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
List<ComicWork> comicWorks = new LinkedList<>();
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "artist", cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
List<IssueWork> issueWorks = new LinkedList<>();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package de.thpeetz.kontor.models.comics;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table
|
||||
@Getter
|
||||
@Setter
|
||||
public class Comic {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@Column(unique = true, nullable = false)
|
||||
private String title;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "publisher_id", nullable = false)
|
||||
@JsonIgnoreProperties({ "comics" })
|
||||
private Publisher publisher;
|
||||
|
||||
@Column(name = "current_order")
|
||||
private Boolean currentOrder = false;
|
||||
|
||||
private Boolean completed = false;
|
||||
|
||||
@Column(nullable = true)
|
||||
private String weblink;
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
List<ComicWork> comicWorks;
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
private List<Issue> issues = new LinkedList<>();
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
private List<StoryArc> storyArcs = new LinkedList<>();
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
private List<TradePaperback> tradePaperbacks = new LinkedList<>();
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
private List<Volume> volumes = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer("Comic{");
|
||||
sb.append("title='").append(title).append('\'');
|
||||
sb.append(", publisher=").append(publisher);
|
||||
sb.append(", currentOrder=").append(currentOrder);
|
||||
sb.append(", completed=").append(completed);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getPublisherName() {
|
||||
return this.publisher.getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.thpeetz.kontor.models.comics;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.StatelessSession;
|
||||
import org.hibernate.annotations.processing.Find;
|
||||
|
||||
public interface ComicQueries {
|
||||
|
||||
@Find
|
||||
List<Comic> getAllComics(StatelessSession session);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package de.thpeetz.kontor.models.comics;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
|
||||
@Entity
|
||||
@Table(name = "comic_work")
|
||||
public class ComicWork {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "comic_id", nullable = false)
|
||||
private Comic comic;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "artist_id", nullable = false)
|
||||
private Artist artist;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "work_type_id", nullable = false)
|
||||
private Worktype workType;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("ComicWork{");
|
||||
sb.append("comic=").append(comic);
|
||||
sb.append(", artist=").append(artist);
|
||||
sb.append(", workType=").append(workType);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package de.thpeetz.kontor.models.comics;
|
||||
|
||||
import java.time.YearMonth;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import io.hypersistence.utils.hibernate.type.basic.YearMonthDateType;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import org.hibernate.annotations.Type;
|
||||
|
||||
@Entity
|
||||
@Table
|
||||
@Getter
|
||||
@Setter
|
||||
public class Issue {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "comic_id", nullable = false)
|
||||
@JsonIgnoreProperties({ "issues" })
|
||||
private Comic comic;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "volume_id", nullable = true)
|
||||
@JsonIgnoreProperties({ "issues" })
|
||||
private Volume volume;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "story_arc_id", nullable = true)
|
||||
@JsonIgnoreProperties({ "issues" })
|
||||
private StoryArc storyArc;
|
||||
|
||||
@Column(name = "issue_number", nullable = false)
|
||||
private String issueNumber;
|
||||
|
||||
@Column(nullable = true)
|
||||
private String title;
|
||||
|
||||
@Type(YearMonthDateType.class)
|
||||
@Column(name = "published_on", columnDefinition = "date", nullable = true)
|
||||
private YearMonth publishedOn;
|
||||
|
||||
@Column(name = "is_read")
|
||||
private Boolean isRead;
|
||||
|
||||
@Column(name = "in_stock")
|
||||
private Boolean inStock;
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "issue", cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
List<IssueWork> issueWorks;
|
||||
|
||||
public String getComicTitle() {
|
||||
return comic.getTitle();
|
||||
}
|
||||
|
||||
public String getVolumeName() {
|
||||
if (volume != null) {
|
||||
return volume.getName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getFullTitle() {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.append(this.getComic().getTitle());
|
||||
stringBuilder.append(" #");
|
||||
stringBuilder.append(this.getIssueNumber());
|
||||
if (this.title != null && !this.title.isEmpty()) {
|
||||
stringBuilder.append(": ");
|
||||
stringBuilder.append(this.title);
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("Issue{");
|
||||
result.append("comic=");
|
||||
result.append(comic);
|
||||
result.append(", volume=");
|
||||
result.append(volume);
|
||||
result.append(", storyArc=");
|
||||
result.append(storyArc);
|
||||
result.append(", issueNumber='");
|
||||
result.append(issueNumber);
|
||||
result.append(", title='");
|
||||
result.append(title);
|
||||
result.append(", publishedOn=");
|
||||
result.append(publishedOn);
|
||||
result.append(", isRead=");
|
||||
result.append(isRead);
|
||||
result.append(", inStock=");
|
||||
result.append(inStock);
|
||||
result.append("}");
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package de.thpeetz.kontor.models.comics;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "issue_work")
|
||||
@Getter
|
||||
@Setter
|
||||
public class IssueWork {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "issue_id", nullable = false)
|
||||
private Issue issue;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "artist_id", nullable = false)
|
||||
private Artist artist;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "work_type_id", nullable = false)
|
||||
private Worktype workType;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("IssueWork{");
|
||||
sb.append("issue=").append(issue);
|
||||
sb.append(", artist=").append(artist);
|
||||
sb.append(", workType=").append(workType);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package de.thpeetz.kontor.models.comics;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonBackReference;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table
|
||||
@Getter
|
||||
@Setter
|
||||
public class Publisher {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@Column(unique = true, nullable = false)
|
||||
private String name;
|
||||
|
||||
private String weblink;
|
||||
|
||||
@JsonBackReference
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "parent_publisher_id", nullable = true)
|
||||
@JsonIgnoreProperties({ "comics" })
|
||||
private Publisher parentCompany;
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.LAZY, mappedBy = "parentCompany", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
private List<Publisher> imprints = new LinkedList<>();
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "publisher", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
private List<Comic> comics = new LinkedList<>();
|
||||
|
||||
public String getParentCompanyName() {
|
||||
if (parentCompany != null) {
|
||||
return parentCompany.name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer("Publisher{");
|
||||
sb.append("name='").append(name).append('\'');
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package de.thpeetz.kontor.models.comics;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "story_arc")
|
||||
@Getter
|
||||
@Setter
|
||||
public class StoryArc {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "comic_id", nullable = false)
|
||||
@JsonIgnoreProperties({ "storyArcs" })
|
||||
private Comic comic;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "volume_id", nullable = true)
|
||||
@JsonIgnoreProperties({ "storyArcs" })
|
||||
private Volume volume;
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "storyArc", cascade = CascadeType.REMOVE, orphanRemoval = true)
|
||||
private List<Issue> issues = new LinkedList<>();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package de.thpeetz.kontor.models.comics;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "trade_paperback")
|
||||
@Getter
|
||||
@Setter
|
||||
public class TradePaperback {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "comic_id", nullable = false)
|
||||
private Comic comic;
|
||||
|
||||
@Column
|
||||
private Integer issueStart;
|
||||
|
||||
@Column
|
||||
private Integer issueEnd;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package de.thpeetz.kontor.models.comics;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Entity
|
||||
@Table
|
||||
@Getter
|
||||
@Setter
|
||||
public class Volume {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String name;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "comic_id", nullable = false)
|
||||
@JsonIgnoreProperties({ "volumes" })
|
||||
private Comic comic;
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "volume", cascade = CascadeType.REMOVE, orphanRemoval = true)
|
||||
private List<StoryArc> storyArcs = new LinkedList<>();
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "volume", cascade = CascadeType.REMOVE, orphanRemoval = true)
|
||||
private List<Issue> issues = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder("Volume{");
|
||||
result.append("name=");
|
||||
result.append(name);
|
||||
result.append("comic=Comic{title=");
|
||||
result.append(comic.getTitle());
|
||||
result.append("}}");
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package de.thpeetz.kontor.models.comics;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
|
||||
@Entity
|
||||
@Table
|
||||
public class Worktype {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@Column(unique = true, nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "workType", cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
List<ComicWork> comicWorks = new LinkedList<>();
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "workType", cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
List<IssueWork> issueWorks = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer("Worktype{");
|
||||
sb.append("name='").append(name).append('\'');
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package de.thpeetz.kontor.models.media;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Entity
|
||||
@Table(name = "media_actor")
|
||||
public class MediaActor {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@NotEmpty
|
||||
@Column(unique = true)
|
||||
private String name;
|
||||
|
||||
@Column(nullable = true)
|
||||
private String url;
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "media_actor", cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
List<MediaActorFile> mediaActorFiles = new LinkedList<>();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package de.thpeetz.kontor.models.media;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import jakarta.persistence.Version;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Entity
|
||||
@Table(name = "media_actor_file", indexes = {
|
||||
@Index(columnList = "media_file_id, media_actor_id") }, uniqueConstraints = @UniqueConstraint(columnNames = {
|
||||
"media_file_id", "media_actor_id" }))
|
||||
public class MediaActorFile {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "media_file_id")
|
||||
@NotNull
|
||||
private MediaFile media_file;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "media_actor_id")
|
||||
@NotNull
|
||||
private MediaActor media_actor;
|
||||
|
||||
public String getTitle() {
|
||||
return media_file.getTitle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package de.thpeetz.kontor.models.media;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Index;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import jakarta.persistence.Version;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "media_article", indexes = @Index(columnList = "url"), uniqueConstraints = @UniqueConstraint(columnNames = {
|
||||
"url" }))
|
||||
public class MediaArticle {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date", nullable = true)
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@NotEmpty
|
||||
private String url;
|
||||
|
||||
@Column
|
||||
private boolean review;
|
||||
|
||||
@Column(nullable = true)
|
||||
private String title;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package de.thpeetz.kontor.models.media;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Version;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Entity
|
||||
@Table(name = "media_file")
|
||||
public class MediaFile {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date")
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@Column(nullable = true)
|
||||
private String url;
|
||||
|
||||
@Column
|
||||
private boolean review;
|
||||
|
||||
@Column(name = "should_download")
|
||||
private boolean shouldDownload;
|
||||
|
||||
@Column(nullable = true)
|
||||
private String title;
|
||||
|
||||
@Column(name = "cloud_link", nullable = true)
|
||||
private String cloudLink;
|
||||
|
||||
@Column(name = "file_name", nullable = true)
|
||||
private String fileName;
|
||||
|
||||
@Column(nullable = true)
|
||||
private String path;
|
||||
|
||||
@Column(nullable = true)
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "media_file", cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
List<MediaActorFile> mediaActorFiles = new LinkedList<>();
|
||||
|
||||
public static MediaFile newMediaFile(String url) {
|
||||
var mediaFile = new MediaFile();
|
||||
mediaFile.setUrl(url);
|
||||
return mediaFile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.thpeetz.kontor.models.media;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.annotations.processing.Find;
|
||||
|
||||
public interface MediaFileQueries {
|
||||
|
||||
@Find
|
||||
List<MediaFile> getAllMediaFiles();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package de.thpeetz.kontor.models.media;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import jakarta.persistence.Version;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Entity
|
||||
@Table(name = "media_video", uniqueConstraints = { @UniqueConstraint(columnNames = { "url" }) })
|
||||
public class MediaVideo {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.UUID)
|
||||
private String id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private Date createdDate;
|
||||
|
||||
@Column(name = "last_modified_date", nullable = true)
|
||||
private Date lastModifiedDate;
|
||||
|
||||
@Column
|
||||
private String url;
|
||||
|
||||
@Column
|
||||
private boolean review;
|
||||
|
||||
@Column(name = "should_download")
|
||||
private boolean shouldDownload;
|
||||
|
||||
@Column(nullable = true)
|
||||
private String title;
|
||||
|
||||
@Column(name = "cloud_link", nullable = true)
|
||||
private String cloudLink;
|
||||
|
||||
@Column(name = "file_name", nullable = true)
|
||||
private String fileName;
|
||||
|
||||
@Column(nullable = true)
|
||||
private String path;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package de.thpeetz.kontor.services.api;
|
||||
|
||||
import de.thpeetz.kontor.models.Person;
|
||||
import java.util.List;
|
||||
|
||||
public interface PersonReader {
|
||||
List<Person> getAll();
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
package de.thpeetz.kontor.services.inmemory;
|
||||
|
||||
import de.thpeetz.kontor.models.Person;
|
||||
import java.util.List;
|
||||
|
||||
import de.thpeetz.kontor.services.api.PersonReader;
|
||||
|
||||
public class InMemoryPersonReader implements PersonReader {
|
||||
|
||||
@Override
|
||||
public List<Person> getAll() {
|
||||
return List.of(
|
||||
new Person("Vincent Vega", 73),
|
||||
new Person("Jules Winnfield", 12));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.thpeetz.kontor.web;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.thpeetz.kontor.infrastructure.AppHibernate;
|
||||
import de.thpeetz.kontor.models.comics.Comic;
|
||||
import de.thpeetz.kontor.models.comics.ComicQueries_;
|
||||
import de.thpeetz.kontor.web.model.ResultComic;
|
||||
import io.javalin.http.Handler;
|
||||
|
||||
public class ComicHandler {
|
||||
public static Handler listAll = (context) -> {
|
||||
List<Comic> result = AppHibernate.fromTransaction(ComicQueries_::getAllComics);
|
||||
context.json(new ResultComic(result));
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package de.thpeetz.kontor.web;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import de.thpeetz.kontor.infrastructure.AppHibernate;
|
||||
import io.javalin.http.Handler;
|
||||
import io.javalin.http.HttpStatus;
|
||||
import de.thpeetz.kontor.models.media.MediaFile;
|
||||
import de.thpeetz.kontor.web.model.NewMediaFile;
|
||||
import de.thpeetz.kontor.web.model.ResultMediaFile;
|
||||
|
||||
public class MediaFileHandler {
|
||||
|
||||
public static Handler listAll = (context) -> {
|
||||
List<MediaFile> result = new LinkedList<>();
|
||||
context.json(new ResultMediaFile(result));
|
||||
};
|
||||
|
||||
public static Handler save = (context) -> {
|
||||
var newMediaFile = context.bodyAsClass(NewMediaFile.class);
|
||||
var result = AppHibernate.fromTransaction(session -> {
|
||||
var insertedId = session.insert(MediaFile.newMediaFile(newMediaFile.url()));
|
||||
return session.get(MediaFile.class, insertedId);
|
||||
});
|
||||
context.json(result).status(HttpStatus.CREATED);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package de.thpeetz.kontor.web;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import de.thpeetz.kontor.services.inmemory.InMemoryPersonReader;
|
||||
import io.javalin.http.Handler;
|
||||
|
||||
public class PersonHandler {
|
||||
|
||||
public static Handler listAll = (context) -> {
|
||||
var personReader = new InMemoryPersonReader();
|
||||
var objMapper = new ObjectMapper();
|
||||
var result = objMapper.valueToTree(personReader.getAll());
|
||||
context.json(result);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package de.thpeetz.kontor.web.model;
|
||||
|
||||
public record NewMediaFile(String url) {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.thpeetz.kontor.web.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.thpeetz.kontor.models.comics.Comic;
|
||||
|
||||
public record ResultComic(List<Comic> comics) {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.thpeetz.kontor.web.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.thpeetz.kontor.models.media.MediaFile;
|
||||
|
||||
public record ResultMediaFile(List<MediaFile> mediaFiles) {
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<logger name="org.hibernate.SQL" level="debug" additivity="false">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</logger>
|
||||
|
||||
<!-- <logger name="org.hibernate.orm.jdbc.bind" level="trace" additivity="false">-->
|
||||
<!-- <appender-ref ref="STDOUT" />-->
|
||||
<!-- </logger>-->
|
||||
|
||||
<!-- <logger name="org.hibernate.orm.jdbc.extract" level="trace" additivity="false">-->
|
||||
<!-- <appender-ref ref="STDOUT" />-->
|
||||
<!-- </logger>-->
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,9 @@
|
||||
*
|
||||
!build.gradle.kts
|
||||
!gradle.properties
|
||||
!settings.gradle.kts
|
||||
!src/*
|
||||
!build/*-runner
|
||||
!build/*-runner.jar
|
||||
!build/lib/*
|
||||
!build/quarkus-app/*
|
||||
@@ -0,0 +1,42 @@
|
||||
# Gradle
|
||||
.gradle/
|
||||
.kotlin/
|
||||
build/
|
||||
|
||||
# Eclipse
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
bin/
|
||||
|
||||
# IntelliJ
|
||||
.idea
|
||||
*.ipr
|
||||
*.iml
|
||||
*.iws
|
||||
|
||||
# NetBeans
|
||||
nb-configuration.xml
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode
|
||||
.factorypath
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Vim
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# patch
|
||||
*.orig
|
||||
*.rej
|
||||
|
||||
# Local environment
|
||||
.env
|
||||
|
||||
# Plugin directory
|
||||
/.quarkus/cli/plugins/
|
||||
# TLS Certificates
|
||||
.certs/
|
||||
@@ -0,0 +1,37 @@
|
||||
# ----------------------------------------------------------------------- #
|
||||
FROM gradle:9.2-jdk AS builder
|
||||
WORKDIR /
|
||||
COPY ./src/ ./src/
|
||||
COPY ./build.gradle.kts ./
|
||||
COPY ./gradle.properties ./
|
||||
COPY ./settings.gradle.kts ./
|
||||
RUN gradle build --no-daemon
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
#FROM alpine/java:21-jdk AS run
|
||||
|
||||
#RUN adduser --system appuser
|
||||
|
||||
#COPY --from=builder --chown=appuser:appuser /build/libs/kontor-quarkus-0.2.0-SNAPSHOT.jar app.jar
|
||||
|
||||
#EXPOSE 8800
|
||||
#USER appuser
|
||||
#CMD ["java", "-jar", "-Dquarkus.http.host=0.0.0.0", "app.jar"]
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
FROM registry.access.redhat.com/ubi9/openjdk-21:1.23
|
||||
|
||||
ENV LANGUAGE='en_US:en'
|
||||
|
||||
# We make four distinct layers so if there are application changes the library layers can be re-used
|
||||
COPY --from=builder --chown=185 build/quarkus-app/lib/ /deployments/lib/
|
||||
COPY --from=builder --chown=185 build/quarkus-app/*.jar /deployments/
|
||||
COPY --from=builder --chown=185 build/quarkus-app/app/ /deployments/app/
|
||||
COPY --from=builder --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/
|
||||
|
||||
EXPOSE 8800
|
||||
USER 185
|
||||
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
|
||||
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
|
||||
|
||||
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
|
||||
@@ -0,0 +1,67 @@
|
||||
# kontor-quarkus
|
||||
|
||||
This project uses Quarkus, the Supersonic Subatomic Java Framework.
|
||||
|
||||
If you want to learn more about Quarkus, please visit its website: <https://quarkus.io/>.
|
||||
|
||||
## Running the application in dev mode
|
||||
|
||||
You can run your application in dev mode that enables live coding using:
|
||||
|
||||
```shell script
|
||||
./gradlew quarkusDev
|
||||
```
|
||||
|
||||
> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at <http://localhost:8080/q/dev/>.
|
||||
|
||||
## Packaging and running the application
|
||||
|
||||
The application can be packaged using:
|
||||
|
||||
```shell script
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
It produces the `quarkus-run.jar` file in the `build/quarkus-app/` directory.
|
||||
Be aware that 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`.
|
||||
|
||||
If you want to build an _über-jar_, execute the following command:
|
||||
|
||||
```shell script
|
||||
./gradlew build -Dquarkus.package.jar.type=uber-jar
|
||||
```
|
||||
|
||||
The application, packaged as an _über-jar_, is now runnable using `java -jar build/*-runner.jar`.
|
||||
|
||||
## Creating a native executable
|
||||
|
||||
You can create a native executable using:
|
||||
|
||||
```shell script
|
||||
./gradlew build -Dquarkus.native.enabled=true
|
||||
```
|
||||
|
||||
Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
|
||||
|
||||
```shell script
|
||||
./gradlew build -Dquarkus.native.enabled=true -Dquarkus.native.container-build=true
|
||||
```
|
||||
|
||||
You can then execute your native executable with: `./build/kontor-quarkus-1.0.0-SNAPSHOT-runner`
|
||||
|
||||
If you want to learn more about building native executables, please consult <https://quarkus.io/guides/gradle-tooling>.
|
||||
|
||||
## Related Guides
|
||||
|
||||
- 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
|
||||
- Kotlin ([guide](https://quarkus.io/guides/kotlin)): Write your services in Kotlin
|
||||
|
||||
## Provided Code
|
||||
|
||||
### REST
|
||||
|
||||
Easily start your REST Web Services
|
||||
|
||||
[Related guide section...](https://quarkus.io/guides/getting-started-reactive#reactive-jax-rs-resources)
|
||||
@@ -0,0 +1,57 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "2.2.21"
|
||||
kotlin("plugin.allopen") version "2.2.21"
|
||||
id("io.quarkus")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
val quarkusPlatformGroupId: String by project
|
||||
val quarkusPlatformArtifactId: String by project
|
||||
val quarkusPlatformVersion: String by project
|
||||
|
||||
dependencies {
|
||||
implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
|
||||
implementation("io.quarkus:quarkus-container-image-docker")
|
||||
implementation("io.quarkus:quarkus-rest-jackson")
|
||||
implementation("io.quarkus:quarkus-kotlin")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
implementation("io.quarkus:quarkus-arc")
|
||||
implementation("io.quarkus:quarkus-rest")
|
||||
implementation("io.quarkus:quarkus-smallrye-openapi")
|
||||
implementation("io.quarkus:quarkus-smallrye-health")
|
||||
implementation("io.quarkus:quarkus-hibernate-orm-panache-kotlin")
|
||||
implementation("io.quarkus:quarkus-jdbc-h2")
|
||||
implementation("io.quarkus:quarkus-jdbc-postgresql")
|
||||
testImplementation("io.quarkus:quarkus-junit5")
|
||||
testImplementation("io.rest-assured:rest-assured")
|
||||
}
|
||||
|
||||
group = "de.thpeetz"
|
||||
version = "0.2.0-SNAPSHOT"
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
tasks.withType<Test> {
|
||||
systemProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager")
|
||||
jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED")
|
||||
}
|
||||
allOpen {
|
||||
annotation("jakarta.ws.rs.Path")
|
||||
annotation("jakarta.enterprise.context.ApplicationScoped")
|
||||
annotation("jakarta.persistence.Entity")
|
||||
annotation("io.quarkus.test.junit.QuarkusTest")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21
|
||||
javaParameters = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# Gradle properties
|
||||
|
||||
quarkusPluginId=io.quarkus
|
||||
quarkusPluginVersion=3.30.6
|
||||
quarkusPlatformGroupId=io.quarkus.platform
|
||||
quarkusPlatformArtifactId=quarkus-bom
|
||||
quarkusPlatformVersion=3.30.6
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
# https://gradle.org/release-checksums/
|
||||
distributionSha256Sum=f86344275d1b194688dd330abf9f6f2344cd02872ffee035f2d1ea2fd60cf7f3
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-all.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
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" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+92
@@ -0,0 +1,92 @@
|
||||
@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=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
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 execute
|
||||
|
||||
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
|
||||
|
||||
: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 %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,13 @@
|
||||
pluginManagement {
|
||||
val quarkusPluginVersion: String by settings
|
||||
val quarkusPluginId: String by settings
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
mavenLocal()
|
||||
}
|
||||
plugins {
|
||||
id(quarkusPluginId) version quarkusPluginVersion
|
||||
}
|
||||
}
|
||||
rootProject.name="kontor-quarkus"
|
||||
@@ -0,0 +1,98 @@
|
||||
####
|
||||
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
|
||||
#
|
||||
# Before building the container image run:
|
||||
#
|
||||
# ./gradlew build
|
||||
#
|
||||
# Then, build the image with:
|
||||
#
|
||||
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/kontor-quarkus-jvm .
|
||||
#
|
||||
# Then run the container using:
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus-jvm
|
||||
#
|
||||
# If you want to include the debug port into your docker image
|
||||
# you will have to expose the debug port (default 5005 being the default) 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 :
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus-jvm
|
||||
#
|
||||
# This image uses the `run-java.sh` script to run the application.
|
||||
# This scripts computes the command line to execute your Java application, and
|
||||
# includes memory/GC tuning.
|
||||
# You can configure the behavior using the following environment properties:
|
||||
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
|
||||
# the default JVM options, use `JAVA_OPTS_APPEND` to append options
|
||||
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
|
||||
# in JAVA_OPTS (example: "-Dsome.property=foo")
|
||||
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
|
||||
# used to calculate a default maximal heap memory based on a containers restriction.
|
||||
# If used in a container without any memory constraints for the container then this
|
||||
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
|
||||
# of the container available memory as set here. The default is `50` which means 50%
|
||||
# of the available memory is used as an upper boundary. You can skip this mechanism by
|
||||
# setting this value to `0` in which case no `-Xmx` option is added.
|
||||
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
|
||||
# is used to calculate a default initial heap memory based on the maximum heap memory.
|
||||
# If used in a container without any memory constraints for the container then this
|
||||
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
|
||||
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
|
||||
# is used as the initial heap size. You can skip this mechanism by setting this value
|
||||
# to `0` in which case no `-Xms` option is added (example: "25")
|
||||
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
|
||||
# This is used to calculate the maximum value of the initial heap memory. If used in
|
||||
# a container without any memory constraints for the container then this option has
|
||||
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
|
||||
# here. The default is 4096MB which means the calculated value of `-Xms` never will
|
||||
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
|
||||
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
|
||||
# when things are happening. This option, if set to true, will set
|
||||
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
|
||||
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
|
||||
# true").
|
||||
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
|
||||
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
|
||||
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
|
||||
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
|
||||
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
|
||||
# (example: "20")
|
||||
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
|
||||
# (example: "40")
|
||||
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
|
||||
# (example: "4")
|
||||
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
|
||||
# previous GC times. (example: "90")
|
||||
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
|
||||
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
|
||||
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
|
||||
# contain the necessary JRE command-line options to specify the required GC, which
|
||||
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
|
||||
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
|
||||
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
|
||||
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
|
||||
# accessed directly. (example: "foo.example.com,bar.example.com")
|
||||
#
|
||||
###
|
||||
FROM registry.access.redhat.com/ubi9/openjdk-21:1.23
|
||||
|
||||
ENV LANGUAGE='en_US:en'
|
||||
|
||||
|
||||
# We make four distinct layers so if there are application changes the library layers can be re-used
|
||||
COPY --chown=185 build/quarkus-app/lib/ /deployments/lib/
|
||||
COPY --chown=185 build/quarkus-app/*.jar /deployments/
|
||||
COPY --chown=185 build/quarkus-app/app/ /deployments/app/
|
||||
COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/
|
||||
|
||||
EXPOSE 8080
|
||||
USER 185
|
||||
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
|
||||
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
|
||||
|
||||
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
####
|
||||
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
|
||||
#
|
||||
# Before building the container image run:
|
||||
#
|
||||
# ./gradlew build -Dquarkus.package.jar.type=legacy-jar
|
||||
#
|
||||
# Then, build the image with:
|
||||
#
|
||||
# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/kontor-quarkus-legacy-jar .
|
||||
#
|
||||
# Then run the container using:
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus-legacy-jar
|
||||
#
|
||||
# If you want to include the debug port into your docker image
|
||||
# you will have to expose the debug port (default 5005 being the default) 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 :
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus-legacy-jar
|
||||
#
|
||||
# This image uses the `run-java.sh` script to run the application.
|
||||
# This scripts computes the command line to execute your Java application, and
|
||||
# includes memory/GC tuning.
|
||||
# You can configure the behavior using the following environment properties:
|
||||
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
|
||||
# the default JVM options, use `JAVA_OPTS_APPEND` to append options
|
||||
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
|
||||
# in JAVA_OPTS (example: "-Dsome.property=foo")
|
||||
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
|
||||
# used to calculate a default maximal heap memory based on a containers restriction.
|
||||
# If used in a container without any memory constraints for the container then this
|
||||
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
|
||||
# of the container available memory as set here. The default is `50` which means 50%
|
||||
# of the available memory is used as an upper boundary. You can skip this mechanism by
|
||||
# setting this value to `0` in which case no `-Xmx` option is added.
|
||||
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
|
||||
# is used to calculate a default initial heap memory based on the maximum heap memory.
|
||||
# If used in a container without any memory constraints for the container then this
|
||||
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
|
||||
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
|
||||
# is used as the initial heap size. You can skip this mechanism by setting this value
|
||||
# to `0` in which case no `-Xms` option is added (example: "25")
|
||||
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
|
||||
# This is used to calculate the maximum value of the initial heap memory. If used in
|
||||
# a container without any memory constraints for the container then this option has
|
||||
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
|
||||
# here. The default is 4096MB which means the calculated value of `-Xms` never will
|
||||
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
|
||||
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
|
||||
# when things are happening. This option, if set to true, will set
|
||||
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
|
||||
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
|
||||
# true").
|
||||
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
|
||||
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
|
||||
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
|
||||
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
|
||||
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
|
||||
# (example: "20")
|
||||
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
|
||||
# (example: "40")
|
||||
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
|
||||
# (example: "4")
|
||||
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
|
||||
# previous GC times. (example: "90")
|
||||
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
|
||||
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
|
||||
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
|
||||
# contain the necessary JRE command-line options to specify the required GC, which
|
||||
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
|
||||
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
|
||||
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
|
||||
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
|
||||
# accessed directly. (example: "foo.example.com,bar.example.com")
|
||||
#
|
||||
###
|
||||
FROM registry.access.redhat.com/ubi9/openjdk-21:1.23
|
||||
|
||||
ENV LANGUAGE='en_US:en'
|
||||
|
||||
|
||||
COPY build/lib/* /deployments/lib/
|
||||
COPY build/*-runner.jar /deployments/quarkus-run.jar
|
||||
|
||||
EXPOSE 8080
|
||||
USER 185
|
||||
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
|
||||
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
|
||||
|
||||
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
|
||||
@@ -0,0 +1,29 @@
|
||||
####
|
||||
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
|
||||
#
|
||||
# Before building the container image run:
|
||||
#
|
||||
# ./gradlew build -Dquarkus.native.enabled=true
|
||||
#
|
||||
# Then, build the image with:
|
||||
#
|
||||
# docker build -f src/main/docker/Dockerfile.native -t quarkus/kontor-quarkus .
|
||||
#
|
||||
# Then run the container using:
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus
|
||||
#
|
||||
# The ` registry.access.redhat.com/ubi9/ubi-minimal:9.7` 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.7
|
||||
WORKDIR /work/
|
||||
RUN chown 1001 /work \
|
||||
&& chmod "g+rwX" /work \
|
||||
&& chown 1001:root /work
|
||||
COPY --chown=1001:root --chmod=0755 build/*-runner /work/application
|
||||
|
||||
EXPOSE 8080
|
||||
USER 1001
|
||||
|
||||
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
|
||||
@@ -0,0 +1,32 @@
|
||||
####
|
||||
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
|
||||
# It uses a micro base image, tuned for Quarkus native executables.
|
||||
# It reduces the size of the resulting container image.
|
||||
# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
|
||||
#
|
||||
# Before building the container image run:
|
||||
#
|
||||
# ./gradlew build -Dquarkus.native.enabled=true
|
||||
#
|
||||
# Then, build the image with:
|
||||
#
|
||||
# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/kontor-quarkus .
|
||||
#
|
||||
# Then run the container using:
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/kontor-quarkus
|
||||
#
|
||||
# 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
|
||||
WORKDIR /work/
|
||||
RUN chown 1001 /work \
|
||||
&& chmod "g+rwX" /work \
|
||||
&& chown 1001:root /work
|
||||
COPY --chown=1001:root --chmod=0755 build/*-runner /work/application
|
||||
|
||||
EXPOSE 8080
|
||||
USER 1001
|
||||
|
||||
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
|
||||
@@ -0,0 +1,14 @@
|
||||
package de.thpeetz
|
||||
|
||||
import jakarta.ws.rs.GET
|
||||
import jakarta.ws.rs.Path
|
||||
import jakarta.ws.rs.Produces
|
||||
import jakarta.ws.rs.core.MediaType
|
||||
|
||||
@Path("/hello")
|
||||
class GreetingResource {
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
fun hello() = "Hello from Quarkus REST"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package de.thpeetz.kontor
|
||||
|
||||
import jakarta.ws.rs.core.Application
|
||||
import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition
|
||||
import org.eclipse.microprofile.openapi.annotations.info.Info
|
||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag
|
||||
|
||||
@OpenAPIDefinition(
|
||||
info = Info(
|
||||
title = "Kontor",
|
||||
version = "0.2.0"
|
||||
)
|
||||
)
|
||||
class KontorApplication: Application()
|
||||
@@ -0,0 +1,25 @@
|
||||
package de.thpeetz.kontor.comics.domain
|
||||
|
||||
import io.quarkus.hibernate.orm.panache.kotlin.PanacheEntityBase
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.Table
|
||||
import org.eclipse.microprofile.openapi.annotations.media.Schema
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
@Entity
|
||||
@Table(name = "comic")
|
||||
class Comic: PanacheEntityBase {
|
||||
@Id
|
||||
lateinit var id: String
|
||||
lateinit var createdDate: LocalDateTime
|
||||
lateinit var lastModifiedDate: LocalDateTime
|
||||
lateinit var version: Integer
|
||||
lateinit var title: String
|
||||
lateinit var webLink: String
|
||||
var completed: Boolean = false
|
||||
var currentOrder: Boolean = false
|
||||
lateinit var publisherId: String
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user