+++ /dev/null
-repos:
- - repo: https://github.com/psf/black
- rev: 24.4.2
- hooks:
- - id: black
- - repo: https://github.com/pre-commit/mirrors-prettier
- rev: v4.0.0-alpha.8
- hooks:
- - id: prettier
--- /dev/null
+FROM python:3.11-slim
+
+RUN apt-get update && apt-get install -y \
+ build-essential \
+ && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /app/
+COPY . /app/
+
+RUN pip install --no-cache-dir \
+ gunicorn \
+ flask \
+ schoolopy \
+ cachecontrol \
+ google-auth \
+ google_auth_oauthlib
+
+ENV PATH="/app/venv/bin:$PATH"
+
+EXPOSE 80
+
+CMD ["gunicorn", "-b", "0.0.0.0:80", "main:app"]
-DOMAIN = "https://schoology.d214.org"
-GROUP_ID = 6454678062
-REDIRECT_URL = "127.0.0.1:5000"
+import os
+
+DOMAIN = "https://" + os.environ["DOMAIN"]
+
+GROUP_ID = os.environ["GROUP_ID"]
+SCHOOLOGY_API_KEY = os.environ["SCHOOLOGY_API_KEY"]
+SCHOOLOGY_API_SECRET = os.environ["SCHOOLOGY_API_SECRET"]
+++ /dev/null
-{
- "py/object": "database.Data",
- "projects": [
- {
- "py/object": "project.Project",
- "name": "Computer Science Club Website",
- "description": "(this website)",
- "authors": ["Damian Myrda"],
- "source": "https://github.com/moncheeta/computer_science_club.git"
- }
- ]
-}
from models import Project
-import jsonpickle as json
import sqlite3
import pickle
-class Data:
- projects = []
-
- def __init__(self, projects=[]):
- self.projects = projects
-
-
-class JSONDatabase:
+class Database:
def __init__(self):
- self.projects = []
- self.read()
-
- def read(self):
- with open("database.json", "r") as file:
- self.projects = json.decode(file.read()).projects
- return self.projects
-
- def write(self):
- with open("database.json", "w") as file:
- file.write(json.encode(Data(self.projects)))
-
- def add(self, project):
- self.projects.append(project)
- self.write()
-
-
-class SQLDatabase:
- def __init__(self):
- self.connection = sqlite3.connect("database.db", check_same_thread=False)
+ self.connection = sqlite3.connect(
+ "database.db", check_same_thread=False
+ )
self.cursor = self.connection.cursor()
self.cursor.execute(
"""CREATE TABLE IF NOT EXISTS projects (
self.write()
-class ProjectDatabase:
- def __init__(self):
- # self.database = JSONDatabase()
- self.database = SQLDatabase()
-
- def read(self):
- return self.database.read()
-
- def write(self):
- self.database.write()
-
- def add(self, project):
- self.database.add(project)
-
-
-database = ProjectDatabase()
+database = Database()
sys.path.append(os.path.join(PROJECT_DIR, "models"))
sys.path.append(os.path.join(PROJECT_DIR, "projects.py"))
sys.path.append(os.path.join(PROJECT_DIR, "schoology.py"))
-from config import REDIRECT_URL
-from models import Project
+from config import DOMAIN
+from models import Group, Project
from database import database
from schoology import group
from flask import Flask, request, redirect, abort, render_template, session
from cachecontrol import CacheControl
app = Flask(__name__)
-app.secret_key = "https://computer-science-club.moncheeto.repl.co"
+app.secret_key = DOMAIN
@app.route("/")
def index():
- return render_template("home.html", group=group, account=session.get("name"))
+ return render_template(
+ "home.html", group=group, account=session.get("name")
+ )
@app.route("/updates")
def updates():
- return render_template("updates.html", group=group, account=session.get("name"))
+ return render_template(
+ "updates.html", group=group, account=session.get("name")
+ )
@app.route("/discussions")
def discussions():
- return render_template("discussions.html", group=group, account=session.get("name"))
+ return render_template(
+ "discussions.html", group=group, account=session.get("name")
+ )
@app.route("/projects", methods=["GET", "POST"])
@app.route("/members")
def members():
- return render_template("members.html", group=group, account=session.get("name"))
+ return render_template(
+ "members.html", group=group, account=session.get("name")
+ )
import requests
"openid",
]
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
+
flow = Flow.from_client_secrets_file(
client_secrets_file=GOOGLE_CLIENT_SECRETS_FILE,
scopes=GOOGLE_AUTH_SCOPES,
- redirect_uri="http://" + REDIRECT_URL + "/login/callback",
+ redirect_uri=DOMAIN + "/login/callback",
)
credentials = flow.credentials
request_session = requests.session()
cached_session = CacheControl(request_session)
- token_request = google.auth.transport.requests.Request(session=cached_session)
+ token_request = google.auth.transport.requests.Request(
+ session=cached_session
+ )
id_info = id_token.verify_oauth2_token(
- id_token=credentials._id_token, request=token_request, audience=GOOGLE_CLIENT_ID
+ id_token=credentials._id_token,
+ request=token_request,
+ audience=GOOGLE_CLIENT_ID,
)
session["name"] = id_info.get("name")
return redirect("/")
if __name__ == "__main__":
- app.run(host="0.0.0.0")
+ app.run(host="0.0.0.0", port=80)
class Discussion:
- name = ""
- description = ""
- link = f"{DOMAIN}/discussion/"
-
def __init__(self, id, name, description):
- self.link += str(id)
- self.name = name
- self.description = description
-
-
-from datetime import datetime
-from config import DOMAIN
-
-
-class Event:
- name = ""
- description = ""
- start = datetime.now()
- end = None
- differentDay = False
- link = f"{DOMAIN}/event/"
-
- def __init__(self, id, name, description, start, end):
- self.link += str(id)
self.name = name
self.description = description
- self.start = start
- self.end = end
- if (
- end
- and self.end.year >= self.start.year
- and self.end.month >= self.start.month
- and self.end.day > start.day
- ):
- differentDay = True
+ self.link = f"{DOMAIN}/discussion/{str(id)}"
-from datetime import datetime
from config import DOMAIN
class Event:
- name = ""
- description = ""
- start = datetime.now()
- end = None
- differentDay = False
- link = f"{DOMAIN}/event/"
-
- def __init__(self, id, name, description, start, end):
- self.link += str(id)
+ def __init__(self, id, name, description, start, end=None):
self.name = name
self.description = description
+ self.link = f"{DOMAIN}/event/{str(id)}"
self.start = start
self.end = end
- if (
- end
- and self.end.year >= self.start.year
- and self.end.month >= self.start.month
- and self.end.day > start.day
+ if end and (
+ self.end.year != self.start.year
+ or self.end.month != self.start.month
+ or self.end.day != start.day
):
- differentDay = True
+ self.differentDay = True
+ else:
+ self.differentDay = False
class Group:
- name = ""
- description = ""
- picture = "https://www.shutterstock.com/image-vector/computer-science-icon-outline-thin-600nw-1613513884.jpg"
- leaders = []
- members = []
- events = []
- updates = []
- discussions = []
- projects = []
-
- def __init__(
- self, name, description, events, updates, discussions, projects, members
- ):
+ def __init__(self, name, description):
self.name = name
self.description = description
- self.events = events
- self.updates = updates
- self.discussions = discussions
- self.projects = projects
- for member in members:
- if member.leader:
- self.leaders.append(member)
- continue
- self.members.append(member)
+ self.picture = "https://www.shutterstock.com/image-vector/computer-science-icon-outline-thin-600nw-1613513884.jpg"
+
+ self.events = []
+ self.updates = []
+ self.discussions = []
+
+ self.leaders = []
+ self.members = []
class Member:
- name = ""
- leader = False
-
- def __init__(self, name="", leader=False):
+ def __init__(self, name, leader=False):
self.name = name
self.leader = leader
class Project:
- name = ""
- description = ""
- authors = []
- source = None
-
def __init__(self, name, description, authors, source=None):
self.name = name
self.description = description
-from datetime import datetime
-from member import Member
-
-
class Update:
- member = Member()
- text = ""
- time = datetime.now()
-
- def __init__(self, member, text, time):
+ def __init__(self, member, content, created):
self.member = member
- self.text = text
- self.time = time
+ self.content = content
+ self.created = created
+++ /dev/null
-#!/usr/bin/env bash
-
-. ./bin/activate
-flask --app main.py run
-import os
+from config import DOMAIN, GROUP_ID, SCHOOLOGY_API_KEY, SCHOOLOGY_API_SECRET
from datetime import datetime
-from config import DOMAIN, GROUP_ID
from models import Group, Event, Update, Discussion, Member
-from database import database
from schoolopy import Schoology, Auth
+from database import database
+
+
+auth = Auth(
+ SCHOOLOGY_API_KEY,
+ SCHOOLOGY_API_SECRET,
+ domain=DOMAIN,
+)
+api = Schoology(auth)
+api.limit = 64
+
+group = Group(
+ api.get_group(GROUP_ID).title, api.get_group(GROUP_ID).description
+)
+
+
+def get_members():
+ for enrolled in api.get_group_enrollments(GROUP_ID):
+ member = Member(enrolled.name_display)
+ if enrolled.admin == 1:
+ group.leaders.append(member)
+ else:
+ group.members.append(member)
+
+
+def find_member(name):
+ for member in group.members:
+ if member.name == name:
+ return member
+ return None
+
+
+def get_updates():
+ for update in api.get_group_updates(GROUP_ID):
+ user = api.get_user(update.uid)
+ member = find_member(user.name_display)
+ if not member:
+ continue
+ created = datetime.utcfromtimestamp(int(update.created))
+ group.updates.append(Update(member, update.body, created))
+
+
+def get_events():
+ for event in api.get_group_events(GROUP_ID):
+ start = datetime.strptime(event.start, "%Y-%m-%d %H:%M:%S")
+ end = None
+ if event.has_end:
+ end = datetime.strptime(event.end, "%Y-%m-%d %H:%M:%S")
+ event = Event(event.id, event.title, event.description, start, end)
+ group.events.append(event)
+
+
+def get_discussions():
+ for discussion in api.get_group_discussions(GROUP_ID):
+ group.discussions.append(
+ Discussion(discussion.id, discussion.title, discussion.body)
+ )
+
+
+def update():
+ get_members()
+ get_updates()
+ get_events()
+ get_discussions()
+ group.projects = database.read()
-class SchoologyAPI:
- api = None
- auth = Auth(
- os.environ["SCHOOLOGY_API_KEY"],
- os.environ["SCHOOLOGY_API_SECRET"],
- domain=DOMAIN,
- )
-
- def __init__(self):
- self.api = Schoology(self.auth)
- self.api.limit = 64
-
- def name(self):
- return self.api.get_group(GROUP_ID).title
-
- def description(self):
- return self.api.get_group(GROUP_ID).description
-
- def events(self):
- events = []
- for event in self.api.get_group_events(GROUP_ID):
- start = datetime.strptime(event.start, "%Y-%m-%d %H:%M:%S")
- end = None
- if event.has_end:
- end = datetime.strptime(event.end, "%Y-%m-%d %H:%M:%S")
- event = Event(event.id, event.title, event.description, start, end)
- events.append(event)
- return events
-
- def updates(self):
- updates = []
- for update in self.api.get_group_updates(GROUP_ID):
- user = self.api.get_user(update.uid)
- member = Member(user.name_display)
- # time = datetime.utcfromtimestamp(int(update.created))
- time = datetime.utcfromtimestamp(int(update.last_updated))
- updates.append(Update(member, update.body, time))
- return updates
-
- def discussions(self):
- discussions = []
- for discussion in self.api.get_group_discussions(GROUP_ID):
- discussions.append(
- Discussion(discussion.id, discussion.title, discussion.body)
- )
- return discussions
-
- def members(self):
- members = []
- for enrolled in self.api.get_group_enrollments(GROUP_ID):
- member = Member(enrolled.name_display)
- if enrolled.admin == 1:
- member.leader = True
- members.append(member)
- return members
-
- def group(self):
- name = self.name()
- description = self.description()
- events = self.events()
- updates = self.updates()
- discussions = self.discussions()
- projects = database.read()
- members = self.members()
- return Group(name, description, events, updates, discussions, projects, members)
-
-
-api = SchoologyAPI()
-group = api.group()
+update()
* {
color: #ffff82;
- text-align: center;
background-color: black;
+ text-align: center;
+}
+
+html, body {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
}
#board {
* {
color: white;
+ background-color: black;
font-family: monospace;
}
+html, body {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+}
+
body {
- background-color: black;
+ padding: 8px;
}
-input,
-textarea {
+input, textarea {
color: black;
}
+<!DOCTYPE html>
<html>
<head>
- {% import "elements/metadata.html" as metadata %} {{ metadata.head(group,
- "Discussions") }}
+ {% import "elements/metadata.html" as metadata %}
+ {{ metadata.head(group, "Discussions") }}
<link
rel="stylesheet"
- href="{{ url_for('static', filename='style.css') }}"
+ href="{{ url_for('static', filename='main.css') }}"
/>
</head>
- <header>
- {% import "elements/navigation.html" as navigation %} {{
- navigation.bar(account) }}
- </header>
-
<body>
- {% import "elements/space.html" as space %} {{ space.stars(500) }}
+ <header>
+ {% import "elements/navigation.html" as navigation %}
+ {{ navigation.bar(account) }}
+ </header>
+
+ {% import "elements/space.html" as space %}
+ {{ space.stars(500) }}
<h1>Discussions</h1>
- {% import "elements/discussions.html" as discussions %} {{
- discussions.list(group.discussions) }}
+ {% import "elements/discussions.html" as discussions %}
+ {{ discussions.list(group.discussions) }}
</body>
</html>
{% macro view(author) -%}
<a>{{ author }}</a>
-{%- endmacro %} {% macro list(authors) -%}
+{%- endmacro %}
+
+{% macro list(authors) -%}
<div>
- By: {{ view(authors[0]) }}{% for author in authors[1:] %}, {{ author }}{%
- endfor %}
+ By: {{ view(authors[0]) }}{% for author in authors[1:] %}, {{ author }}{% endfor %}
</div>
{%- endmacro %}
-{% macro time(time) -%} {{ time.hour }}:{{ time.minute }} {%- endmacro %} {%
-macro date(date) -%} {{ date.year }}.{{ date.month }}.{{ date.day }} {%-
-endmacro %}
+{% macro time(time) -%}
+{{ time.hour }}:{{ time.minute }}
+{%- endmacro %}
+
+{% macro date(date) -%}
+{{ date.year }}.{{ date.month }}.{{ date.day }}
+{%-endmacro %}
<a target="_blank" href="{{ discussion.link }}">
{{ discussion.name }} {{ discussion.description }}
</a>
-{%- endmacro %} {% macro list(discussions) -%} {% for discussion in discussions
-%}
+{%- endmacro %}
+
+{% macro list(discussions) -%}
+{% for discussion in discussions %}
<div>{{ view(discussion) }}</div>
-{% endfor %} {%- endmacro %}
+{% endfor %}
+{%- endmacro %}
if event.end %} {% if event.differentDay %} to {{ datetime.date(event.end) }}
{% endif %} {% endif %}
</a>
-{%- endmacro %} {% macro next(event) -%}
+{%- endmacro %}
+
+{% macro next(event) -%}
<a target="_blank" href="{{ event.link }}">
- Upcoming: {{ event.name }} at {{ datetime.time(event.start) }} {% if event.end
- %} to {{ datetime.time(event.end) }} {% endif %} on {{
- datetime.date(event.start) }} {% if event.end %} {% if event.differentDay %}
- to {{ datetime.date(event.end) }} {% endif %} {% endif %}
+ Upcoming: {{ event.name }} at {{ datetime.time(event.start) }} {% if event.end %} to {{
+ datetime.time(event.end) }} {% endif %} on {{ datetime.date(event.start) }} {%
+ if event.end %} {% if event.differentDay %} to {{ datetime.date(event.end) }}
+ {% endif %} {% endif %}
</a>
-{%- endmacro %} {% macro upcoming(events) -%} {% for event in events %}
+{%- endmacro %}
+
+{% macro upcoming(events) -%}
+{% for event in events %}
<div>view(event)</div>
-{% endfor %} {%- endmacro %}
+{% endfor %}
+{%- endmacro %}
-{% import "elements/authors.html" as authors %} {% macro view(project) -%}
+{% import "elements/authors.html" as authors %}
+
+{% macro view(project) -%}
<h2>{{ project.name }}</h2>
{% if project.description %}
<p>{{ project.description }}</p>
-{% endif %} {% if project.source %}
+{% endif %}
+{% if project.source %}
<a target="_blank" href="{{ project.source }}">Source Code</a>
<br />
-{% endif %} {{ authors.list(project.authors) }} {% if project.images %} {% for
-image in project.images %}
+{% endif %}
+{{ authors.list(project.authors) }}
+{% if project.images %}
+{% for image in project.images %}
<img src="{{ image }}" />
-{% endfor %} {% endif %} {%- endmacro %} {% macro list(projects) -%} {% for
-project in projects %}
+{% endfor %}
+{% endif %}
+{%- endmacro %}
+
+{% macro list(projects) -%}
+{% for project in projects %}
<div>
{{ view(project) }}
<br />
</div>
-{% endfor %} {%- endmacro %} {% macro new() -%}
+{% endfor %}
+{%- endmacro %}
+
+{% macro new() -%}
<center>
<h1>Add New Project</h1>
<form method="post" enctype="multipart/form-data">
<style>
#star {
position: absolute;
+ background-color: white !important;
width: 1px;
height: 1px;
- background-color: white;
}
</style>
<script>
+ function randomPosition() {
+ var rw = Math.floor(Math.random() * window.innerWidth);
+ var rh = Math.floor(Math.random() * window.innerHeight);
+ return [rw, rh];
+ }
+
for (let i = 0; i < {{ num }}; i++) {
let star = document.createElement("div");
star.id = "star";
star.style.left = w + "px";
document.body.append(star);
}
-
- function randomPosition() {
- var rw = Math.floor(Math.random() * window.innerWidth);
- var rh = Math.floor(Math.random() * window.innerHeight);
- return [rw, rh];
- }
</script>
{%- endmacro %}
-{% import "elements/datetime.html" as datetime %} {% macro view(update) -%}
+{% import "elements/datetime.html" as datetime %}
+
+{% macro recent(update) -%}
<p>
- {{ update.member.name }} on {{ datetime.date(update.time) }} at {{
- datetime.time(update.time) }}: {{ update.text }}
+ Annocement from {{ update.member.name }}: {{ update.content }}
</p>
-{%- endmacro %} {% macro recent(update) -%}
+{%- endmacro %}
+
+{% macro view(update) -%}
<p>
- Annocement from {{ update.member.name }} on {{ datetime.date(update.time) }}
- at {{ datetime.time(update.time) }}: {{ update.text }}
+ {{ update.member.name }} on {{ datetime.date(update.created) }} at {{
+ datetime.time(update.created) }}: {{ update.content }}
</p>
-{%- endmacro %} {% macro list(updates) -%} {% for update in updates %}
+{%- endmacro %}
+
+{% macro list(updates) -%}
+{% for update in updates %}
<div>{{ view(update) }}</div>
-{% endfor %} {%- endmacro %}
+{% endfor %}
+{%- endmacro %}
+<!DOCTYPE html>
<html>
<head>
- {% import "elements/metadata.html" as metadata %} {{ metadata.head(group)}}
+ {% import "elements/metadata.html" as metadata %}
+ {{ metadata.head(group)}}
<link
rel="stylesheet"
- href="{{ url_for('static', filename='style.css') }}"
+ href="{{ url_for('static', filename='main.css') }}"
/>
</head>
- <header>
- {% import "elements/navigation.html" as navigation %} {{
- navigation.bar(account) }}
- </header>
-
<body>
- {% import "elements/space.html" as space %} {{ space.stars(500) }}
+ <header>
+ {% import "elements/navigation.html" as navigation %} {{
+ navigation.bar(account) }}
+ </header>
+
+ {% import "elements/space.html" as space %}
+ {{ space.stars(500) }}
<center>
<h1>{{ group.name }}</h1>
<p>{{ group.description }}</p>
- {% import "elements/events.html" as events %} {{
- events.next(group.events[0]) }} {% import "elements/updates.html" as
- updates %} {{ updates.recent(group.updates[0]) }}
+ {% import "elements/events.html" as events %}
+ {% if group.events | length > 0 %}
+ {{ events.next(group.events[0]) }}
+ {% else %}
+ <p>No upcoming events.</p>
+ {% endif %}
+
+ {% import "elements/updates.html" as updates %}
+ {% if group.updates | length > 0 %}
+ {{ updates.recent(group.updates[0]) }}
+ {% else %}
+ <p>No recent updates.</p>
+ {% endif %}
</center>
</body>
</html>
+<!DOCTYPE html>
<html>
<head>
- {% import "elements/metadata.html" as metadata %} {{ metadata.head(group,
- "Members") }}
+ {% import "elements/metadata.html" as metadata %}
+ {{ metadata.head(group, "Members") }}
<link
rel="stylesheet"
href="{{ url_for('static', filename='intro.css') }}"
</head>
<body>
- {% import "elements/space.html" as space %} {{ space.stars(200) }}
+ {% import "elements/space.html" as space %}
+ {{ space.stars(200) }}
<div id="board">
<div id="content">
<center>
<p>{{ member.name }}</p>
{% endfor %}
<br />
- <h2>CREATED BY</h2>
+ <h2>THIS WAS CREATED BY</h2>
<p>Damian Myrda</p>
</center>
</div>
+<!DOCTYPE html>
<html>
<head>
- {% import "elements/metadata.html" as metadata %} {% if create %} {{
- metadata.head(group, "New Project") }} {% else %} {{ metadata.head(group,
- "Projects") }} {% endif %}
+ {% import "elements/metadata.html" as metadata %}
+ {% if create %}
+ {{ metadata.head(group, "New Project") }}
+ {% else %}
+ {{ metadata.head(group, "Projects") }}
+ {% endif %}
<link
rel="stylesheet"
- href="{{ url_for('static', filename='style.css') }}"
+ href="{{ url_for('static', filename='main.css') }}"
/>
</head>
- <header>
- {% import "elements/navigation.html" as navigation %} {{
- navigation.bar(account) }}
- </header>
-
<body>
- {% import "elements/space.html" as space %} {{ space.stars(500) }} {% import
- "elements/projects.html" as projects %} {% if create %} {{ projects.new() }}
+ <header>
+ {% import "elements/navigation.html" as navigation %}
+ {{ navigation.bar(account) }}
+ </header>
+
+ {% import "elements/space.html" as space %}
+ {{ space.stars(500) }}
+ {% import "elements/projects.html" as projects %}
+ {% if create %}
+ {{ projects.new() }}
{% else %}
<h1>Projects</h1>
- {{ projects.list(group.projects) }} {% if account %}
+ {{ projects.list(group.projects) }}
+ {% if account %}
<a href="/projects/add">Add a Project</a>
- {% endif %} {% endif %}
+ {% endif %}
+ {% endif %}
</body>
</html>
+<!DOCTYPE html>
<html>
<head>
- {% import "elements/metadata.html" as metadata %} {{ metadata.head(group,
- "Updates") }}
+ {% import "elements/metadata.html" as metadata %}
+ {{ metadata.head(group, "Updates") }}
<link
rel="stylesheet"
- href="{{ url_for('static', filename='style.css') }}"
+ href="{{ url_for('static', filename='main.css') }}"
/>
</head>
- <header>
- {% import "elements/navigation.html" as navigation %} {{
- navigation.bar(account) }}
- </header>
-
<body>
- {% import "elements/space.html" as space %} {{ space.stars(500) }}
+ <header>
+ {% import "elements/navigation.html" as navigation %}
+ {{ navigation.bar(account) }}
+ </header>
+
+ {% import "elements/space.html" as space %}
+ {{ space.stars(500) }}
<h1>Updates</h1>
- {% import "elements/updates.html" as updates %} {{
- updates.list(group.updates) }}
+ {% import "elements/updates.html" as updates %}
+ {{ updates.list(group.updates) }}
</body>
</html>