Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a1179a9ee | |||
| df2d8f33a5 | |||
| 4170eb14fb |
@@ -2,6 +2,8 @@ FROM python:3.13-alpine
|
|||||||
|
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
VOLUME /data
|
||||||
|
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
||||||
# Install uv
|
# Install uv
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ from sqlmodel import Session
|
|||||||
|
|
||||||
from app.core.db import engine
|
from app.core.db import engine
|
||||||
|
|
||||||
|
|
||||||
def get_db() -> Generator[Session, None, None]:
|
def get_db() -> Generator[Session, None, None]:
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
yield session
|
yield session
|
||||||
|
|
||||||
|
|
||||||
SessionDep = Annotated[Session, Depends(get_db)]
|
SessionDep = Annotated[Session, Depends(get_db)]
|
||||||
|
|||||||
@@ -5,27 +5,28 @@ from app.models import RegistrationCreate
|
|||||||
from app import crud
|
from app import crud
|
||||||
from app.api.deps import SessionDep
|
from app.api.deps import SessionDep
|
||||||
|
|
||||||
|
from app.core.utils import is_registration_open
|
||||||
|
|
||||||
|
from fastapi.responses import RedirectResponse
|
||||||
|
|
||||||
router = APIRouter(prefix="/registration")
|
router = APIRouter(prefix="/registration")
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/register",
|
"/register_form",
|
||||||
)
|
)
|
||||||
def register(*, session: SessionDep, registration_create: Annotated[RegistrationCreate, Form()]):
|
def register(
|
||||||
print(registration_create)
|
*, session: SessionDep, registration_create: Annotated[RegistrationCreate, Form()]
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Register
|
Register
|
||||||
"""
|
"""
|
||||||
|
|
||||||
registration = crud.create_registration(session=session, registration_create=registration_create)
|
if is_registration_open():
|
||||||
#if settings.emails_enabled and user_in.email:
|
crud.create_registration(
|
||||||
# email_data = generate_new_account_email(
|
session=session, registration_create=registration_create
|
||||||
# email_to=user_in.email, username=user_in.email, password=user_in.password
|
)
|
||||||
# )
|
|
||||||
# send_email(
|
|
||||||
# email_to=user_in.email,
|
|
||||||
# subject=email_data.subject,
|
|
||||||
# html_content=email_data.html_content,
|
|
||||||
# )
|
|
||||||
|
|
||||||
return registration
|
return RedirectResponse("/success.html", status_code=303)
|
||||||
|
else:
|
||||||
|
return RedirectResponse("/", status_code=303)
|
||||||
|
|||||||
@@ -1,10 +1,24 @@
|
|||||||
from pydantic_settings import BaseSettings
|
from pydantic_settings import BaseSettings
|
||||||
import os
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
date_format = "%Y-%m-%dT%H:%M:%S%z"
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
API_V1_STR: str = ""
|
API_V1_STR: str = ""
|
||||||
PROJECT_NAME: str = "Choriosity Anmeldung"
|
PROJECT_NAME: str = "Choriosity Anmeldung"
|
||||||
SQLALCHEMY_DATABASE_URI: str = "sqlite:///" + os.environ.get("DATABASE_URL", "./db.sqlite")
|
SQLALCHEMY_DATABASE_URI: str = "sqlite:///" + os.environ.get(
|
||||||
|
"DATABASE_URL", "/data/db.sqlite"
|
||||||
|
)
|
||||||
|
NOT_BEFORE: datetime = datetime.strptime(
|
||||||
|
os.environ.get("NOT_BEFORE", "2000-01-01T00:00:01+02:00"), date_format
|
||||||
|
)
|
||||||
|
NOT_AFTER: datetime = datetime.strptime(
|
||||||
|
os.environ.get("NOT_AFTER", "2100-01-01T00:00:01+02:00"), date_format
|
||||||
|
)
|
||||||
|
TZ: ZoneInfo = ZoneInfo(os.environ.get("TZ", "Europe/Berlin"))
|
||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI))
|
|||||||
# otherwise, SQLModel might fail to initialize relationships properly
|
# otherwise, SQLModel might fail to initialize relationships properly
|
||||||
# for more details: https://github.com/fastapi/full-stack-fastapi-template/issues/28
|
# for more details: https://github.com/fastapi/full-stack-fastapi-template/issues/28
|
||||||
|
|
||||||
|
|
||||||
def init_db(session: Session) -> None:
|
def init_db(session: Session) -> None:
|
||||||
# Tables should be created with Alembic migrations
|
# Tables should be created with Alembic migrations
|
||||||
# But if you don't want to use migrations, create
|
# But if you don't want to use migrations, create
|
||||||
|
|||||||
9
app/core/utils.py
Normal file
9
app/core/utils.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from app.core.config import settings
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def is_registration_open():
|
||||||
|
now = datetime.now(tz=settings.TZ)
|
||||||
|
not_before = now >= settings.NOT_BEFORE
|
||||||
|
not_after = now <= settings.NOT_AFTER
|
||||||
|
return not_before and not_after, not_before, not_after
|
||||||
@@ -3,7 +3,9 @@ from sqlmodel import Session
|
|||||||
from app.models import Registration, RegistrationCreate
|
from app.models import Registration, RegistrationCreate
|
||||||
|
|
||||||
|
|
||||||
def create_registration(*, session: Session, registration_create: RegistrationCreate) -> Registration:
|
def create_registration(
|
||||||
|
*, session: Session, registration_create: RegistrationCreate
|
||||||
|
) -> Registration:
|
||||||
db_obj = Registration.model_validate(registration_create)
|
db_obj = Registration.model_validate(registration_create)
|
||||||
session.add(db_obj)
|
session.add(db_obj)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|||||||
35
app/main.py
35
app/main.py
@@ -1,7 +1,10 @@
|
|||||||
from fastapi import FastAPI
|
from fastapi import FastAPI, Request
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
|
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
from app.api.main import api_router
|
from app.api.main import api_router
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
|
|
||||||
@@ -9,6 +12,8 @@ from app.core.db import init_db, engine
|
|||||||
|
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
|
|
||||||
|
from app.core.utils import is_registration_open
|
||||||
|
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
init_db(session)
|
init_db(session)
|
||||||
|
|
||||||
@@ -16,10 +21,34 @@ app = FastAPI(
|
|||||||
title=settings.PROJECT_NAME, openapi_url=f"{settings.API_V1_STR}/openapi.json"
|
title=settings.PROJECT_NAME, openapi_url=f"{settings.API_V1_STR}/openapi.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
templates = Jinja2Templates(directory="app/templates")
|
||||||
|
|
||||||
@api_router.get("/")
|
@api_router.get("/")
|
||||||
def index():
|
def index(request: Request):
|
||||||
return FileResponse("app/index.html")
|
reg_open, not_before, not_after = is_registration_open()
|
||||||
|
if reg_open:
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request=request, name="registration-open.html", context={}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request=request, name="registration-closed.html", context={
|
||||||
|
"not_before": settings.NOT_BEFORE,
|
||||||
|
"not_after": settings.NOT_AFTER,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@api_router.get("/success.html")
|
||||||
|
def success(request: Request):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
request=request, name="registration-success.html", context={
|
||||||
|
"not_before": settings.NOT_BEFORE,
|
||||||
|
"not_after": settings.NOT_AFTER,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
app.include_router(api_router, prefix=settings.API_V1_STR)
|
app.include_router(api_router, prefix=settings.API_V1_STR)
|
||||||
|
|
||||||
|
app.mount("/static", StaticFiles(directory="app/static"), name="static")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,18 +25,21 @@ class PeriodEnum(str, Enum):
|
|||||||
twoyears = "1 - 2 Jahre"
|
twoyears = "1 - 2 Jahre"
|
||||||
longterm = "mehr als zwei Jahre"
|
longterm = "mehr als zwei Jahre"
|
||||||
|
|
||||||
|
|
||||||
class RegistrationBase(SQLModel):
|
class RegistrationBase(SQLModel):
|
||||||
email: EmailStr = Field(unique=True, index=True, max_length=255)
|
email: EmailStr = Field(max_length=255)
|
||||||
first_name: str
|
first_name: str
|
||||||
last_name: str
|
last_name: str
|
||||||
birthday: date
|
birthday: date
|
||||||
voice: VoiceEnum
|
voice: VoiceEnum
|
||||||
duration: PeriodEnum
|
duration: PeriodEnum
|
||||||
|
number_of_attempts: int
|
||||||
|
|
||||||
|
|
||||||
class RegistrationCreate(RegistrationBase):
|
class RegistrationCreate(RegistrationBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Registration(RegistrationBase, table=True):
|
class Registration(RegistrationBase, table=True):
|
||||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||||
timestamp: datetime = Field(default_factory=func.now)
|
timestamp: datetime = Field(default_factory=func.now)
|
||||||
|
|
||||||
|
|||||||
11
app/templates/registration-closed.html
Normal file
11
app/templates/registration-closed.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Choriosity Anmeldung</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
Die Anmeldung ist leider noch nicht geöffnet. Sie öffnet am {{ not_before }} und schließt am {{ not_after }}.
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<form method="POST" action="/registration/register">
|
<form method="POST" action="/registration/register_form">
|
||||||
|
|
||||||
|
|
||||||
<label for="email">E-Mail-Adresse:</label> <input type="email" id="email" name="email" />
|
<label for="email">E-Mail-Adresse:</label> <input type="email" id="email" name="email" />
|
||||||
@@ -17,32 +17,32 @@
|
|||||||
<legend>Welche Stimme singst du?</legend>
|
<legend>Welche Stimme singst du?</legend>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="base" name="voice" value="Bass" checked />
|
<input type="radio" id="base" name="voice" value="Bass" />
|
||||||
<label for="base">Bass</label>
|
<label for="base">Bass</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="tenor" name="voice" value="Tenor" checked />
|
<input type="radio" id="tenor" name="voice" value="Tenor" />
|
||||||
<label for="tenor">Tenor</label>
|
<label for="tenor">Tenor</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="alto" name="voice" value="Alt" checked />
|
<input type="radio" id="alto" name="voice" value="Alt" />
|
||||||
<label for="alto">Alt</label>
|
<label for="alto">Alt</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="soprano" name="voice" value="Sopran" checked />
|
<input type="radio" id="soprano" name="voice" value="Sopran" />
|
||||||
<label for="soprano">Sopran</label>
|
<label for="soprano">Sopran</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="women" name="voice" value="Alt oder Sopran" checked />
|
<input type="radio" id="women" name="voice" value="Alt oder Sopran" />
|
||||||
<label for="women">Sopran oder Alt</label>
|
<label for="women">Sopran oder Alt</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="men" name="voice" value="Bass oder Tenor" checked />
|
<input type="radio" id="men" name="voice" value="Bass oder Tenor" />
|
||||||
<label for="men">Bass oder Tenor</label>
|
<label for="men">Bass oder Tenor</label>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@@ -51,26 +51,28 @@
|
|||||||
<legend>Wie lange bleibst du in Ulm?</legend>
|
<legend>Wie lange bleibst du in Ulm?</legend>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="halfyear" name="duration" value="1/2 Jahr" checked />
|
<input type="radio" id="halfyear" name="duration" value="1/2 Jahr" />
|
||||||
<label for="halfyear">1/2 Jahr</label>
|
<label for="halfyear">1/2 Jahr</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="halfyear" name="duration" value="1/2 - 1 Jahr" checked />
|
<input type="radio" id="halfyear" name="duration" value="1/2 - 1 Jahr" />
|
||||||
<label for="halfyear">1/2 - 1 Jahr</label>
|
<label for="halfyear">1/2 - 1 Jahr</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="twoyears" name="duration" value="1 - 2 Jahre" checked />
|
<input type="radio" id="twoyears" name="duration" value="1 - 2 Jahre" />
|
||||||
<label for="twoyears">1 - 2 jahre</label>
|
<label for="twoyears">1 - 2 jahre</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input type="radio" id="longterm" name="duration" value="mehr als zwei Jahre" checked />
|
<input type="radio" id="longterm" name="duration" value="mehr als zwei Jahre" />
|
||||||
<label for="longterm">länger als 2 Jahre</label>
|
<label for="longterm">länger als 2 Jahre</label>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
<label for="number_of_attempts">Wie oft hast du schon versucht, dich anzumelden?</label><input type="number" id="number_of_attempts" name="number_of_attempts" />
|
||||||
|
|
||||||
<input type="submit" value="Anmelden" />
|
<input type="submit" value="Anmelden" />
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
11
app/templates/registration-success.html
Normal file
11
app/templates/registration-success.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Choriosity Anmeldung</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
Danke für deine Anmeldung
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -9,6 +9,7 @@ dependencies = [
|
|||||||
"pydantic>=2.11.5",
|
"pydantic>=2.11.5",
|
||||||
"pydantic-settings>=2.9.1",
|
"pydantic-settings>=2.9.1",
|
||||||
"sqlmodel>=0.0.24",
|
"sqlmodel>=0.0.24",
|
||||||
|
"tzdata>=2025.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.uv]
|
[tool.uv]
|
||||||
|
|||||||
11
uv.lock
generated
11
uv.lock
generated
@@ -43,6 +43,7 @@ dependencies = [
|
|||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
{ name = "pydantic-settings" },
|
{ name = "pydantic-settings" },
|
||||||
{ name = "sqlmodel" },
|
{ name = "sqlmodel" },
|
||||||
|
{ name = "tzdata" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
@@ -58,6 +59,7 @@ requires-dist = [
|
|||||||
{ name = "pydantic", specifier = ">=2.11.5" },
|
{ name = "pydantic", specifier = ">=2.11.5" },
|
||||||
{ name = "pydantic-settings", specifier = ">=2.9.1" },
|
{ name = "pydantic-settings", specifier = ">=2.9.1" },
|
||||||
{ name = "sqlmodel", specifier = ">=0.0.24" },
|
{ name = "sqlmodel", specifier = ">=0.0.24" },
|
||||||
|
{ name = "tzdata", specifier = ">=2025.2" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
@@ -718,6 +720,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 },
|
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tzdata"
|
||||||
|
version = "2025.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uvicorn"
|
name = "uvicorn"
|
||||||
version = "0.34.2"
|
version = "0.34.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user