Skip to content
Snippets Groups Projects
Commit 2abf719f authored by Remco Tukker's avatar Remco Tukker
Browse files

add backend lock and unlock endpoints

parent a54de35d
No related branches found
No related tags found
1 merge request!57Feature/survey locking system
......@@ -4,8 +4,9 @@ from __future__ import annotations
import logging
from enum import Enum
from typing import Dict, Any, List
from typing import Dict, Any, List, Optional
from typing_extensions import Annotated
from uuid import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.schema import ForeignKey
......@@ -22,6 +23,7 @@ logger = logging.getLogger(__name__)
int_pk = Annotated[int, mapped_column(primary_key=True)]
int_pk_fkNREN = Annotated[int, mapped_column(ForeignKey("nren.id"), primary_key=True)]
int_pk_fkSurvey = Annotated[int, mapped_column(ForeignKey("survey.year"), primary_key=True)]
uuid_nullable_fkUser = Annotated[Optional[UUID], mapped_column(ForeignKey("user.id"))]
json = Annotated[Dict[str, Any], mapped_column(JSON)]
......@@ -57,3 +59,5 @@ class SurveyResponse(db.Model):
survey: Mapped[Survey] = relationship(lazy='joined', back_populates="responses")
answers: Mapped[json]
status: Mapped[ResponseStatus]
locked_by: Mapped[uuid_nullable_fkUser]
lock_uuid: Mapped[Optional[UUID]]
"""Add reponse lock fields
Revision ID: 45bcb38901aa
Revises: 5c9016cc6348
Create Date: 2023-08-06 21:43:10.089217
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '45bcb38901aa'
down_revision = '5c9016cc6348'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('survey_response', schema=None) as batch_op:
batch_op.add_column(sa.Column('locked_by', sa.Uuid(), nullable=True))
batch_op.add_column(sa.Column('lock_uuid', sa.Uuid(), nullable=True))
batch_op.create_foreign_key(batch_op.f('fk_survey_response_locked_by_user'), 'user', ['locked_by'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('survey_response', schema=None) as batch_op:
batch_op.drop_constraint(batch_op.f('fk_survey_response_locked_by_user'), type_='foreignkey')
batch_op.drop_column('lock_uuid')
batch_op.drop_column('locked_by')
# ### end Alembic commands ###
import logging
from enum import Enum
from typing import Any, TypedDict, List, Dict
from uuid import uuid4
from flask import Blueprint, jsonify, request
from flask_login import login_required, current_user # type: ignore
......@@ -323,7 +324,7 @@ def load_survey(year, nren_name) -> Any:
"""
nren = db.session.scalar(select(NREN).filter(NREN.name == nren_name))
if not nren:
return jsonify({'success': False, 'message': 'Survey not found'}), 404
return jsonify({'success': False, 'message': 'NREN not found'}), 404
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if not survey:
......@@ -373,7 +374,7 @@ def save_survey(year, nren_name) -> Any:
nren = db.session.scalar(select(NREN).filter(NREN.name == nren_name))
if nren is None:
return jsonify({'success': False, 'message': 'Survey not found'}), 404
return jsonify({'success': False, 'message': 'NREN not found'}), 404
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if survey is None:
......@@ -396,16 +397,78 @@ def save_survey(year, nren_name) -> Any:
if not save_survey:
return jsonify({'success': False, 'message': 'Invalid Survey Format'}), 400
# TODO check if the lock_uuid matches
# TODO add a 'save and stop editing' and a 'complete' field and remove the lock in those two cases
# TODO the checked ResponseStatus should probably be removed
response.answers = {
"data": save_survey["data"],
"page": save_survey["page"],
"verification_status": save_survey["verification_status"]
}
db.session.commit()
return {'success': True}
@routes.route('/unlock/<int:year>/<string:nren_name>', methods=['POST'])
@common.require_accepts_json
@login_required
def unlock_survey(year, nren_name) -> Any:
nren = db.session.scalar(select(NREN).filter(NREN.name == nren_name))
if nren is None:
return jsonify({'success': False, 'message': 'NREN not found'}), 404
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if survey is None:
return jsonify({'success': False, 'message': 'Survey not found'}), 404
if not check_access_nren(current_user, nren):
return jsonify({'success': False, 'message': 'You do not have permission to edit this survey.'}), 403
# TODO set status on complete or checked when necessary
# if admin: completed: set to checked / not completed: leave as is (started if new)
# if nren: completed: set to completed / not completed: leave as is (started if new)
response = db.session.scalar(
select(SurveyResponse).where(SurveyResponse.survey_year == year).where(SurveyResponse.nren_id == nren.id)
)
if response is None:
return jsonify({'success': False, 'message': 'Survey response not found'}), 404
if not current_user.is_admin and current_user.id != response.locked_by:
return jsonify({'success': False, 'message': 'This survey was not locked by you.'}), 403
response.locked_by = None
# we leave lock_uuid, it will be overwritten when a new lock is acquired
db.session.commit()
return {'success': True}
@routes.route('/lock/<int:year>/<string:nren_name>', methods=['POST'])
@common.require_accepts_json
@login_required
def lock_survey(year, nren_name) -> Any:
nren = db.session.scalar(select(NREN).filter(NREN.name == nren_name))
if nren is None:
return jsonify({'success': False, 'message': 'NREN not found'}), 404
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if survey is None:
return jsonify({'success': False, 'message': 'Survey not found'}), 404
if not check_access_nren(current_user, nren):
return jsonify({'success': False, 'message': 'You do not have permission to edit this survey.'}), 403
response = db.session.scalar(
select(SurveyResponse).where(SurveyResponse.survey_year == year).where(SurveyResponse.nren_id == nren.id)
)
if response is None:
response = SurveyResponse(survey_year=year, nren_id=nren.id, status=ResponseStatus.started)
db.session.add(response)
if response.locked_by is not None:
return jsonify({'success': False, 'message': 'This survey is already locked.'}), 403
lock_uuid = uuid4()
response.lock_uuid = lock_uuid
response.locked_by = current_user.id
db.session.commit()
return {'success': True, 'lock_uuid': lock_uuid}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment