Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,8 @@ dmypy.json
.pyre/


*.csv
*.csv
*.swo
*.swp
aaron-*
*-hwm.json
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM docker.io/python:3.11.8

RUN mkdir /website

WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir -r requirements-frozen.txt
RUN pip install gunicorn
ENV PYTHONUNBUFFERED=TRUE
CMD ["gunicorn", "--enable-stdio-inheritance", "-w", "2", "-b", "unix:/website/hackspace_mgmt.sock", "--error-logfile", "-", "--access-logfile", "-", "--capture-output", "hackspace_mgmt:create_app()"]
6 changes: 6 additions & 0 deletions container-build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

CONTAINER_CMD=$(which podman || which docker)

$CONTAINER_CMD build -t localhost/hackspace-mgmt:latest -f Dockerfile

5 changes: 5 additions & 0 deletions container-login.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

CONTAINER_CMD=$(which podman || which docker)

ssh -T registry.bristolhackspace.org 'hs-registry-token hackspace-mgmt' | $CONTAINER_CMD login --username oauth2 --password-stdin registry.bristolhackspace.org
8 changes: 8 additions & 0 deletions container-push.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

CONTAINER_CMD=$(which podman || which docker)

$CONTAINER_CMD tag localhost/hackspace-mgmt:latest registry.bristolhackspace.org/hackspace-mgmt:latest

$CONTAINER_CMD push registry.bristolhackspace.org/hackspace-mgmt:latest

15 changes: 13 additions & 2 deletions hackspace_mgmt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,26 @@
from flask import Flask
from flask_assets import Environment, Bundle

from .limiter import limiter

def create_app(test_config=None):
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY="dev",
SQLALCHEMY_DATABASE_URI="postgresql+psycopg2://postgres:postgres@localhost:5432/hackspace",
SQLALCHEMY_ENGINE_OPTIONS={
'pool_size': 5,
'pool_recycle' : 60,
'pool_pre_ping' : True
},
STORAGE_LOGIN_SECRET="dev",
STORAGE_APP_URL="http://example.com"
)

app.config.from_prefixed_env('MGMT')

limiter.init_app(app)

if test_config is None:
app.config.from_pyfile("config.py", silent=True)
else:
Expand Down Expand Up @@ -46,5 +58,4 @@ def create_app(test_config=None):
app.register_blueprint(label_api.bp)



return app
return app
5 changes: 3 additions & 2 deletions hackspace_mgmt/admin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from flask_admin import Admin

from . import machine, induction, firmware_update, card, bulk_card, member, label, quiz, audit
from . import machine, induction, firmware_update, card, bulk_card, member, label, quiz, audit, tag

admin = Admin(None, 'Hackspace Management Admin', template_mode='bootstrap4', endpoint="admin", url="/admin")

Expand All @@ -12,4 +12,5 @@
member.create_views(admin)
label.create_views(admin)
quiz.create_views(admin)
audit.create_views(admin)
audit.create_views(admin)
tag.create_views(admin)
4 changes: 2 additions & 2 deletions hackspace_mgmt/admin/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

class MachineView(ModelView):
column_searchable_list = ['name']
column_list = ('name', 'controllers', 'requires_in_person', 'valid_for_days', 'hide_from_home', 'quizzes')
column_list = ('name', 'controllers', 'requires_in_person', 'valid_for_days', 'hide_from_home', 'import_enabled', 'import_message', 'quizzes')
inline_models = (MachineController,)
form_excluded_columns = ('inductions',)
column_formatters = dict()


def create_views(admin: Admin):
admin.add_view(MachineView(Machine, db.session, endpoint="machine_view", category="Access Control"))
admin.add_view(MachineView(Machine, db.session, endpoint="machine_view", category="Access Control"))
14 changes: 14 additions & 0 deletions hackspace_mgmt/admin/tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from hackspace_mgmt.models import db, Tag
from flask_admin.model.form import InlineFormAdmin


class TagView(ModelView):
column_searchable_list = ['title']
column_list = ('title',)
column_formatters = dict()


def create_views(admin: Admin):
admin.add_view(TagView(Tag, db.session, endpoint="tag_view", category="Access Control"))
4 changes: 2 additions & 2 deletions hackspace_mgmt/general/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def enroll_personal():
@login_required
def storage_login():
login_secret = current_app.config["STORAGE_LOGIN_SECRET"]
name = g.member.display_name
name = str(g.member)
sub = f"member_{g.member.id}"
email = g.member.email

Expand All @@ -238,4 +238,4 @@ def init_app(app):
app.register_blueprint(quiz.bp)
app.register_blueprint(label.bp)
app.register_blueprint(profile.bp)
app.register_blueprint(induction.bp)
app.register_blueprint(induction.bp)
73 changes: 71 additions & 2 deletions hackspace_mgmt/general/induction.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
from flask import Blueprint, render_template, g
from flask import Blueprint, flash, redirect, render_template, url_for, request, g
from flask_wtf import FlaskForm
from wtforms import fields, widgets, ValidationError, validators
from datetime import datetime, timezone

