Commit c11cccd6 authored by Grégor JOUET's avatar Grégor JOUET 🔧
Browse files

messages & format

parent 9ab2b00e
Loading
Loading
Loading
Loading
+29 −29
Original line number Diff line number Diff line
@@ -4,12 +4,12 @@ Fablab 3d printing request application

## Minimum Viable Version

- [ ] *Tests*
- [ ] _Tests_
- [x] User Connection
    - [ ] User administration
        - [ ] Backend
        - [ ] List users,
        - [ ] Change access level
  - [x] User administration
    - [x] Backend
    - [x] List users,
    - [x] Change access level
- [x] Submit a print request
  - [x] Upload a STL file
    - [x] Backend
+205 −122
Original line number Diff line number Diff line
import myfab.log as log
from flask import Flask, request
from flask_jwt_extended import (
    JWTManager, jwt_required, create_refresh_token, get_raw_jwt,
    current_user
    JWTManager,
    jwt_required,
    create_refresh_token,
    get_raw_jwt,
    current_user,
)

import datetime
from myfab.model import Event, PrintRequest, User, init_db_connection, Queue, QueueElement
from myfab.model import (
    Event,
    PrintRequest,
    User,
    init_db_connection,
    Queue,
    QueueElement,
)
from flask_api import status
from flask import jsonify
import string
@@ -20,17 +30,21 @@ log.info("Database connected")
app = Flask(__name__)
CORS(app)
app.debug = True
app.config['SECRET_KEY'] = 'super-secret' #! CHANGEME
app.config['JWT_IDENTITY_CLAIM'] = "login_name"
app.config["SECRET_KEY"] = "super-secret"  #! CHANGEME
app.config["JWT_IDENTITY_CLAIM"] = "login_name"

FILE_SAVE_PATH = "../ui/static/files"

jwt = JWTManager(app)


@jwt.user_loader_callback_loader
def identity(payload):
    payload = get_raw_jwt()
    return get_or_create_user(payload['login_name'], payload['firstname'] + ' '+ payload['lastname'])
    return get_or_create_user(
        payload["login_name"], payload["firstname"] + " " + payload["lastname"]
    )


### ENUMS ###

@@ -60,13 +74,14 @@ REQUEST_USER_ELEMENTS = set(["title","description","project"])
# region UTILS
def get_or_create_user(username: str, fullname: str = None):
    req = User.select().where(User.username == username)
    if(req.count()):
    if req.count():
        return req.get()
    else:
        if not fullname:
            raise Exception("fullname cannot be None")
        return User.create(username=username, fullname=fullname)


def lookup_requests(author: str = None, status=None, req_id=None):
    where_req = []
    if author:
@@ -84,28 +99,38 @@ def lookup_requests(author: str = None, status = None, req_id = None):
    if req_id:
        where_req.append(PrintRequest.id == req_id)

    req = PrintRequest.select().join(User, on=(PrintRequest.author == User.username)).limit(30) # .join(Queue, on=(PrintRequest.queue == Queue.id))
    req = (
        PrintRequest.select()
        .join(User, on=(PrintRequest.author == User.username))
        .limit(30)
    )  # .join(Queue, on=(PrintRequest.queue == Queue.id))
    if len(where_req) > 0:
        req = req.where(*where_req)

    req = req.order_by(PrintRequest.last_modification.desc())
    return req


def get_request_by_id(rid):
    req = PrintRequest.select().where(PrintRequest.id == rid)
    return req.get() if req.count() else None


def add_request_event(req: PrintRequest, desc: str, op: User = None):
    log.info("Req %s: %s" % (req.id, desc))
    req.last_modification = datetime.datetime.now()
    req.save()
    Event.create(request=req, description=desc, operator=op)


def extract_date_time(elem):
    if type(elem) == tuple:
        elem = elem[0]
    # elem = datetime.datetime.fromisoformat(repr(elem))
    return f'{elem.day}/{elem.month}/{elem.year} {elem.hour}:{elem.minute}:{elem.second}'
    return (
        f"{elem.day}/{elem.month}/{elem.year} {elem.hour}:{elem.minute}:{elem.second}"
    )


