summaryrefslogtreecommitdiff
path: root/django/factwise-python/factwise_submission/plannerapp
diff options
context:
space:
mode:
Diffstat (limited to 'django/factwise-python/factwise_submission/plannerapp')
-rw-r--r--django/factwise-python/factwise_submission/plannerapp/__init__.py0
-rw-r--r--django/factwise-python/factwise_submission/plannerapp/admin.py3
-rw-r--r--django/factwise-python/factwise_submission/plannerapp/apps.py6
-rw-r--r--django/factwise-python/factwise_submission/plannerapp/base/project_board_base.py105
-rw-r--r--django/factwise-python/factwise_submission/plannerapp/base/team_base.py133
-rw-r--r--django/factwise-python/factwise_submission/plannerapp/base/user_base.py94
-rw-r--r--django/factwise-python/factwise_submission/plannerapp/exceptions.py9
-rw-r--r--django/factwise-python/factwise_submission/plannerapp/migrations/__init__.py0
-rw-r--r--django/factwise-python/factwise_submission/plannerapp/models.py3
-rw-r--r--django/factwise-python/factwise_submission/plannerapp/services.py88
-rw-r--r--django/factwise-python/factwise_submission/plannerapp/storage.py36
-rw-r--r--django/factwise-python/factwise_submission/plannerapp/tests.py3
-rw-r--r--django/factwise-python/factwise_submission/plannerapp/utils.py9
-rw-r--r--django/factwise-python/factwise_submission/plannerapp/views.py3
14 files changed, 492 insertions, 0 deletions
diff --git a/django/factwise-python/factwise_submission/plannerapp/__init__.py b/django/factwise-python/factwise_submission/plannerapp/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/django/factwise-python/factwise_submission/plannerapp/__init__.py
diff --git a/django/factwise-python/factwise_submission/plannerapp/admin.py b/django/factwise-python/factwise_submission/plannerapp/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/django/factwise-python/factwise_submission/plannerapp/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/django/factwise-python/factwise_submission/plannerapp/apps.py b/django/factwise-python/factwise_submission/plannerapp/apps.py
new file mode 100644
index 0000000..2eaa95f
--- /dev/null
+++ b/django/factwise-python/factwise_submission/plannerapp/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class PlannerappConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'plannerapp'
diff --git a/django/factwise-python/factwise_submission/plannerapp/base/project_board_base.py b/django/factwise-python/factwise_submission/plannerapp/base/project_board_base.py
new file mode 100644
index 0000000..71262f3
--- /dev/null
+++ b/django/factwise-python/factwise_submission/plannerapp/base/project_board_base.py
@@ -0,0 +1,105 @@
+class ProjectBoardBase:
+ """
+ A project board is a unit of delivery for a project. Each board will have a set of tasks assigned to a user.
+ """
+
+ # create a board
+ def create_board(self, request: str):
+ """
+ :param request: A json string with the board details.
+ {
+ "name" : "<board_name>",
+ "description" : "<description>",
+ "team_id" : "<team id>"
+ "creation_time" : "<date:time when board was created>"
+ }
+ :return: A json string with the response {"id" : "<board_id>"}
+
+ Constraint:
+ * board name must be unique for a team
+ * board name can be max 64 characters
+ * description can be max 128 characters
+ """
+ pass
+
+ # close a board
+ def close_board(self, request: str) -> str:
+ """
+ :param request: A json string with the user details
+ {
+ "id" : "<board_id>"
+ }
+
+ :return:
+
+ Constraint:
+ * Set the board status to CLOSED and record the end_time date:time
+ * You can only close boards with all tasks marked as COMPLETE
+ """
+ pass
+
+ # add task to board
+ def add_task(self, request: str) -> str:
+ """
+ :param request: A json string with the task details. Task is assigned to a user_id who works on the task
+ {
+ "title" : "<board_name>",
+ "description" : "<description>",
+ "user_id" : "<team id>"
+ "creation_time" : "<date:time when task was created>"
+ }
+ :return: A json string with the response {"id" : "<task_id>"}
+
+ Constraint:
+ * task title must be unique for a board
+ * title name can be max 64 characters
+ * description can be max 128 characters
+
+ Constraints:
+ * Can only add task to an OPEN board
+ """
+ pass
+
+ # update the status of a task
+ def update_task_status(self, request: str):
+ """
+ :param request: A json string with the user details
+ {
+ "id" : "<task_id>",
+ "status" : "OPEN | IN_PROGRESS | COMPLETE"
+ }
+ """
+ pass
+
+ # list all open boards for a team
+ def list_boards(self, request: str) -> str:
+ """
+ :param request: A json string with the team identifier
+ {
+ "id" : "<team_id>"
+ }
+
+ :return:
+ [
+ {
+ "id" : "<board_id>",
+ "name" : "<board_name>"
+ }
+ ]
+ """
+ pass
+
+ def export_board(self, request: str) -> str:
+ """
+ Export a board in the out folder. The output will be a txt file.
+ We want you to be creative. Output a presentable view of the board and its tasks with the available data.
+ :param request:
+ {
+ "id" : "<board_id>"
+ }
+ :return:
+ {
+ "out_file" : "<name of the file created>"
+ }
+ """
+ pass
diff --git a/django/factwise-python/factwise_submission/plannerapp/base/team_base.py b/django/factwise-python/factwise_submission/plannerapp/base/team_base.py
new file mode 100644
index 0000000..29b1a5d
--- /dev/null
+++ b/django/factwise-python/factwise_submission/plannerapp/base/team_base.py
@@ -0,0 +1,133 @@
+class TeamBase:
+ """
+ Base interface implementation for API's to manage teams.
+ For simplicity a single team manages a single project. And there is a separate team per project.
+ Users can be
+ """
+
+ # create a team
+ def create_team(self, request: str) -> str:
+ """
+ :param request: A json string with the team details
+ {
+ "name" : "<team_name>",
+ "description" : "<some description>",
+ "admin": "<id of a user>"
+ }
+ :return: A json string with the response {"id" : "<team_id>"}
+
+ Constraint:
+ * Team name must be unique
+ * Name can be max 64 characters
+ * Description can be max 128 characters
+ """
+ pass
+
+ # list all teams
+ def list_teams(self) -> str:
+ """
+ :return: A json list with the response.
+ [
+ {
+ "name" : "<team_name>",
+ "description" : "<some description>",
+ "creation_time" : "<some date:time format>",
+ "admin": "<id of a user>"
+ }
+ ]
+ """
+ pass
+
+ # describe team
+ def describe_team(self, request: str) -> str:
+ """
+ :param request: A json string with the team details
+ {
+ "id" : "<team_id>"
+ }
+
+ :return: A json string with the response
+
+ {
+ "name" : "<team_name>",
+ "description" : "<some description>",
+ "creation_time" : "<some date:time format>",
+ "admin": "<id of a user>"
+ }
+
+ """
+ pass
+
+ # update team
+ def update_team(self, request: str) -> str:
+ """
+ :param request: A json string with the team details
+ {
+ "id" : "<team_id>",
+ "team" : {
+ "name" : "<team_name>",
+ "description" : "<team_description>",
+ "admin": "<id of a user>"
+ }
+ }
+
+ :return:
+
+ Constraint:
+ * Team name must be unique
+ * Name can be max 64 characters
+ * Description can be max 128 characters
+ """
+ pass
+
+ # add users to team
+ def add_users_to_team(self, request: str):
+ """
+ :param request: A json string with the team details
+ {
+ "id" : "<team_id>",
+ "users" : ["user_id 1", "user_id2"]
+ }
+
+ :return:
+
+ Constraint:
+ * Cap the max users that can be added to 50
+ """
+ pass
+
+ # add users to team
+ def remove_users_from_team(self, request: str):
+ """
+ :param request: A json string with the team details
+ {
+ "id" : "<team_id>",
+ "users" : ["user_id 1", "user_id2"]
+ }
+
+ :return:
+
+ Constraint:
+ * Cap the max users that can be added to 50
+ """
+ pass
+
+ # list users of a team
+ def list_team_users(self, request: str):
+ """
+ :param request: A json string with the team identifier
+ {
+ "id" : "<team_id>"
+ }
+
+ :return:
+ [
+ {
+ "id" : "<user_id>",
+ "name" : "<user_name>",
+ "display_name" : "<display name>"
+ }
+ ]
+ """
+ pass
+
diff --git a/django/factwise-python/factwise_submission/plannerapp/base/user_base.py b/django/factwise-python/factwise_submission/plannerapp/base/user_base.py
new file mode 100644
index 0000000..ec4dbc7
--- /dev/null
+++ b/django/factwise-python/factwise_submission/plannerapp/base/user_base.py
@@ -0,0 +1,94 @@
+class UserBase:
+ """
+ Base interface implementation for API's to manage users.
+ """
+
+ # create a user
+ def create_user(self, request: str) -> str:
+ """
+ :param request: A json string with the user details
+ {
+ "name" : "<user_name>",
+ "display_name" : "<display name>"
+ }
+ :return: A json string with the response {"id" : "<user_id>"}
+
+ Constraint:
+ * user name must be unique
+ * name can be max 64 characters
+ * display name can be max 64 characters
+ """
+ pass
+
+ # list all users
+ def list_users(self) -> str:
+ """
+ :return: A json list with the response
+ [
+ {
+ "name" : "<user_name>",
+ "display_name" : "<display name>",
+ "creation_time" : "<some date:time format>"
+ }
+ ]
+ """
+ pass
+
+ # describe user
+ def describe_user(self, request: str) -> str:
+ """
+ :param request: A json string with the user details
+ {
+ "id" : "<user_id>"
+ }
+
+ :return: A json string with the response
+
+ {
+ "name" : "<user_name>",
+ "description" : "<some description>",
+ "creation_time" : "<some date:time format>"
+ }
+
+ """
+ pass
+
+ # update user
+ def update_user(self, request: str) -> str:
+ """
+ :param request: A json string with the user details
+ {
+ "id" : "<user_id>",
+ "user" : {
+ "name" : "<user_name>",
+ "display_name" : "<display name>"
+ }
+ }
+
+ :return:
+
+ Constraint:
+ * user name cannot be updated
+ * name can be max 64 characters
+ * display name can be max 128 characters
+ """
+ pass
+
+ def get_user_teams(self, request: str) -> str:
+ """
+ :param request:
+ {
+ "id" : "<user_id>"
+ }
+
+ :return: A json list with the response.
+ [
+ {
+ "name" : "<team_name>",
+ "description" : "<some description>",
+ "creation_time" : "<some date:time format>"
+ }
+ ]
+ """
+ pass
+
diff --git a/django/factwise-python/factwise_submission/plannerapp/exceptions.py b/django/factwise-python/factwise_submission/plannerapp/exceptions.py
new file mode 100644
index 0000000..7be61ca
--- /dev/null
+++ b/django/factwise-python/factwise_submission/plannerapp/exceptions.py
@@ -0,0 +1,9 @@
+class ValidationError(ValueError):
+ pass
+
+class NotFoundError(KeyError):
+ pass
+
+class ConflictError(Exception):
+ pass
+
diff --git a/django/factwise-python/factwise_submission/plannerapp/migrations/__init__.py b/django/factwise-python/factwise_submission/plannerapp/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/django/factwise-python/factwise_submission/plannerapp/migrations/__init__.py
diff --git a/django/factwise-python/factwise_submission/plannerapp/models.py b/django/factwise-python/factwise_submission/plannerapp/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/django/factwise-python/factwise_submission/plannerapp/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/django/factwise-python/factwise_submission/plannerapp/services.py b/django/factwise-python/factwise_submission/plannerapp/services.py
new file mode 100644
index 0000000..fb8dbdf
--- /dev/null
+++ b/django/factwise-python/factwise_submission/plannerapp/services.py
@@ -0,0 +1,88 @@
+# planner/services.py
+import json
+from .base import user_base, team_base, project_board_base
+from .storage import FileStorage
+from .exceptions import ValidationError, NotFoundError, ConflictError
+from .utils import now_iso, new_id
+
+MAX_NAME = 64
+MAX_DISPLAY = 128
+
+USERS = FileStorage("users.json")
+TEAMS = FileStorage("teams.json")
+
+
+class UserService(user_base):
+ def create_user(self, request: str) -> str:
+ payload = json.loads(request)
+ name = payload.get("name", "").strip()
+ display_name = payload.get("display_name", "").strip()
+
+ if not name:
+ raise ValidationError("User name required")
+ if len(name) > MAX_NAME:
+ raise ValidationError("User name too long")
+ if len(display_name) > MAX_NAME:
+ raise ValidationError("Display name too long")
+
+ users = USERS.read_all()
+ if any(u["name"] == name for u in users):
+ raise ConflictError("User name must be unique")
+
+ user = {
+ "id": new_id(),
+ "name": name,
+ "display_name": display_name,
+ "creation_time": now_iso()
+ }
+ USERS.append(user)
+ return json.dumps({"id": user["id"]})
+
+ def list_users(self) -> str:
+ users = USERS.read_all()
+ return json.dumps([{
+ "name": u["name"],
+ "display_name": u["display_name"],
+ "creation_time": u["creation_time"]
+ } for u in users])
+
+ def describe_user(self, request: str) -> str:
+ user_id = json.loads(request)["id"]
+ users = USERS.read_all()
+ for u in users:
+ if u["id"] == user_id:
+ return json.dumps(u)
+ raise NotFoundError("User not found")
+
+ def update_user(self, request: str) -> str:
+ payload = json.loads(request)
+ user_id = payload["id"]
+ updates = payload.get("user", {})
+
+ if "name" in updates:
+ raise ValidationError("User name cannot be updated")
+ if "display_name" in updates and len(updates["display_name"]) > MAX_DISPLAY:
+ raise ValidationError("Display name too long")
+
+ users = USERS.read_all()
+ for i, u in enumerate(users):
+ if u["id"] == user_id:
+ u.update(updates)
+ users[i] = u
+ USERS.replace(users)
+ return json.dumps({})
+ raise NotFoundError("User not found")
+
+ def get_user_teams(self, request: str) -> str:
+ user_id = json.loads(request)["id"]
+ teams = TEAMS.read_all()
+ result = []
+ for t in teams:
+ if user_id == t.get("admin") or user_id in t.get("users", []):
+ result.append({
+ "name": t["name"],
+ "description": t.get("description"),
+ "creation_time": t.get("creation_time")
+ })
+ return json.dumps(result)
+
diff --git a/django/factwise-python/factwise_submission/plannerapp/storage.py b/django/factwise-python/factwise_submission/plannerapp/storage.py
new file mode 100644
index 0000000..324e0c0
--- /dev/null
+++ b/django/factwise-python/factwise_submission/plannerapp/storage.py
@@ -0,0 +1,36 @@
+import json
+import os
+from filelock import FileLock
+
+DB_DIR = os.path.join(os.path.dirname(__file__), '..', 'db')
+os.makedirs(DB_DIR, exist_ok=True)
+
+class FileStorage:
+ def __init__(self, filename):
+ self.path = os.path.join(DB_DIR, filename)
+ self.lock = FileLock(self.path + ".lock")
+
+ def _ensure(self):
+ if not os.path.exists(self.path):
+ with open(self.path, "w") as f:
+ json.dump([], f)
+
+ def read_all(self):
+ self._ensure()
+ with self.lock:
+ with open(self.path, "r") as f:
+ return json.load(f)
+
+ def write_all(self, data):
+ with self.lock:
+ with open(self.path, "w") as f:
+ json.dump(data, f, indent=2)
+
+ def append(self, item):
+ data = self.read_all()
+ data.append(item)
+ self.write_all(data)
+
+ def replace(self, data):
+ self.write_all(data)
+
diff --git a/django/factwise-python/factwise_submission/plannerapp/tests.py b/django/factwise-python/factwise_submission/plannerapp/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/django/factwise-python/factwise_submission/plannerapp/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/django/factwise-python/factwise_submission/plannerapp/utils.py b/django/factwise-python/factwise_submission/plannerapp/utils.py
new file mode 100644
index 0000000..af1d1cf
--- /dev/null
+++ b/django/factwise-python/factwise_submission/plannerapp/utils.py
@@ -0,0 +1,9 @@
+import uuid
+from datetime import datetime
+
+def now_iso():
+ return datetime.utcnow().isoformat() + "Z"
+
+def new_id():
+ return str(uuid.uuid4())[:8]
+
diff --git a/django/factwise-python/factwise_submission/plannerapp/views.py b/django/factwise-python/factwise_submission/plannerapp/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/django/factwise-python/factwise_submission/plannerapp/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.