diff --git a/.gitignore b/.gitignore
index abc3bb4d0b13f29e3e2af5a20e30543ed3cfdec3..4641bcecd3639bd95fd758cc120a25825b7cf1c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 .vscode/
-logs
\ No newline at end of file
+logs
+__pycache__/
diff --git a/atlas_controller/.idea/atlas_controller.iml b/atlas_controller/.idea/atlas_controller.iml
index beb492c84684dc1965df41e22394a0604e7e6289..7d0c25c84f9cb103fdae42733e9fca547cb33c93 100644
--- a/atlas_controller/.idea/atlas_controller.iml
+++ b/atlas_controller/.idea/atlas_controller.iml
@@ -2,8 +2,17 @@
 <module type="PYTHON_MODULE" version="4">
   <component name="NewModuleRootManager">
     <content url="file://$MODULE_DIR$" />
-    <orderEntry type="inheritedJdk" />
+    <orderEntry type="jdk" jdkName="Python 3.7 (atlas_ui)" jdkType="Python SDK" />
     <orderEntry type="sourceFolder" forTests="false" />
     <orderEntry type="module" module-name="atlas_server" />
   </component>
+  <component name="PackageRequirementsSettings">
+    <option name="requirementsPath" value="" />
+  </component>
+  <component name="PyDocumentationSettings">
+    <option name="myDocStringFormat" value="Google" />
+  </component>
+  <component name="TestRunnerService">
+    <option name="PROJECT_TEST_RUNNER" value="Unittests" />
+  </component>
 </module>
\ No newline at end of file
diff --git a/atlas_controller/atlas_controller b/atlas_controller/atlas_controller
new file mode 120000
index 0000000000000000000000000000000000000000..5590cf639e308fa42d65af25bdbcfc0a8f63557c
--- /dev/null
+++ b/atlas_controller/atlas_controller
@@ -0,0 +1 @@
+/p/home/jusers/schiffer1/jureca/project_jinm16/atlasui/atlas_controller/
\ No newline at end of file
diff --git a/atlas_controller/atlas_controller/__init__.py b/atlas_controller/atlas_controller/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/atlas_controller/atlas_controller/app.py b/atlas_controller/atlas_controller/app.py
deleted file mode 100644
index c9162fd099831b4fbcdd6645ae7f77e10e6f5b98..0000000000000000000000000000000000000000
--- a/atlas_controller/atlas_controller/app.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env python3
-# Server controlling all actions on JURECA
-from flask import Flask, request
-from flask_restful import Resource, Api
-
-app = Flask(__name__)
-api = Api(app, prefix="/api")
-
-
-@app.route("/")
-def welcome():
-    return """<h1>Welcome to ATLaS UI</h1>
-API available <a href="/api">here</a>
-    """
-
-
-def get_json_data():
-    assert request.is_json
-    data = request.get_json()
-    return data
-
-
-class SetupTraining(Resource):
-    def post(self):
-        data = get_json_data()
-        print(data)
-        return "Thank you from JURECA!"
-
-    def get(self):
-        return "This is a test page!"
-
-
-api.add_resource(SetupTraining, "/setup_training")
-
-if __name__ == "__main__":
-    app.run(debug=True, port=8000)
diff --git a/atlas_controller/atlas_controller/main.py b/atlas_controller/atlas_controller/main.py
deleted file mode 100644
index 712251f6e384d4714703536998669632bb198e3d..0000000000000000000000000000000000000000
--- a/atlas_controller/atlas_controller/main.py
+++ /dev/null
@@ -1,120 +0,0 @@
-#!/usr/bin/env python3
-import os
-from time import sleep
-import glob as glob
-import json
-from atlas.experiments import get_logger, init_logging, require_directory, setup_file_logger
-from atlas.util.iterable import group_by
-
-WAIT_INTERVAL = 2
-EVENT_DIRECTORY = "events"
-DELETE_EVENTS = True
-WORK_DIR = "work"
-
-
-class Event(object):
-    def __init__(self, event_file):
-        self.event_file = event_file
-
-        with open(self.event_file, "r") as f:
-            self.event_content = json.load(f)
-            self.event_type = self.event_content["event_type"]
-            self.keep_event = self.event_content.get("keep_event", False)
-            self.id = self.event_content.get("id", None)
-
-
-def make_id_dir(id):
-    return require_directory(os.path.join(WORK_DIR, id))
-
-
-def search_for_events(directory):
-    event_files = glob.glob(os.path.join(directory, "atlas_*.json"))
-    return event_files
-
-
-def stop(event, log):
-    log.info("Received STOP event")
-    return True
-
-
-def setup_training(event, log):
-    if event.id is None:
-        log.info(f"Event contains no id, cannot create annotations! (File: {event.event_file})")
-        return False
-    annotations = event.event_content.get("annotations", None)
-    if annotations is None:
-        log.info(f"Event contains no annotations, cannot create annotations! (File: {event.event_file})")
-        return False
-
-    # Create a directory to write the annotation files to
-    id_dir = make_id_dir(event.id)
-    annotation_dir = require_directory(os.path.join(id_dir, "annotations"))
-    # Group annotations by section
-    annotations_by_image = group_by(annotations, lambda item: item["image"])
-
-    for image, annotations_for_image in annotations_by_image.items():
-        content_to_write = {
-            "Regions": [{"name": annotation["name"], "path": annotation["path"]} for annotation in annotations_for_image],
-        }
-        fname = os.path.join(annotation_dir, f"test_{image}.json")
-        log.info(f"Writing annotations to {fname}")
-        with open(fname, "w") as f:
-            json.dump(content_to_write, f)
-
-
-def unknown_event(event, log):
-    log.error(f"No handler for event type {event.event_type}")
-    event.keep_event = True
-    return False
-
-
-event_handlers = {
-    "stop": stop,
-    "setup_training": setup_training,
-}
-
-
-def main():
-    init_logging()
-    log = get_logger()
-
-    log.info(f"Wait interval: {WAIT_INTERVAL}")
-    log.info(f"Event directory: {EVENT_DIRECTORY}")
-
-    # Create a directory to write logs to
-    log_folder = require_directory(".")
-    setup_file_logger(root=log_folder, prefix="altas_controller")
-    log.info("Started ATLaS controller")
-
-    # This signal specifies when the program will stop
-    stop_signal = False
-
-    while not stop_signal:
-        if not os.path.exists(EVENT_DIRECTORY):
-            log.warning(f"Event directory \"{EVENT_DIRECTORY}\" does not exist!")
-            sleep(WAIT_INTERVAL)
-            continue
-
-        # log.info(f"Searching directory \"{EVENT_DIRECTORY}\" for events")
-        event_files = search_for_events(EVENT_DIRECTORY)
-        if event_files:
-            log.info(f"Found {len(event_files)} event(s)")
-
-        for event_file in event_files:
-            event = Event(event_file)
-            handler = event_handlers.get(event.event_type, unknown_event)
-            stop_signal = handler(event, log)
-
-            if not event.keep_event and DELETE_EVENTS:
-                log.info(f"Deleting event file \"{event_file}\"")
-                if os.path.exists(event_file):
-                    os.remove(event_file)
-
-        if not stop_signal:
-            sleep(WAIT_INTERVAL)
-
-    log.info("ATLaS controller stopped")
-
-
-if __name__ == "__main__":
-    main()
diff --git a/atlas_controller/start_remote_server.sh b/atlas_controller/start_remote_server.sh
index 8fb0f56ba2a7ac6a1c7ac30cc2860556bc06bf69..1f59dba7a094c8053a4344357a6be3db4df54fb0 100755
--- a/atlas_controller/start_remote_server.sh
+++ b/atlas_controller/start_remote_server.sh
@@ -3,4 +3,5 @@
 
 USER=schiffer1
 HOST=jureca06.fz-juelich.de
