Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
843e161
Filter out specific filenames when cloning project
harminius Dec 11, 2025
33183c5
test for files exclution when cloning
harminius Dec 11, 2025
22b481f
fix tests
harminius Dec 11, 2025
eea191c
Add fullname to v2 GET collaborators
harminius Dec 12, 2025
0f47148
Introduce UserSummary component and adjust project collaborators
harminius Dec 12, 2025
08e99c0
Merge pull request #546 from MerginMaps/exlude_clone_filenames
MarcelGeo Dec 15, 2025
2ebcae8
Integrate last_signed_in in admin
harminius Dec 18, 2025
ebe286a
add last_signed_in to API response
harminius Jan 9, 2026
ed67787
introduce user card component
harminius Jan 9, 2026
3204c2c
return v2 project info from project versions
MarcelGeo Jan 12, 2026
b52d8c8
Merge pull request #555 from MerginMaps/v2_project_info
MarcelGeo Jan 12, 2026
72bdc7e
Use fullname for avatar
harminius Jan 13, 2026
bc8ddf7
Merge remote-tracking branch 'origin/develop' into integrate_last_sig…
harminius Jan 13, 2026
424c6bc
Make fullname and last_signed_in optional
harminius Jan 13, 2026
a046c75
Get last signed in when missing in admin
harminius Jan 14, 2026
768d18e
Rename fullname to name
harminius Jan 14, 2026
f67d86e
Ensure we have user when rendering user details
harminius Jan 14, 2026
1ac849f
rename fullname to name 2
harminius Jan 14, 2026
8ada302
rm last_signed_in from collaborators
harminius Jan 14, 2026
e2fba85
UserSummary width to auto
harminius Jan 14, 2026
513d9b6
Gap in UserSummary to 10px
harminius Jan 14, 2026
6685b21
Add guest as Workspace role in workspace
harminius Jan 14, 2026
4500cf5
Create v2 project list endpoint
harminius Jan 14, 2026
ce84102
Logic sequence of if-elif-else branches
harminius Jan 15, 2026
900f39c
Gap 12px
harminius Jan 15, 2026
d57b3a8
Use vue class for spacing
harminius Jan 15, 2026
1d54e3e
add props to custom usage of UserSummary
harminius Jan 15, 2026
0b99238
Merge pull request #551 from MerginMaps/integrate_last_sign_in
MarcelGeo Jan 15, 2026
855b5fb
Merge branch 'develop' into v2_get_ws_projects
harminius Jan 16, 2026
e650d74
Address review
harminius Jan 16, 2026
ffc541f
fix page type in docstring
harminius Jan 16, 2026
a792beb
Merge pull request #558 from MerginMaps/v2_get_ws_projects
MarcelGeo Jan 16, 2026
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
5 changes: 5 additions & 0 deletions server/mergin/auth/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,11 @@ components:
type: string
format: date-time
example: 2023-07-30T08:47:58Z
last_signed_in:
nullable: true
type: string
format: date-time
example: 2025-12-18T08:47:58Z
profile:
$ref: "#/components/schemas/UserProfile"
PaginatedUsers:
Expand Down
3 changes: 3 additions & 0 deletions server/mergin/auth/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,9 @@ def register_user(): # pylint: disable=W0613,W0612
@auth_required(permissions=["admin"])
def get_user(username):
user = User.query.filter(User.username == username).first_or_404()
if not user.last_signed_in:
last_signed_in = LoginHistory.get_users_last_signed_in([user.id])
user.last_signed_in = last_signed_in.get(user.id)
data = UserSchema().dump(user)
return data, 200

Expand Down
2 changes: 1 addition & 1 deletion server/mergin/auth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ class UserProfile(db.Model):
),
)

def name(self):
def name(self) -> Optional[str]:
return f'{self.first_name if self.first_name else ""} {self.last_name if self.last_name else ""}'.strip()


Expand Down
3 changes: 2 additions & 1 deletion server/mergin/auth/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

class UserProfileSchema(ma.SQLAlchemyAutoSchema):
name = ma.Function(
lambda obj: f'{obj.first_name if obj.first_name else ""} {obj.last_name if obj.last_name else ""}'.strip(),
lambda obj: obj.name(),
dump_only=True,
)
storage = fields.Method("get_storage", dump_only=True)
Expand Down Expand Up @@ -70,6 +70,7 @@ class Meta:
"profile",
"scheduled_removal",
"registration_date",
"last_signed_in",
)
load_instance = True

Expand Down
3 changes: 3 additions & 0 deletions server/mergin/sync/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,6 @@ class Configuration(object):
UPLOAD_CHUNKS_EXPIRATION = config(
"UPLOAD_CHUNKS_EXPIRATION", default=86400, cast=int
)
EXCLUDED_CLONE_FILENAMES = config(
"EXCLUDED_CLONE_FILENAMES", default="qgis_cfg.xml", cast=Csv()
)
2 changes: 2 additions & 0 deletions server/mergin/sync/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ def get_member(self, user_id: int) -> Optional[ProjectMember]:
project_role=ProjectRole(member.role),
workspace_role=self.workspace.get_user_role(member.user),
role=ProjectPermissions.get_user_project_role(self, member.user),
name=member.user.profile.name(),
)

def members_by_role(self, role: ProjectRole) -> List[int]:
Expand Down Expand Up @@ -364,6 +365,7 @@ class ProjectMember:
workspace_role: WorkspaceRole
project_role: Optional[ProjectRole]
role: ProjectRole
name: Optional[str]


@dataclass
Expand Down
6 changes: 6 additions & 0 deletions server/mergin/sync/private_api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,12 @@ components:
type: string
format: date-time
example: 2018-11-30T08:47:58.636074Z
last_signed_in:
description: Present only for type `member`
nullable: true
type: string
format: date-time
example: 2025-12-18T08:47:58Z
ProjectAccessUpdated:
type: object
properties:
Expand Down
7 changes: 6 additions & 1 deletion server/mergin/sync/public_api_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -1136,9 +1136,12 @@ def clone_project(namespace, project_name): # noqa: E501
)
p.updated = datetime.utcnow()
db.session.add(p)
files_to_exclude = current_app.config.get("EXCLUDED_CLONE_FILENAMES", [])

try:
p.storage.initialize(template_project=cloned_project)
p.storage.initialize(
template_project=cloned_project, excluded_files=files_to_exclude
)
except InitializationError as e:
abort(400, f"Failed to clone project: {str(e)}")

Expand All @@ -1149,6 +1152,8 @@ def clone_project(namespace, project_name): # noqa: E501
# transform source files to new uploaded files
file_changes = []
for file in cloned_project.files:
if os.path.basename(file.path) in files_to_exclude:
continue
file_changes.append(
ProjectFileChange(
file.path,
Expand Down
176 changes: 95 additions & 81 deletions server/mergin/sync/public_api_v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/Project"
$ref: "#/components/schemas/ProjectDetail"
"204":
$ref: "#/components/responses/NoContent"
"400":
Expand Down Expand Up @@ -367,6 +367,73 @@ paths:
$ref: "#/components/schemas/ProjectLocked"

x-openapi-router-controller: mergin.sync.public_api_v2_controller
/workspaces/{workspace_id}/projects:
get:
tags:
- workspace
summary: List projects in the workspace
operationId: list_workspace_projects
parameters:
- $ref: '#/components/parameters/WorkspaceId'
- name: page
in: query
description: page number
required: true
schema:
type: integer
minimum: 1
example: 1
- name: per_page
in: query
description: Number of results per page
required: true
schema:
type: integer
maximum: 50
example: 50
- name: order_params
in: query
description: Sorting fields
required: false
schema:
type: string
example: name ASC, expire DESC
- name: q
in: query
description: Filter by name with ilike pattern
required: false
schema:
type: string
example: my-survey
responses:
"200":
description: List of workspace projects that match the query limited to 50
content:
application/json:
schema:
type: object
properties:
page:
type: integer
example: 1
per_page:
type: integer
example: 20
count:
type: integer
example: 10
projects:
type: array
maxItems: 50
items:
$ref: "#/components/schemas/Project"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
x-openapi-router-controller: mergin.sync.public_api_v2_controller
components:
responses:
NoContent:
Expand All @@ -393,6 +460,13 @@ components:
type: string
format: uuid
pattern: \b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b
WorkspaceId:
name: workspace_id
in: path
description: Workspace id
required: true
schema:
type: integer
schemas:
# Errors
CustomError:
Expand Down Expand Up @@ -528,7 +602,7 @@ components:
$ref: "#/components/schemas/ProjectRole"
role:
$ref: "#/components/schemas/Role"
ProjectDetail:
Project:
type: object
required:
- id
Expand Down Expand Up @@ -586,14 +660,32 @@ components:
description: List of files in the project
items:
allOf:
- $ref: '#/components/schemas/File'
- $ref: "#/components/schemas/File"
- type: object
properties:
mtime:
type: string
format: date-time
description: File modification timestamp
example: 2024-11-19T13:50:00Z
ProjectDetail:
allOf:
- $ref: "#/components/schemas/Project"
- type: object
properties:
files:
type: array
description: List of files in the project
items:
allOf:
- $ref: "#/components/schemas/File"
- type: object
properties:
mtime:
type: string
format: date-time
description: File modification timestamp
example: 2024-11-19T13:50:00Z
File:
type: object
description: Project file metadata
Expand Down Expand Up @@ -768,84 +860,6 @@ components:
type: string
format: date-time
example: 2019-02-26T08:47:58.636074Z
Project:
type: object
required:
- name
properties:
id:
type: string
example: f9ef87ac-1dae-48ab-85cb-062a4784fb83
description: Project UUID
name:
type: string
example: mergin
namespace:
type: string
example: mergin
creator:
nullable: true
type: integer
example: 1
description: Project creator ID
created:
type: string
format: date-time
example: 2018-11-30T08:47:58.636074Z
updated:
type: string
nullable: true
format: date-time
example: 2018-11-30T08:47:58.636074Z
description: Last modified
version:
type: string
nullable: true
example: v2
description: Last project version
disk_usage:
type: integer
example: 25324373
description: Project size in bytes
permissions:
type: object
properties:
delete:
type: boolean
example: false
update:
type: boolean
example: false
upload:
type: boolean
example: true
tags:
type: array
nullable: true
items:
$ref: "#/components/schemas/MerginTag"
uploads:
type: array
nullable: true
items:
type: string
example: 669b838e-a30b-4338-b2b6-3da144742a82
description: UUID for ongoing upload
access:
$ref: "#/components/schemas/Access"
files:
type: array
items:
allOf:
- $ref: "#/components/schemas/FileInfo"
role:
nullable: true
type: string
enum:
- reader
- editor
- writer
- owner
VersionName:
type: string
pattern: '^$|^v\d+$'
Expand Down
Loading
Loading