commit 67bc482155f9cd3fa058ac605ee2ada9a4ad3f81 Author: QTech Servers Date: Thu Oct 3 22:05:46 2024 -0400 Fuck diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1702e2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,191 @@ +# Created by https://www.toptal.com/developers/gitignore/api/vim,flask +# Edit at https://www.toptal.com/developers/gitignore?templates=vim,flask + +### Flask ### +instance/* +!instance/.gitignore +.webassets-cache +.env + +### Flask.Python Stack ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +# End of https://www.toptal.com/developers/gitignore/api/vim,flask diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..48b13f2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM alpine:latest + +RUN apk add python3 py3-pip + +WORKDIR /app + +ADD requirements.txt . + +RUN python3 -m pip install -r requirements.txt + +COPY . /app + +ENTRYPOINT [ "python3" ] + +CMD [ "main.py" ] diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..f5a51a9 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,24 @@ +from flask import Flask +# from flask_login import LoginManager +from flask_sqlalchemy import SQLAlchemy + +UPLOAD_FOLDER = 'app/static/uploads/' + +app = Flask(__name__) +app.secret_key = 'secret key' +app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER +app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////db/db.sqlite3' +# app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://user:password@sql/ece3553' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + +# login_manager = LoginManager(app) + +db = SQLAlchemy(app) + +from app import views +from app import admin_views +from app import tables + +with app.app_context(): + db.create_all() diff --git a/app/admin_views.py b/app/admin_views.py new file mode 100644 index 0000000..53bc38f --- /dev/null +++ b/app/admin_views.py @@ -0,0 +1,9 @@ +from functools import wraps +from app import app, db +from app.tables import Swipe, User +from flask import request, redirect, url_for, render_template, session, abort +from sqlalchemy import desc +# from flask_login import login_user, login_required, logout_user, current_user + + + diff --git a/app/static/css/main.css b/app/static/css/main.css new file mode 100644 index 0000000..efb6399 --- /dev/null +++ b/app/static/css/main.css @@ -0,0 +1,70 @@ +body { + background-color: #242424; + color: #fff; +} + +header { +} + +nav { + display: inline-block; + background-color: #141414; + width: 100%; + padding: 5px; +} + +nav ul { + list-style-type: none; + padding: 0; + margin: 0; +} + +nav ul li { + float: left; + margin: 0px 5px; +} + +nav ul li a { + display: block; + color: white; + background-color: #888; + text-decoration: none; + padding: 15px 24px; +} + +nav ul li a:hover { + background-color: #005c75; +} + +.nav-cur { + background-color: #008cff; + transform: translate(0px, 10px); +} + +.nav-cur:hover { + background-color: #00bcff; +} + +.content { + display: block; + min-height: 200px; + position: relative; +} + +.center { + position: absolute; + left: 50%; + transform: translate(-50%); +} + +.hcenter { + text-align: center; +} + +.hvcenter { + margin: 0; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} diff --git a/app/static/js/kiosk.js b/app/static/js/kiosk.js new file mode 100644 index 0000000..f6bf771 --- /dev/null +++ b/app/static/js/kiosk.js @@ -0,0 +1,22 @@ +function update() { + fetch('https://time.qtechofficial.com/kiosk/data') + .then(response => response.json()) + .then(data => { + let tbody = document.getElementById('tbody'); + tbody.innerHTML = ""; + console.log(data); + data.forEach(person => { + let tr = document.createElement('tr'); + tbody.appendChild(tr); + let td_name = document.createElement('td'); + td_name.innerHTML = person.first_name + " " + person.last_name; + tr.appendChild(td_name); + let td_time_here = document.createElement('td'); + td_time_here.textContent = person.time_here; + tr.appendChild(td_time_here); + }) + }) + .catch(error => console.error(error)) +} + +setInterval(update, 1000); diff --git a/app/tables.py b/app/tables.py new file mode 100644 index 0000000..002704d --- /dev/null +++ b/app/tables.py @@ -0,0 +1,21 @@ +from datetime import datetime +from app import db + + +class Swipe(db.Model): + __tablename__ = 'swipes' + id = db.Column('id', db.Integer, primary_key=True) + time = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + uuid = db.Column(db.String(100)) + + def __init__(self, uuid): + self.uuid = uuid + + +class User(db.Model): + __tablename__ = 'users' + id = db.Column('id', db.Integer, primary_key=True) + uuid = db.Column(db.String(100)) + first_name = db.Column(db.String(100)) + last_name = db.Column(db.String(100)) + role = db.Column(db.String(100)) diff --git a/app/templates/fuckyou.html b/app/templates/fuckyou.html new file mode 100644 index 0000000..dd2e670 --- /dev/null +++ b/app/templates/fuckyou.html @@ -0,0 +1,46 @@ + + + + + + + Go Fuck Yourself + + +
+
+
+
+

