Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9e4c43d
Cleanup of permission utils from un neccessary objects
MarcelGeo May 7, 2025
34fcd74
Merge pull request #449 from MerginMaps/master
MarcelGeo May 8, 2025
eec6710
Merge remote-tracking branch 'origin/develop' into cleanup-refactor-p…
MarcelGeo May 8, 2025
e683272
Hide receive notifications for can_edit_profile users false
MarcelGeo May 9, 2025
5ba12b0
Initial version for diagnostic logs:
MarcelGeo May 9, 2025
0079a0b
fix config getting
MarcelGeo May 9, 2025
58ffbde
small variable change
MarcelGeo May 12, 2025
c6b3c24
bump 2025.4.1
MarcelGeo May 12, 2025
784f7c5
Some text to run tests
MarcelGeo May 12, 2025
909eee1
Merge pull request #450 from MerginMaps/cleanup-refactor-permissions-…
MarcelGeo May 12, 2025
e4b29f6
add back 413
MarcelGeo May 12, 2025
ec75688
Remove 413
MarcelGeo May 12, 2025
c1f9133
change test
MarcelGeo May 12, 2025
d83e4a5
just trying 413 replced by 422 (as it's real)
MarcelGeo May 12, 2025
4c5b6e2
Refactor:
MarcelGeo May 13, 2025
e8885d5
Merge pull request #454 from MerginMaps/make_tests_working
MarcelGeo May 13, 2025
8854e23
Merge pull request #453 from MerginMaps/bump_2025.4.1
MarcelGeo May 13, 2025
5c295e7
Added diagnostic logs to deployment
MarcelGeo May 13, 2025
9f864ba
Merge pull request #452 from MerginMaps/diagnostic-logs-endpoint
MarcelGeo May 14, 2025
5cc2883
check_password method can handle epmty password field
harminius May 22, 2025
ab79d97
Do not use abort in models
harminius May 26, 2025
a99bc7a
Make test closer to real behaviour
harminius May 27, 2025
0136d12
revert back
harminius May 27, 2025
290c688
Merge pull request #456 from MerginMaps/fix_login_without_pwd
MarcelGeo May 27, 2025
f6009d0
Merge remote-tracking branch 'origin/master' into merge-master-2025.4.1
MarcelGeo Jun 2, 2025
9069d81
Merge pull request #462 from MerginMaps/merge-master-2025.4.1
MarcelGeo Jun 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions deployment/community/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,7 @@ GEVENT_WORKER=True
# Deprecated from 2024.7.0, replacement is to set GEVENT_WORKER=True
NO_MONKEY_PATCH=False

# Diagnostic logs

DIAGNOSTIC_LOGS_DIR=/diagnostic_logs

2 changes: 2 additions & 0 deletions deployment/community/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ cp .env.template .prod.env

Next step is to create data directory for mergin maps `projects` with proper permissions. Should you prefer a different location, please do search and replace it in config files (`.prod.env`, `docker-compose.yml`). Make sure your volume is large enough since mergin maps keeps all projects files, their history and also needs some space for temporary processing.

If you want to persist diagnostic logs, create data directory `diagnostic_logs` with proper permissions.

For more details about deployment please check [docs](https://merginmaps.com/docs/server/install/#deployment).
1 change: 1 addition & 0 deletions deployment/community/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ services:
user: 901:999
volumes:
- ./projects:/data
- ./diagnostic_logs:/diagnostic_logs
- ../common/entrypoint.sh:/app/entrypoint.sh
env_file:
- .prod.env
Expand Down
3 changes: 3 additions & 0 deletions deployment/enterprise/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,6 @@ VECTOR_TILES_STYLE_URL=https://tiles-ee.merginmaps.com//styles/default.json
#QGIS_EXTRACTOR_TIMEOUT=60

#OVERVIEW_MAX_FILE_SIZE=1048576 # 1MB

# Diagnostic logs from Mobile and QGIS Plugin
DIAGNOSTIC_LOGS_DIR=/diagnostic_logs
1 change: 1 addition & 0 deletions deployment/enterprise/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ services:
command: ["gunicorn -w 4 --config config.py application:application"]
volumes:
- ./data:/data # map data dir to host
- ./diagnostic_logs:/diagnostic_logs # diagnostic logs dir
- ../common/entrypoint.sh:/app/entrypoint.sh
env_file:
- .prod.env
Expand Down
1 change: 1 addition & 0 deletions server/.test.env
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ GEODIFF_WORKING_DIR=/tmp/geodiff
SECURITY_BEARER_SALT='bearer'
SECURITY_EMAIL_SALT='email'
SECURITY_PASSWORD_SALT='password'
DIAGNOSTIC_LOGS_DIR=/tmp/diagnostic_logs
762 changes: 381 additions & 381 deletions server/Pipfile.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions server/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"GLOBAL_READ",
"GLOBAL_WRITE",
"ENABLE_SUPERADMIN_ASSIGNMENT",
"DIAGNOSTIC_LOGS_URL",
]
)
register_stats(application)
Expand Down
84 changes: 84 additions & 0 deletions server/mergin/api.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
openapi: 3.0.0
info:
description: Common Mergin Maps API
version: "0.1"
title: Common Mergin Maps API
servers:
- url: /
paths:
/v1/latest-version:
get:
summary: Fetch latest available server version
operationId: get_latest_version
responses:
"200":
description: Latest version info
content:
application/json:
schema:
$ref: "#/components/schemas/ServerVersion"
"400":
$ref: "#/components/responses/BadRequestResp"
"404":
$ref: "#/components/responses/NotFoundResp"
x-openapi-router-controller: mergin.controller
/v2/diagnostic-logs:
post:
summary: Save diagnostic log to the server
description: This endpoint allows users to upload diagnostic logs for troubleshooting purposes from mobile and plugin.
operationId: save_diagnostic_log
x-openapi-router-controller: mergin.controller
parameters:
- name: app
in: query
description: Application name (e.g., "input-android-0.9.0")
required: true
schema:
type: string
requestBody:
required: true
content:
text/plain:
schema:
type: string
description: Log content in plain text
responses:
"200":
description: Log saved successfully
"400":
$ref: "#/components/responses/BadRequestResp"
"404":
$ref: "#/components/responses/NotFoundResp"
"413":
$ref: "#/components/responses/RequestTooLarge"
components:
responses:
UnauthorizedError:
description: Authentication information is missing or invalid.
NotFoundResp:
description: Not found
BadRequestResp:
description: Invalid request.
RequestTooLarge:
description: Request Entity Too Large.
schemas:
ServerVersion:
type: object
properties:
version:
type: string
example: 2023.1.1
major:
type: integer
example: 2023
minor:
type: integer
example: 1
fix:
nullable: true
type: integer
example: 1
info_url:
nullable: true
type: string
example: "https://github.com/MerginMaps/mergin/releases/tag/2023.1"
6 changes: 6 additions & 0 deletions server/mergin/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ def create_app(public_keys: List[str] = None) -> Flask:
options={"swagger_ui": False, "serve_spec": False},
validate_responses=True,
)
app.add_api(
"api.yaml",
arguments={"title": "Mergin"},
options={"swagger_ui": False, "serve_spec": False},
validate_responses=True,
)

