vibetuner 2.7.0__py3-none-any.whl → 2.18.1__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.

Files changed (52) hide show
  1. vibetuner/cli/__init__.py +13 -2
  2. vibetuner/cli/run.py +0 -1
  3. vibetuner/cli/scaffold.py +187 -0
  4. vibetuner/config.py +27 -11
  5. vibetuner/context.py +3 -0
  6. vibetuner/frontend/__init__.py +7 -2
  7. vibetuner/frontend/lifespan.py +12 -7
  8. vibetuner/frontend/middleware.py +3 -3
  9. vibetuner/frontend/routes/auth.py +19 -13
  10. vibetuner/frontend/routes/debug.py +1 -1
  11. vibetuner/frontend/routes/health.py +4 -0
  12. vibetuner/frontend/routes/user.py +1 -1
  13. vibetuner/mongo.py +1 -1
  14. vibetuner/paths.py +197 -80
  15. vibetuner/tasks/worker.py +1 -1
  16. vibetuner/templates/email/{default/magic_link.html.jinja → magic_link.html.jinja} +2 -1
  17. vibetuner/templates/frontend/base/favicons.html.jinja +1 -1
  18. vibetuner/templates/frontend/base/skeleton.html.jinja +5 -2
  19. vibetuner/templates/frontend/debug/collections.html.jinja +2 -0
  20. vibetuner/templates/frontend/debug/components/debug_nav.html.jinja +6 -6
  21. vibetuner/templates/frontend/debug/index.html.jinja +6 -4
  22. vibetuner/templates/frontend/debug/info.html.jinja +2 -0
  23. vibetuner/templates/frontend/debug/users.html.jinja +4 -2
  24. vibetuner/templates/frontend/debug/version.html.jinja +2 -0
  25. vibetuner/templates/frontend/email_sent.html.jinja +2 -1
  26. vibetuner/templates/frontend/index.html.jinja +1 -0
  27. vibetuner/templates/frontend/login.html.jinja +8 -3
  28. vibetuner/templates/frontend/user/edit.html.jinja +3 -2
  29. vibetuner/templates/frontend/user/profile.html.jinja +2 -1
  30. vibetuner/templates.py +9 -15
  31. vibetuner/versioning.py +1 -1
  32. vibetuner-2.18.1.dist-info/METADATA +241 -0
  33. vibetuner-2.18.1.dist-info/RECORD +72 -0
  34. {vibetuner-2.7.0.dist-info → vibetuner-2.18.1.dist-info}/WHEEL +1 -1
  35. vibetuner-2.18.1.dist-info/entry_points.txt +3 -0
  36. vibetuner/frontend/AGENTS.md +0 -113
  37. vibetuner/frontend/CLAUDE.md +0 -113
  38. vibetuner/models/AGENTS.md +0 -165
  39. vibetuner/models/CLAUDE.md +0 -165
  40. vibetuner/services/AGENTS.md +0 -104
  41. vibetuner/services/CLAUDE.md +0 -104
  42. vibetuner/tasks/AGENTS.md +0 -98
  43. vibetuner/tasks/CLAUDE.md +0 -98
  44. vibetuner/templates/email/AGENTS.md +0 -48
  45. vibetuner/templates/email/CLAUDE.md +0 -48
  46. vibetuner/templates/frontend/AGENTS.md +0 -74
  47. vibetuner/templates/frontend/CLAUDE.md +0 -74
  48. vibetuner/templates/markdown/AGENTS.md +0 -29
  49. vibetuner/templates/markdown/CLAUDE.md +0 -29
  50. vibetuner-2.7.0.dist-info/METADATA +0 -48
  51. vibetuner-2.7.0.dist-info/RECORD +0 -84
  52. /vibetuner/templates/email/{default/magic_link.txt.jinja → magic_link.txt.jinja} +0 -0
vibetuner/paths.py CHANGED
@@ -1,112 +1,229 @@
1
1
  from importlib.resources import files
2
2
  from pathlib import Path
3
+ from typing import Self
4
+
5
+ from pydantic import computed_field, model_validator
6
+ from pydantic_settings import BaseSettings, SettingsConfigDict
7
+
3
8
 
4
9
  # Package-relative paths (for bundled templates in the vibetuner package)
5
10
  _package_files = files("vibetuner")