def loop_requests(reqs):
    rqs = []
@@ -114,7 +139,8 @@ def loop_requests(reqs):
    for req in reqs:
        if get_access_level_to_request(req, current_user) == REQUEST_ACCESS_DENIED:
            continue
        rqs.append({
        rqs.append(
            {
                "request_id": req.id,
                "title": req.title,
                "creation": extract_date_time(req.creation),
@@ -128,10 +154,12 @@ def loop_requests(reqs):
                "gcode": req.gcode,
                "operator": req.operator.username if req.operator is not None else "",
                "queue": req.queue.name if req.queue is not None else "",
            "recup": req.recup_id
        })
                "recup": req.recup_id,
            }
        )
    return rqs


def get_access_level_to_request(req: PrintRequest, user):
    # req is id or req same for user
    # TODO
@@ -152,28 +180,39 @@ def get_access_level_to_request(req: PrintRequest, user):
        else:
            return REQUEST_ACCESS_DENIED  # user does not have access


def update_requires_operator_rights(update_json):
    s = set(update_json)
    s.discard(REQUEST_USER_ELEMENTS)
    return len(s) > 0


# endregion UTILS

# region routes

# region Project management & basic routes


@app.route("/whoami")
@jwt_required
def whoami():
    return jsonify({'username': current_user.username, 'fullname': current_user.fullname, 'access': current_user.access})
    return jsonify(
        {
            "username": current_user.username,
            "fullname": current_user.fullname,
            "access": current_user.access,
        }
    )


@app.route("/requests/my")
@jwt_required
def get_my_requests():
    return jsonify(loop_requests(lookup_requests(current_user.username)))

@app.route("/requests/<req_id>", methods=['PUT'])

@app.route("/requests/<req_id>", methods=["PUT"])
@jwt_required
def update_request(req_id):
    # Author can update request initial parameters when status is 0
@@ -183,14 +222,18 @@ def update_request(req_id):
    req: PrintRequest = get_request_by_id(req_id)
    changes = request.get_json()
    if not changes:
        return jsonify({'error': 'No modifications'}), status.HTTP_400_BAD_REQUEST
        return jsonify({"error": "No modifications"}), status.HTTP_400_BAD_REQUEST
    if req is None:
        return jsonify({'error': 'No such request'}), status.HTTP_404_NOT_FOUND
        return jsonify({"error": "No such request"}), status.HTTP_404_NOT_FOUND

    required_access = REQUEST_ACCESS_OPERATOR if req.status > 0 or update_requires_operator_rights(changes) else REQUEST_ACCESS_USER
    required_access = (
        REQUEST_ACCESS_OPERATOR
        if req.status > 0 or update_requires_operator_rights(changes)
        else REQUEST_ACCESS_USER
    )

    if get_access_level_to_request(req, current_user) < required_access:
        return jsonify({'error': 'Access denied'}), status.HTTP_401_UNAUTHORIZED
        return jsonify({"error": "Access denied"}), status.HTTP_401_UNAUTHORIZED

    for e in changes:
        setattr(req, e, changes[e])
@@ -198,46 +241,59 @@ def update_request(req_id):
    req.save()
    return jsonify({})

@app.route("/requests/new", methods=['POST'])

