platzky 0.3.1__tar.gz → 0.3.4__tar.gz
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.
- {platzky-0.3.1 → platzky-0.3.4}/PKG-INFO +2 -1
- platzky-0.3.4/platzky/admin/fake_login.py +104 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/admin/templates/admin.html +1 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/admin/templates/login.html +4 -2
- {platzky-0.3.1 → platzky-0.3.4}/platzky/blog/blog.py +0 -1
- platzky-0.3.4/platzky/db/README.md +70 -0
- platzky-0.3.4/platzky/db/github_json_db.py +69 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/db/google_json_db.py +2 -2
- {platzky-0.3.1 → platzky-0.3.4}/platzky/db/json_db.py +2 -2
- {platzky-0.3.1 → platzky-0.3.4}/platzky/db/json_file_db.py +2 -2
- {platzky-0.3.1 → platzky-0.3.4}/platzky/engine.py +0 -1
- {platzky-0.3.1 → platzky-0.3.4}/platzky/platzky.py +12 -7
- {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/base.html +11 -0
- {platzky-0.3.1 → platzky-0.3.4}/pyproject.toml +2 -1
- {platzky-0.3.1 → platzky-0.3.4}/README.md +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/__init__.py +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/admin/admin.py +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/admin/templates/module.html +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/blog/__init__.py +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/blog/comment_form.py +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/config.py +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/db/__init__.py +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/db/db.py +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/db/db_loader.py +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/db/graph_ql_db.py +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/locale/en/LC_MESSAGES/messages.po +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/locale/pl/LC_MESSAGES/messages.po +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/models.py +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/plugin/plugin.py +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/plugin/plugin_loader.py +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/seo/seo.py +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/static/blog.css +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/static/styles.css +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/404.html +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/blog.html +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/body_meta.html +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/dynamic_css.html +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/feed.xml +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/head_meta.html +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/page.html +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/post.html +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/robots.txt +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/templates/sitemap.xml +0 -0
- {platzky-0.3.1 → platzky-0.3.4}/platzky/www_handler.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: platzky
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Not only blog engine
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.10,<4.0
|
|
@@ -21,6 +21,7 @@ Requires-Dist: google-cloud-storage (>=2.5.0,<3.0.0)
|
|
|
21
21
|
Requires-Dist: gql (>=3.4.0,<4.0.0)
|
|
22
22
|
Requires-Dist: humanize (>=4.9.0,<5.0.0)
|
|
23
23
|
Requires-Dist: pydantic (>=2.7.1,<3.0.0)
|
|
24
|
+
Requires-Dist: pygithub (>=2.6.1,<3.0.0)
|
|
24
25
|
Description-Content-Type: text/markdown
|
|
25
26
|
|
|
26
27
|

|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Fake login functionality for development environments only.
|
|
3
|
+
|
|
4
|
+
WARNING: This module provides fake login functionality and should NEVER be used in production
|
|
5
|
+
environments as it bypasses proper authentication and authorization controls.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from typing import Any, Callable
|
|
10
|
+
|
|
11
|
+
from flask import Blueprint, flash, redirect, render_template_string, session, url_for
|
|
12
|
+
from flask_wtf import FlaskForm
|
|
13
|
+
from markupsafe import Markup
|
|
14
|
+
|
|
15
|
+
ROLE_ADMIN = "admin"
|
|
16
|
+
ROLE_NONADMIN = "nonadmin"
|
|
17
|
+
VALID_ROLES = [ROLE_ADMIN, ROLE_NONADMIN]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FakeLoginForm(FlaskForm):
|
|
21
|
+
"""
|
|
22
|
+
Empty form class that inherits CSRF protection from FlaskForm.
|
|
23
|
+
|
|
24
|
+
Used specifically for the fake login functionality to enable
|
|
25
|
+
CSRF token validation on form submissions.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_fake_login_html() -> Callable[[], str]:
|
|
32
|
+
"""Return a callable that generates HTML for fake login buttons."""
|
|
33
|
+
|
|
34
|
+
def generate_html() -> str:
|
|
35
|
+
admin_url = url_for("admin.handle_fake_login", role="admin")
|
|
36
|
+
nonadmin_url = url_for("admin.handle_fake_login", role="nonadmin")
|
|
37
|
+
|
|
38
|
+
# Create a form instance to get the CSRF token
|
|
39
|
+
form = FakeLoginForm()
|
|
40
|
+
|
|
41
|
+
html = render_template_string(
|
|
42
|
+
"""
|
|
43
|
+
<div class="col-md-6 mb-4">
|
|
44
|
+
<div class="card">
|
|
45
|
+
<div class="card-header">
|
|
46
|
+
Development Login
|
|
47
|
+
</div>
|
|
48
|
+
<div class="card-body">
|
|
49
|
+
<p class="text-danger"><strong>Warning:</strong> For development only</p>
|
|
50
|
+
<div class="d-flex justify-content-around">
|
|
51
|
+
<form method="post" action="{{ admin_url }}" style="display: inline;">
|
|
52
|
+
{{ form.csrf_token }}
|
|
53
|
+
<button type="submit" class="btn btn-primary">Login as Admin</button>
|
|
54
|
+
</form>
|
|
55
|
+
<form method="post" action="{{ nonadmin_url }}" style="display: inline;">
|
|
56
|
+
{{ form.csrf_token }}
|
|
57
|
+
<button type="submit" class="btn btn-secondary">Login as Non-Admin</button>
|
|
58
|
+
</form>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
""",
|
|
64
|
+
form=form,
|
|
65
|
+
admin_url=admin_url,
|
|
66
|
+
nonadmin_url=nonadmin_url,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return Markup(html)
|
|
70
|
+
|
|
71
|
+
return generate_html
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def setup_fake_login_routes(admin_blueprint: Blueprint) -> Blueprint:
|
|
75
|
+
"""Add fake login routes to the provided admin_blueprint."""
|
|
76
|
+
|
|
77
|
+
env = os.environ
|
|
78
|
+
is_testing = "PYTEST_CURRENT_TEST" in env.keys() or env.get("FLASK_DEBUG") in (
|
|
79
|
+
"1",
|
|
80
|
+
"true",
|
|
81
|
+
"True",
|
|
82
|
+
True,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if not is_testing:
|
|
86
|
+
raise RuntimeError(
|
|
87
|
+
"SECURITY ERROR: Fake login routes are enabled outside of a testing environment! "
|
|
88
|
+
"This functionality must only be used during development or testing."
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@admin_blueprint.route("/fake-login/<role>", methods=["POST"])
|
|
92
|
+
def handle_fake_login(role: str) -> Any:
|
|
93
|
+
form = FakeLoginForm()
|
|
94
|
+
if form.validate_on_submit() and role in VALID_ROLES:
|
|
95
|
+
if role == ROLE_ADMIN:
|
|
96
|
+
session["user"] = {"username": ROLE_ADMIN, "role": ROLE_ADMIN}
|
|
97
|
+
else:
|
|
98
|
+
session["user"] = {"username": "user", "role": ROLE_NONADMIN}
|
|
99
|
+
return redirect(url_for("admin.admin_panel_home"))
|
|
100
|
+
|
|
101
|
+
flash(f"Invalid role: {role}. Must be one of: {', '.join(VALID_ROLES)}", "error")
|
|
102
|
+
return redirect(url_for("admin.admin_panel_home"))
|
|
103
|
+
|
|
104
|
+
return admin_blueprint
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Platzky Database Modules
|
|
2
|
+
|
|
3
|
+
This directory contains the database abstraction layer for the Platzky application. The database modules provide a consistent interface for accessing content regardless of where it's stored.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
The database layer is built on an abstract base class (DB) that defines a common interface. Multiple implementations are provided for different storage backends:
|
|
8
|
+
|
|
9
|
+
- **Json**: Base implementation for JSON data sources
|
|
10
|
+
- **JsonFile**: Local JSON file storage
|
|
11
|
+
- **GithubJsonDb**: JSON files stored in GitHub repository
|
|
12
|
+
- **GoogleJsonDb**: JSON files stored in Google Cloud Storage
|
|
13
|
+
- **GraphQL**: Content stored in a GraphQL API
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
Database configuration is specified in your application config file. Each database type has its own configuration schema.
|
|
19
|
+
|
|
20
|
+
### JSON File Database
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
DB:
|
|
24
|
+
TYPE: json_file
|
|
25
|
+
PATH: "/path/to/data.json"
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
### GitHub JSON Database
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
DB:
|
|
32
|
+
TYPE: github_json
|
|
33
|
+
REPO_NAME: "username/repository"
|
|
34
|
+
GITHUB_TOKEN: "your_github_token"
|
|
35
|
+
BRANCH_NAME: "main"
|
|
36
|
+
PATH_TO_FILE: "data.json"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Google JSON Database
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
DB:
|
|
43
|
+
TYPE: google_json
|
|
44
|
+
BUCKET_NAME: "your-bucket-name"
|
|
45
|
+
SOURCE_BLOB_NAME: "data.json"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### GraphQL Database
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
DB:
|
|
52
|
+
TYPE: graph_ql
|
|
53
|
+
CMS_ENDPOINT: "https://your-graphql-endpoint.com/api"
|
|
54
|
+
CMS_TOKEN: "your_graphql_token"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
The database is automatically initialized based on your configuration. The application will use the appropriate database implementation
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from platzky.db.db_loader import get_db
|
|
63
|
+
|
|
64
|
+
# db_config is loaded from your application config
|
|
65
|
+
db = get_db(db_config)
|
|
66
|
+
|
|
67
|
+
# Now you can use any of the standard DB methods
|
|
68
|
+
posts = db.get_all_posts("en")
|
|
69
|
+
menu_items = db.get_menu_items_in_lang("en")
|
|
70
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
from github import Github
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from platzky.db.db import DBConfig
|
|
8
|
+
from platzky.db.json_db import Json as JsonDB
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def db_config_type():
|
|
12
|
+
return GithubJsonDbConfig
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GithubJsonDbConfig(DBConfig):
|
|
16
|
+
github_token: str = Field(alias="GITHUB_TOKEN")
|
|
17
|
+
repo_name: str = Field(alias="REPO_NAME")
|
|
18
|
+
path_to_file: str = Field(alias="PATH_TO_FILE")
|
|
19
|
+
branch_name: str = Field(alias="BRANCH_NAME", default="main")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def db_from_config(config: GithubJsonDbConfig):
|
|
23
|
+
return GithubJsonDb(
|
|
24
|
+
config.github_token, config.repo_name, config.branch_name, config.path_to_file
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_db(config):
|
|
29
|
+
github_json_db_config = GithubJsonDbConfig.model_validate(config)
|
|
30
|
+
return GithubJsonDb(
|
|
31
|
+
github_json_db_config.github_token,
|
|
32
|
+
github_json_db_config.repo_name,
|
|
33
|
+
github_json_db_config.branch_name,
|
|
34
|
+
github_json_db_config.path_to_file,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class GithubJsonDb(JsonDB):
|
|
39
|
+
def __init__(self, github_token: str, repo_name: str, branch_name: str, path_to_file: str):
|
|
40
|
+
self.branch_name = branch_name
|
|
41
|
+
self.repo = Github(github_token).get_repo(repo_name)
|
|
42
|
+
self.file_path = path_to_file
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
file_content = self.repo.get_contents(self.file_path, ref=self.branch_name)
|
|
46
|
+
|
|
47
|
+
if isinstance(file_content, list):
|
|
48
|
+
raise ValueError(f"Path '{self.file_path}' points to a directory, not a file")
|
|
49
|
+
|
|
50
|
+
if file_content.content:
|
|
51
|
+
raw_data = file_content.decoded_content.decode("utf-8")
|
|
52
|
+
else:
|
|
53
|
+
|
|
54
|
+
download_url = file_content.download_url
|
|
55
|
+
response = requests.get(download_url, timeout=40)
|
|
56
|
+
response.raise_for_status()
|
|
57
|
+
raw_data = response.text
|
|
58
|
+
|
|
59
|
+
self.data = json.loads(raw_data)
|
|
60
|
+
|
|
61
|
+
except (json.JSONDecodeError, requests.RequestException) as e:
|
|
62
|
+
raise ValueError(f"Error parsing JSON content: {e}")
|
|
63
|
+
except Exception as e:
|
|
64
|
+
raise ValueError(f"Error retrieving GitHub content: {e}")
|
|
65
|
+
|
|
66
|
+
super().__init__(self.data)
|
|
67
|
+
|
|
68
|
+
self.module_name = "github_json_db"
|
|
69
|
+
self.db_name = "GithubJsonDb"
|
|
@@ -18,7 +18,6 @@ class Engine(Flask):
|
|
|
18
18
|
directory = os.path.dirname(os.path.realpath(__file__))
|
|
19
19
|
locale_dir = os.path.join(directory, "locale")
|
|
20
20
|
config.translation_directories.append(locale_dir)
|
|
21
|
-
|
|
22
21
|
babel_translation_directories = ";".join(config.translation_directories)
|
|
23
22
|
self.babel = Babel(
|
|
24
23
|
self,
|
|
@@ -3,6 +3,7 @@ import urllib.parse
|
|
|
3
3
|
|
|
4
4
|
from flask import redirect, render_template, request, session
|
|
5
5
|
from flask_minify import Minify
|
|
6
|
+
from flask_wtf import CSRFProtect
|
|
6
7
|
|
|
7
8
|
from platzky.admin import admin
|
|
8
9
|
from platzky.blog import blog
|
|
@@ -81,10 +82,19 @@ def create_engine(config: Config, db) -> Engine:
|
|
|
81
82
|
|
|
82
83
|
|
|
83
84
|
def create_app_from_config(config: Config) -> Engine:
|
|
84
|
-
|
|
85
|
+
db = get_db(config.db)
|
|
86
|
+
engine = create_engine(config, db)
|
|
87
|
+
|
|
85
88
|
admin_blueprint = admin.create_admin_blueprint(
|
|
86
89
|
login_methods=engine.login_methods, db=engine.db, locale_func=engine.get_locale
|
|
87
90
|
)
|
|
91
|
+
|
|
92
|
+
if config.feature_flags and config.feature_flags.get("FAKE_LOGIN", False):
|
|
93
|
+
from platzky.admin.fake_login import get_fake_login_html, setup_fake_login_routes
|
|
94
|
+
|
|
95
|
+
engine.login_methods.append(get_fake_login_html())
|
|
96
|
+
admin_blueprint = setup_fake_login_routes(admin_blueprint)
|
|
97
|
+
|
|
88
98
|
blog_blueprint = blog.create_blog_blueprint(
|
|
89
99
|
db=engine.db,
|
|
90
100
|
blog_prefix=config.blog_prefix,
|
|
@@ -98,15 +108,10 @@ def create_app_from_config(config: Config) -> Engine:
|
|
|
98
108
|
engine.register_blueprint(seo_blueprint)
|
|
99
109
|
|
|
100
110
|
Minify(app=engine, html=True, js=True, cssless=True)
|
|
111
|
+
CSRFProtect(app=engine)
|
|
101
112
|
return engine
|
|
102
113
|
|
|
103
114
|
|
|
104
|
-
def create_engine_from_config(config: Config) -> Engine:
|
|
105
|
-
"""Create an engine from a config."""
|
|
106
|
-
db = get_db(config.db)
|
|
107
|
-
return create_engine(config, db)
|
|
108
|
-
|
|
109
|
-
|
|
110
115
|
def create_app(config_path: str) -> Engine:
|
|
111
116
|
config = Config.parse_yaml(config_path)
|
|
112
117
|
return create_app_from_config(config)
|
|
@@ -14,6 +14,17 @@
|
|
|
14
14
|
{% block body_meta %}
|
|
15
15
|
{% include "body_meta.html" %}
|
|
16
16
|
{% endblock %}
|
|
17
|
+
|
|
18
|
+
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
19
|
+
{% if messages %}
|
|
20
|
+
<div class="flash-messages">
|
|
21
|
+
{% for category, message in messages %}
|
|
22
|
+
<div class="alert alert-{{ category }}">{{ message }}</div>
|
|
23
|
+
{% endfor %}
|
|
24
|
+
</div>
|
|
25
|
+
{% endif %}
|
|
26
|
+
{% endwith %}
|
|
27
|
+
|
|
17
28
|
<div class="container-fluid d-flex flex-column h-100 g-0">
|
|
18
29
|
<div class="row header-row bg-light g-0">
|
|
19
30
|
<nav class="navbar navbar-expand-lg navbar-light px-3 py-1" id="mainNav">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "platzky"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.4"
|
|
4
4
|
description = "Not only blog engine"
|
|
5
5
|
authors = []
|
|
6
6
|
license = "MIT"
|
|
@@ -19,6 +19,7 @@ google-cloud-storage = "^2.5.0"
|
|
|
19
19
|
humanize = "^4.9.0"
|
|
20
20
|
pydantic = "^2.7.1"
|
|
21
21
|
deprecation = "^2.1.0"
|
|
22
|
+
pygithub = "^2.6.1"
|
|
22
23
|
|
|
23
24
|
[tool.poetry.group.dev.dependencies]
|
|
24
25
|
pytest = "^8.2.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|