6
11
  _package_templates_traversable = _package_files / "templates"
7
12
 
8
- # Convert to Path when actually used (handles both filesystem and zip-based packages)
13
+
9
14
  def _get_package_templates_path() -> Path:
10
15
  """Get package templates path, works for both installed and editable installs."""
11
- # For most cases, we can convert directly to Path
12
- # For zip files, importlib.resources handles extraction automatically
13
16
  try:
14
17
  return Path(str(_package_templates_traversable))
15
18
  except (TypeError, ValueError):
16
- # If we can't convert to Path, we're in a zip or similar
17
- # In this case, we'll need to use as_file() context manager when accessing
18
- # For now, raise an error - we can enhance this later if needed
19
19
  raise RuntimeError(
20
20
  "Package templates are in a non-filesystem location. "
21
21
  "This is not yet supported."
22
- )
22
+ ) from None
23
23
 
24
24
 
25
+ # Package templates always available
25
26
  package_templates = _get_package_templates_path()
27
+ core_templates = package_templates # Alias for backwards compatibility
26
28
 
27
- # Project root (set at runtime by the application using vibetuner)
28
- # When None, only package templates are available
29
- root: Path | None = None
30
- fallback_path = "defaults"
31
29
 
30
+ class PathSettings(BaseSettings):
31
+ """Path settings with lazy auto-detection of project root."""
32
32
 
33
- def set_project_root(project_root: Path) -> None:
34
- """Set the project root directory for the application using vibetuner.
33
+ model_config = SettingsConfigDict(
34
+ case_sensitive=False,
35
+ extra="ignore",
36
+ validate_default=True,
37
+ )
35
38
 
36
- This enables access to project-specific templates, assets, and locales.
37
- Must be called before accessing project-specific paths.
38
- """
39
- global root, templates, app_templates, locales, config_vars
40
- global assets, statics, css, js, favicons, img
41
- global frontend_templates, email_templates, markdown_templates
39
+ root: Path | None = None
40
+ fallback_path: str = "defaults"
41
+
42
+ @model_validator(mode="after")
43
+ def detect_project_root(self) -> Self:
44
+ """Auto-detect project root if not explicitly set."""
45
+ if self.root is None:
46
+ detected = self._find_project_root()
47
+ if detected is not None:
48
+ self.root = detected
49
+ return self
50
+
51
+ @staticmethod
52
+ def _find_project_root() -> Path | None:
53
+ """Find project root by searching for marker files."""
54
+ markers = [".copier-answers.yml", "pyproject.toml", ".git"]
55
+ current = Path.cwd()
56
+
57
+ for parent in [current, *current.parents]:
58
+ if any((parent / marker).exists() for marker in markers):
59
+ return parent
60
+
61
+ return None
62
+
63
+ # Project-specific paths (only available if root is set)
64
+ @computed_field
65
+ @property
66
+ def templates(self) -> Path | None:
67
+ """Project templates directory."""
68
+ return self.root / "templates" if self.root else None
69
+
70
+ @computed_field
71
+ @property
72
+ def app_templates(self) -> Path | None:
73
+ """Deprecated: use templates instead."""
74
+ return self.templates
75
+
76
+ @computed_field
77
+ @property
78
+ def locales(self) -> Path | None:
79
+ """Project locales directory."""
80
+ return self.root / "locales" if self.root else None
81
+
82
+ @computed_field
83
+ @property
84
+ def config_vars(self) -> Path | None:
85
+ """Copier answers file."""
86
+ return self.root / ".copier-answers.yml" if self.root else None
87
+
88
+ @computed_field
89
+ @property
90
+ def assets(self) -> Path | None:
91
+ """Project assets directory."""
92
+ return self.root / "assets" if self.root else None
93
+
94
+ @computed_field
95
+ @property
96
+ def statics(self) -> Path | None:
97
+ """Project static assets directory."""
98
+ return self.root / "assets" / "statics" if self.root else None
99
+
100
+ @computed_field
101
+ @property
102
+ def css(self) -> Path | None:
103
+ """Project CSS directory."""
104
+ return self.root / "assets" / "statics" / "css" if self.root else None
105
+
106
+ @computed_field
107
+ @property
108
+ def js(self) -> Path | None:
109
+ """Project JavaScript directory."""
110
+ return self.root / "assets" / "statics" / "js" if self.root else None
111
+
112
+ @computed_field
113
+ @property
114
+ def favicons(self) -> Path | None:
115
+ """Project favicons directory."""
116
+ return self.root / "assets" / "statics" / "favicons" if self.root else None
117
+
118
+ @computed_field
119
+ @property
120
+ def img(self) -> Path | None:
121
+ """Project images directory."""
122
+ return self.root / "assets" / "statics" / "img" if self.root else None
123
+
124
+ # Template paths (always return a list, project + package)
125
+ @computed_field
126
+ @property
127
+ def frontend_templates(self) -> list[Path]:
128
+ """Frontend template search paths (project overrides, then package)."""
129
+ paths = []
130
+ if self.root:
131
+ project_path = self.root / "templates" / "frontend"
132
+ if project_path.exists():
133
+ paths.append(project_path)
134
+ paths.append(package_templates / "frontend")
135
+ return paths
136
+
137
+ @computed_field
138
+ @property
139
+ def email_templates(self) -> list[Path]:
140
+ """Email template search paths (project overrides, then package)."""
141
+ paths = []
142
+ if self.root:
143
+ project_path = self.root / "templates" / "email"
144
+ if project_path.exists():
145
+ paths.append(project_path)
146
+ paths.append(package_templates / "email")
147
+ return paths
148
+
149
+ @computed_field
150
+ @property
151
+ def markdown_templates(self) -> list[Path]:
152
+ """Markdown template search paths (project overrides, then package)."""
153
+ paths = []
154
+ if self.root:
155
+ project_path = self.root / "templates" / "markdown"
156
+ if project_path.exists():
157
+ paths.append(project_path)
158
+ paths.append(package_templates / "markdown")
159
+ return paths
160
+
161
+ def set_root(self, project_root: Path) -> None:
162
+ """Explicitly set project root (overrides auto-detection)."""
163
+ self.root = project_root
164
+
165
+ def to_template_path_list(self, path: Path) -> list[Path]:
166
+ """Convert path to list with fallback."""
167
+ return [path, path / self.fallback_path]
168
+
169
+ def fallback_static_default(self, static_type: str, file_name: str) -> Path:
170
+ """Return a fallback path for a static file."""
171
+ if self.statics is None:
172
+ raise RuntimeError(
173
+ "Project root not detected. Cannot access static assets."
174
+ )
175
+
176
+ paths_to_check = [
177
+ self.statics / static_type / file_name,
178
+ self.statics / self.fallback_path / static_type / file_name,
179
+ ]
180
+
181
+ for path in paths_to_check:
182
+ if path.exists():
183
+ return path
184
+
185
+ raise FileNotFoundError(
186
+ f"Could not find {file_name} in any of the fallback paths: {paths_to_check}"
187
+ )
42
188
 
43
- root = project_root
44
189
 
45
- # Update project-specific paths
46
- templates = root / "templates"
47
- app_templates = templates # Deprecated: projects now use templates/ directly
48
- locales = root / "locales"
49
- config_vars = root / ".copier-answers.yml"
190
+ # Global settings instance with lazy auto-detection
191
+ _settings = PathSettings()
50
192
 
51
- # Update asset paths
52
- assets = root / "assets"
53
- statics = assets / "statics"
54
- css = statics / "css"
55
- js = statics / "js"
56
- favicons = statics / "favicons"
57
- img = statics / "img"
58
193
 
59
- # Update template lists to include project overrides
60
- frontend_templates = [templates / "frontend", package_templates / "frontend"]
61
- email_templates = [templates / "email", package_templates / "email"]
62
- markdown_templates = [templates / "markdown", package_templates / "markdown"]
194
+ # Backwards-compatible module-level API
195
+ def set_project_root(project_root: Path) -> None:
196
+ """Set the project root directory explicitly."""
197
+ _settings.set_root(project_root)
63
198
 
64
199
 
65
200
  def to_template_path_list(path: Path) -> list[Path]:
66
- return [
67
- path,
68
- path / fallback_path,
69
- ]
201
+ """Convert path to list with fallback."""
202
+ return _settings.to_template_path_list(path)
70
203
 
71
204
 
72
205
  def fallback_static_default(static_type: str, file_name: str) -> Path:
73
- """Return a fallback path for a file."""
74
- if root is None:
75
- raise RuntimeError(
76
- "Project root not set. Call set_project_root() before accessing assets."
77
- )
78
-
79
- paths_to_check = [
80
- statics / static_type / file_name,
81
- statics / fallback_path / static_type / file_name,
82
- ]
83
-
84
- for path in paths_to_check:
85
- if path.exists():
86
- return path
87
-
88
- raise FileNotFoundError(
89
- f"Could not find {file_name} in any of the fallback paths: {paths_to_check}"
90
- )
91
-
92
-
93
- # Core templates point to package (always available)
94
- core_templates = package_templates
95
-
96
- # Template paths - initially only package templates, updated when set_project_root() is called
97
- frontend_templates = [package_templates / "frontend"]
98
- email_templates = [package_templates / "email"]
99
- markdown_templates = [package_templates / "markdown"]
100
-
101
- # Project-specific paths - will be None until set_project_root() is called
102
- # These get updated by set_project_root()
103
- templates: Path | None = None
104
- app_templates: Path | None = None
105
- locales: Path | None = None
106
- config_vars: Path | None = None
107
- assets: Path | None = None
108
- statics: Path | None = None
109
- css: Path | None = None
110
- js: Path | None = None
111
- favicons: Path | None = None
112
- img: Path | None = None
206
+ """Return a fallback path for a static file."""
207
+ return _settings.fallback_static_default(static_type, file_name)
208
+
209
+
210
+ # Expose settings instance for direct access
211
+ paths = _settings
212
+
213
+ # Module-level variables that delegate to settings (backwards compatibility)
214
+ # Access like: from vibetuner.paths import frontend_templates
215
+ # Or better: from vibetuner.paths import paths; paths.frontend_templates
216
+ root = _settings.root
217
+ templates = _settings.templates
218
+ app_templates = _settings.app_templates
219
+ locales = _settings.locales
220
+ config_vars = _settings.config_vars
221
+ assets = _settings.assets
222
+ statics = _settings.statics
223
+ css = _settings.css
224
+ js = _settings.js
225
+ favicons = _settings.favicons
226
+ img = _settings.img
227
+ frontend_templates = _settings.frontend_templates
228
+ email_templates = _settings.email_templates
229
+ markdown_templates = _settings.markdown_templates
vibetuner/tasks/worker.py CHANGED
@@ -5,7 +5,7 @@ from vibetuner.tasks.context import lifespan
5
5
 
6
6
 
