Browse Source

Add games page and in progress games list

mistress
Daniel Muckerman 4 years ago
parent
commit
e58cbef97a
15 changed files with 480 additions and 11 deletions
  1. +1
    -0
      Dockerfile
  2. +137
    -3
      app.py
  3. +28
    -1
      config/config.yaml
  4. BIN
      config/games_in_progress.db
  5. +6
    -2
      requirements.txt
  6. BIN
      static/favicon.png
  7. BIN
      static/images/gameboy.png
  8. BIN
      static/images/mars.png
  9. BIN
      static/images/minecraft.png
  10. +37
    -0
      static/style.css
  11. +36
    -0
      templates/card_list.j2
  12. +150
    -0
      templates/games.j2
  13. +20
    -5
      templates/index.j2
  14. +37
    -0
      templates/login.j2
  15. +28
    -0
      templates/new_game_modal.j2

+ 1
- 0
Dockerfile View File

@ -4,6 +4,7 @@ LABEL maintainer="Dan Muckerman "
WORKDIR /project
ADD . /project
RUN rm /project/.envrc
RUN rm -rf /project/env
RUN pip install -r requirements.txt

+ 137
- 3
app.py View File

@ -1,14 +1,33 @@
import ldap as l
from ldap3 import Server, Connection, ALL, MODIFY_REPLACE
from flask import Flask, g, request, session, redirect, url_for, render_template
from flask_bootstrap import Bootstrap
from flask_bs4 import Bootstrap
from flask_simpleldap import LDAP
import yaml
import datetime as dt
import pytz
import os
import sqlite3
app = Flask(__name__)
Bootstrap(app)
app.secret_key = 'asdf'
app.debug = True
# Base
app.config['LDAP_REALM_NAME'] = 'OpenLDAP Authentication'
app.config['LDAP_HOST'] = os.environ.get('LDAP_HOST')
app.config['LDAP_BASE_DN'] = os.environ.get('LDAP_BASE_DN')
app.config['LDAP_USERNAME'] = os.environ.get('LDAP_USERNAME')
app.config['LDAP_PASSWORD'] = os.environ.get('LDAP_PASSWORD')
# OpenLDAP
app.config['LDAP_OBJECTS_DN'] = 'dn'
app.config['LDAP_OPENLDAP'] = True
app.config['LDAP_USER_OBJECT_FILTER'] = '(&(objectclass=posixAccount)(uid=%s))'
ldap = LDAP(app)
eastern = pytz.timezone('US/Eastern')
with open('config/config.yaml') as f:
@ -18,24 +37,40 @@ search = yaml_data['search']
account_url = yaml_data['accounts']['account_url']
description = yaml_data['description']
game_description = yaml_data['game_description']
countdown_data = None
if yaml_data['countdown']['active'] == True:
countdown_data = yaml_data['countdown']
print(countdown_data)
final_countdown_data = None
final_time = None
if yaml_data['final_countdown']['active'] == True:
final_countdown_data = yaml_data['final_countdown']
final_time = eastern.localize(dt.datetime.strptime(final_countdown_data['timestamp'], '%B %d %Y %H:%M:%S%z').replace(tzinfo=None))
print(final_countdown_data)
apps = []
for itm in yaml_data['apps'].items():
apps.append(itm[1])
games = []
for itm in yaml_data['games'].items():
games.append(itm[1])
server = Server(app.config['LDAP_HOST'])
conn = Connection(server, app.config['LDAP_USERNAME'], app.config['LDAP_PASSWORD'], auto_bind=True)
@app.before_request
def before_request():
g.user = None
if 'user_id' in session:
# This is where you'd query your database to get the user info.
g.user = {}
@app.route('/')
def index():
current_time = eastern.localize(dt.datetime.now())
@ -47,5 +82,104 @@ def index():
return render_template('index.j2', apps = apps, search = search, account_url = account_url, description = description)
@app.route('/games')
def game():
if 'user_id' in session:
user_dict = ldap.get_object_details(session['user_id'])
user = {'dn': 'cn={},cn=usergroup,ou=users,dc=technicalincompetence,dc=club'.format(user_dict['cn'][0].decode('ascii')),
'firstName': user_dict['givenName'][0].decode('ascii'),
'lastName': user_dict['sn'][0].decode('ascii'),
'email': user_dict['mail'][0].decode('ascii'),
'userName': user_dict['uid'][0].decode('ascii'),
}
current_time = eastern.localize(dt.datetime.now())
if final_countdown_data != None:
if (final_time - current_time).days > -1:
return render_template('final_countdown.j2', final_countdown = final_countdown_data)
if countdown_data != None:
return render_template('games.j2', apps = games, search = search, account_url = account_url, description = game_description, countdown = countdown_data)
if 'user_id' in session:
return render_template('games.j2', apps = games, search = search, account_url = account_url, description = game_description, user = user, game_list = generate_game_list())
return render_template('games.j2', apps = games, search = search, account_url = account_url, description = game_description)
@app.route('/login', methods=['GET', 'POST'])
def login():
if g.user:
return redirect(url_for('index'))
if request.method == 'POST':
user = request.form['user']
passwd = request.form['passwd']
test = ldap.bind_user(user, passwd)
if test is None or passwd == '':
return render_template('login.j2', error='Invalid credentials')
else:
session['user_id'] = request.form['user']
session['passwd'] = request.form['passwd']
return redirect('/games')
return render_template('login.j2')
@ldap.login_required
@app.route('/add', methods=['POST'])
def add_game():
if request.method == 'POST':
game_title = request.form['game_title']
game_link = request.form['game_link']
conn = sqlite3.connect('config/games_in_progress.db')
c = conn.cursor()
if game_title is not None and len(game_title) > 0 and game_link is not None and len(game_link) > 0:
c.execute("INSERT INTO games (user_id, game_title, game_link) VALUES (?, ?, ?)", (session['user_id'], game_title, game_link,))
conn.commit()
conn.close()
return 'Success'
conn.commit()
conn.close()
return 'Error'
@ldap.login_required
@app.route('/delete', methods=['POST'])
def delete_game():
if request.method == 'POST':
game_id = request.form['game_id']
conn = sqlite3.connect('config/games_in_progress.db')
c = conn.cursor()
if game_id is not None and len(game_id) > 0:
c.execute("DELETE FROM games WHERE id=? AND user_id=?", (game_id, session['user_id'],))
conn.commit()
conn.close()
return 'Success'
conn.commit()
conn.close()
return 'Error'
def generate_game_list():
conn = sqlite3.connect('config/games_in_progress.db')
c = conn.cursor()
if 'user_id' in session:
c.execute('SELECT * FROM games WHERE user_id=?', (session['user_id'], ))
rows = c.fetchall()
conn.close()
return rows
conn.close()
return []
@app.route('/logout')
def logout():
session.pop('user_id', None)
return redirect(url_for('game'))
if __name__ == '__main__':
app.run(extra_files="config/config.yaml")

+ 28
- 1
config/config.yaml View File

@ -6,6 +6,7 @@ accounts:
account_url: 'https://account.technicalincompetence.club'
description: "It turns out Technical Incompetence's amazingness is just too great for this lame internet - we need a new one to maximize our full potential. Welcome to the Technical Incompetence internet. Population - US."
game_description: "We're all gamers at heart, so welcome to the Valhalla of self hosted gaming. You name it, we have it. Unless we don't. We're gamers, not wizards."
countdown:
active: False
@ -22,6 +23,11 @@ apps:
description: "<br>The moment you're all been waiting for... Zulip, our chat lord and savior is finally here. Third only to iMessage and AOL - prepare for a superior chatting experience. The move from Slack has never been so sweet. Trust us... you won't be disappointed. There's even a mobile app!"
link: "https://zillow.technicalincompetence.club"
image: "zulip.png"
rocketchat:
name: "Rocketchat"
description: "<br>Stay in touch with your friends, family, and people you've never even met with Technical Incompetence's amazing chat service. Experience the joy's of late-1990s AIM all over again."
link: "https://rocketchat.technicalincompetence.club"
image: "rocketchat.png"
cloud:
name: "Nextcloud"
description: "<br>What would Technical Incompetence be without the ability to give up ownership of all your data and store it on our servers. Don't worry though, our servers will <sub>probably</sub> never crash or lose data so all you care about is safe with us."
@ -35,7 +41,7 @@ apps:
jellyfin:
name: "Jellyfin"
description: "<br>Angry about the StReAmInG WaRs? Well our Jellyfin instance has you covered. Listen to all your embarassing weeb music, or watch your barely-not-porn weeb shows and movies. We're not judging you."
link: "http://octotep.com:8096"
link: "https://jellyfin.octotep.com"
image: "jellyfin.png"
rss:
name: "FreshRSS"
@ -47,4 +53,25 @@ apps:
description: "<br>Have you ever felt self conscious about the links you send to people? We feel you. That's why we're proud to introduce our brand new link shortening service! For all those rickrolls you're still sending in current year."
link: "https://links.technicalincompetence.club"
image: "links.png"
wiki:
name: "Bookstack"
description: "<br>We did it, we finally got a wiki. All of our history is held here, both recorded and not recorded history, the travels of the drewniverse, and a full explanation of the drewian calendar."
link: "https://wiki.technicalincompetence.club"
image: "wiki.png"
games:
gameboy:
name: "NewGamePlus"
description: "<br>Too many games, not enough time to play them? Don't worry, we're also slowly sinking into the existential tar pit that is realizing the things you once loved to do fit less and less into your adult life even though they are the very things that hold back the tide of weapons grade ennui that threatens to consume your every waking moment in a boiling pot that will eventually one day bubble over into an anomic explosion of grief, pain, and depression. So that's why we made it easy to keep track of your backlog, checkout NewGamePlus today!"
link: "https://technicalincompetence.games"
image: "gameboy.png"
terraformingmars:
name: "Terraforming Mars"
description: "<br>Experience the glory of \"cooperative\" gameplay through corporate simulation!"
link: "https://terraforming-mars.flamwenco.com"
image: "mars.png"
minecraft:
name: "Minecraft"
description: "<br>Remember playing Minecraft in high school? Or that one month on PS4? Or that one Bedrock server we used for a month?<br><br>Me neither, but join us in Java Edition at: <strong>mc.flamwenco.com</strong>"
link: "#"
image: "minecraft.png"

BIN
config/games_in_progress.db View File


+ 6
- 2
requirements.txt View File

@ -1,4 +1,8 @@
Flask==1.1.2
Flask-Bootstrap4==4.0.2
Flask-BS4==4.5.2.0
PyYAML==5.3.1
pytz
pytz
Flask-Login==0.5.0
Flask-SimpleLDAP==1.4.0
python-ldap==3.2.0
ldap3==2.7

BIN
static/favicon.png View File

Before After
Width: 32  |  Height: 32  |  Size: 715 B

BIN
static/images/gameboy.png View File

Before After
Width: 512  |  Height: 512  |  Size: 153 KiB

BIN
static/images/mars.png View File

Before After
Width: 512  |  Height: 512  |  Size: 398 KiB

BIN
static/images/minecraft.png View File

Before After
Width: 512  |  Height: 512  |  Size: 78 KiB

+ 37
- 0
static/style.css View File

@ -13,6 +13,24 @@
background-color: #f8f9fa!important;
}
#game-table {
border-radius: .25rem !important;
border-collapse: separate !important;
border-spacing: 0 !important;
border: 1px solid rgba(0,0,0,.125) !important;
}
#game-table thead {
color: #495057;
background-color: #e9ecef;
border-color: #dee2e6;
}
.game-link {
color: #000;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #111 !important;
@ -79,6 +97,25 @@
color: white !important;
}
#game-table {
border-radius: 5px !important;
border-collapse: separate !important;
border-spacing: 0 !important;
background-color: #000 !important;
border: 1px solid #6c757d !important;
color: #fff !important;
}
#game-table thead {
color: #fff;
background-color: #343a40;
border-color: #454d55;
}
.game-link {
color: #fff;
}
@media (prefers-reduced-motion: reduce) {
.form-control {
transition: none;

+ 36
- 0
templates/card_list.j2 View File

@ -0,0 +1,36 @@
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 mb-12">
<div class="card" id="game-list">
<div class="card-body" style="padding-bottom: 0 !important;">
<h5 class="card-title" style="margin-bottom: 1.25rem !important;">Games In Progress</h5>
{% if user %}
<table class="table table-borderless" id="game-table">
<thead>
<tr>
<th style="vertical-align: middle;">Game</th>
<th style="width: 50px; text-align: center; font-weight: normal;"><a href="#game-table" class="text-primary" data-toggle="modal" data-target="#modalNewGame">Add</a></th>
</tr>
</thead>
<tbody>
{% if game_list is defined and account_url != [] %}
{% for game in game_list %}
<tr>
<th><a href="{{ game[3] }}" class="game-link">{{ game[2] }}</a></th>
<td style="vertical-align: middle;"><a href="#game-table" onclick="deleteGame({{ game[0] }})" class="text-danger">Delete</a></td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
<div style="text-align: center;">
<a href="/logout" class="text-warning">Log out</a>
<br><br>
</div>
{% else %}
<div style="text-align: center;">
<a href="/login" class="text-success">Log in</a>
<br><br>
</div>
{% endif %}
</div>
</div>
</div>

+ 150
- 0
templates/games.j2 View File

@ -0,0 +1,150 @@
{% extends "bootstrap/base.html" %}
{% block title %}Technical Incompetence - Home{% endblock %}
{% block styles %}
{{super()}}
<link rel="icon" type="image/png" href="{{url_for('.static', filename='favicon.png')}}" />
<link rel="stylesheet" href="{{url_for('.static', filename='style.css')}}">
{% if countdown is defined %}
<link rel="stylesheet" href="{{url_for('.static', filename='clock.css')}}">
{% endif %}
{% endblock %}
{% block navbar %}
<nav class="navbar navbar-expand-lg sticky-top navbar-dark bg-dark">
<div class="navbar-brand">Technical Incompetence</div>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown"
aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="#">Games <span class="sr-only">(current)</span></a>
</li>
</ul>
<form class="form-inline ml-auto">
{% if account_url is defined and account_url != '' %}
<a class="btn btn-primary" href="{{ account_url }}" role="button">My Account</a>
{% endif %}
</form>
</div>
</nav>
{% endblock %}
{% block content %}
<div class="container" style="margin-top: 15px">
{% if search['active'] == True %}
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 col-xl-12">
<input type="text" class="form-control" id="search-box" placeholder="Search">
</div>
<br><br>
{% endif %}
{% if countdown is defined %}
{% include "countdown.j2" %}
{% endif %}
<p>{{ description }}</p>
<div class="row">
{% for app in apps %}
{% include "card.j2" %}
{% endfor %}
</div>
<div class="row">
{% include "card_list.j2" %}
</div>
<br>
</div>
{% include "new_game_modal.j2" %}
{% endblock %}
{% block scripts %}
{{ super () }}
{% if countdown is defined %}
<script>
const deadline = '{{ countdown['timestamp'] }}';
const tripName = '{{ countdown['name'] }}';
</script>
<script src="{{url_for('.static', filename='clock.js')}}"></script>
{% endif %}
<script>
$("#search-box").keyup(function(event) {
if (event.keyCode === 13) {
window.location = '{{ search['search_url'] }}' + $('#search-box').val();
}
});
$( ".card" ).hover(
function() {
if (!$(this).attr('id')) {
$(this).addClass('shadow-lg').css('cursor', 'pointer');
$(this).addClass('card-hover');
}
}, function() {
$(this).removeClass('shadow-lg');
$(this).removeClass('card-hover');
});
function goToLink(link) {
window.location = link;
}
function addGame() {
game_title = $('#game-title').val();
game_link = $('#game-link').val();
if (game_title.trim().length === 0) {
showError('Title is required');
return false;
}
if (game_link.trim().length === 0) {
showError('Link is required');
return false;
}
$.ajax({
url: '/add',
method: 'POST',
data: { "game_title": game_title,
"game_link": game_link },
success: function(data) {
if (data !== 'Error') {
$('#game-title').val('');
$('#game-link').val('');
window.location.reload();
} else {
showError('Something went wrong!');
}
}
});
}
function deleteGame(game_id) {
$.ajax({
url: '/delete',
method: 'POST',
data: { "game_id": game_id },
success: function(data) {
if (data !== 'Error') {
window.location.reload();
} else {
showError('Something went wrong!');
}
}
});
}
function showError(error) {
hideSuccess();
$('#error-alert').text(error);
$('#error-alert').show();
}
function hideError(error) {
$('#error-alert').hide();
}
</script>
{% endblock %}

+ 20
- 5
templates/index.j2 View File

@ -3,6 +3,7 @@
{% block styles %}
{{super()}}
<link rel="icon" type="image/png" href="{{url_for('.static', filename='favicon.png')}}" />
<link rel="stylesheet" href="{{url_for('.static', filename='style.css')}}">
{% if countdown is defined %}
<link rel="stylesheet" href="{{url_for('.static', filename='clock.css')}}">
@ -12,11 +13,25 @@
{% block navbar %}
<nav class="navbar navbar-expand-lg sticky-top navbar-dark bg-dark">
<div class="navbar-brand">Technical Incompetence</div>
<form class="form-inline ml-auto">
{% if account_url is defined and account_url != '' %}
<a class="btn btn-primary" href="{{ account_url }}" role="button">My Account</a>
{% endif %}
</form>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown"
aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/games">Games</a>
</li>
</ul>
<form class="form-inline ml-auto">
{% if account_url is defined and account_url != '' %}
<a class="btn btn-primary" href="{{ account_url }}" role="button">My Account</a>
{% endif %}
</form>
</div>
</nav>
{% endblock %}

+ 37
- 0
templates/login.j2 View File

@ -0,0 +1,37 @@
{% extends "bootstrap/base.html" %}
{% block title %}Manage your Technical Incompetence account{% endblock %}
{% block styles %}
{{super()}}
<link rel="stylesheet" href="{{url_for('.static', filename='style.css')}}">
{% endblock %}
{% block navbar %}
<nav class="navbar navbar-expand-lg sticky-top navbar-dark bg-dark">
<div class="navbar-brand">Technical Incompetence Account</div>
</nav>
{% endblock %}
{% block content %}
<div class="container" style="margin-top: 15px">
<div class="jumbotron">
<h1>Sign in for our awesome service</h1>
<p>Forgot your password? Too bad! We don't have emails working yet!</p>
</div>
<div class="col-md-12">
{% if error is defined %}
<div id="error-alert" class="alert alert-danger" role="alert">
{{ error }}
</div>
{% endif %}
<form action="" method="post">
<label for="exampleFormControlInput1">Username</label>
<input name="user" class="form-control"><br>
<label for="exampleFormControlInput2">Password</label>
<input type="password" name="passwd" class="form-control"><br>
<button type="submit" class="btn btn-primary">Log In</button>
</form>
</div>
{% endblock %}

+ 28
- 0
templates/new_game_modal.j2 View File

@ -0,0 +1,28 @@
<div class="modal fade" id="modalNewGame" tabindex="-1" role="dialog" aria-labelledby="modalNewGameTitle"
aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add New Game</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<label>Game Title</label>
<input type="text" class="form-control" placeholder="Game Title" aria-label="Game title"
aria-describedby="basic-addon2" id="game-title" name="game-title">
</div>
<div class="form-group">
<label>Game Link</label>
<input type="text" class="form-control" placeholder="Game Link" aria-label="Game link"
aria-describedby="basic-addon2" id="game-link" name="game-link">
</div>
</form>
<button class="btn btn-primary" href="#" role="button" onclick="addGame();">Save</button>
</div>
</div>
</div>
</div>

Loading…
Cancel
Save