vibetuner 2.6.1__py3-none-any.whl → 2.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of vibetuner might be problematic. Click here for more details.
- vibetuner/__init__.py +2 -0
- vibetuner/__main__.py +4 -0
- vibetuner/cli/__init__.py +68 -0
- vibetuner/cli/run.py +161 -0
- vibetuner/config.py +128 -0
- vibetuner/context.py +25 -0
- vibetuner/frontend/AGENTS.md +113 -0
- vibetuner/frontend/CLAUDE.md +113 -0
- vibetuner/frontend/__init__.py +94 -0
- vibetuner/frontend/context.py +10 -0
- vibetuner/frontend/deps.py +41 -0
- vibetuner/frontend/email.py +45 -0
- vibetuner/frontend/hotreload.py +13 -0
- vibetuner/frontend/lifespan.py +26 -0
- vibetuner/frontend/middleware.py +151 -0
- vibetuner/frontend/oauth.py +196 -0
- vibetuner/frontend/routes/__init__.py +12 -0
- vibetuner/frontend/routes/auth.py +150 -0
- vibetuner/frontend/routes/debug.py +414 -0
- vibetuner/frontend/routes/health.py +33 -0
- vibetuner/frontend/routes/language.py +43 -0
- vibetuner/frontend/routes/meta.py +55 -0
- vibetuner/frontend/routes/user.py +94 -0
- vibetuner/frontend/templates.py +176 -0
- vibetuner/logging.py +87 -0
- vibetuner/models/AGENTS.md +165 -0
- vibetuner/models/CLAUDE.md +165 -0
- vibetuner/models/__init__.py +14 -0
- vibetuner/models/blob.py +89 -0
- vibetuner/models/email_verification.py +84 -0
- vibetuner/models/mixins.py +76 -0
- vibetuner/models/oauth.py +57 -0
- vibetuner/models/registry.py +15 -0
- vibetuner/models/types.py +16 -0
- vibetuner/models/user.py +91 -0
- vibetuner/mongo.py +18 -0
- vibetuner/paths.py +112 -0
- vibetuner/services/AGENTS.md +104 -0
- vibetuner/services/CLAUDE.md +104 -0
- vibetuner/services/__init__.py +0 -0
- vibetuner/services/blob.py +175 -0
- vibetuner/services/email.py +50 -0
- vibetuner/tasks/AGENTS.md +98 -0
- vibetuner/tasks/CLAUDE.md +98 -0
- vibetuner/tasks/__init__.py +2 -0
- vibetuner/tasks/context.py +34 -0
- vibetuner/tasks/worker.py +18 -0
- vibetuner/templates/email/AGENTS.md +48 -0
- vibetuner/templates/email/CLAUDE.md +48 -0
- vibetuner/templates/email/default/magic_link.html.jinja +16 -0
- vibetuner/templates/email/default/magic_link.txt.jinja +5 -0
- vibetuner/templates/frontend/AGENTS.md +74 -0
- vibetuner/templates/frontend/CLAUDE.md +74 -0
- vibetuner/templates/frontend/base/favicons.html.jinja +1 -0
- vibetuner/templates/frontend/base/footer.html.jinja +3 -0
- vibetuner/templates/frontend/base/header.html.jinja +0 -0
- vibetuner/templates/frontend/base/opengraph.html.jinja +7 -0
- vibetuner/templates/frontend/base/skeleton.html.jinja +42 -0
- vibetuner/templates/frontend/debug/collections.html.jinja +103 -0
- vibetuner/templates/frontend/debug/components/debug_nav.html.jinja +55 -0
- vibetuner/templates/frontend/debug/index.html.jinja +83 -0
- vibetuner/templates/frontend/debug/info.html.jinja +256 -0
- vibetuner/templates/frontend/debug/users.html.jinja +137 -0
- vibetuner/templates/frontend/debug/version.html.jinja +53 -0
- vibetuner/templates/frontend/email/magic_link.txt.jinja +5 -0
- vibetuner/templates/frontend/email_sent.html.jinja +82 -0
- vibetuner/templates/frontend/index.html.jinja +19 -0
- vibetuner/templates/frontend/lang/select.html.jinja +4 -0
- vibetuner/templates/frontend/login.html.jinja +84 -0
- vibetuner/templates/frontend/meta/browserconfig.xml.jinja +10 -0
- vibetuner/templates/frontend/meta/robots.txt.jinja +3 -0
- vibetuner/templates/frontend/meta/site.webmanifest.jinja +7 -0
- vibetuner/templates/frontend/meta/sitemap.xml.jinja +6 -0
- vibetuner/templates/frontend/user/edit.html.jinja +85 -0
- vibetuner/templates/frontend/user/profile.html.jinja +156 -0
- vibetuner/templates/markdown/.placeholder +0 -0
- vibetuner/templates/markdown/AGENTS.md +29 -0
- vibetuner/templates/markdown/CLAUDE.md +29 -0
- vibetuner/templates.py +152 -0
- vibetuner/time.py +57 -0
- vibetuner/versioning.py +8 -0
- {vibetuner-2.6.1.dist-info → vibetuner-2.7.0.dist-info}/METADATA +2 -1
- vibetuner-2.7.0.dist-info/RECORD +84 -0
- vibetuner-2.6.1.dist-info/RECORD +0 -4
- {vibetuner-2.6.1.dist-info → vibetuner-2.7.0.dist-info}/WHEEL +0 -0
vibetuner/__init__.py
CHANGED
vibetuner/__main__.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# ABOUTME: Core CLI setup with AsyncTyper wrapper and base configuration
|
|
2
|
+
# ABOUTME: Provides main CLI entry point and logging configuration
|
|
3
|
+
import inspect
|
|
4
|
+
from functools import partial, wraps
|
|
5
|
+
from importlib import import_module
|
|
6
|
+
|
|
7
|
+
import asyncer
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from vibetuner.cli.run import run_app
|
|
12
|
+
from vibetuner.config import settings
|
|
13
|
+
from vibetuner.logging import LogLevel, setup_logging
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AsyncTyper(typer.Typer):
|
|
20
|
+
def __init__(self, *args, **kwargs):
|
|
21
|
+
kwargs.setdefault("no_args_is_help", True)
|
|
22
|
+
super().__init__(*args, **kwargs)
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def maybe_run_async(decorator, f):
|
|
26
|
+
if inspect.iscoroutinefunction(f):
|
|
27
|
+
|
|
28
|
+
@wraps(f)
|
|
29
|
+
def runner(*args, **kwargs):
|
|
30
|
+
return asyncer.runnify(f)(*args, **kwargs)
|
|
31
|
+
|
|
32
|
+
decorator(runner)
|
|
33
|
+
else:
|
|
34
|
+
decorator(f)
|
|
35
|
+
return f
|
|
36
|
+
|
|
37
|
+
def callback(self, *args, **kwargs):
|
|
38
|
+
decorator = super().callback(*args, **kwargs)
|
|
39
|
+
return partial(self.maybe_run_async, decorator)
|
|
40
|
+
|
|
41
|
+
def command(self, *args, **kwargs):
|
|
42
|
+
decorator = super().command(*args, **kwargs)
|
|
43
|
+
return partial(self.maybe_run_async, decorator)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
app = AsyncTyper(help=f"{settings.project.project_name.title()} CLI")
|
|
47
|
+
|
|
48
|
+
LOG_LEVEL_OPTION = typer.Option(
|
|
49
|
+
LogLevel.INFO,
|
|
50
|
+
"--log-level",
|
|
51
|
+
"-l",
|
|
52
|
+
case_sensitive=False,
|
|
53
|
+
help="Set the logging level",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.callback()
|
|
58
|
+
def callback(log_level: LogLevel | None = LOG_LEVEL_OPTION) -> None:
|
|
59
|
+
"""Initialize logging and other global settings."""
|
|
60
|
+
setup_logging(level=log_level)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
app.add_typer(run_app, name="run")
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
import_module("app.cli")
|
|
67
|
+
except (ImportError, ModuleNotFoundError):
|
|
68
|
+
pass
|
vibetuner/cli/run.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# ABOUTME: Run commands for starting the application in different modes
|
|
2
|
+
# ABOUTME: Supports dev/prod modes for frontend and worker services
|
|
3
|
+
import os
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
run_app = typer.Typer(
|
|
13
|
+
help="Run the application in different modes", no_args_is_help=True
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@run_app.command(name="dev")
|
|
18
|
+
def dev(
|
|
19
|
+
service: Annotated[
|
|
20
|
+
str, typer.Argument(help="Service to run: 'frontend' or 'worker'")
|
|
21
|
+
] = "frontend",
|
|
22
|
+
port: int = typer.Option(
|
|
23
|
+
None, help="Port to run on (8000 for frontend, 11111 for worker)"
|
|
24
|
+
),
|
|
25
|
+
host: str = typer.Option("0.0.0.0", help="Host to bind to (frontend only)"), # noqa: S104
|
|
26
|
+
workers_count: int = typer.Option(
|
|
27
|
+
1, "--workers", help="Number of worker processes"
|
|
28
|
+
),
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Run in development mode with hot reload (frontend or worker)."""
|
|
31
|
+
os.environ["DEBUG"] = "1"
|
|
32
|
+
|
|
33
|
+
if service == "worker":
|
|
34
|
+
# Worker mode
|
|
35
|
+
from streaq.cli import main as streaq_main
|
|
36
|
+
|
|
37
|
+
worker_port = port if port else 11111
|
|
38
|
+
console.print(
|
|
39
|
+
f"[green]Starting worker in dev mode on port {worker_port}[/green]"
|
|
40
|
+
)
|
|
41
|
+
console.print("[dim]Hot reload enabled[/dim]")
|
|
42
|
+
|
|
43
|
+
if workers_count > 1:
|
|
44
|
+
console.print(
|
|
45
|
+
"[yellow]Warning: Multiple workers not supported in dev mode, using 1[/yellow]"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Call streaq programmatically
|
|
49
|
+
streaq_main(
|
|
50
|
+
worker_path="app.tasks.worker.worker",
|
|
51
|
+
workers=1,
|
|
52
|
+
reload=True,
|
|
53
|
+
verbose=True,
|
|
54
|
+
web=True,
|
|
55
|
+
host="0.0.0.0", # noqa: S104
|
|
56
|
+
port=worker_port,
|
|
57
|
+
)
|
|
58
|
+
elif service == "frontend":
|
|
59
|
+
# Frontend mode
|
|
60
|
+
from pathlib import Path
|
|
61
|
+
|
|
62
|
+
from granian import Granian
|
|
63
|
+
from granian.constants import Interfaces
|
|
64
|
+
|
|
65
|
+
frontend_port = port if port else 8000
|
|
66
|
+
console.print(
|
|
67
|
+
f"[green]Starting frontend in dev mode on {host}:{frontend_port}[/green]"
|
|
68
|
+
)
|
|
69
|
+
console.print("[dim]Watching for changes in src/ and templates/[/dim]")
|
|
70
|
+
|
|
71
|
+
# Define paths to watch for changes
|
|
72
|
+
reload_paths = [
|
|
73
|
+
Path("src/vibetuner"),
|
|
74
|
+
Path("src/app"),
|
|
75
|
+
Path("templates/frontend"),
|
|
76
|
+
Path("templates/email"),
|
|
77
|
+
Path("templates/markdown"),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
server = Granian(
|
|
81
|
+
target="vibetuner.frontend:app",
|
|
82
|
+
address=host,
|
|
83
|
+
port=frontend_port,
|
|
84
|
+
interface=Interfaces.ASGI,
|
|
85
|
+
workers=workers_count,
|
|
86
|
+
reload=True,
|
|
87
|
+
reload_paths=reload_paths,
|
|
88
|
+
log_level="info",
|
|
89
|
+
log_access=True,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
server.serve()
|
|
93
|
+
else:
|
|
94
|
+
console.print(f"[red]Error: Unknown service '{service}'[/red]")
|
|
95
|
+
console.print("[yellow]Valid services: 'frontend' or 'worker'[/yellow]")
|
|
96
|
+
raise typer.Exit(code=1)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@run_app.command(name="prod")
|
|
100
|
+
def prod(
|
|
101
|
+
service: Annotated[
|
|
102
|
+
str, typer.Argument(help="Service to run: 'frontend' or 'worker'")
|
|
103
|
+
] = "frontend",
|
|
104
|
+
port: int = typer.Option(
|
|
105
|
+
None, help="Port to run on (8000 for frontend, 11111 for worker)"
|
|
106
|
+
),
|
|
107
|
+
host: str = typer.Option("0.0.0.0", help="Host to bind to (frontend only)"), # noqa: S104
|
|
108
|
+
workers_count: int = typer.Option(
|
|
109
|
+
4, "--workers", help="Number of worker processes"
|
|
110
|
+
),
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Run in production mode (frontend or worker)."""
|
|
113
|
+
os.environ["ENVIRONMENT"] = "production"
|
|
114
|
+
|
|
115
|
+
if service == "worker":
|
|
116
|
+
# Worker mode
|
|
117
|
+
from streaq.cli import main as streaq_main
|
|
118
|
+
|
|
119
|
+
worker_port = port if port else 11111
|
|
120
|
+
console.print(
|
|
121
|
+
f"[green]Starting worker in prod mode on port {worker_port}[/green]"
|
|
122
|
+
)
|
|
123
|
+
console.print(f"[dim]Workers: {workers_count}[/dim]")
|
|
124
|
+
|
|
125
|
+
# Call streaq programmatically
|
|
126
|
+
streaq_main(
|
|
127
|
+
worker_path="app.tasks.worker.worker",
|
|
128
|
+
workers=workers_count,
|
|
129
|
+
reload=False,
|
|
130
|
+
verbose=False,
|
|
131
|
+
web=True,
|
|
132
|
+
host="0.0.0.0", # noqa: S104
|
|
133
|
+
port=worker_port,
|
|
134
|
+
)
|
|
135
|
+
elif service == "frontend":
|
|
136
|
+
# Frontend mode
|
|
137
|
+
from granian import Granian
|
|
138
|
+
from granian.constants import Interfaces
|
|
139
|
+
|
|
140
|
+
frontend_port = port if port else 8000
|
|
141
|
+
console.print(
|
|
142
|
+
f"[green]Starting frontend in prod mode on {host}:{frontend_port}[/green]"
|
|
143
|
+
)
|
|
144
|
+
console.print(f"[dim]Workers: {workers_count}[/dim]")
|
|
145
|
+
|
|
146
|
+
server = Granian(
|
|
147
|
+
target="vibetuner.frontend:app",
|
|
148
|
+
address=host,
|
|
149
|
+
port=frontend_port,
|
|
150
|
+
interface=Interfaces.ASGI,
|
|
151
|
+
workers=workers_count,
|
|
152
|
+
reload=False,
|
|
153
|
+
log_level="info",
|
|
154
|
+
log_access=True,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
server.serve()
|
|
158
|
+
else:
|
|
159
|
+
console.print(f"[red]Error: Unknown service '{service}'[/red]")
|
|
160
|
+
console.print("[yellow]Valid services: 'frontend' or 'worker'[/yellow]")
|
|
161
|
+
raise typer.Exit(code=1)
|
vibetuner/config.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
from pydantic import (
|
|
9
|
+
UUID4,
|
|
10
|
+
Field,
|
|
11
|
+
HttpUrl,
|
|
12
|
+
MongoDsn,
|
|
13
|
+
RedisDsn,
|
|
14
|
+
SecretStr,
|
|
15
|
+
computed_field,
|
|
16
|
+
)
|
|
17
|
+
from pydantic_extra_types.language_code import LanguageAlpha2
|
|
18
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
19
|
+
|
|
20
|
+
from vibetuner.paths import config_vars as config_vars_path
|
|
21
|
+
from vibetuner.versioning import version
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
current_year: int = datetime.now().year
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _load_project_config() -> "ProjectConfiguration":
|
|
28
|
+
if not config_vars_path.exists():
|
|
29
|
+
return ProjectConfiguration()
|
|
30
|
+
return ProjectConfiguration(
|
|
31
|
+
**yaml.safe_load(config_vars_path.read_text(encoding="utf-8"))
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ProjectConfiguration(BaseSettings):
|
|
36
|
+
project_slug: str = "default_project"
|
|
37
|
+
project_name: str = "default_project"
|
|
38
|
+
|
|
39
|
+
project_description: str = "A default project description."
|
|
40
|
+
|
|
41
|
+
# Language Related Settings
|
|
42
|
+
supported_languages: set[LanguageAlpha2] | None = None
|
|
43
|
+
default_language: LanguageAlpha2 = LanguageAlpha2("en")
|
|
44
|
+
|
|
45
|
+
mongodb_url: MongoDsn | None = None
|
|
46
|
+
redis_url: RedisDsn | None = None
|
|
47
|
+
|
|
48
|
+
# AWS Parameters
|
|
49
|
+
aws_default_region: str = "eu-central-1"
|
|
50
|
+
|
|
51
|
+
# Company Name
|
|
52
|
+
company_name: str = "Acme Corp"
|
|
53
|
+
|
|
54
|
+
# From Email for transactional emails
|
|
55
|
+
from_email: str = "no-reply@example.com"
|
|
56
|
+
|
|
57
|
+
# Copyright
|
|
58
|
+
copyright_start: Annotated[int, Field(strict=True, gt=1714, lt=2048)] = current_year
|
|
59
|
+
|
|
60
|
+
# Analytics
|
|
61
|
+
umami_website_id: UUID4 | None = None
|
|
62
|
+
|
|
63
|
+
# Fully Qualified Domain Name
|
|
64
|
+
fqdn: str | None = None
|
|
65
|
+
|
|
66
|
+
@cached_property
|
|
67
|
+
def languages(self) -> set[str]:
|
|
68
|
+
if self.supported_languages is None:
|
|
69
|
+
return {self.language}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
str(lang) for lang in (*self.supported_languages, self.default_language)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@cached_property
|
|
76
|
+
def language(self) -> str:
|
|
77
|
+
return str(self.default_language)
|
|
78
|
+
|
|
79
|
+
@cached_property
|
|
80
|
+
def copyright(self) -> str:
|
|
81
|
+
year_part = (
|
|
82
|
+
f"{self.copyright_start}-{current_year}"
|
|
83
|
+
if self.copyright_start and self.copyright_start != current_year
|
|
84
|
+
else str(current_year)
|
|
85
|
+
)
|
|
86
|
+
return f"© {year_part}{f' {self.company_name}' if self.company_name else ''}"
|
|
87
|
+
|
|
88
|
+
model_config = SettingsConfigDict(extra="ignore")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class CoreConfiguration(BaseSettings):
|
|
92
|
+
project: ProjectConfiguration
|
|
93
|
+
|
|
94
|
+
debug: bool = False
|
|
95
|
+
version: str = version
|
|
96
|
+
session_key: SecretStr = SecretStr("ct-!secret-must-change-me")
|
|
97
|
+
|
|
98
|
+
aws_access_key_id: SecretStr | None = None
|
|
99
|
+
aws_secret_access_key: SecretStr | None = None
|
|
100
|
+
|
|
101
|
+
r2_default_bucket_name: str | None = None
|
|
102
|
+
r2_bucket_endpoint_url: HttpUrl | None = None
|
|
103
|
+
r2_access_key: SecretStr | None = None
|
|
104
|
+
r2_secret_key: SecretStr | None = None
|
|
105
|
+
r2_default_region: str = "auto"
|
|
106
|
+
|
|
107
|
+
@computed_field
|
|
108
|
+
@cached_property
|
|
109
|
+
def v_hash(self) -> str:
|
|
110
|
+
hash_object = hashlib.sha256(self.version.encode("utf-8"))
|
|
111
|
+
hash_bytes = hash_object.digest()
|
|
112
|
+
|
|
113
|
+
b64_hash = base64.urlsafe_b64encode(hash_bytes).decode("utf-8")
|
|
114
|
+
|
|
115
|
+
url_safe_hash = b64_hash.rstrip("=")[:8]
|
|
116
|
+
|
|
117
|
+
return url_safe_hash
|
|
118
|
+
|
|
119
|
+
@cached_property
|
|
120
|
+
def mongo_dbname(self) -> str:
|
|
121
|
+
return self.project.project_slug
|
|
122
|
+
|
|
123
|
+
model_config = SettingsConfigDict(
|
|
124
|
+
case_sensitive=False, extra="ignore", env_file=".env"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
settings = CoreConfiguration(project=_load_project_config())
|
vibetuner/context.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from pydantic import UUID4, BaseModel
|
|
2
|
+
|
|
3
|
+
from vibetuner.config import settings
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Context(BaseModel):
|
|
7
|
+
DEBUG: bool = settings.debug
|
|
8
|
+
|
|
9
|
+
project_name: str = settings.project.project_name
|
|
10
|
+
project_slug: str = settings.project.project_slug
|
|
11
|
+
project_description: str = settings.project.project_description
|
|
12
|
+
|
|
13
|
+
version: str = settings.version
|
|
14
|
+
v_hash: str = settings.v_hash
|
|
15
|
+
|
|
16
|
+
copyright: str = settings.project.copyright
|
|
17
|
+
|
|
18
|
+
default_language: str = settings.project.language
|
|
19
|
+
supported_languages: set[str] = settings.project.languages
|
|
20
|
+
|
|
21
|
+
umami_website_id: UUID4 | None = settings.project.umami_website_id
|
|
22
|
+
|
|
23
|
+
fqdn: str | None = settings.project.fqdn
|
|
24
|
+
|
|
25
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Core Frontend Module
|
|
2
|
+
|
|
3
|
+
**IMMUTABLE SCAFFOLDING CODE** - This is the framework's core frontend infrastructure.
|
|
4
|
+
|
|
5
|
+
## What's Here
|
|
6
|
+
|
|
7
|
+
This module contains the scaffolding's core frontend components:
|
|
8
|
+
|
|
9
|
+
- **routes/** - Essential default routes (auth, health, debug, language, user, meta)
|
|
10
|
+
- **templates.py** - Template rendering with automatic context injection
|
|
11
|
+
- **deps.py** - FastAPI dependencies (authentication, language, etc.)
|
|
12
|
+
- **middleware.py** - Request/response middleware
|
|
13
|
+
- **oauth.py** - OAuth provider integration
|
|
14
|
+
- **email.py** - Magic link email authentication
|
|
15
|
+
- **lifespan.py** - Application startup/shutdown lifecycle
|
|
16
|
+
- **context.py** - Request context management
|
|
17
|
+
- **hotreload.py** - Development hot-reload support
|
|
18
|
+
|
|
19
|
+
## Important Rules
|
|
20
|
+
|
|
21
|
+
⚠️ **DO NOT MODIFY** these core frontend components directly.
|
|
22
|
+
|
|
23
|
+
**For changes to core frontend:**
|
|
24
|
+
|
|
25
|
+
- File an issue at `https://github.com/alltuner/scaffolding`
|
|
26
|
+
- Core changes benefit all projects using the scaffolding
|
|
27
|
+
|
|
28
|
+
**For your application routes:**
|
|
29
|
+
|
|
30
|
+
- Create them in `src/app/frontend/routes/` instead
|
|
31
|
+
- Import core components when needed:
|
|
32
|
+
- `from vibetuner.frontend.deps import get_current_user`
|
|
33
|
+
- `from vibetuner.frontend.templates import render_template`
|
|
34
|
+
|
|
35
|
+
## User Route Pattern (for reference)
|
|
36
|
+
|
|
37
|
+
Your application routes in `src/app/frontend/routes/` should follow this pattern:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
# src/app/frontend/routes/dashboard.py
|
|
41
|
+
from fastapi import APIRouter, Request, Depends
|
|
42
|
+
from vibetuner.frontend.deps import get_current_user
|
|
43
|
+
from vibetuner.frontend.templates import render_template
|
|
44
|
+
|
|
45
|
+
router = APIRouter()
|
|
46
|
+
|
|
47
|
+
@router.get("/dashboard")
|
|
48
|
+
async def dashboard(request: Request, user=Depends(get_current_user)):
|
|
49
|
+
return render_template("dashboard.html.jinja", request, {"user": user})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Template Rendering
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
# Automatic context in every template:
|
|
56
|
+
{
|
|
57
|
+
"request": request,
|
|
58
|
+
"DEBUG": settings.DEBUG,
|
|
59
|
+
"hotreload": hotreload, # Dev mode
|
|
60
|
+
# ... plus your custom context
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Template Filters
|
|
65
|
+
|
|
66
|
+
- `{{ datetime | timeago }}` - "2 hours ago"
|
|
67
|
+
- `{{ datetime | format_date }}` - "January 15, 2024"
|
|
68
|
+
- `{{ text | markdown }}` - Convert Markdown to HTML
|
|
69
|
+
|
|
70
|
+
## Core Dependencies Available
|
|
71
|
+
|
|
72
|
+
Import these from `vibetuner.frontend.deps`:
|
|
73
|
+
|
|
74
|
+
- `get_current_user` - Require authenticated user (raises 403 if not authenticated)
|
|
75
|
+
- `get_current_user_optional` - Optional auth check (returns None if not authenticated)
|
|
76
|
+
- `LangDep` - Current language from cookie/header
|
|
77
|
+
- `MagicCookieDep` - Magic link cookie for authentication
|
|
78
|
+
|
|
79
|
+
## HTMX Patterns
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<!-- Partial updates -->
|
|
83
|
+
<button hx-post="/api/action" hx-target="#result">Click</button>
|
|
84
|
+
|
|
85
|
+
<!-- Form submission -->
|
|
86
|
+
<form hx-post="/submit" hx-swap="outerHTML">...</form>
|
|
87
|
+
|
|
88
|
+
<!-- Polling -->
|
|
89
|
+
<div hx-get="/status" hx-trigger="every 2s">...</div>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Default Routes Provided
|
|
93
|
+
|
|
94
|
+
The following routes are automatically available (DO NOT MODIFY):
|
|
95
|
+
|
|
96
|
+
- **/auth/*** - OAuth and magic link authentication
|
|
97
|
+
- **/health/ping** - Health check endpoint
|
|
98
|
+
- **/debug/** - Debug info (only in DEBUG mode)
|
|
99
|
+
- **/lang/** - Language selection
|
|
100
|
+
- **/user/** - User profile routes
|
|
101
|
+
- **/meta/** - Metadata endpoints
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
**CRITICAL**: Both processes required:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Terminal 1: Frontend assets
|
|
109
|
+
bun dev
|
|
110
|
+
|
|
111
|
+
# Terminal 2: Backend server
|
|
112
|
+
just local-dev
|
|
113
|
+
```
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Core Frontend Module
|
|
2
|
+
|
|
3
|
+
**IMMUTABLE SCAFFOLDING CODE** - This is the framework's core frontend infrastructure.
|
|
4
|
+
|
|
5
|
+
## What's Here
|
|
6
|
+
|
|
7
|
+
This module contains the scaffolding's core frontend components:
|
|
8
|
+
|
|
9
|
+
- **routes/** - Essential default routes (auth, health, debug, language, user, meta)
|
|
10
|
+
- **templates.py** - Template rendering with automatic context injection
|
|
11
|
+
- **deps.py** - FastAPI dependencies (authentication, language, etc.)
|
|
12
|
+
- **middleware.py** - Request/response middleware
|
|
13
|
+
- **oauth.py** - OAuth provider integration
|
|
14
|
+
- **email.py** - Magic link email authentication
|
|
15
|
+
- **lifespan.py** - Application startup/shutdown lifecycle
|
|
16
|
+
- **context.py** - Request context management
|
|
17
|
+
- **hotreload.py** - Development hot-reload support
|
|
18
|
+
|
|
19
|
+
## Important Rules
|
|
20
|
+
|
|
21
|
+
⚠️ **DO NOT MODIFY** these core frontend components directly.
|
|
22
|
+
|
|
23
|
+
**For changes to core frontend:**
|
|
24
|
+
|
|
25
|
+
- File an issue at `https://github.com/alltuner/scaffolding`
|
|
26
|
+
- Core changes benefit all projects using the scaffolding
|
|
27
|
+
|
|
28
|
+
**For your application routes:**
|
|
29
|
+
|
|
30
|
+
- Create them in `src/app/frontend/routes/` instead
|
|
31
|
+
- Import core components when needed:
|
|
32
|
+
- `from vibetuner.frontend.deps import get_current_user`
|
|
33
|
+
- `from vibetuner.frontend.templates import render_template`
|
|
34
|
+
|
|
35
|
+
## User Route Pattern (for reference)
|
|
36
|
+
|
|
37
|
+
Your application routes in `src/app/frontend/routes/` should follow this pattern:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
# src/app/frontend/routes/dashboard.py
|
|
41
|
+
from fastapi import APIRouter, Request, Depends
|
|
42
|
+
from vibetuner.frontend.deps import get_current_user
|
|
43
|
+
from vibetuner.frontend.templates import render_template
|
|
44
|
+
|
|
45
|
+
router = APIRouter()
|
|
46
|
+
|
|
47
|
+
@router.get("/dashboard")
|
|
48
|
+
async def dashboard(request: Request, user=Depends(get_current_user)):
|
|
49
|
+
return render_template("dashboard.html.jinja", request, {"user": user})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Template Rendering
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
# Automatic context in every template:
|
|
56
|
+
{
|
|
57
|
+
"request": request,
|
|
58
|
+
"DEBUG": settings.DEBUG,
|
|
59
|
+
"hotreload": hotreload, # Dev mode
|
|
60
|
+
# ... plus your custom context
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Template Filters
|
|
65
|
+
|
|
66
|
+
- `{{ datetime | timeago }}` - "2 hours ago"
|
|
67
|
+
- `{{ datetime | format_date }}` - "January 15, 2024"
|
|
68
|
+
- `{{ text | markdown }}` - Convert Markdown to HTML
|
|
69
|
+
|
|
70
|
+
## Core Dependencies Available
|
|
71
|
+
|
|
72
|
+
Import these from `vibetuner.frontend.deps`:
|
|
73
|
+
|
|
74
|
+
- `get_current_user` - Require authenticated user (raises 403 if not authenticated)
|
|
75
|
+
- `get_current_user_optional` - Optional auth check (returns None if not authenticated)
|
|
76
|
+
- `LangDep` - Current language from cookie/header
|
|
77
|
+
- `MagicCookieDep` - Magic link cookie for authentication
|
|
78
|
+
|
|
79
|
+
## HTMX Patterns
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<!-- Partial updates -->
|
|
83
|
+
<button hx-post="/api/action" hx-target="#result">Click</button>
|
|
84
|
+
|
|
85
|
+
<!-- Form submission -->
|
|
86
|
+
<form hx-post="/submit" hx-swap="outerHTML">...</form>
|
|
87
|
+
|
|
88
|
+
<!-- Polling -->
|
|
89
|
+
<div hx-get="/status" hx-trigger="every 2s">...</div>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Default Routes Provided
|
|
93
|
+
|
|
94
|
+
The following routes are automatically available (DO NOT MODIFY):
|
|
95
|
+
|
|
96
|
+
- **/auth/*** - OAuth and magic link authentication
|
|
97
|
+
- **/health/ping** - Health check endpoint
|
|
98
|
+
- **/debug/** - Debug info (only in DEBUG mode)
|
|
99
|
+
- **/lang/** - Language selection
|
|
100
|
+
- **/user/** - User profile routes
|
|
101
|
+
- **/meta/** - Metadata endpoints
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
**CRITICAL**: Both processes required:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Terminal 1: Frontend assets
|
|
109
|
+
bun dev
|
|
110
|
+
|
|
111
|
+
# Terminal 2: Backend server
|
|
112
|
+
just local-dev
|
|
113
|
+
```
|