import logging

from hackspace_mgmt.models import db, Machine, Induction, LegacyMachineAuth, Member
from hackspace_mgmt.general.helpers import login_required
from hackspace_mgmt.audit import create_audit_log

from sqlalchemy.dialects.postgresql import insert

bp = Blueprint("induction", __name__)

logger = logging.Logger(__name__)

from .. import limiter

@bp.route("/induction")
@login_required
def index():
Expand Down Expand Up @@ -39,4 +48,64 @@ def machine(machine_id):
expired_quizes=expired_quizes,
induction=induction,
LegacyMachineAuth=LegacyMachineAuth
)
)

@bp.route("/induction/<int:machine_id>/import", methods=["POST", "GET"])
@login_required
@limiter.limit("5 per minute")
def induction_import(machine_id):
machine = db.get_or_404(Machine, machine_id)

member: Member = g.member

class ImportForm(FlaskForm):
submit_label = "Import"
secret = fields.PasswordField(str(machine.legacy_auth).title(),[validators.Length(max=Machine.legacy_password.type.length)])

import_form = ImportForm(request.form);

secret_error = ''
now=datetime.now(timezone.utc)

if import_form.validate_on_submit():

if machine.legacy_password == import_form.secret.data:
#Add Induction
secret_error = "adding induction"

if not machine.is_member_inducted(member) :
insert_stmt = insert(Induction).values(
member_id=member.id,
machine_id=machine.id,
inducted_on=now
)

db.session.execute(insert_stmt)

create_audit_log(
"induction",
"import",
data = {
"machine": {
"id": machine.id,
"name": machine.name
},
"inductee": member.id
},
member=member
)

db.session.commit()

flash("Induction imported")
return redirect(url_for("induction.machine", machine_id=machine_id))

else:
secret_error = "Secret Error, check capitalisation or spaces."

return render_template(
"machine_induction_import.html",
secret_error=secret_error,
machine=machine,
import_form=import_form
);
2 changes: 1 addition & 1 deletion hackspace_mgmt/general/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,4 @@ def create():
"expiry": label.expiry.strftime("%d %b %Y"),
"caption": label.caption
}
}
}
2 changes: 1 addition & 1 deletion hackspace_mgmt/general/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ def index():
return redirect(url_for(".index"))


return render_template("profile.html", profile_form=profile_form, return_url=url_for("general.index"))
return render_template("profile.html", profile_form=profile_form, return_url=url_for("general.index"))
92 changes: 92 additions & 0 deletions hackspace_mgmt/high_water_mark_and_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import os
import json

from sqlalchemy import create_engine, text, select, DateTime
from sqlalchemy.orm import Session
from urllib.request import urlopen, Request
from urllib.error import HTTPError, URLError
from models import Member
from datetime import date, datetime, timedelta, timezone
from time import sleep

SQLALCHEMY_DATABASE_URI=os.environ['SQLALCHEMY_DATABASE_URI']
BHS_SYNC_URL=os.environ['BHS_SYNC_URL']
BHS_SYNC_TOKEN=os.environ['BHS_SYNC_TOKEN']

engine = create_engine(SQLALCHEMY_DATABASE_URI, isolation_level="AUTOCOMMIT")
conn = engine.connect()

with open("member-hwm.json") as hwm_data:
hwm = json.loads(hwm_data.read())
hwm_data.close()

print(hwm)

with Session(engine) as session:
latest = datetime.fromisoformat(hwm['latest'])
latest_next = latest;
stmt = select(Member).where(Member.updated > latest)
for user in session.scalars(stmt):

if not hwm.get('initialised') and user.end_date:
continue

if user.preferred_name :
display_name = user.preferred_name
else :
display_name = user.first_name

if user.last_name :
display_name = display_name + ' ' + user.last_name

body = {
"email" : user.email,
"display_name" : display_name,
"updated" : user.updated.isoformat(),
"join_date" : str(user.join_date),
"end_date" : str(user.end_date)
}

body_json = json.dumps(body).encode("utf-8")

request = Request(
BHS_SYNC_URL + str(user.id),
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + BHS_SYNC_TOKEN
},
data = body_json,
method = "PUT"
)

try:
urlopen(request, timeout=5)

except HTTPError as error:
print(error.status, error.reason)
break;

except URLError as error:
print(error, error.reason)
break;

except TimeoutError:
print("Request timeout")
break;

latest_next = user.updated

sleep(0.1)

hwm_next = {
"initialised" : True,
"now" : datetime.now().isoformat(),
"latest" : latest_next.isoformat()
}

if not hwm_next :
exit(1)

with open("member-hwm.json", "w") as f:
json.dump(hwm_next, f)

7 changes: 7 additions & 0 deletions hackspace_mgmt/limiter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

limiter = Limiter(
get_remote_address,
storage_uri="memory://"
)
Loading