disallow saving a process model file if it has changed w/ burnettk
This commit is contained in:
parent
1ec0deaf3c
commit
558c616744
|
@ -1245,6 +1245,13 @@ paths:
|
|||
put:
|
||||
operationId: spiffworkflow_backend.routes.process_models_controller.process_model_file_update
|
||||
summary: save the contents to the given file
|
||||
parameters:
|
||||
- name: file_contents_hash
|
||||
in: query
|
||||
required: true
|
||||
description: The hash of the file contents that originally came with the file.
|
||||
schema:
|
||||
type: string
|
||||
tags:
|
||||
- Process Model Files
|
||||
requestBody:
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
"""File."""
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from dataclasses import field
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
import marshmallow
|
||||
from marshmallow import INCLUDE
|
||||
from marshmallow import Schema
|
||||
|
||||
from spiffworkflow_backend.helpers.spiff_enum import SpiffEnum
|
||||
from spiffworkflow_backend.models.spec_reference import SpecReference
|
||||
|
||||
|
@ -77,7 +74,7 @@ class File:
|
|||
references: Optional[list[SpecReference]] = None
|
||||
file_contents: Optional[bytes] = None
|
||||
process_model_id: Optional[str] = None
|
||||
process_group_id: Optional[str] = None
|
||||
file_contents_hash: Optional[str] = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""__post_init__."""
|
||||
|
@ -102,28 +99,9 @@ class File:
|
|||
)
|
||||
return instance
|
||||
|
||||
|
||||
class FileSchema(Schema):
|
||||
"""FileSchema."""
|
||||
|
||||
class Meta:
|
||||
"""Meta."""
|
||||
|
||||
model = File
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"content_type",
|
||||
"last_modified",
|
||||
"type",
|
||||
"size",
|
||||
"data_store",
|
||||
"user_uid",
|
||||
"url",
|
||||
"file_contents",
|
||||
"references",
|
||||
"process_group_id",
|
||||
"process_model_id",
|
||||
]
|
||||
unknown = INCLUDE
|
||||
references = marshmallow.fields.List(marshmallow.fields.Nested("SpecReferenceSchema"))
|
||||
@property
|
||||
def serialized(self) -> dict[str, Any]:
|
||||
dictionary = self.__dict__
|
||||
if isinstance(self.file_contents, bytes):
|
||||
dictionary['file_contents'] = self.file_contents.decode('utf-8')
|
||||
return dictionary
|
||||
|
|
|
@ -87,7 +87,7 @@ class ProcessModelInfoSchema(Schema):
|
|||
display_order = marshmallow.fields.Integer(allow_none=True)
|
||||
primary_file_name = marshmallow.fields.String(allow_none=True)
|
||||
primary_process_id = marshmallow.fields.String(allow_none=True)
|
||||
files = marshmallow.fields.List(marshmallow.fields.Nested("FileSchema"))
|
||||
files = marshmallow.fields.List(marshmallow.fields.Nested("File"))
|
||||
fault_or_suspend_on_exception = marshmallow.fields.String()
|
||||
exception_notification_addresses = marshmallow.fields.List(marshmallow.fields.String)
|
||||
metadata_extraction_paths = marshmallow.fields.List(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""APIs for dealing with process groups, process models, and process instances."""
|
||||
from hashlib import sha256
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
@ -18,7 +19,6 @@ from werkzeug.datastructures import FileStorage
|
|||
|
||||
from spiffworkflow_backend.exceptions.api_error import ApiError
|
||||
from spiffworkflow_backend.interfaces import IdToProcessGroupMapping
|
||||
from spiffworkflow_backend.models.file import FileSchema
|
||||
from spiffworkflow_backend.models.process_group import ProcessGroup
|
||||
from spiffworkflow_backend.models.process_instance_report import (
|
||||
ProcessInstanceReportModel,
|
||||
|
@ -245,10 +245,9 @@ def process_model_list(
|
|||
return make_response(jsonify(response_json), 200)
|
||||
|
||||
|
||||
def process_model_file_update(modified_process_model_identifier: str, file_name: str) -> flask.wrappers.Response:
|
||||
"""Process_model_file_update."""
|
||||
def process_model_file_update(modified_process_model_identifier: str, file_name: str, file_contents_hash: str) -> flask.wrappers.Response:
|
||||
message = f"User: {g.user.username} clicked save for"
|
||||
return _create_or_update_process_model_file(modified_process_model_identifier, message, 200)
|
||||
return _create_or_update_process_model_file(modified_process_model_identifier, message, 200, file_contents_hash=file_contents_hash)
|
||||
|
||||
|
||||
def process_model_file_delete(modified_process_model_identifier: str, file_name: str) -> flask.wrappers.Response:
|
||||
|
@ -293,7 +292,6 @@ def process_model_file_create(
|
|||
|
||||
|
||||
def process_model_file_show(modified_process_model_identifier: str, file_name: str) -> Any:
|
||||
"""Process_model_file_show."""
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
process_model = _get_process_model(process_model_identifier)
|
||||
files = SpecFileService.get_files(process_model, file_name)
|
||||
|
@ -309,8 +307,10 @@ def process_model_file_show(modified_process_model_identifier: str, file_name: s
|
|||
file = files[0]
|
||||
file_contents = SpecFileService.get_data(process_model, file.name)
|
||||
file.file_contents = file_contents
|
||||
file_contents_hash = sha256(file_contents).hexdigest()
|
||||
file.file_contents_hash = file_contents_hash
|
||||
file.process_model_id = process_model.id
|
||||
return FileSchema().dump(file)
|
||||
return make_response(jsonify(file), 200)
|
||||
|
||||
|
||||
# {
|
||||
|
@ -477,6 +477,7 @@ def _create_or_update_process_model_file(
|
|||
modified_process_model_identifier: str,
|
||||
message_for_git_commit: str,
|
||||
http_status_to_return: int,
|
||||
file_contents_hash: Optional[str],
|
||||
) -> flask.wrappers.Response:
|
||||
"""_create_or_update_process_model_file."""
|
||||
process_model_identifier = modified_process_model_identifier.replace(":", "/")
|
||||
|
@ -498,6 +499,16 @@ def _create_or_update_process_model_file(
|
|||
status_code=400,
|
||||
)
|
||||
|
||||
current_file_contents_bytes = SpecFileService.get_data(process_model, request_file.filename)
|
||||
if current_file_contents_bytes and file_contents_hash:
|
||||
current_file_contents_hash = sha256(current_file_contents_bytes).hexdigest()
|
||||
if current_file_contents_hash != file_contents_hash:
|
||||
raise ApiError(
|
||||
error_code="process_model_file_has_changed",
|
||||
message=f"Process model file: {request_file.filename} was already changed by someone else. Please reload before making changes.",
|
||||
status_code=409,
|
||||
)
|
||||
|
||||
file = None
|
||||
try:
|
||||
file = SpecFileService.update_file(process_model, request_file.filename, request_file_contents)
|
||||
|
@ -514,8 +525,4 @@ def _create_or_update_process_model_file(
|
|||
file.process_model_id = process_model.id
|
||||
_commit_and_push_to_git(f"{message_for_git_commit} {process_model_identifier}/{file.name}")
|
||||
|
||||
return Response(
|
||||
json.dumps(FileSchema().dump(file)),
|
||||
status=http_status_to_return,
|
||||
mimetype="application/json",
|
||||
)
|
||||
return make_response(jsonify(file), http_status_to_return)
|
||||
|
|
|
@ -175,7 +175,6 @@ class SpecFileService(FileSystemService):
|
|||
|
||||
@classmethod
|
||||
def update_file(cls, process_model_info: ProcessModelInfo, file_name: str, binary_data: bytes) -> File:
|
||||
"""Update_file."""
|
||||
SpecFileService.assert_valid_file_name(file_name)
|
||||
cls.validate_bpmn_xml(file_name, binary_data)
|
||||
|
||||
|
|
|
@ -114,6 +114,7 @@ export interface ProcessFile {
|
|||
size: number;
|
||||
type: string;
|
||||
file_contents?: string;
|
||||
file_contents_hash?: string;
|
||||
}
|
||||
|
||||
export interface ProcessInstanceMetadata {
|
||||
|
|
|
@ -206,6 +206,9 @@ export default function ProcessModelEditDiagram() {
|
|||
httpMethod = 'POST';
|
||||
} else {
|
||||
url += `/${fileNameWithExtension}`;
|
||||
if (processModelFile && processModelFile.file_contents_hash) {
|
||||
url += `?file_contents_hash=${processModelFile.file_contents_hash}`;
|
||||
}
|
||||
}
|
||||
if (!fileNameWithExtension) {
|
||||
handleShowFileNameEditor();
|
||||
|
|
Loading…
Reference in New Issue