Go Fuck Yourself

+

Consider Eating A Dick

+
+
+
+ +
+

+

Why you should go fuck yourself

+

+

+

hello and welcome to my presentation to why you should go fuck yourself.

+

Spoiler Alert, it's because you are a piece of shit that no one likes. But why are you shit? were you raised this way or, unfortunate circumstances to blame?, No, your just terrible. Still confused? of course you are, here's a pie chart that should straighten things up, here's a quick visual aid and i think this next slide really drives the point home, fuck you go fuck yourself.

+

Now this graph is, really interesting. As you can see it shows the number of people that want you to go fuck yourself over time. Of note, this isn't 99% percent of the population. This isn't even 99.999% of the population. This is the 100% of the population, that's right, even you want you to go fuck yourself so what are you doing here? Go on go do it now, You may be asking, does all this hatred represent a weakness on my own part?

+

Could it be that nobody deserves to hear the words "go fuck yourself" because every single human being on the planet is a worthwhile and a valuable member of society? No!, go fuck yourself.

+

In conclusion, i want you to know that i don't really mean anything i'm saying here. I think you're a good person and ultimately i don't want you to go fuck yourself. Just kidding haha, go fuck yourself.

+

Thank you.

+

+
+
+ + diff --git a/app/templates/kiosk.html b/app/templates/kiosk.html new file mode 100644 index 0000000..12aa862 --- /dev/null +++ b/app/templates/kiosk.html @@ -0,0 +1,16 @@ + + + + + + + + Kiosk + + + + + +
+ + diff --git a/app/templates/users.html b/app/templates/users.html new file mode 100644 index 0000000..f804bbf --- /dev/null +++ b/app/templates/users.html @@ -0,0 +1,29 @@ + + + + + + + Users + + +
+ {% for user in users %} + + {{ user.id }}, {{ user.uuid }}, {{ user.first_name | safe }}, {{ user.last_name | safe }}, {{ user.role }} +
+ {% endfor %} + +
+
+
+ + + +
+
+ + diff --git a/app/views.py b/app/views.py new file mode 100644 index 0000000..1cc7d43 --- /dev/null +++ b/app/views.py @@ -0,0 +1,240 @@ +from app import app, db +from app.tables import Swipe, User +from flask import request, redirect, url_for, render_template +from sqlalchemy import desc +from pytz import timezone +from datetime import datetime +from datetime import date +import json +# from flask_login import login_user, login_required, logout_user + +@app.route('/') +def index(): + return 'No.' + +''' +@app.route('/auth', methods=['POST']) +def auth(): + data = request.get_json(force=True) + + print(data) + if 'uuid' in data: + print(data['uuid']) + uuid = AllowedUUIDs.query.filter_by(uuid=data['uuid']).first() + print(uuid) + if uuid: + print('login') + login_user(uuid) + return '' +''' + +@app.route('/users', methods=['GET']) +def users(): + users = db.session.query(User) + return render_template('users.html', users=users) + +@app.route('/users/modify', methods=['POST']) +def users_modify(): + data = request.form + if data['action'] == 'set_name': + if data['first_name'] == '' or data['last_name'] == '': + return 'Fill out the form...' + if data['uuid'] == '0000005222': + return 'No.' + + user = User.query.filter_by(uuid=data['uuid']).first() + user.first_name = data['first_name'] + user.last_name = data['last_name'] + db.session.commit() + elif data['action'] == 'set_uid': + if data['uuid'] == '': + return 'Fill out the form...' + if data['uuid'] == '0000005222': + return 'No.' + + user = User.query.filter_by(uuid=data['uuid']).first() + user.uuid = data['new_uuid'] + db.session.commit() + elif data['action'] == 'delete': + if data['uuid'] == '0000005222': + return 'No.' + user = User.query.filter_by(uuid=data['uuid']).first() + db.session.delete(user) + db.session.commit() + elif data['action'] == 'new_user': + if data['new_uuid'] == '0000005222': + return 'No.' + user = User() + user.uuid = data['new_uuid'] + user.first_name = data['first_name'] + user.last_name = data['last_name'] + user.role = data['role'] + db.session.add(user) + db.session.commit() + + + return redirect(url_for('users'), code=302) + +@app.route('/swipe', methods=['POST']) +def swipe(): + data = request.get_json(force=True) + uuid = data.get('uuid') + if not uuid: + return 'NACK' + + print(uuid) + + swipe = Swipe(uuid) + db.session.add(swipe) + db.session.commit() + + return 'OK' + +@app.route('/log', methods=['GET']) +def log(): + swipes = db.session.query(Swipe) + result = '' + for swipe in swipes: + names = User.query.filter_by(uuid=swipe.uuid).first() + if names: + result += f'{swipe.time} | {names.first_name} | {names.last_name}
' + else: + result += f'{swipe.time} | WHO DIS {swipe.uuid}
' + return result + +@app.route('/kiosk', methods=['GET']) +def kiosk(): + return render_template('kiosk.html') + +@app.route('/kiosk/data', methods=['GET']) +def kiosk_data(): + swipes = db.session.query(Swipe).filter(Swipe.time >= '2023-07-16') + + counts = {} + last_swipes = {} + for swipe in swipes: + if swipe.uuid not in counts: + counts[swipe.uuid] = 0 + counts[swipe.uuid] += 1 + last_swipes[swipe.uuid] = swipe + + res = [] + for uuid, count in counts.items(): + # User already signed out + if count % 2 == 0: + continue + + swipe = last_swipes[uuid] + + dt = swipe.time + tz = timezone('EST') + est = dt.astimezone(tz) + + user = db.session.query(User).filter(User.uuid == swipe.uuid).first() + + time_here = datetime.now() - dt + time_here_str = f'{time_here.seconds//3600}:{(time_here.seconds//60)%60:02d}' + + rec = { + 'first_name': user.first_name, + 'last_name': user.last_name, + 'time_here': time_here_str + } + res.append(rec) + return json.dumps(res) + +@app.route('/report', methods=['GET', 'POST']) +def report(): + after_date = '2024-08-22' + if request.method == 'POST': + after_date = request.form['after_date'] + days = {} + swipes = db.session.query(Swipe).filter(Swipe.time >= after_date) + for swipe in swipes: + dt = swipe.time + tz = timezone('EST') + est = dt.astimezone(tz) + date = est.date() + + if date not in days: + days[date] = {} + name_dict = days[date] + + if swipe.uuid not in name_dict: + name_dict[swipe.uuid] = [] + name_list = name_dict[swipe.uuid] + + name_list.append(swipe.time) + + + + result = f'
' + result += '' + + name_query = db.session.query(User) + names = [] + result += '' + for name_obj in name_query: + name = f'{name_obj.first_name} {name_obj.last_name}' + names.append(name) + result += f'' + result += '' + + + totals = {} + last_weekday = 0 + for date, name_dict in days.items(): + day = date.isoformat() + column = {} + for uuid, times in name_dict.items(): + names_query = User.query.filter_by(uuid=uuid).first() + if names_query: + name = f'{names_query.first_name} {names_query.last_name}' + else: + name = f'Unknown: {uuid}' + + # result += f'{swipe.time} | WHO DIS {swipe.uuid}
' + # result += f'{day} {uuid}: {times}
' + if len(times) % 2 == 1: + # result += f'{day} | {name} uneven scans
' + column[name] = 'X' + continue + total_hours = 0.0 + for i in range(len(times) // 2): + in_ = times[i * 2 + 0] + out = times[i * 2 + 1] + dt = out - in_ + total_hours += dt.seconds / 60 / 60 + if name not in totals: + totals[name] = 0.0 + totals[name] += total_hours + # result += f'{day} | {name}: {total_hours:0.2f}
' + column[name] = f'{total_hours:0.2f}' + if date.weekday() < last_weekday: + result += f'' + last_weekday = date.weekday() + + result += f'' + for name in names: + if name in column: + result += f'' + else: + result += '' + result += '' + + result += '' + for name_obj in name_query: + name = f'{name_obj.first_name} {name_obj.last_name}' + if name in totals: + total = totals[name] + else: + total = 0.0 + result += f'' + result += '' + result += '
{name}
' + result += '
' + result += '
{day}{column[name]}
Totals{total:0.2f}
' + return result + + + diff --git a/env b/env new file mode 100644 index 0000000..38eca62 --- /dev/null +++ b/env @@ -0,0 +1,2 @@ +export FLASK_APP=main.py +export FLASK_ENV=development diff --git a/main.py b/main.py new file mode 100644 index 0000000..aefff21 --- /dev/null +++ b/main.py @@ -0,0 +1,5 @@ +from app import app + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..72c58cb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Flask +flask-sqlalchemy +flask-login +pymysql +pytz