goodmap 1.3.1__tar.gz → 1.4.0__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.
- {goodmap-1.3.1 → goodmap-1.4.0}/PKG-INFO +2 -2
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/admin_api.py +6 -2
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/core_api.py +43 -9
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/goodmap.py +17 -1
- {goodmap-1.3.1 → goodmap-1.4.0}/pyproject.toml +2 -2
- {goodmap-1.3.1 → goodmap-1.4.0}/LICENSE.md +0 -0
- {goodmap-1.3.1 → goodmap-1.4.0}/README.md +0 -0
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/__init__.py +0 -0
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/api_models.py +0 -0
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/clustering.py +0 -0
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/config.py +0 -0
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/core.py +0 -0
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/data_models/location.py +0 -0
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/data_validator.py +0 -0
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/db.py +0 -0
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/exceptions.py +0 -0
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/formatter.py +0 -0
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/json_security.py +0 -0
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/templates/goodmap-admin.html +0 -0
- {goodmap-1.3.1 → goodmap-1.4.0}/goodmap/templates/map.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: goodmap
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Map engine to serve all the people :)
|
|
5
5
|
Author: Krzysztof Kolodzinski
|
|
6
6
|
Author-email: krzysztof.kolodzinski@problematy.pl
|
|
@@ -24,7 +24,7 @@ Requires-Dist: gunicorn (>=20.1,<24.0)
|
|
|
24
24
|
Requires-Dist: humanize (>=4.6.0,<5.0.0)
|
|
25
25
|
Requires-Dist: myst-parser (>=4.0.0,<5.0.0) ; extra == "docs"
|
|
26
26
|
Requires-Dist: numpy (>=2.2.0,<3.0.0)
|
|
27
|
-
Requires-Dist: platzky (>=1.
|
|
27
|
+
Requires-Dist: platzky (>=1.3.0,<2.0.0)
|
|
28
28
|
Requires-Dist: pydantic (>=2.7.1,<3.0.0)
|
|
29
29
|
Requires-Dist: pysupercluster-problematy (>=0.7.8,<0.8.0)
|
|
30
30
|
Requires-Dist: scipy (>=1.15.1,<2.0.0)
|
|
@@ -34,7 +34,9 @@ def _clean_model_name(model: Type[Any]) -> str:
|
|
|
34
34
|
def _handle_location_validation_error(e: LocationValidationError):
|
|
35
35
|
"""Handle LocationValidationError and return appropriate response."""
|
|
36
36
|
logger.warning(
|
|
37
|
-
"Location validation failed",
|
|
37
|
+
"Location validation failed - uuid: %s, errors: %s",
|
|
38
|
+
e.uuid,
|
|
39
|
+
e.validation_errors,
|
|
38
40
|
extra={"uuid": e.uuid, "errors": e.validation_errors},
|
|
39
41
|
)
|
|
40
42
|
return make_response(jsonify({"message": ERROR_INVALID_LOCATION_DATA}), 400)
|
|
@@ -126,7 +128,9 @@ def _update_suggestion_handler(database, suggestion_id):
|
|
|
126
128
|
database.update_suggestion(suggestion_id, status)
|
|
127
129
|
except LocationValidationError as e:
|
|
128
130
|
logger.warning(
|
|
129
|
-
"Location validation failed in suggestion",
|
|
131
|
+
"Location validation failed in suggestion - uuid: %s, errors: %s",
|
|
132
|
+
e.uuid,
|
|
133
|
+
e.validation_errors,
|
|
130
134
|
extra={"uuid": e.uuid, "errors": e.validation_errors},
|
|
131
135
|
)
|
|
132
136
|
return make_response(jsonify({"message": ERROR_INVALID_LOCATION_DATA}), 400)
|
|
@@ -7,7 +7,8 @@ import numpy
|
|
|
7
7
|
import pysupercluster
|
|
8
8
|
from flask import Blueprint, jsonify, make_response, request
|
|
9
9
|
from flask_babel import gettext
|
|
10
|
-
from platzky.
|
|
10
|
+
from platzky.attachment import AttachmentProtocol
|
|
11
|
+
from platzky.config import AttachmentConfig, LanguagesMapping
|
|
11
12
|
from spectree import Response, SpecTree
|
|
12
13
|
|
|
13
14
|
from goodmap.api_models import (
|
|
@@ -80,15 +81,21 @@ def core_pages(
|
|
|
80
81
|
notifier_function,
|
|
81
82
|
csrf_generator,
|
|
82
83
|
location_model,
|
|
84
|
+
photo_attachment_class: type[AttachmentProtocol],
|
|
85
|
+
photo_attachment_config: AttachmentConfig,
|
|
83
86
|
feature_flags={},
|
|
84
87
|
) -> Blueprint:
|
|
85
88
|
core_api_blueprint = Blueprint("api", __name__, url_prefix="/api")
|
|
86
89
|
|
|
87
|
-
#
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
# Build photo error message from config
|
|
91
|
+
allowed_ext = ", ".join(sorted(photo_attachment_config.allowed_extensions or []))
|
|
92
|
+
max_size_mb = photo_attachment_config.max_size / (1024 * 1024)
|
|
93
|
+
error_invalid_photo = (
|
|
94
|
+
f"Invalid photo. Allowed formats: {allowed_ext}. Max size: {max_size_mb:.0f}MB."
|
|
95
|
+
)
|
|
90
96
|
|
|
91
|
-
|
|
97
|
+
# Initialize Spectree for API documentation and validation
|
|
98
|
+
def _clean_model_name(model: type) -> str:
|
|
92
99
|
return model.__name__
|
|
93
100
|
|
|
94
101
|
spec = SpecTree(
|
|
@@ -111,6 +118,9 @@ def core_pages(
|
|
|
111
118
|
import json as json_lib
|
|
112
119
|
|
|
113
120
|
try:
|
|
121
|
+
# Initialize photo attachment (only populated for multipart/form-data)
|
|
122
|
+
photo_attachment = None
|
|
123
|
+
|
|
114
124
|
# Handle both multipart/form-data (with file uploads) and JSON
|
|
115
125
|
if request.content_type and request.content_type.startswith("multipart/form-data"):
|
|
116
126
|
# Parse form data dynamically
|
|
@@ -146,8 +156,24 @@ def core_pages(
|
|
|
146
156
|
# If not JSON, use as-is (simple string values)
|
|
147
157
|
suggested_location[key] = value
|
|
148
158
|
|
|
149
|
-
#
|
|
150
|
-
|
|
159
|
+
# Extract and validate photo attachment if present
|
|
160
|
+
photo_file = request.files.get("photo")
|
|
161
|
+
if photo_file and photo_file.filename:
|
|
162
|
+
photo_content = photo_file.read()
|
|
163
|
+
photo_mime = photo_file.content_type or "application/octet-stream"
|
|
164
|
+
|
|
165
|
+
# Validate using configured Attachment class
|
|
166
|
+
try:
|
|
167
|
+
photo_attachment = photo_attachment_class(
|
|
168
|
+
photo_file.filename, photo_content, photo_mime
|
|
169
|
+
)
|
|
170
|
+
except ValueError as e:
|
|
171
|
+
logger.warning(
|
|
172
|
+
"Rejected photo: %s",
|
|
173
|
+
e,
|
|
174
|
+
extra={"photo_filename": photo_file.filename},
|
|
175
|
+
)
|
|
176
|
+
return make_response(jsonify({"message": error_invalid_photo}), 400)
|
|
151
177
|
else:
|
|
152
178
|
# Parse JSON data with security checks (depth/size protection)
|
|
153
179
|
raw_data = request.get_data(as_text=True)
|
|
@@ -186,10 +212,18 @@ def core_pages(
|
|
|
186
212
|
database.add_suggestion(location.model_dump())
|
|
187
213
|
message = gettext("A new location has been suggested with details")
|
|
188
214
|
notifier_message = f"{message}: {json_lib.dumps(suggested_location, indent=2)}"
|
|
189
|
-
|
|
215
|
+
attachments = [photo_attachment] if photo_attachment else None
|
|
216
|
+
notifier_function(notifier_message, attachments=attachments)
|
|
190
217
|
except LocationValidationError as e:
|
|
218
|
+
# NOTE: validation_errors includes input values from the location model fields:
|
|
219
|
+
# - Core fields: position (lat/long), uuid, remark
|
|
220
|
+
# - Dynamic fields: categories and obligatory_fields configured per deployment
|
|
221
|
+
# These are geographic/categorical data, NOT PII (no email, phone, names of people).
|
|
222
|
+
# Safe to log for debugging. If PII fields are ever added to the location model,
|
|
223
|
+
# strip 'input' from validation_errors before logging.
|
|
191
224
|
logger.warning(
|
|
192
|
-
"Location validation failed in suggest endpoint",
|
|
225
|
+
"Location validation failed in suggest endpoint: %s",
|
|
226
|
+
e.validation_errors,
|
|
193
227
|
extra={"errors": e.validation_errors},
|
|
194
228
|
)
|
|
195
229
|
return make_response(jsonify({"message": ERROR_INVALID_LOCATION_DATA}), 400)
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"""Goodmap engine with location management and admin interface."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
4
5
|
|
|
5
6
|
from flask import Blueprint, redirect, render_template, session
|
|
6
7
|
from flask_wtf.csrf import CSRFProtect, generate_csrf
|
|
7
8
|
from platzky import platzky
|
|
8
|
-
from platzky.
|
|
9
|
+
from platzky.attachment import create_attachment_class
|
|
10
|
+
from platzky.config import AttachmentConfig, languages_dict
|
|
9
11
|
from platzky.models import CmsModule
|
|
10
12
|
|
|
11
13
|
from goodmap.admin_api import admin_pages
|
|
@@ -17,6 +19,8 @@ from goodmap.db import (
|
|
|
17
19
|
get_location_obligatory_fields,
|
|
18
20
|
)
|
|
19
21
|
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
20
24
|
|
|
21
25
|
def create_app(config_path: str) -> platzky.Engine:
|
|
22
26
|
"""Create Goodmap application from YAML configuration file.
|
|
@@ -97,12 +101,24 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine:
|
|
|
97
101
|
|
|
98
102
|
CSRFProtect(app)
|
|
99
103
|
|
|
104
|
+
# Create Attachment class for photo uploads
|
|
105
|
+
# JPEG-only: universal browser/device support, good compression for location photos,
|
|
106
|
+
# no transparency needed. PNG/WebP can be added if user demand warrants it.
|
|
107
|
+
photo_attachment_config = AttachmentConfig(
|
|
108
|
+
allowed_mime_types=frozenset({"image/jpeg"}),
|
|
109
|
+
allowed_extensions=frozenset({"jpg", "jpeg"}),
|
|
110
|
+
max_size=5 * 1024 * 1024, # 5MB - reasonable for location photos
|
|
111
|
+
)
|
|
112
|
+
PhotoAttachment = create_attachment_class(photo_attachment_config)
|
|
113
|
+
|
|
100
114
|
cp = core_pages(
|
|
101
115
|
app.db,
|
|
102
116
|
languages_dict(config.languages),
|
|
103
117
|
app.notify,
|
|
104
118
|
generate_csrf,
|
|
105
119
|
location_model,
|
|
120
|
+
photo_attachment_class=PhotoAttachment,
|
|
121
|
+
photo_attachment_config=photo_attachment_config,
|
|
106
122
|
feature_flags=config.feature_flags,
|
|
107
123
|
)
|
|
108
124
|
app.register_blueprint(cp)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "goodmap"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.4.0"
|
|
4
4
|
description = "Map engine to serve all the people :)"
|
|
5
5
|
authors = ["Krzysztof Kolodzinski <krzysztof.kolodzinski@problematy.pl>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -20,7 +20,7 @@ Flask-WTF = "^1.2.1"
|
|
|
20
20
|
gql = "^3.4.0"
|
|
21
21
|
aiohttp = "^3.8.4"
|
|
22
22
|
pydantic = "^2.7.1"
|
|
23
|
-
platzky = "^1.
|
|
23
|
+
platzky = "^1.3.0"
|
|
24
24
|
deprecation = "^2.1.0"
|
|
25
25
|
numpy = "^2.2.0"
|
|
26
26
|
# Using fork because official PyPI version (0.7.7) has outdated numpy setup hack
|
|
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
|