-ssh ${USER}@${HOST} -t "bash -cl 'cd /p/home/jusers/schiffer1/jureca/project_jinm16/atlasui/; source load_modules.sh; source venv/bin/activate; python app.py'"
\ No newline at end of file
+COMMAND='cd /p/home/jusers/schiffer1/jureca/project_jinm16/atlasui/; source load_modules.sh; source venv/bin/activate; export PYTHONPATH=`pwd`:$PYTHONPATH; python atlas_controller/app.py'
+ssh ${USER}@${HOST} -t "bash -cl '${COMMAND}'"
\ No newline at end of file
diff --git a/atlas_server/.idea/atlas_server.iml b/atlas_server/.idea/atlas_server.iml
index 2942a30ab8aea01b3393f1d1e098342c0bf997c4..1972284c026c854b3f8a86d88eb8e1cb02192ff6 100644
--- a/atlas_server/.idea/atlas_server.iml
+++ b/atlas_server/.idea/atlas_server.iml
@@ -2,8 +2,17 @@
 <module type="PYTHON_MODULE" version="4">
   <component name="NewModuleRootManager">
     <content url="file://$MODULE_DIR$" />
-    <orderEntry type="inheritedJdk" />
+    <orderEntry type="jdk" jdkName="Python 3.7 (atlas_ui)" jdkType="Python SDK" />
     <orderEntry type="sourceFolder" forTests="false" />
     <orderEntry type="module" module-name="atlas_controller" />
   </component>
+  <component name="PackageRequirementsSettings">
+    <option name="requirementsPath" value="" />
+  </component>
+  <component name="PyDocumentationSettings">
+    <option name="myDocStringFormat" value="Google" />
+  </component>
+  <component name="TestRunnerService">
+    <option name="PROJECT_TEST_RUNNER" value="Unittests" />
+  </component>
 </module>
\ No newline at end of file
diff --git a/atlas_server/atlas_server/app.py b/atlas_server/atlas_server/app.py
index 7a46790dfdb7ce1e0a6cede0f9dcb292ca839aa9..44b5ed2d09723167acc3d25e9abd464875c6f0a9 100644
--- a/atlas_server/atlas_server/app.py
+++ b/atlas_server/atlas_server/app.py
@@ -1,170 +1,210 @@
 #!/usr/bin/env python3
-import requests
-from bson.objectid import ObjectId
-from flask import Flask, request
+from flask import Flask
 from flask_cors import CORS