7
7
  worker = Worker(
8
- redis_url=str(settings.project.redis_url),
8
+ redis_url=str(settings.redis_url),
9
9
  queue_name=(
10
10
  settings.project.project_slug
11
11
  if not settings.debug
@@ -1,6 +1,6 @@
1
+ {# djlint:off #}
1
2
  <html>
2
3
  <body>
3
- <h2>Sign in to {{ project_name }}</h2>
4
4
  <p>Click the link below to sign in to your account:</p>
5
5
  <p>
6
6
  <a href="{{ login_url }}"
@@ -14,3 +14,4 @@
14
14
  <p>If you didn't request this, you can safely ignore this email.</p>
15
15
  </body>
16
16
  </html>
17
+ {# djlint:on #}
@@ -1 +1 @@
1
- <link rel="manifest" href="{{ url_for("site_webmanifest").path }}" />
1
+ <link rel="manifest" href="{{ url_for('site_webmanifest').path }}" />
@@ -3,8 +3,8 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <meta name="description" content="{{ meta_description | default("") }}" />
7
- <meta name="keywords" content="{{ meta_keywords | default("") }}" />
6
+ <meta name="description" content="{{ meta_description | default('') }}" />
7
+ <meta name="keywords" content="{{ meta_keywords | default('') }}" />
8
8
  <meta name="color-scheme" content="light" />
9
9
  <title>
10
10
  {% block title %}
@@ -17,6 +17,7 @@
17
17
  {% endif %}
18
18
  {% include "base/favicons.html.jinja" %}
19
19
  {% include "base/opengraph.html.jinja" %}
20
+
20
21
  {% block head %}
21
22
  {% endblock head %}
22
23
  </head>
@@ -26,6 +27,7 @@
26
27
  {% block header %}
27
28
  {% if not SKIP_HEADER %}
28
29
  {% include "base/header.html.jinja" %}
30
+
29
31
  {% endif %}
30
32
  {% endblock header %}
31
33
  {% block body %}
@@ -33,6 +35,7 @@
33
35
  {% block footer %}
34
36
  {% if not SKIP_FOOTER %}
35
37
  {% include "base/footer.html.jinja" %}
38
+
36
39
  {% endif %}
37
40
  {% endblock footer %}
38
41
  {% if DEBUG %}{{ hotreload.script(url_for('hot-reload') ) | safe }}{% endif %}
@@ -1,4 +1,5 @@
1
1
  {% extends "base/skeleton.html.jinja" %}
2
+
2
3
  {% block title %}
3
4
  Database Collections Schema - Debug
4
5
  {% endblock title %}
@@ -99,5 +100,6 @@
99
100
  </div>
100
101
  <!-- Debug Navigation Footer -->
101
102
  {% include "debug/components/debug_nav.html.jinja" %}
103
+
102
104
  </div>
103
105
  {% endblock body %}
@@ -1,7 +1,7 @@
1
1
  <!-- Debug Navigation Component -->
2
2
  <nav class="mt-8 pt-6 border-t border-base-200">
3
3
  <div class="flex flex-wrap gap-4">
4
- <a href="{{ url_for("debug_index") }}"
4
+ <a href="{{ url_for('debug_index') }}"
5
5
  class="btn btn-outline btn-primary gap-2">
6
6
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
7
7
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z">
@@ -11,7 +11,7 @@
11
11
  </svg>
12
12
  Debug Home
13
13
  </a>
14
- <a href="{{ url_for("debug_info") }}"
14
+ <a href="{{ url_for('debug_info') }}"
15
15
  class="btn btn-outline btn-primary gap-2">
16
16
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
17
17
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z">
@@ -19,7 +19,7 @@
19
19
  </svg>
20
20
  System Info
21
21
  </a>
22
- <a href="{{ url_for("debug_collections") }}"
22
+ <a href="{{ url_for('debug_collections') }}"
23
23
  class="btn btn-outline btn-primary gap-2">
24
24
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
25
25
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4">
@@ -27,7 +27,7 @@
27
27
  </svg>
28
28
  Collections
29
29
  </a>
30
- <a href="{{ url_for("debug_users") }}"
30
+ <a href="{{ url_for('debug_users') }}"
31
31
  class="btn btn-outline btn-warning gap-2">
32
32
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
33
33
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z">
@@ -35,7 +35,7 @@
35
35
  </svg>
36
36
  User Impersonation
37
37
  </a>
38
- <a href="{{ url_for("debug_version") }}"
38
+ <a href="{{ url_for('debug_version') }}"
39
39
  class="btn btn-outline btn-primary gap-2">
40
40
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
41
41
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z">
@@ -43,7 +43,7 @@
43
43
  </svg>
44
44
  Version
45
45
  </a>
46
- <a href="{{ url_for("health_ping") }}"
46
+ <a href="{{ url_for('health_ping') }}"
47
47
  class="btn btn-outline btn-success gap-2">
48
48
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
49
49
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z">
@@ -1,4 +1,5 @@
1
1
  {% extends "base/skeleton.html.jinja" %}
2
+
2
3
  {% block title %}
3
4
  Debug Dashboard
4
5
  {% endblock title %}
@@ -23,7 +24,7 @@
23
24
  </h2>
24
25
  <p class="text-base-content/70 mb-4">View application configuration, cookies, and runtime information</p>
25
26
  <div class="card-actions justify-end">
26
- <a href="{{ url_for("debug_info") }}" class="btn btn-primary btn-sm">View Details</a>
27
+ <a href="{{ url_for('debug_info') }}" class="btn btn-primary btn-sm">View Details</a>
27
28
  </div>
28
29
  </div>
29
30
  </div>
@@ -39,7 +40,7 @@
39
40
  </h2>
40
41
  <p class="text-base-content/70 mb-4">Explore MongoDB collections and their field schemas</p>
41
42
  <div class="card-actions justify-end">
42
- <a href="{{ url_for("debug_collections") }}"
43
+ <a href="{{ url_for('debug_collections') }}"
43
44
  class="btn btn-primary btn-sm">View Schemas</a>
44
45
  </div>
45
46
  </div>
@@ -56,7 +57,7 @@
56
57
  </h2>
57
58
  <p class="text-base-content/70 mb-4">Check application version and build information</p>
58
59
  <div class="card-actions justify-end">
59
- <a href="{{ url_for("debug_version") }}" class="btn btn-primary btn-sm">View Version</a>
60
+ <a href="{{ url_for('debug_version') }}" class="btn btn-primary btn-sm">View Version</a>
60
61
  </div>
61
62
  </div>
62
63
  </div>
@@ -72,12 +73,13 @@
72
73
  </h2>
73
74
  <p class="text-base-content/70 mb-4">Verify application health and API endpoint status</p>
74
75
  <div class="card-actions justify-end">
75
- <a href="{{ url_for("health_ping") }}" class="btn btn-success btn-sm">Check Health</a>
76
+ <a href="{{ url_for('health_ping') }}" class="btn btn-success btn-sm">Check Health</a>
76
77
  </div>
77
78
  </div>
78
79
  </div>
79
80
  </div>
80
81
  <!-- Debug Navigation Footer -->
81
82
  {% include "debug/components/debug_nav.html.jinja" %}
83
+
82
84
  </div>
83
85
  {% endblock body %}
@@ -1,4 +1,5 @@
1
1
  {% extends "base/skeleton.html.jinja" %}
2
+
2
3
  {% block title %}
3
4
  System Information - Debug
4
5
  {% endblock title %}
@@ -252,5 +253,6 @@
252
253
  </div>
253
254
  <!-- Debug Navigation Footer -->
254
255
  {% include "debug/components/debug_nav.html.jinja" %}
256
+
255
257
  </div>
256
258
  {% endblock body %}
@@ -1,4 +1,5 @@
1
1
  {% extends "base/skeleton.html.jinja" %}
2
+
2
3
  {% block title %}
3
4
  Debug - User Impersonation
4
5
  {% endblock title %}
@@ -36,7 +37,7 @@
36
37
  User ID: <code>{{ current_user_id }}</code>
37
38
  </p>
38
39
  <div class="card-actions justify-end">
39
- <form method="post" action="/debug/stop-impersonation">
40
+ <form method="post" action="{{ url_for('debug.stop_impersonation') }}">
40
41
  <button type="submit" class="btn btn-secondary btn-sm">Stop Impersonation</button>
41
42
  </form>
42
43
  </div>
@@ -105,7 +106,7 @@
105
106
  <td class="text-center">
106
107
  {% if current_user_id != user.id|string %}
107
108
  <form method="post"
108
- action="/debug/impersonate/{{ user.id }}"
109
+ action="{{ url_for('debug.impersonate_user', user_id=user.id) }}"
109
110
  class="inline">
110
111
  <button type="submit" class="btn btn-outline btn-sm">Impersonate</button>
111
112
  </form>
@@ -133,5 +134,6 @@
133
134
  </div>
134
135
  <!-- Debug Navigation Footer -->
135
136
  {% include "debug/components/debug_nav.html.jinja" %}
137
+
136
138
  </div>
137
139
  {% endblock body %}
@@ -1,4 +1,5 @@
1
1
  {% extends "base/skeleton.html.jinja" %}
2
+
2
3
  {% block title %}
3
4
  Version Information - Debug
4
5
  {% endblock title %}
@@ -49,5 +50,6 @@
49
50
  </div>
50
51
  <!-- Debug Navigation Footer -->
51
52
  {% include "debug/components/debug_nav.html.jinja" %}
53
+
52
54
  </div>
53
55
  {% endblock body %}
@@ -1,4 +1,5 @@
1
1
  {% extends "base/skeleton.html.jinja" %}
2
+
2
3
  {% block title %}
3
4
  {{ _("Check Your Email") }}
4
5
  {% endblock title %}
@@ -64,7 +65,7 @@
64
65
  {{ _("Try Again") }}
65
66
  </a>
66
67
  <!-- Back to Home -->
67
- <a href="{{ url_for("homepage") }}"
68
+ <a href="{{ url_for('homepage') }}"
68
69
  class="btn btn-ghost btn-block gap-2 text-base-content/70 hover:text-base-content transition-all duration-300">
69
70
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
70
71
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
@@ -1,5 +1,6 @@
1
1
  {% set BODY_CLASS = "min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 flex flex-col justify-between" %}
2
2
  {% extends "base/skeleton.html.jinja" %}
3
+
3
4
  {% block body %}
4
5
  <!-- Main Content -->
5
6
  <div class="flex-1 flex items-center justify-center px-6">
@@ -1,4 +1,5 @@
1
1
  {% extends "base/skeleton.html.jinja" %}
2
+
2
3
  {% block title %}
3
4
  {{ _("Sign In") }}
4
5
  {% endblock title %}
@@ -15,7 +16,11 @@
15
16
  {% if has_oauth and providers %}
16
17
  <div class="space-y-3 mb-6">
17
18
  {% for provider in providers %}
18
- <a href="{{ url_for('login_with_' + provider) }}{%- if next %}?next={{ next }}{%- endif %}"
19
+ {% set login_url = url_for('login_with_' + provider) %}
20
+ {% if next %}
21
+ {% set login_url = login_url ~ '?next=' ~ next %}
22
+ {% endif %}
23
+ <a href="{{ login_url }}"
19
24
  class="btn btn-outline btn-block justify-start gap-3 hover:btn-primary group transition-all duration-300 hover:scale-[1.02]">
20
25
  <span>{{ _("Continue with %(provider)s") | format(provider=provider.title()) }}</span>
21
26
  </a>
@@ -29,7 +34,7 @@
29
34
  <div class="bg-base-200/50 rounded-2xl p-6 border border-base-300/50">
30
35
  <form method="post"
31
36
  class="space-y-4"
32
- action="{{ url_for("send_magic_link") }}">
37
+ action="{{ url_for('send_magic_link') }}">
33
38
  {% if next %}<input type="hidden" name="next" value="{{ next }}" />{% endif %}
34
39
  <div class="form-control">
35
40
  <label class="label" for="email">
@@ -40,7 +45,7 @@
40
45
  id="email"
41
46
  name="email"
42
47
  class="input input-bordered w-full pr-12 focus:input-primary transition-all duration-300"
43
- placeholder="{{ _("Enter your email address") }}"
48
+ placeholder="{{ _('Enter your email address') }}"
44
49
  required
45
50
  autocomplete="email" />
46
51
  <div class="absolute inset-y-0 right-0 flex items-center pr-3">
@@ -1,4 +1,5 @@
1
1
  {% extends "base/skeleton.html.jinja" %}
2
+
2
3
  {% block title %}
3
4
  {{ _("Edit Profile") }}
4
5
  {% endblock title %}
@@ -13,7 +14,7 @@
13
14
  </div>
14
15
  <!-- Edit Form -->
15
16
  <div class="bg-white shadow-lg rounded-lg p-6">
16
- <form method="post" action="{{ url_for("user_edit_submit") }}">
17
+ <form method="post" action="{{ url_for('user_edit_submit') }}">
17
18
  <div class="space-y-6">
18
19
  <!-- Name Field -->
19
20
  <div>
@@ -55,7 +56,7 @@
55
56
  </div>
56
57
  <!-- Form Actions -->
57
58
  <div class="flex justify-between items-center mt-8 pt-6 border-t border-gray-200">
58
- <a href="{{ url_for("user_profile") }}" class="btn btn-ghost">{{ _("Cancel") }}</a>
59
+ <a href="{{ url_for('user_profile') }}" class="btn btn-ghost">{{ _("Cancel") }}</a>
59
60
  <button type="submit" class="btn btn-primary">{{ _("Save Changes") }}</button>
60
61
  </div>
61
62
  </form>
@@ -1,4 +1,5 @@
1
1
  {% extends "base/skeleton.html.jinja" %}
2
+
2
3
  {% block title %}
3
4
  {{ _("User Profile") }}
4
5
  {% endblock title %}
@@ -108,7 +109,7 @@
108
109
  hx-get="/user/edit"
109
110
  hx-target="#main-content"
110
111
  hx-swap="innerHTML">{{ _("Edit Profile") }}</button>
111
- <a href="{{ url_for("auth_logout") }}" class="btn btn-outline btn-error">{{ _("Sign Out") }}</a>
112
+ <a href="{{ url_for('auth_logout') }}" class="btn btn-outline btn-error">{{ _("Sign Out") }}</a>
112
113
  </div>
113
114
  </div>
114
115
  </div>