app.app.config.from_object(SyncConfig)
app.app.connexion_app = app
Expand Down
3 changes: 3 additions & 0 deletions server/mergin/auth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ def __repr__(self):
return "<User %r>" % self.username

def check_password(self, password):
# users created through SSO
if self.passwd is None:
return
if isinstance(password, str):
password = password.encode("utf-8")
return bcrypt.checkpw(password, self.passwd.encode("utf-8"))
Expand Down
15 changes: 15 additions & 0 deletions server/mergin/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

from .version import get_version

config_dir = os.path.abspath(os.path.dirname(__file__))


class Configuration(object):
# flask/connexion variables
Expand Down Expand Up @@ -107,3 +109,16 @@ class Configuration(object):
# using gevent type of worker impose some requirements on code, e.g. to be greenlet safe, custom timeouts
GEVENT_WORKER = config("GEVENT_WORKER", default=False, cast=bool)
GEVENT_REQUEST_TIMEOUT = config("GEVENT_REQUEST_TIMEOUT", default=30, cast=int)
DIAGNOSTIC_LOGS_URL = config(
"DIAGNOSTIC_LOGS_URL",
default="",
)
DIAGNOSTIC_LOGS_DIR = config(
"DIAGNOSTIC_LOGS_DIR",
default=os.path.join(
config_dir, os.pardir, os.pardir, os.pardir, "diagnostic_logs"
),
)
DIAGNOSTIC_LOGS_MAX_SIZE = config(
"DIAGNOSTIC_LOGS_MAX_SIZE", default=1024 * 1024, cast=int
)
61 changes: 61 additions & 0 deletions server/mergin/controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import json
import logging
import os
from flask import abort, current_app, request
from flask_login import current_user
from magic import from_buffer
import time

import requests

from .utils import save_diagnostic_log_file
from .app import parse_version_string, db


def get_latest_version():
"""Parse information about available server updates from 3rd party service"""
try:
req = requests.get(current_app.config["STATISTICS_URL"] + "/latest-versions")
except requests.exceptions.RequestException:
abort(400, "Updates information not available")