@app.route("/requests/new", methods=["POST"])
@jwt_required
def add_new_request():
    data = request.get_json()
    if not 'title' in data or data['title'] == "":
        return jsonify({'error': 'Title field required'}), status.HTTP_404_NOT_FOUND
    if not 'description' in data or data['description'] == "":
        return jsonify({'error': 'Description field is required'}), status.HTTP_404_NOT_FOUND
    if not 'project' in data or data['project'] == "":
        return jsonify({'error': 'Project field is required'}), status.HTTP_404_NOT_FOUND
        
    req = PrintRequest.create(title = data['title'],
                             description = data['description'],
    if not "title" in data or data["title"] == "":
        return jsonify({"error": "Title field required"}), status.HTTP_404_NOT_FOUND
    if not "description" in data or data["description"] == "":
        return (
            jsonify({"error": "Description field is required"}),
            status.HTTP_404_NOT_FOUND,
        )
    if not "project" in data or data["project"] == "":
        return (
            jsonify({"error": "Project field is required"}),
            status.HTTP_404_NOT_FOUND,
        )

    req = PrintRequest.create(
        title=data["title"],
        description=data["description"],
        author=current_user,
                             project = data['project'])
    log.info("Created print request '%s' by user %s (%s)"%(req.title, current_user.username, current_user.fullname))
        project=data["project"],
    )
    log.info(
        "Created print request '%s' by user %s (%s)"
        % (req.title, current_user.username, current_user.fullname)
    )
    add_request_event(req, "Request created")

    return jsonify({
        "request_id": req.id
    })
    return jsonify({"request_id": req.id})


@app.route("/files/<kind>/upload/<req_id>", methods=['POST'])
@app.route("/files/<kind>/upload/<req_id>", methods=["POST"])
@jwt_required
def stl_upload(kind, req_id):
    if not kind in ['stl', 'gcode']:
        return jsonify({'error': 'Invalid file kind'}, status.HTTP_400_BAD_REQUEST)
    if not kind in ["stl", "gcode"]:
        return jsonify({"error": "Invalid file kind"}, status.HTTP_400_BAD_REQUEST)

    req: PrintRequest = get_request_by_id(int(req_id))
    if req == None:
        return jsonify({'error': 'No such print request'}, status.HTTP_404_NOT_FOUND)
        return jsonify({"error": "No such print request"}, status.HTTP_404_NOT_FOUND)
    required_access = REQUEST_ACCESS_USER if kind == "stl" else REQUEST_ACCESS_OPERATOR

    if get_access_level_to_request(req_id, current_user) < required_access:
        return jsonify({'error': 'Access denied'}, status.HTTP_401_UNAUTHORIZED)
    file = request.files['file']
    if file.filename == '':
        return jsonify({'error': 'No file'}, status.HTTP_400_BAD_REQUEST)
        return jsonify({"error": "Access denied"}, status.HTTP_401_UNAUTHORIZED)
    file = request.files["file"]
    if file.filename == "":
        return jsonify({"error": "No file"}, status.HTTP_400_BAD_REQUEST)

    name = ''.join([random.choice(string.ascii_letters + string.digits) for n in range(64)])
    name = "".join(
        [random.choice(string.ascii_letters + string.digits) for n in range(64)]
    )

    if kind == "stl":
        req.stl = name
@@ -245,28 +301,33 @@ def stl_upload(kind, req_id):
        req.gcode = name
        req.status = REQUEST_STATUS_AWAITING_PRINT
    else:
        return jsonify({'error': "Invalid file kind"}, status.HTTP_400_BAD_REQUEST)
        return jsonify({"error": "Invalid file kind"}, status.HTTP_400_BAD_REQUEST)

    file.save(os.path.join(FILE_SAVE_PATH, name))
    req.save()
    add_request_event(req, f'Added {kind} file')
    add_request_event(req, f"Added {kind} file")

    return jsonify({"ok": True})

    return jsonify({'ok': True})

@app.route("/requests/<req_id>")
@jwt_required
def get_request_infos(req_id):
    return jsonify(loop_requests(lookup_requests(req_id=req_id))) # ! FIXME Don't get request if no access
    return jsonify(
        loop_requests(lookup_requests(req_id=req_id))
    )  # ! FIXME Don't get request if no access

@app.route("/requests/list", methods=['POST'])

@app.route("/requests/list", methods=["POST"])
@jwt_required
def get_requests():  #! FIXME Don't get request if not access
    params = request.get_json()
    if not 'username' in params:
        params['username'] = None
    if not 'status' in params:
        params['status'] = None
    return jsonify(loop_requests(lookup_requests(params['username'], params['status'])))
    if not "username" in params:
        params["username"] = None
    if not "status" in params:
        params["status"] = None
    return jsonify(loop_requests(lookup_requests(params["username"], params["status"])))


