Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ docker-compose.local.yml
CLAUDE.md
# Local Claude Code skills
.claude/commands/

# local dev server settings, see development.md
server/.dev.env
2 changes: 2 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
python 3.12.15
nodejs 22.23.3
19 changes: 16 additions & 3 deletions development.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ This page contains useful information for those who wish to develop Mergin.
## Running locally (for dev)
Install dependencies and run services:

### Python and Node.js

Required versions are pinned in `.tool-versions` in the repository root. With [asdf](https://asdf-vm.com) or [mise](https://mise.jdx.dev) installed, run in the repository root:

```shell
$ asdf install # or: mise install
$ corepack enable # provides yarn
```

### Postgres and Redis

```shell
Expand All @@ -24,8 +33,9 @@ $ pipenv install --dev
$ pipenv install --categories telemetry
$ pipenv run pre-commit install
$ pipenv run pre-commit run --all-files
# pipenv loads server/.env automatically; secrets and salts have no defaults
$ cat > .env <<EOF
# dev settings, secrets and salts have no defaults
# not named .env on purpose: pipenv and flask would load it automatically, also into tests
$ cat > .dev.env <<EOF
FLASK_APP=application
COLLECT_STATISTICS=0
SECRET_KEY=$(python3 -c 'import secrets;print(secrets.token_hex(32))')
Expand All @@ -36,6 +46,8 @@ SECURITY_UNLOCK_SALT=$(python3 -c 'import secrets;print(secrets.token_hex(16))')
MAIL_DEFAULT_SENDER=dev@localhost
MERGIN_BASE_URL=http://localhost:8080
EOF
# let pipenv load it for commands in this shell
$ export PIPENV_DOTENV_LOCATION=$PWD/.dev.env
# folder for project files (LOCAL_PROJECTS default)
$ mkdir -p ../projects
$ pipenv run flask init-db
Expand Down Expand Up @@ -123,5 +135,6 @@ To launch the unit tests run:
$ docker run -d --rm --name testing_pg -p 5435:5432 -e POSTGRES_PASSWORD=postgres postgres:14
$ cd server
$ pipenv install --dev --sequential --verbose
$ pipenv run pytest -v --cov=mergin mergin/tests
# tests use .test.env only, do not load dev settings (e.g. exported PIPENV_DOTENV_LOCATION)
$ PIPENV_DONT_LOAD_ENV=1 pipenv run pytest -v --cov=mergin mergin/tests
```
10 changes: 9 additions & 1 deletion server/mergin/sync/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -468,9 +468,17 @@ def is_supported_type(filepath) -> bool:
return mime_type.startswith("image/") or mime_type not in FORBIDDEN_MIME_TYPES


# libmagic >= 5.46 (e.g. Ubuntu 26.04) reports some types differently than older versions,
# map them to the older names to keep responses stable regardless of the system libmagic
MIME_TYPE_ALIASES = {
"application/geopackage+sqlite3": "application/vnd.sqlite3",
}


def get_mimetype(filepath: str) -> str:
"""Identifies file types by checking their headers"""
return magic.from_file(filepath, mime=True)
mime_type = magic.from_file(filepath, mime=True)
return MIME_TYPE_ALIASES.get(mime_type, mime_type)


def get_x_accel_uri(*url_parts):
Expand Down
17 changes: 17 additions & 0 deletions server/mergin/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
check_filename,
is_supported_extension,
is_supported_type,
get_mimetype,
is_valid_path,
get_x_accel_uri,
Checkpoint,
Expand Down Expand Up @@ -404,6 +405,22 @@ def test_mime_type_validation_skip():
assert not is_supported_type("other.js")


@pytest.mark.parametrize(
"detected,expected",
[
# libmagic < 5.46 (Ubuntu 24.04)
("application/vnd.sqlite3", "application/vnd.sqlite3"),
# libmagic >= 5.46 (Ubuntu 26.04)
("application/geopackage+sqlite3", "application/vnd.sqlite3"),
("image/png", "image/png"),
],
)
def test_get_mimetype_aliases(detected, expected):
"""Mime type is the same regardless of libmagic version"""
with patch("mergin.sync.utils.magic.from_file", return_value=detected):
assert get_mimetype("base.gpkg") == expected


def test_allowed_extensions_override():
"""Extensions in UPLOAD_EXTENSIONS_WHITELIST are accepted even though they are in FORBIDDEN_EXTENSIONS."""
with patch(
Expand Down
Loading