-from flask_restful import Resource, Api
-from pymongo import MongoClient
+from flask_restplus import Api, Resource, fields, abort
 
-# DATABASE_HOST = "medpc018.ime.kfa-juelich.de"
-DATABASE_HOST = "localhost"
-DATABASE_PORT = 27017
-DATABASE_NAME = "atlas_ui_db"
-# UPLOAD_ROOT = "/home/cschiffer/code/atlasui_prototype/atlas_controller/events"
-
-ATLAS_CONTROLLER_URL = "http://localhost:8000/api"
-ATLAS_CONTROLLER_SETUP_TRAINING_ENDPOINT = f"{ATLAS_CONTROLLER_URL}/setup_training"
+from atlas_server import models
+from atlas_server.db import Database, ProjectNotFoundError, TaskNotFoundError, ForbiddenOperationError
 
 app = Flask(__name__)
+# Setup flask with CORS, since we do requests to atlas controller
 CORS(app)
-api = Api(app)
-
-
-class MissingIdError(Exception):
-    pass
-
-
-class InvalidIdError(Exception):
-    pass
-
-
-def create_client():
-    return MongoClient(DATABASE_HOST, DATABASE_PORT)
-
-
-def get_projects_table(client):
-    database = client.get_database(DATABASE_NAME)
-    projects = database.projects
-    return projects
-
-
-def check_id(id_to_check):
-    with create_client() as client:
-        projects = get_projects_table(client)
-        if projects.find_one({"_id": ObjectId(id_to_check)}):
-            return True
-        else:
-            return False
-
-
-def generate_id():
-    with create_client() as client:
-        projects = get_projects_table(client)
-        inserted_element = projects.insert_one({})
-        new_id = str(inserted_element.inserted_id)
-    return new_id
-
-
-def validate_id_from_data(data):
-    # Check if the given id exists and is valid
-    if "id" not in data:
-        raise MissingIdError("Id is missing")
-
-    id_to_check = data["id"]
-    if not check_id(id_to_check):
-        raise InvalidIdError(f"Id {id_to_check} is invalid. Please request a valid id.")
-
-    return id_to_check
-
-
-def find_project_for_id(id):
-    with create_client() as client:
-        projects = get_projects_table(client)
-        project = projects.find_one({"_id": ObjectId(id)})
-        return project
-
-
-def get_data_for_id(id):
-    with create_client() as client:
-        projects = get_projects_table(client)
-        return projects.find_one({"_id": ObjectId(id)})
-
-
-def add_data_for_id(id, data):
-    with create_client() as client:
-        projects = get_projects_table(client)
-        projects.update_one({"_id": ObjectId(id)}, data)
-
-
-def add_annotation(id, annotation_name, annotation_path, image):
-    print(f"Adding annotation: {annotation_name}")
-    add_data_for_id(id, {"$push": {
-        "annotations": {
-            "name": annotation_name,
-            "path": annotation_path,
-            "image": image,
-        }
-    }})
-
-
-def get_json_data():
-    assert request.is_json
-    data = request.get_json()
-    return data
-
-
-# def upload_data(fname, data):
-#     fname = os.path.join(UPLOAD_ROOT, fname)
-#     # Write data
-#     with open(fname, "w") as f:
-#         json.dump(data, f, indent=4)
-
-class RequestId(Resource):
-    def post(self):
-        # TODO Authentication
-        # Generate entry for the database
-        new_id = generate_id()
-        print(f"Generated new ID: {new_id}")
-
-        return {"id": new_id}
-
-
-api.add_resource(RequestId, "/request_id")
-
-
-class SubmitAnnotations(Resource):
+api = Api(app,
+          prefix="/api",
+          version="1.0",
+          title="ATLaS UI API",
+          description="API serving as backend for the ATLaS UI")
+
+# -----------------------------------
+# Namespace definitions
+# -----------------------------------
+
+project_namespace = api.namespace("projects", description="Operations to deal with projects")
+
+# -----------------------------------
+# Model definitions
+# -----------------------------------
+
+task_model = api.model("TaskModel",
+                       {
+                           "task_id": fields.Integer(description="ID of the task."),
+                           "name": fields.String(description="The name of the task."),
+                           "annotations": fields.List(fields.String(description="Annotations to add to the task.")),
+                       })
+
+project_model = api.model("ProjectModel",
+                          {
+                              "project_id": fields.Integer(description="ID of the project."),
+                              "name": fields.String(description="The name of the project."),
+                              "brain": fields.Integer(description="Number of the brain the projects refers to."),
+                              "owner": fields.String(description="Owner of the project."),
+                              "tasks": fields.List(fields.Nested(task_model), description="List of tasks of the project.")
+                          })
+
+_project_schema = models.ProjectSchema()
+_task_schema = models.TaskSchema()
+
+
+# -----------------------------------
+# Actual API definitions
+# -----------------------------------
+
+@project_namespace.route("/")
+class ProjectList(Resource):
+    @project_namespace.doc("Get all projects")
+    def get(self):
+        # TODO This function should be hidden behind an authentication mechanism
+        with Database() as db:
+            projects = db.projects.find({})
+        if not projects:
+            abort(404, f"No projects found")
+        return _project_schema.dump(projects, many=True)
+
+    @project_namespace.doc("Create a new project")
+    @project_namespace.expect(project_model)
     def post(self):