if not req.ok:
abort(400, "Updates information not available")

data = req.json().get(current_app.config["SERVER_TYPE"].lower(), None)
if not data:
abort(400, "Updates information not available")

parsed_version = parse_version_string(data.get("version", ""))
if not parsed_version:
abort(400, "Updates information not available")

data = {**data, **parsed_version}
return data, 200


def save_diagnostic_log():
"""Save diagnostic logs"""
# if server is using external storage, we don't want to save logs
if current_app.config.get("DIAGNOSTIC_LOGS_URL"):
abort(404)

# check if plain text body is not larger than 1MB
max_size = current_app.config.get("DIAGNOSTIC_LOGS_MAX_SIZE")
if request.content_length > max_size:
abort(413)
# get body from request
body = request.get_data()
if not body:
abort(400)
if len(body) > max_size:
abort(413)
mime_type = from_buffer(body, mime=True)
if mime_type != "text/plain":
abort(400)

app = request.args.get("app")
username = current_user.username if current_user.is_authenticated else "anonymous"
save_diagnostic_log_file(app, username, body)

return "Log saved successfully", 200
41 changes: 2 additions & 39 deletions server/mergin/stats/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,6 @@ info:
servers:
- url: /
paths:
/v1/latest-version:
get:
summary: Fetch latest available server version
operationId: get_latest_version
responses:
"200":
description: Latest version info
content:
application/json:
schema:
$ref: "#/components/schemas/ServerVersion"
"400":
$ref: "#/components/responses/BadStatusResp"
"404":
$ref: "#/components/responses/NotFoundResp"
x-openapi-router-controller: mergin.stats.controller
/app/admin/report:
get:
summary: Download statistics for server
Expand Down Expand Up @@ -50,7 +34,7 @@ paths:
schema:
type: string
"400":
$ref: "#/components/responses/BadStatusResp"
$ref: "#/components/responses/BadRequestResp"
"404":
$ref: "#/components/responses/NotFoundResp"
components:
Expand All @@ -59,26 +43,5 @@ components:
description: Authentication information is missing or invalid.
NotFoundResp:
description: Project not found.
BadStatusResp:
BadRequestResp:
description: Invalid request.
schemas:
ServerVersion:
type: object
properties:
version:
type: string
example: 2023.1.1
major:
type: integer
example: 2023
minor:
type: integer
example: 1
fix:
nullable: true
type: integer
example: 1
info_url:
nullable: true
type: string
example: "https://github.com/MerginMaps/mergin/releases/tag/2023.1"
1 change: 1 addition & 0 deletions server/mergin/stats/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial

import os
from decouple import config


Expand Down
25 changes: 1 addition & 24 deletions server/mergin/stats/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial

from dataclasses import asdict
import requests
from flask import abort, current_app, make_response
from datetime import datetime, time
Expand All @@ -12,7 +11,7 @@
from mergin.stats.models import MerginStatistics, ServerCallhomeData

from .config import Configuration
from ..app import parse_version_string, db
from ..app import db


class CsvTextBuilder(object):
Expand All @@ -27,28 +26,6 @@ def write(self, row):
self.data.append(row)


def get_latest_version():
"""Parse information about available server updates from 3rd party service"""
try:
req = requests.get(Configuration.STATISTICS_URL + "/latest-versions")
except requests.exceptions.RequestException:
abort(400, "Updates information not available")

if not req.ok:
abort(400, "Updates information not available")

data = req.json().get(current_app.config["SERVER_TYPE"].lower(), None)
if not data:
abort(400, "Updates information not available")

parsed_version = parse_version_string(data.get("version", ""))
if not parsed_version:
abort(400, "Updates information not available")

data = {**data, **parsed_version}
return data, 200


@auth_required(permissions=["admin"])
def download_report(date_from: str, date_to: str):
"""Download statistics from server instance"""
Expand Down
8 changes: 4 additions & 4 deletions server/mergin/sync/private_api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,8 @@ paths:
description: Accepted
"400":
$ref: "#/components/responses/BadStatusResp"
"413":
$ref: "#/components/responses/FileTooLargeResp"
"422":
$ref: "#/components/responses/UnprocessableEntity"
"403":
$ref: "#/components/responses/Forbidden"
"404":
Expand All @@ -425,8 +425,8 @@ components:
description: Project not found.
BadStatusResp:
description: Invalid request.
FileTooLargeResp:
description: File is too large.
UnprocessableEntity:
description: UnprocessableEntity
InvalidDataResp:
description: Invalid/unprocessable data.
Success:
Expand Down
Loading
Loading