# endregion

@@ -277,23 +338,26 @@ def get_requests(): #! FIXME Don't get request if not access
@jwt_required
def list_users():
    if current_user.access != USER_ACCESS_OPERATOR:
        return jsonify({'error': 'Access denied'}), status.HTTP_401_UNAUTHORIZED
        return jsonify({"error": "Access denied"}), status.HTTP_401_UNAUTHORIZED
    users = []
    for user in User.select():
        users.append({
            'username': user.username,
            'fullname': user.fullname,
            'access': user.access
        })
        users.append(
            {
                "username": user.username,
                "fullname": user.fullname,
                "access": user.access,
            }
        )

    return jsonify(users)


# change user TODO
@app.route("/users/<username>", methods=['PUT'])
@app.route("/users/<username>", methods=["PUT"])
@jwt_required
def update_user(username):
    if current_user.access != USER_ACCESS_OPERATOR:
        return jsonify({'error': 'Access Denied'}), status.HTTP_401_UNAUTHORIZED
        return jsonify({"error": "Access Denied"}), status.HTTP_401_UNAUTHORIZED
    user = get_or_create_user(username)  # FIXME no such user is 500
    changes = request.get_json()

@@ -303,8 +367,25 @@ def update_user(username):
    user.save()
    return jsonify({})


# endregion user management

# region Message system routes

@app.route("/message/<request_id>")
@jwt_required
def get_messages(request_id):
    pass


@app.route("/message/<request_id>", methods=["POST"])
@jwt_required
def post_message(request_id):
    pass


# endregion

# region printer management routes TODO

# list printers & status
@@ -328,6 +409,7 @@ def list_printers():
    """
    pass


# action on printer TODO v2
@app.route("/printers/<printer_id>/action", methods=["POST"])
@jwt_required
@@ -340,12 +422,14 @@ def action_printer(printer_id):
    """
    pass


# Edit printer config
@app.route("/printers/<printer_id>", methods=["PUT"])
@jwt_required
def update_printer(printer_id):
    pass


# Add printer config
@app.route("/printers/new", methods=["POST"])
@jwt_required
@@ -375,6 +459,7 @@ def list_queues():
    """
    pass


# create queue

# delete queue
@@ -415,8 +500,6 @@ POST /slicer/usage
# endregion routes




# peewee usage

# user = User.select().where(User.username == "test").count()
+3 −2
Original line number Diff line number Diff line
from peewee import Model, MySQLDatabase, AutoField, IntegerField, CharField, TextField, ForeignKeyField, DateTimeField, DoubleField
from peewee import Model, MySQLDatabase, AutoField, IntegerField, CharField, TextField, ForeignKeyField, DateTimeField, DoubleField, BooleanField
import datetime
import sys, os
import myfab.log as log
@@ -39,6 +39,7 @@ class User(Model):

class Queue(Model):
    id = AutoField(primary_key=True)
    enabled = BooleanField()
    name = CharField()
    weight = IntegerField(default=1)

@@ -82,7 +83,7 @@ class Printer(Model): # with connection information
    name = CharField()
    model = CharField() 
    config = CharField() # json
    current_print = ForeignKeyField(PrintRequest)
    current_print = ForeignKeyField(PrintRequest, null = True)

class PrinterQueue(Model):
    printer = ForeignKeyField(Printer)
+11 −3
Original line number Diff line number Diff line
@@ -46,9 +46,17 @@ def get_queue_element_pos(queue_element: QueueElement):
#region PRINTER UTILS & class

def get_printers_from_db():
  # for p  in Printer.select():
  #   p.model
  pass
  printers =  []
  for p in Printer.select():
    if p.model == "fake":
      # fake printer
      printers.append(FakePrinter(p))
    elif p.model == "octoprint":
      # octo printer
      printers.append(OctoPrinter(p))
    else:
      raise Exception("Unknown printer model")
  

class PrinterBase: