Skip to content

Commit c118ca9

Browse files
committed
Create endpoint
- new marshmallow schema - controller function
1 parent b828b6c commit c118ca9

File tree

4 files changed

+162
-3
lines changed

4 files changed

+162
-3
lines changed

server/mergin/sync/files.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,5 +122,5 @@ class ProjectFileSchema(FileSchema):
122122
def patch_field(self, data, **kwargs):
123123
# drop 'diff' key entirely if empty or None as clients would expect
124124
if not data.get("diff"):
125-
data.pop("diff")
125+
data.pop("diff", None)
126126
return data

server/mergin/sync/public_api_v2.yaml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,35 @@ paths:
7676
"409":
7777
$ref: "#/components/responses/Conflict"
7878
x-openapi-router-controller: mergin.sync.public_api_v2_controller
79+
get:
80+
tags:
81+
- project
82+
summary: Get project info
83+
operationId: get_project
84+
parameters:
85+
- name: files_at_version
86+
in: query
87+
description: Include list of files at specific version
88+
required: false
89+
schema:
90+
type: string
91+
example: v3
92+
responses:
93+
"200":
94+
description: Success
95+
content:
96+
application/json:
97+
schema:
98+
$ref: "#/components/schemas/ProjectDetail"
99+
"400":
100+
$ref: "#/components/responses/BadRequest"
101+
"401":
102+
$ref: "#/components/responses/Unauthorized"
103+
"403":
104+
$ref: "#/components/responses/Forbidden"
105+
"404":
106+
$ref: "#/components/responses/NotFound"
107+
x-openapi-router-controller: mergin.sync.public_api_v2_controller
79108
/projects/{id}/scheduleDelete:
80109
post:
81110
tags:
@@ -287,3 +316,80 @@ components:
287316
$ref: "#/components/schemas/ProjectRole"
288317
role:
289318
$ref: "#/components/schemas/Role"
319+
ProjectDetail:
320+
type: object
321+
required:
322+
- id
323+
- name
324+
- workspace
325+
- role
326+
- version
327+
- created_at
328+
- updated_at
329+
- public
330+
- size
331+
properties:
332+
id:
333+
type: string
334+
description: project uuid
335+
example: c1ae6439-0056-42df-a06d-79cc430dd7df
336+
name:
337+
type: string
338+
example: survey
339+
workspace:
340+
type: object
341+
properties:
342+
id:
343+
type: integer
344+
example: 123
345+
name:
346+
type: string
347+
example: mergin
348+
role:
349+
$ref: "#/components/schemas/ProjectRole"
350+
version:
351+
type: string
352+
description: latest project version
353+
example: v2
354+
created_at:
355+
type: string
356+
format: date-time
357+
description: project creation timestamp
358+
example: 2025-10-24T08:27:56Z
359+
updated_at:
360+
type: string
361+
format: date-time
362+
description: last project update timestamp
363+
example: 2025-10-24T08:28:00.279699Z
364+
public:
365+
type: boolean
366+
description: whether the project is public
367+
example: false
368+
size:
369+
type: integer
370+
description: project size in bytes for this version
371+
example: 17092380
372+
files:
373+
type: array
374+
description: List of files in the project
375+
items:
376+
type: object
377+
properties:
378+
path:
379+
type: string
380+
description: File name including path from project root
381+
example: data/layer.gpkg
382+
mtime:
383+
type: string
384+
format: date-time
385+
description: File modification timestamp
386+
example: 2024-11-19T13:50:00Z
387+
size:
388+
type: integer
389+
description: File size in bytes
390+
example: 1234
391+
checksum:
392+
type: string
393+
description: File checksum hash
394+
example: 9adb76bf81a34880209040ffe5ee262a090b62ab
395+

server/mergin/sync/public_api_v2_controller.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
from flask_login import current_user
99

1010
from mergin.sync.forms import project_name_validation
11+
from .files import ProjectFileSchema
1112

12-
from .schemas import ProjectMemberSchema
13+
from .schemas import ProjectMemberSchema, ProjectSchemaV2
1314
from .workspace import WorkspaceRole
1415
from ..app import db
1516
from ..auth import auth_required
1617
from ..auth.models import User
17-
from .models import Project, ProjectRole, ProjectMember
18+
from .models import Project, ProjectRole, ProjectMember, ProjectVersion
1819
from .permissions import ProjectPermissions, require_project_by_uuid
1920

2021

@@ -128,3 +129,18 @@ def remove_project_collaborator(id, user_id):
128129
project.unset_role(user_id)
129130
db.session.commit()
130131
return NoContent, 204
132+
133+
134+
def get_project(id, files_at_version=None):
135+
"""Get project info. Include list of files at specific version if requested."""
136+
project = require_project_by_uuid(id, ProjectPermissions.Read)
137+
data = ProjectSchemaV2().dump(project)
138+
if files_at_version:
139+
pv = ProjectVersion.query.filter_by(
140+
project_id=project.id, name=ProjectVersion.from_v_name(files_at_version)
141+
).first_or_404()
142+
data["files"] = ProjectFileSchema(
143+
only=("path", "mtime", "size", "checksum"), many=True
144+
).dump(pv.files)
145+
146+
return data, 200

server/mergin/sync/schemas.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,3 +405,40 @@ class ProjectMemberSchema(Schema):
405405
project_role = fields.Enum(enum=ProjectRole, by_value=True)
406406
workspace_role = fields.Enum(enum=WorkspaceRole, by_value=True)
407407
role = fields.Enum(enum=ProjectRole, by_value=True)
408+
409+
410+
class ProjectSchemaV2(ma.SQLAlchemyAutoSchema):
411+
id = fields.UUID()
412+
name = fields.String()
413+
version = fields.Function(lambda obj: ProjectVersion.to_v_name(obj.latest_version))
414+
public = fields.Boolean()
415+
size = fields.Integer(attribute="disk_usage")
416+
417+
created_at = DateTimeWithZ(attribute="created")
418+
updated_at = DateTimeWithZ(attribute="updated")
419+
420+
workspace = fields.Function(
421+
lambda obj: {"id": obj.workspace.id, "name": obj.workspace.name}
422+
)
423+
role = fields.Method("_role")
424+
425+
def _role(self, obj):
426+
role = ProjectPermissions.get_user_project_role(obj, current_user)
427+
if not role:
428+
return None
429+
return role.value
430+
431+
class Meta:
432+
model = Project
433+
load_instance = True
434+
fields = (
435+
"id",
436+
"name",
437+
"version",
438+
"public",
439+
"size",
440+
"created_at",
441+
"updated_at",
442+
"workspace",
443+
"role",
444+
)

0 commit comments

Comments
 (0)