Unverified Commit 59f2ce4b authored by Peter Stanko's avatar Peter Stanko
Browse files

Full refactor of the service layer

parent 302dfef3
Pipeline #13268 passed with stage
in 13 minutes and 9 seconds
...@@ -8,8 +8,7 @@ from management.data.data_dev import init_dev_data ...@@ -8,8 +8,7 @@ from management.data.data_dev import init_dev_data
from management.data.data_prod import init_prod_data from management.data.data_prod import init_prod_data
from management.data.data_test import init_test_data from management.data.data_test import init_test_data
from portal.database.models import Course, Role, Secret, Submission, SubmissionState, User from portal.database.models import Course, Role, Secret, Submission, SubmissionState, User
from portal.service import general from portal.service.rest import RestService
from portal.service.general import write_entity
from portal.tools import time from portal.tools import time
...@@ -23,6 +22,7 @@ class DataManagement(object): ...@@ -23,6 +22,7 @@ class DataManagement(object):
self.app = app self.app = app
self.db = db self.db = db
self.creator = shared.DataFactory(self.db) self.creator = shared.DataFactory(self.db)
self.rest = RestService()
def reset_db(self): def reset_db(self):
"""Resets the database """Resets the database
...@@ -43,14 +43,14 @@ class DataManagement(object): ...@@ -43,14 +43,14 @@ class DataManagement(object):
else: else:
init_dev_data(db=self.db, app=self.app) init_dev_data(db=self.db, app=self.app)
def delete_user(self, name): def delete_user(self, name: str):
"""Deletes user """Deletes user
Args: Args:
name(str): name of the user name(str): name of the user
""" """
with self.app.app_context(): with self.app.app_context():
user = general.find_user(name) user = self.rest.users.find(name)
general.delete_entity(user) self.rest.users(user).delete()
def update_users_password(self, name: str, password: str) -> User: def update_users_password(self, name: str, password: str) -> User:
"""Updates user's password """Updates user's password
...@@ -60,7 +60,7 @@ class DataManagement(object): ...@@ -60,7 +60,7 @@ class DataManagement(object):
Returns(User): Updated user Returns(User): Updated user
""" """
with self.app.app_context(): with self.app.app_context():
user = general.find_user(name) user = self.rest.find.user(name)
user.set_password(password=password) user.set_password(password=password)
self.db.session.add(user) self.db.session.add(user)
self.db.session.commit() self.db.session.commit()
...@@ -105,7 +105,7 @@ class DataManagement(object): ...@@ -105,7 +105,7 @@ class DataManagement(object):
Returns(Role): Created role Returns(Role): Created role
""" """
with self.app.app_context(): with self.app.app_context():
course = general.find_course(course_name) course = self.rest.find.course(course_name)
role = self.creator.scaffold_role( role = self.creator.scaffold_role(
course, role_type=role_type, name=name) course, role_type=role_type, name=name)
self.db.session.commit() self.db.session.commit()
...@@ -118,17 +118,17 @@ class DataManagement(object): ...@@ -118,17 +118,17 @@ class DataManagement(object):
def activate_project(self, course: str, project: str): def activate_project(self, course: str, project: str):
with self.app.app_context(): with self.app.app_context():
course = general.find_course(course) course = self.rest.find.course(course)
project = general.find_project(course, project) project = self.rest.find.project(course, project)
days_allow_to = time.current_time() + datetime.timedelta(days=1000) days_allow_to = time.current_time() + datetime.timedelta(days=1000)
project.config.archive_from = None project.config.archive_from = None
project.config.submissions_allowed_to = days_allow_to project.config.submissions_allowed_to = days_allow_to
write_entity(project) self.rest.projects.write_entity(project)
return project return project
def list_projects(self, course: str): def list_projects(self, course: str):
with self.app.app_context(): with self.app.app_context():
course = general.find_course(course) course = self.rest.find.course(course)
for project in course.projects: for project in course.projects:
print(f"{project.name}: {project.description}") print(f"{project.name}: {project.description}")
...@@ -146,14 +146,14 @@ class DataManagement(object): ...@@ -146,14 +146,14 @@ class DataManagement(object):
with self.app.app_context(): with self.app.app_context():
for submission in Submission.query.all(): for submission in Submission.query.all():
submission.change_state(SubmissionState.CANCELLED) submission.change_state(SubmissionState.CANCELLED)
write_entity(submission) self.rest.submissions.write_entity(submission)
self.db.session.commit() self.db.session.commit()
def cancel_submission(self, sid): def cancel_submission(self, sid):
with self.app.app_context(): with self.app.app_context():
submission = general.find_submission(sid) submission = self.rest.find.submission(sid)
submission.change_state(SubmissionState.CANCELLED) submission.change_state(SubmissionState.CANCELLED)
write_entity(submission) self.rest.submissions.write_entity(submission)
self.db.session.commit() self.db.session.commit()
def clean_all_submissions(self): def clean_all_submissions(self):
......
...@@ -6,7 +6,6 @@ from storage import UploadedEntity ...@@ -6,7 +6,6 @@ from storage import UploadedEntity
import portal.tools.worker_client import portal.tools.worker_client
from portal import logger, tools from portal import logger, tools
from portal.database import Project, Submission, SubmissionState, Worker from portal.database import Project, Submission, SubmissionState, Worker
from portal.service import general
log = logger.get_logger(__name__) log = logger.get_logger(__name__)
...@@ -15,6 +14,8 @@ class SubmissionProcessor: ...@@ -15,6 +14,8 @@ class SubmissionProcessor:
def __init__(self, submission: Submission, params: dict = None): def __init__(self, submission: Submission, params: dict = None):
self._submission = submission self._submission = submission
self._params = params self._params = params
from portal.service.rest import RestService
self._rest = RestService()
@property @property
def submission(self) -> Submission: def submission(self) -> Submission:
...@@ -43,7 +44,7 @@ class SubmissionProcessor: ...@@ -43,7 +44,7 @@ class SubmissionProcessor:
self.submission.state = state self.submission.state = state
self.submission.async_task_id = None self.submission.async_task_id = None
general.write_entity(self.submission) self._rest.submissions.write_entity(self.submission)
def submission_enqueue_ended(self): def submission_enqueue_ended(self):
log.info(f"[ASYNC] Submission enqueue ended {self.submission.log_name}: {self.submission}") log.info(f"[ASYNC] Submission enqueue ended {self.submission.log_name}: {self.submission}")
......
...@@ -3,20 +3,21 @@ from storage import UploadedEntity ...@@ -3,20 +3,21 @@ from storage import UploadedEntity
from portal import storage from portal import storage
from portal.async_celery import celery_app, submission_processor from portal.async_celery import celery_app, submission_processor
from portal.service.general import find_course, find_project, find_submission, write_entity
log = get_task_logger(__name__) log = get_task_logger(__name__)
@celery_app.task(name='upload-submission-to-storage') @celery_app.task(name='upload-submission-to-storage')
def process_submission(new_submission_id: str): def process_submission(new_submission_id: str):
new_submission = find_submission(new_submission_id) from portal.service.rest import RestService
rest_service = RestService()
new_submission = rest_service.find.submission(new_submission_id)
project = new_submission.project project = new_submission.project
course = project.course course = project.course
if not project.config.test_files_commit_hash: if not project.config.test_files_commit_hash:
log.warning(f"Project test files not found: {project.log_name}") log.warning(f"Project test files not found: {project.log_name}")
update_project_test_files(course_id=course.id, project_id=project.id) update_project_test_files(course_id=course.id, project_id=project.id)
new_submission = find_submission(new_submission_id) new_submission = rest_service.find.submission(new_submission_id)
processor = submission_processor.SubmissionProcessor(new_submission) processor = submission_processor.SubmissionProcessor(new_submission)
processor.process_submission() processor.process_submission()
...@@ -24,7 +25,9 @@ def process_submission(new_submission_id: str): ...@@ -24,7 +25,9 @@ def process_submission(new_submission_id: str):
@celery_app.task(name='upload-results-to-storage') @celery_app.task(name='upload-results-to-storage')
def upload_results_to_storage(new_submission_id: str, path: str): def upload_results_to_storage(new_submission_id: str, path: str):
path = str(path) path = str(path)
new_submission = find_submission(new_submission_id) from portal.service.rest import RestService
rest_service = RestService()
new_submission = rest_service.find.submission(new_submission_id)
processor = submission_processor.SubmissionProcessor(new_submission) processor = submission_processor.SubmissionProcessor(new_submission)
file_params = dict(source=dict(url=path, type='zip')) file_params = dict(source=dict(url=path, type='zip'))
processor.upload_result(path=path, file_params=file_params) processor.upload_result(path=path, file_params=file_params)
...@@ -32,23 +35,31 @@ def upload_results_to_storage(new_submission_id: str, path: str): ...@@ -32,23 +35,31 @@ def upload_results_to_storage(new_submission_id: str, path: str):
@celery_app.task(name='clone-submission-files') @celery_app.task(name='clone-submission-files')
def clone_submission_files(source_id: str, target_id: str): def clone_submission_files(source_id: str, target_id: str):
source = find_submission(source_id) from portal.service.rest import RestService
target = find_submission(target_id) rest_service = RestService()
source = rest_service.find.submission(source_id)
target = rest_service.find.submission(target_id)
processor = submission_processor.SubmissionProcessor(source) processor = submission_processor.SubmissionProcessor(source)
processor.clone(target) processor.clone(target)
@celery_app.task(name='start-processing-submission') @celery_app.task(name='start-processing-submission')
def start_processing_submission(submission_id: str, submission_params): def start_processing_submission(submission_id: str, submission_params):
submission = find_submission(submission_id) from portal.service.rest import RestService
rest_service = RestService()
submission = rest_service.find.submission(submission_id)
processor = submission_processor.SubmissionProcessor(submission, submission_params) processor = submission_processor.SubmissionProcessor(submission, submission_params)
processor.send_to_worker() processor.send_to_worker()
@celery_app.task(name='update-project-test-files') @celery_app.task(name='update-project-test-files')
def update_project_test_files(course_id: str, project_id: str): def update_project_test_files(course_id: str, project_id: str):
course = find_course(course_id) from portal.service.rest import RestService
project = find_project(course, project_id) rest_service = RestService()
course = rest_service.find.course(course_id)
project = rest_service.find.project(course, project_id)
log.info(f"[ASYNC] Updating test files for project: {project.log_name}") log.info(f"[ASYNC] Updating test files for project: {project.log_name}")
params = { params = {
'from_dir': project.config.test_files_subdir, 'from_dir': project.config.test_files_subdir,
...@@ -60,4 +71,4 @@ def update_project_test_files(course_id: str, project_id: str): ...@@ -60,4 +71,4 @@ def update_project_test_files(course_id: str, project_id: str):
updated_entity: UploadedEntity = storage.test_files.update(entity_id=project.id, **params) updated_entity: UploadedEntity = storage.test_files.update(entity_id=project.id, **params)
project.config.test_files_commit_hash = updated_entity.version project.config.test_files_commit_hash = updated_entity.version
log.debug(f"Updated project config {project.log_name}: {project.config}") log.debug(f"Updated project config {project.log_name}: {project.config}")
write_entity(project.config) rest_service.projects.write_entity(project.config)
from flask_jwt_extended import jwt_required from flask_jwt_extended import jwt_required
from flask_restplus import Namespace, Resource from flask_restplus import Namespace
from portal import logger from portal import logger
from portal.database.models import ClientType from portal.database.models import ClientType
from portal.rest import rest_helpers from portal.rest import rest_helpers
from portal.rest.custom_resource import CustomResource
from portal.rest.schemas import SCHEMAS from portal.rest.schemas import SCHEMAS
from portal.service import permissions, general
from portal.service.secrets import create_secret, delete_secret
client_namespace = Namespace('client') client_namespace = Namespace('client')
clients_namespace = Namespace('clients') clients_namespace = Namespace('clients')
...@@ -15,12 +14,11 @@ log = logger.get_logger(__name__) ...@@ -15,12 +14,11 @@ log = logger.get_logger(__name__)
@client_namespace.route('') @client_namespace.route('')
class ClientController(Resource): class ClientController(CustomResource):
@jwt_required @jwt_required
# @users_namespace.response(200, 'List of users', model=users_schema) # @users_namespace.response(200, 'List of users', model=users_schema)
def get(self): def get(self):
perm_service = permissions.PermissionsService() client = self.permissions.client_owner
client = perm_service.client_owner
schema = SCHEMAS.user() if client.type == ClientType.USER else SCHEMAS.worker() schema = SCHEMAS.user() if client.type == ClientType.USER else SCHEMAS.worker()
return schema.dump(client)[0], 200 return schema.dump(client)[0], 200
...@@ -28,21 +26,21 @@ class ClientController(Resource): ...@@ -28,21 +26,21 @@ class ClientController(Resource):
@clients_namespace.route('/<string:cid>/secrets') @clients_namespace.route('/<string:cid>/secrets')
@clients_namespace.param('wid', "Client's id") @clients_namespace.param('wid', "Client's id")
@clients_namespace.response(404, 'Client not found') @clients_namespace.response(404, 'Client not found')
class ClientSecretsController(Resource): class ClientSecretsController(CustomResource):
@jwt_required @jwt_required
@clients_namespace.response(200, 'List client secrets') @clients_namespace.response(200, 'List client secrets')
def get(self, cid: str): def get(self, cid: str):
permissions.PermissionsService().require.sysadmin_or_self(cid) self.permissions.require.sysadmin_or_self(cid)
client = general.find_client(cid) client = self.rest.find.client(cid)
return SCHEMAS.dump('secrets', client.secrets) return SCHEMAS.dump('secrets', client.secrets)
@jwt_required @jwt_required
# @workers_namespace.response(201, 'Created worker secret', model=secret_schema) # @workers_namespace.response(201, 'Created worker secret', model=secret_schema)
def post(self, cid: str): def post(self, cid: str):
permissions.PermissionsService().require.sysadmin_or_self(cid) self.permissions.require.sysadmin_or_self(cid)
data = rest_helpers.parse_request_data(action='create', resource='secret') data = rest_helpers.parse_request_data(action='create', resource='secret')
client = general.find_client(cid) client = self.find.client(cid)
new_secret, value = create_secret(client, **data) new_secret, value = self.rest.secrets(client=client).create(**data)
return {'id': new_secret.id, 'value': value}, 201 return {'id': new_secret.id, 'value': value}, 201
...@@ -50,35 +48,33 @@ class ClientSecretsController(Resource): ...@@ -50,35 +48,33 @@ class ClientSecretsController(Resource):
@clients_namespace.param('wid', "Client's id") @clients_namespace.param('wid', "Client's id")
@clients_namespace.param('sid', "Secret id") @clients_namespace.param('sid', "Secret id")
@clients_namespace.response(404, 'Client not found') @clients_namespace.response(404, 'Client not found')
class ClientSecretController(Resource): class ClientSecretController(CustomResource):
@jwt_required @jwt_required
@clients_namespace.response(200, 'Client secret detail') @clients_namespace.response(200, 'Client secret detail')
def get(self, cid: str, sid: str): def get(self, cid: str, sid: str):
permissions.PermissionsService().require.sysadmin() self.rest.permissions.require.sysadmin_or_self(cid)
client = general.find_client(cid) client = self.rest.find.client(cid)
secret = general.find_secret(client, sid) secret = self.rest.find.secret(client, sid)
return SCHEMAS.dump('secret', secret) return SCHEMAS.dump('secret', secret)
@jwt_required @jwt_required
@clients_namespace.response(204, 'Client secret deleted') @clients_namespace.response(204, 'Client secret deleted')
def delete(self, cid: str, sid: str): def delete(self, cid: str, sid: str):
permissions.PermissionsService().require.sysadmin_or_self(cid) self.rest.permissions.require.sysadmin_or_self(cid)
worker = general.find_client(cid) client = self.find.client(cid)
delete_secret(worker, sid) secret = self.find.secret(client, sid)
self.rest.secrets(secret).delete()
return '', 204 return '', 204
@jwt_required @jwt_required
@clients_namespace.response(204, 'Client secret updated') @clients_namespace.response(204, 'Client secret updated')
@clients_namespace.response(403, 'Not allowed to update client secret') @clients_namespace.response(403, 'Not allowed to update client secret')
def put(self, cid: str, sid: str): def put(self, cid: str, sid: str):
permissions.PermissionsService().require.sysadmin_or_self(cid) self.rest.permissions.require.sysadmin_or_self(cid)
data = rest_helpers.parse_request_data(action='update', resource='secret', partial=True) data = rest_helpers.parse_request_data(action='update', resource='secret', partial=True)
client = general.find_client(cid) client = self.find.client(cid)
secret = general.find_secret(client, sid) secret = self.find.secret(client, sid)
general.update_entity(secret, data, ['name', 'expires_at']) self.rest.secrets(secret).update(**data)
return '', 204 return '', 204
from flask import request from flask import request
from flask_jwt_extended import jwt_required from flask_jwt_extended import jwt_required
from flask_restplus import Namespace, Resource from flask_restplus import Namespace
from portal import logger from portal import logger
from portal.rest import rest_helpers from portal.rest import rest_helpers
from portal.rest.custom_resource import CustomResource
from portal.rest.schemas import SCHEMAS from portal.rest.schemas import SCHEMAS
from portal.service import permissions
from portal.service.auth import find_client
from portal.service.courses import CourseService
from portal.service.errors import ForbiddenError, PortalAPIError from portal.service.errors import ForbiddenError, PortalAPIError
from portal.service.filters import filter_course_dump from portal.service.filters import filter_course_dump
from portal.service.general import find_course
courses_namespace = Namespace('courses') courses_namespace = Namespace('courses')
log = logger.get_logger(__name__) log = logger.get_logger(__name__)
@courses_namespace.route('') @courses_namespace.route('')
class CourseList(Resource): class CourseList(CustomResource):
@jwt_required @jwt_required
# @courses_namespace.response(200, 'Courses list', model=courses_schema) # @courses_namespace.response(200, 'Courses list', model=courses_schema)
@courses_namespace.response(403, 'Not allowed to see courses') @courses_namespace.response(403, 'Not allowed to see courses')
def get(self): def get(self):
# authorization # authorization
permissions.PermissionsService().require.sysadmin() self.permissions.require.sysadmin()
courses_list = CourseService().find_all_courses() courses_list = self.rest.courses.find_all()
return SCHEMAS.dump('courses', courses_list) return SCHEMAS.dump('courses', courses_list)
@jwt_required @jwt_required
# @courses_namespace.response(200, 'Created course', model=course_schema) # @courses_namespace.response(200, 'Created course', model=course_schema)
# @courses_namespace.response(403, 'Not allowed to create course', model=course_schema) # @courses_namespace.response(403, 'Not allowed to create course', model=course_schema)
def post(self): def post(self):
permissions.PermissionsService().require.sysadmin() self.permissions.require.sysadmin()
data = rest_helpers.parse_request_data(resource='course', action='create') data = rest_helpers.parse_request_data(resource='course', action='create')
new_course = CourseService().create_course(**data) new_course = self.rest.courses.create(**data)
return SCHEMAS.dump('course', new_course), 201 return SCHEMAS.dump('course', new_course), 201
@courses_namespace.route('/<string:cid>') @courses_namespace.route('/<string:cid>')
@courses_namespace.param('cid', 'Course id') @courses_namespace.param('cid', 'Course id')
@courses_namespace.response(404, 'Course not found') @courses_namespace.response(404, 'Course not found')
class CourseResource(Resource): class CourseResource(CustomResource):
@jwt_required @jwt_required
# @courses_namespace.response(200, 'Course found', model=course_schema) # @courses_namespace.response(200, 'Course found', model=course_schema)
@courses_namespace.response(403, 'Not allowed to see course') @courses_namespace.response(403, 'Not allowed to see course')
def get(self, cid: str): def get(self, cid: str):
client = find_client() client = self.rest.auth.client
course = find_course(cid) course = self.rest.find.course(cid)
# authorization # authorization
perm_service = permissions.PermissionsService(course=course) perm_service = self.permissions(course=course)
if perm_service.check.permissions(['view_course_full']): if perm_service.check.permissions(['view_course_full']):
return SCHEMAS.dump('course', course) return SCHEMAS.dump('course', course)
...@@ -67,36 +64,36 @@ class CourseResource(Resource): ...@@ -67,36 +64,36 @@ class CourseResource(Resource):
@courses_namespace.response(204, 'Course deleted') @courses_namespace.response(204, 'Course deleted')
@courses_namespace.response(403, 'Not allowed to delete course') @courses_namespace.response(403, 'Not allowed to delete course')
def delete(self, cid: str): def delete(self, cid: str):
permissions.PermissionsService().require.sysadmin() self.permissions.require.sysadmin()
course = find_course(cid) course = self.rest.find.course(cid)
CourseService(course=course).delete_course() self.rest.courses(course).delete()
return '', 204 return '', 204
@jwt_required @jwt_required
@courses_namespace.response(204, 'Course updated') @courses_namespace.response(204, 'Course updated')
@courses_namespace.response(403, 'Not allowed to update course') @courses_namespace.response(403, 'Not allowed to update course')
def put(self, cid: str): def put(self, cid: str):
course = find_course(cid) course = self.rest.find.course(cid)
# authorization # authorization
permissions.PermissionsService(course=course).require.update_course() self.permissions(course=course).require.update_course()
data = rest_helpers.parse_request_data(action='update', resource='course', partial=True) data = rest_helpers.parse_request_data(action='update', resource='course', partial=True)
CourseService(course=course).update_course(data) self.rest.courses(course).update(**data)
return '', 204 return '', 204
@courses_namespace.route('/<string:cid>/notes_access_token') @courses_namespace.route('/<string:cid>/notes_access_token')
@courses_namespace.param('cid', 'Course id') @courses_namespace.param('cid', 'Course id')
@courses_namespace.response(404, 'Course not found') @courses_namespace.response(404, 'Course not found')
class CourseNotesToken(Resource): class CourseNotesToken(CustomResource):
@jwt_required @jwt_required
@