-        data = get_json_data()
-        id = validate_id_from_data(data)
-        for annotation in data["annotations"]:
-            add_annotation(id=id,
-                           annotation_name=annotation["annotation_name"],
-                           annotation_path=annotation["annotation_path"],
-                           image=annotation["image"])
+        # Deserialize project
+        project = _project_schema.load(api.payload)
 
+        with Database() as db:
+            project_id = db.insert_project(project)
 
-api.add_resource(SubmitAnnotations, "/submit_annotations")
+        # Return of newly inserted project
+        return {"project_id": project_id, "status": "success"}
 
 
-class SetupTraining(Resource):
-    def post(self):
-        # Data is received as JSON
-        json_data = get_json_data()
-        # Validate ID
-        id = validate_id_from_data(json_data)
-
-        # Get all fields we need
-        data_for_id = get_data_for_id(id)
-        annotations = json_data.get("annotations", None)
-        if annotations is None:
-            raise RuntimeError(f"Missing field: \"annotations\"")
-
-        brain = json_data.get("brain", None)
-        if brain is None:
-            raise RuntimeError(f"Missing field: \"brain\"")
-
-        # Create a file and upload it to the atlas controller
-        event_content = {
-            "id": id,
-            "brain": brain,
-            "annotations": annotations,
-        }
-        # Send to server
-        response = requests.post(ATLAS_CONTROLLER_SETUP_TRAINING_ENDPOINT, json=event_content)
-        return response.json()
-
-
-api.add_resource(SetupTraining, "/setup_training")
+@project_namespace.route("/<int:project_id>")
+@project_namespace.response(404, "Project not found")
+class Project(Resource):
+    @project_namespace.doc("Get project with the specified id")
+    def get(self, project_id):
+        with Database() as db:
+            project = db.get_project_by_id(project_id=project_id)
+        if not project:
+            abort(404, f"No project found for id {project_id}")
+        else:
+            return _project_schema.dump(project)
+
+    @project_namespace.doc("Delete project with the specified id")
+    @project_namespace.response(204, "Project deleted")
+    def delete(self, project_id):
+        with Database() as db:
+            db.delete_project(project_id=project_id)
+        return {"status": "success"}
+
+    @project_namespace.doc("Update project with the specified id")
+    @project_namespace.expect(project_model)
+    def put(self, project_id):
+        data = api.payload
+        if not data:
+            abort(400, "No fields to update provided")
+
+        for field in ("created", "modified", "project_id", "tasks"):
+            data.pop(field, None)
+
+        try:
+            with Database() as db:
+                db.update_project_values(project_id=project_id, key_value_dict=data)
+        except ProjectNotFoundError:
+            abort(404, f"No project found for id {project_id}")
+        except ForbiddenOperationError as ex:
+            abort(403, ex.message)
+
+        return {"status": "success"}
+
+
+@project_namespace.route("/users/<username>")
+@project_namespace.response(404, "Project for user not found")
+class UserProjectList(Resource):
+    @project_namespace.doc("Get projects of the specified user")
+    def get(self, username):
+        with Database() as db:
+            projects = db.get_projects_for_user(username=username)
+        if not projects:
+            abort(404, f"No projects found for user {username}")
+        return _project_schema.dump(projects, many=True)
+
+
+@project_namespace.route("/<int:project_id>/tasks")
+@project_namespace.response(404, "Project not found")
+class TaskList(Resource):
+    @project_namespace.doc("Get tasks of the specified project")
+    def get(self, project_id):
+        with Database() as db:
+            project = db.get_project_by_id(project_id=project_id)
+        if not project:
+            abort(404, f"No project found for id {project_id}")
+        else:
+            return _task_schema.dump(project.tasks, many=True)
+
+    @project_namespace.doc("Create a new task for the specified project")
+    @project_namespace.expect(task_model)
+    def post(self, project_id):
+        # Create a task
+        task = _task_schema.load(api.payload)
+
+        # Add to project
+        try:
+            with Database() as db:
+                task_id = db.add_task_to_project(project_id=project_id, task=task)
+        except ProjectNotFoundError:
+            abort(404, f"No project found for id {project_id}")
+            return
+
+        # Return ID of newly inserted task
+        return {"task_id": task_id, "status": "success"}
+
+
+@project_namespace.route("/<int:project_id>/tasks/<int:task_id>")
+@project_namespace.response(404, "Task not found")
+class Task(Resource):
+    @project_namespace.doc("Get task with the specified task id and project id")
+    def get(self, project_id, task_id):
+        try:
+            with Database() as db:
+                task = db.get_task_by_id(project_id=project_id,
+                                         task_id=task_id)
+        except TaskNotFoundError:
+            abort(404, f"No task found for project id {project_id} and task id {task_id}")
+            return
+
+        return _task_schema.dump(task)
+
+    @project_namespace.doc("Delete project with the specified id")
+    @project_namespace.response(204, "Task deleted")
+    def delete(self, project_id, task_id):
+        # Delete the task by
+        try:
+            with Database() as db:
+                db.delete_task(project_id=project_id, task_id=task_id)
+        except TaskNotFoundError:
+            abort(404, f"No task found for id {task_id}")
+            return
+
+    @project_namespace.doc("Update task with the specified task id and project id")
+    @project_namespace.expect(task_model)
+    def put(self, project_id, task_id):
+        data = api.payload
+        if not data:
+            abort(400, "No fields to update provided")
+        # Remove fields which may not be updated
+        for field in ("created", "modified", "task_id"):
+            data.pop(field, None)
+
+        try:
+            with Database() as db:
+                db.update_task_values(project_id=project_id, task_id=task_id, key_value_dict=data)
+        except TaskNotFoundError:
+            abort(404, f"No task found for project id {project_id} and task id {task_id}")
+        except ForbiddenOperationError as ex:
+            abort(403, ex.message)
+
+        return {"status": "success"}
+
+
+# -----------------------------------
+# Main entry point
+# -----------------------------------
 
 if __name__ == "__main__":
     app.run(debug=True)
diff --git a/atlas_server/atlas_server/config.py b/atlas_server/atlas_server/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..92456199cebe2df6d855f9d95f87f0adb06c5a55
--- /dev/null
+++ b/atlas_server/atlas_server/config.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+"""
+Configuration for ATLaS server
+"""
+DATABASE_HOST = "localhost"
+DATABASE_PORT = 27017
+DATABASE_NAME = "atlas_ui_db"
+ATLAS_CONTROLLER_URL = "http://localhost:8000/api"
diff --git a/atlas_server/atlas_server/db.py b/atlas_server/atlas_server/db.py
new file mode 100644
index 0000000000000000000000000000000000000000..49739a0573c6547a2c337c921d03416afe3242be
--- /dev/null
+++ b/atlas_server/atlas_server/db.py
@@ -0,0 +1,278 @@
+#!/usr/bin/env python3
+"""
+Access to Mondo database
+"""
+from time import time
+from pymongo import MongoClient, DESCENDING
+from atlas_server import models
+from atlas_server.config import DATABASE_HOST, DATABASE_PORT, DATABASE_NAME
+
+_project_schema = models.ProjectSchema()
+_task_schema = models.TaskSchema()
+
+
+def _project_to_mongodb_item(project):
+    document = _project_schema.dump(project)
+    return document
+
+
+class ProjectNotFoundError(Exception):
+    def __init__(self, project_id):
+        message = f"Project not found: {project_id}"
+        self.project_id = project_id
+        super(ProjectNotFoundError, self).__init__(message)
+
+
+class TaskNotFoundError(Exception):
+    def __init__(self, project_id, task_id):
+        message = f"Task not found: {project_id} - {task_id}"
+        self.project_id = project_id
+        self.task_id = task_id
+        super(TaskNotFoundError, self).__init__(message)
+
+
+class ForbiddenOperationError(Exception):
+    def __init__(self, message):
+        self.message = message
+        super(ForbiddenOperationError, self).__init__(message)
+
+
+class Database(object):
+    """
+    Connection object to encapsulate the MongoDB database
+    """
+
+    def __init__(self):
+        self.client = None
+
+    def open(self):
+        if self.client:
+            raise RuntimeError("Cannot open connection: Connection already open.")
+        self.client = MongoClient(DATABASE_HOST, DATABASE_PORT)
+        return self
+
+    def close(self):
+        if self.client is None:
+            raise RuntimeError("Cannot close connection: Connection already closed.")
+        self.client.close()
+        self.client = None
+
+    def __enter__(self):
+        return self.open()
+
+    def __exit__(self, *args):
+        self.close()
+
+    @property
+    def database(self):
+        return self.client.get_database(DATABASE_NAME)
+
+    @property
+    def projects(self):
+        return self.database.projects
+
+    def _update_project_fields(self, project_id, key_value_dict, operation="set"):
+        """
+        Update a single field
+
+        Args:
+            project_id (int): Id of the project.
+            key_value_dict (dict): Dictionary of keys and values to update.
+        """
+        forbidden_fields = ("modified", "project_id", "created")
+        operations = {
+            "set": "$set",
+            "add": "$push",
+            "delete": "$pull",
+        }
+
+        if any(field in key_value_dict for field in forbidden_fields):
+            message = f"Following fields may not be updated: {', '.join(field for field in key_value_dict if field in forbidden_fields)}"
+            raise ForbiddenOperationError(message)
+
+        mongo_operation = operations[operation]
+        update_res = self.projects.update_one({"project_id": project_id},
+                                              {
+                                                  mongo_operation: key_value_dict
+                                              })
+        # Raise error if no projects were affected
+        # This means the project id does not exist
+        if operation == "delete":
+            affected_count = update_res.deleted_count
+        else:
+            affected_count = update_res.matched_count
+        if affected_count == 0:
+            raise ProjectNotFoundError(project_id=project_id)
+
+        # Update modified date
+        if operation != "delete":
+            self._update_project_modified_date(project_id=project_id)
+
+    def _update_project_modified_date(self, project_id):
+        """
+        Update the modified date of a project.
+        """
+        # Update modified data of project
+        modified_date = time()
+        self.projects.update_one({"project_id": project_id},
+                                 {
+                                     "$set":
+                                         {
+                                             "modified": str(modified_date),
+                                         },
+                                 })
+        return modified_date
+
+    def insert_project(self, project):
+        # Find current maximum project id
+        max_existing = self.projects.find_one(sort=[("project_id", DESCENDING)])
+        if max_existing:
+            new_id = max_existing["project_id"] + 1
+        else:
+            new_id = 1
+        # Insert new id into project
+        project.project_id = new_id
+        # Insert created and modified date
+        project.created = project.modified = time()
+        # If the project has tasks, give them ids and modified/create dates
+        if project.tasks:
+            for i, task in enumerate(project.tasks, 1):
+                task.created = task.modified = project.created
+                task.task_id = i
+        # Serialize the project, while taking care that the id is correctly used by MongoDB
+        document = _project_to_mongodb_item(project=project)
+        # Insert into database
+        self.projects.insert_one(document)
+
+        return new_id
+
+    def update_project_values(self, project_id, key_value_dict):
+        self._update_project_fields(project_id=project_id,
+                                    key_value_dict=key_value_dict,
+                                    operation="set")
+
+    def get_project_by_id(self, project_id):
+        """
+        Get a project with a given id.
+
+        Args:
+            project_id (int): Id of the project.
+
+        Returns:
+            atlas_server.models.Project
+        """
+        project = self.projects.find_one({"project_id": project_id})
+        return _project_schema.load(project, unknown=True)
+
+    def get_projects_for_user(self, username):
+        projects = list(self.projects.find({"owner": username}))
+        # noinspection PyTypeChecker
+        return _project_schema.load(projects, many=True, unknown=True)
+
+    def delete_project(self, project_id):
+        deleted_res = self.projects.delete_one({"project_id": project_id})
+        if deleted_res.deleted_count == 0:
+            raise ProjectNotFoundError(project_id=project_id)
+
+    def _update_task_fields(self, project_id, task_id, key_value_dict, operation="set"):
+        """
+        Update a single field
+
+        Args:
+            project_id (int): Id of the project.
+            key_value_dict (dict): Dictionary of keys and values to update.
+        """
+        forbidden_fields = ("modified", "task_id", "created")
+        operations = {
+            "set": "$set",
+            "add": "$push",
+            "delete": "$pull"
+        }
+
+        if any(field in key_value_dict for field in forbidden_fields):
+            message = f"Following fields may not be updated: {', '.join(field for field in key_value_dict if field in forbidden_fields)}"
+            raise ForbiddenOperationError(message)
+
+        mongo_operation = operations[operation]
+        # Modify the update dict to match the update syntax. We prefix "tasks.$." before each key
+        update_dict = {f"tasks.$.{key}": value for key, value in key_value_dict.items()}
+        update_res = self.projects.update_one({"project_id": project_id, "tasks.task_id": task_id, }, {mongo_operation: update_dict})
+        # Raise error if no projects were affected
+        # This means the project id does not exist
+        if operation == "delete":
+            affected_count = update_res.deleted_count
+        else:
+            affected_count = update_res.matched_count
+        if affected_count == 0:
+            raise TaskNotFoundError(project_id=project_id, task_id=task_id)
+
+        # Update modified date (if we did not delete the task)
+        if operation != "delete":
+            self._update_task_modified_date(project_id=project_id, task_id=task_id)
+
+    def _update_task_modified_date(self, project_id, task_id):
+        """
+        Update the modified date of a task.
+        """
+        # Update modified data of project
+        modified_date = time()
+        self.projects.update_one({"project_id": project_id, "tasks.task_id": task_id, },
+                                 {
+                                     "$set":
+                                         {
+                                             "task.$.modified": str(modified_date),
+                                         },
+                                 })
+        return modified_date
+
+    # noinspection PyUnresolvedReferences
+    def add_task_to_project(self, project_id, task):
+        # Get the project and find out how many tasks it already has
+        project = self.get_project_by_id(project_id=project_id)
+        if project is None:
+            raise ProjectNotFoundError(project_id)
+        if not project.tasks:
+            new_task_id = 1
+        else:
+            new_task_id = max(task.task_id for task in project.tasks) + 1
+        task.task_id = new_task_id
+        # Add created and modified info to task
+        task.created = time()
+        task.modified = time()
+        # Serialize task
+        document = _task_schema.dump(task)
+        # Insert task into the projects task list
+        self._update_project_fields(project_id=project_id,
+                                    key_value_dict={
+                                        "tasks": document
+                                    },
+                                    operation="add")
+        # Return id of new task
+        return new_task_id
+
+    def get_task_by_id(self, project_id, task_id):
+        res = self.projects.find_one(
+            {"project_id": project_id,
+             "tasks.task_id": task_id
+             },
+            # Projection, only get the specific task
+            {
+                "tasks.$": 1,
+            }
+        )
+        if not res:
+            raise TaskNotFoundError(project_id=project_id, task_id=task_id)
+        return _task_schema.load(res["tasks"][0])
+
+    def update_task_values(self, project_id, task_id, key_value_dict):
+        self._update_task_fields(project_id=project_id,
+                                 task_id=task_id,
+                                 key_value_dict=key_value_dict,
+                                 operation="set")
+
+    def delete_task(self, project_id, task_id):
+        # delete_res = self.projects.
+        self._update_task_fields(project_id=project_id,
+                                 task_id=task_id,
+                                 key_value_dict={},
+                                 operation="delete")
diff --git a/atlas_server/atlas_server/models.py b/atlas_server/atlas_server/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..8526a51a41f921b572f5e2fac070ebff9274de40
--- /dev/null
+++ b/atlas_server/atlas_server/models.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python3
+"""
+Model classes for ATLaS UI
+"""
+from marshmallow import Schema, fields, post_load
+
+
+class TaskSchema(Schema):
+    task_id = fields.Integer()
+    name = fields.String()
+    created = fields.Integer()
+    modified = fields.Integer()
+    annotations = fields.List(fields.String())
+
+    @post_load
+    def create_task(self, data, **kwargs):
+        return Task(**data)
+
+
+class ProjectSchema(Schema):
+    project_id = fields.Integer()
+    name = fields.String()
+    brain = fields.Integer()
+    owner = fields.String()
+    tasks = fields.List(fields.Nested(TaskSchema))
+    created = fields.Integer()
+    modified = fields.Integer()
+
+    @post_load
+    def create_project(self, data, **kwargs):
+        return Project(**data)
+
+
+class Project(object):
+    def __init__(self,
+                 project_id=None,
+                 name=None,
+                 brain=None,
+                 owner=None,
+                 tasks=None,
+                 created=None,
+                 modified=None):
+        self.project_id = project_id
+        self.name = name
+        self.brain = brain
+        self.owner = owner
+        self.tasks = tasks
+
+        if self.name is None:
+            self.name = "Untitled project"
+
+        if self.tasks is None:
+            self.tasks = []
+
+        self.created = created
+        self.modified = modified
+
+    def __repr__(self):
+        return f"Project(project_id={self.project_id}, name={self.name}, owner={self.owner})"
+
+
+class Task(object):
+    def __init__(self,
+                 task_id=None,
+                 name=None,
+                 created=None,
+                 modified=None,
+                 annotations=None):
+        self.task_id = task_id
+        self.name = name
+        self.annotations = annotations
+
+        if self.name is None:
+            self.name = "Untitled task"
+
+        if self.annotations is None:
+            self.annotations = []
+
+        self.created = created
+        self.modified = modified
+
+    def __repr__(self):
+        return f"Task(task_id={self.task_id}, name={self.name})"
diff --git a/atlas_server/atlas_server/old_app.py b/atlas_server/atlas_server/old_app.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a46790dfdb7ce1e0a6cede0f9dcb292ca839aa9
--- /dev/null
+++ b/atlas_server/atlas_server/old_app.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python3
+import requests
+from bson.objectid import ObjectId
+from flask import Flask, request
+from flask_cors import CORS
+from flask_restful import Resource, Api
+from pymongo import MongoClient
+
+# DATABASE_HOST = "medpc018.ime.kfa-juelich.de"
+DATABASE_HOST = "localhost"
+DATABASE_PORT = 27017
+DATABASE_NAME = "atlas_ui_db"
+# UPLOAD_ROOT = "/home/cschiffer/code/atlasui_prototype/atlas_controller/events"
+
+ATLAS_CONTROLLER_URL = "http://localhost:8000/api"
+ATLAS_CONTROLLER_SETUP_TRAINING_ENDPOINT = f"{ATLAS_CONTROLLER_URL}/setup_training"
+
+app = Flask(__name__)
+CORS(app)
+api = Api(app)
+
+
+class MissingIdError(Exception):
+    pass
+
+
+class InvalidIdError(Exception):
+    pass
+
+
+def create_client():
+    return MongoClient(DATABASE_HOST, DATABASE_PORT)
+
+
+def get_projects_table(client):
+    database = client.get_database(DATABASE_NAME)
+    projects = database.projects
+    return projects
+
+
+def check_id(id_to_check):
+    with create_client() as client:
+        projects = get_projects_table(client)
+        if projects.find_one({"_id": ObjectId(id_to_check)}):
+            return True
+        else:
+            return False
+
+
+def generate_id():
+    with create_client() as client:
+        projects = get_projects_table(client)
+        inserted_element = projects.insert_one({})
+        new_id = str(inserted_element.inserted_id)
+    return new_id
+
+
+def validate_id_from_data(data):
+    # Check if the given id exists and is valid
+    if "id" not in data:
+        raise MissingIdError("Id is missing")
+
+    id_to_check = data["id"]
+    if not check_id(id_to_check):
+        raise InvalidIdError(f"Id {id_to_check} is invalid. Please request a valid id.")
+
+    return id_to_check
+
+
+def find_project_for_id(id):
+    with create_client() as client:
+        projects = get_projects_table(client)
+        project = projects.find_one({"_id": ObjectId(id)})
+        return project
+
+
+def get_data_for_id(id):
+    with create_client() as client:
+        projects = get_projects_table(client)
+        return projects.find_one({"_id": ObjectId(id)})
+
+
+def add_data_for_id(id, data):
+    with create_client() as client:
+        projects = get_projects_table(client)
+        projects.update_one({"_id": ObjectId(id)}, data)
+
+
+def add_annotation(id, annotation_name, annotation_path, image):
+    print(f"Adding annotation: {annotation_name}")
+    add_data_for_id(id, {"$push": {
+        "annotations": {
+            "name": annotation_name,
+            "path": annotation_path,
+            "image": image,
+        }
+    }})
+
+
+def get_json_data():
+    assert request.is_json
+    data = request.get_json()
+    return data
+
+
+# def upload_data(fname, data):
+#     fname = os.path.join(UPLOAD_ROOT, fname)
+#     # Write data
+#     with open(fname, "w") as f:
+#         json.dump(data, f, indent=4)
+
+class RequestId(Resource):
+    def post(self):
+        # TODO Authentication
+        # Generate entry for the database
+        new_id = generate_id()
+        print(f"Generated new ID: {new_id}")
+
+        return {"id": new_id}
+
+
+api.add_resource(RequestId, "/request_id")
+
+
+class SubmitAnnotations(Resource):
+    def post(self):
+        data = get_json_data()
+        id = validate_id_from_data(data)
+        for annotation in data["annotations"]:
+            add_annotation(id=id,
+                           annotation_name=annotation["annotation_name"],
+                           annotation_path=annotation["annotation_path"],
+                           image=annotation["image"])
+
+
+api.add_resource(SubmitAnnotations, "/submit_annotations")
+
+
+class SetupTraining(Resource):
+    def post(self):
+        # Data is received as JSON
+        json_data = get_json_data()
+        # Validate ID
+        id = validate_id_from_data(json_data)
+
+        # Get all fields we need
+        data_for_id = get_data_for_id(id)
+        annotations = json_data.get("annotations", None)
+        if annotations is None:
+            raise RuntimeError(f"Missing field: \"annotations\"")
+
+        brain = json_data.get("brain", None)
+        if brain is None:
+            raise RuntimeError(f"Missing field: \"brain\"")
+
+        # Create a file and upload it to the atlas controller
+        event_content = {
+            "id": id,
+            "brain": brain,
+            "annotations": annotations,
+        }
+        # Send to server
+        response = requests.post(ATLAS_CONTROLLER_SETUP_TRAINING_ENDPOINT, json=event_content)
+        return response.json()
+
+
+api.add_resource(SetupTraining, "/setup_training")
+
+if __name__ == "__main__":
+    app.run(debug=True)
diff --git a/atlas_server/atlas_server/wsgi_app.py b/atlas_server/atlas_server/wsgi_app.py
index 64c52ba3491640ae43f9837a6cb28031a69e2232..15caddf10fcd176c16edb7779304452fc0690ac1 100644
--- a/atlas_server/atlas_server/wsgi_app.py
+++ b/atlas_server/atlas_server/wsgi_app.py
@@ -1,3 +1,5 @@
 #!/usr/bin/env python3
 import sys
-sys.path.insert(0, "/home/cschiffer/code/atlasui_prototype/atlas_server")
+sys.path.insert(0, "/home/cschiffer/code/atlasui/atlas_server")
+# noinspection PyUnresolvedReferences
+from atlas_server.app import app as application
\ No newline at end of file
diff --git a/config/httpd/httpd.conf b/config/httpd/httpd.conf
index 5e9370514f69c65b35e41e4e5f68e64e0bcd4007..3bbf2aa10f6b87951d27e984f8de5dd25847c2f1 100644
--- a/config/httpd/httpd.conf
+++ b/config/httpd/httpd.conf
@@ -545,7 +545,7 @@ SSLRandomSeed connect builtin
 
 AddHandler php7-script php
 
-WSGIScriptAlias /atlas_server /srv/http/atlas_server/wsgi_app.py
+WSGIScriptAlias /atlas_server /srv/http/atlas_server/atlas_server/wsgi_app.py
 WSGIPythonPath /srv/http/atlas_server/:/home/cschiffer/miniconda3/envs/atlas_ui/lib/python3.7/site-packages
 
 WSGIScriptReloading On
diff --git a/microdraw/atlas_ui.js b/microdraw/atlas_ui.js
index 455ff5118c07972375de661dd90ba23b8e58ce6a..3e156dd113b5f2fcb175a9d52ae2c36455119d2e 100644
--- a/microdraw/atlas_ui.js
+++ b/microdraw/atlas_ui.js
@@ -56,9 +56,6 @@ function removeRegionFromList(region, list) {
 }
 
 var app = new Vue({
-    components: {
-        ContextMenu,
-    },
     el: '#atlas_app',
     data: {
         brain: 20,
diff --git a/microdraw/lib/mylogin/login.js b/microdraw/lib/mylogin/login.js
index 08562ca84f3c0e611ef676fde3019f9de894e864..888d918a5d7dbae21d538b870c0ef9b4c7472949 100755
--- a/microdraw/lib/mylogin/login.js
+++ b/microdraw/lib/mylogin/login.js
@@ -71,6 +71,7 @@ var MyLoginWidget = {
 	},
 	sendLogin: function() {
 		var	me=this;
+		console.log($("#password").val())
 		$.get(root+"login.php",{"action":"login","username":$("#username").val(),"password":$("#password").val()},function(data){
 			try {
 				var msg=JSON.parse(data);