first commit

This commit is contained in:
Jadowyne Ulve 2025-09-06 18:49:27 -05:00
parent 0761828631
commit 5b7d018750
26 changed files with 1639 additions and 0 deletions

71
.gitignore vendored Normal file
View File

@ -0,0 +1,71 @@
media/2024-11-23 11-49-Rowan.pcmp
media/2024-11-23 18-11-Gabriella.pcmp
media/2024-11-24 09-14-Default.pcmp
media/2024-11-24 11-04-Gabriella.pcmp
media/2024-11-24 19-44-Rowan.pcmp
media/2025-09-06 17-56-Gabriella.pcmp
media/2025-09-06 19-06-Rowan.pcmp
instance/db.sqlite
static/avatars/0d1c5f6dbb1d44c59f43c7d10cd3f9d3_l.png
static/avatars/0d1c5f6dbb1d44c59f43c7d10cd3f9d3_m.png
static/avatars/0d1c5f6dbb1d44c59f43c7d10cd3f9d3_s.png
static/avatars/1c1e283bc3974a338306df55809c3eb9_l.png
static/avatars/1c1e283bc3974a338306df55809c3eb9_m.png
static/avatars/1c1e283bc3974a338306df55809c3eb9_s.png
static/avatars/3e9717b8a2484c36801c983a7c98389e_raw.png
static/avatars/6f9d062c23774444bf97b30768aa7e3f_raw.png
static/avatars/8d7ae602f4e744e2b0de0cad75280c4b_l.png
static/avatars/8d7ae602f4e744e2b0de0cad75280c4b_m.png
static/avatars/8d7ae602f4e744e2b0de0cad75280c4b_s.png
static/avatars/8de5d46847d243b7946fb987c1d7a158_raw.png
static/avatars/9d7056cd58604367b3da797d47bf7de3_raw.png
static/avatars/9df04f769ebd4b8298198abbbd4696ee_l.png
static/avatars/9df04f769ebd4b8298198abbbd4696ee_m.png
static/avatars/9df04f769ebd4b8298198abbbd4696ee_s.png
static/avatars/12a6ce4e6eb9455c9fd300bc5bdcc68f_raw.png
static/avatars/13d09f763cf5423c93c476475e6fedb5_raw.png
static/avatars/39fb0118e7c247abbe5104ad039332c6_raw.png
static/avatars/071c522d704d42eebad3114439d01548_l.png
static/avatars/071c522d704d42eebad3114439d01548_m.png
static/avatars/071c522d704d42eebad3114439d01548_s.png
static/avatars/79ccc4d8993b4d7297f9ec0e01245fa4_raw.png
static/avatars/613fc5aac72c42c9b9f034d039098787_l.png
static/avatars/613fc5aac72c42c9b9f034d039098787_m.png
static/avatars/613fc5aac72c42c9b9f034d039098787_s.png
static/avatars/684ed5cda7bd4eb08decee8d3f5ceb33_raw.png
static/avatars/747c34da7f59464fa1e19856c6263c6b_l.png
static/avatars/747c34da7f59464fa1e19856c6263c6b_m.png
static/avatars/747c34da7f59464fa1e19856c6263c6b_s.png
static/avatars/956ff2dbbc8b485d9a4b2a509d67b195_raw.png
static/avatars/5041d223f89940e3ad0546baed22f99c_raw.png
static/avatars/9037d37a43f54163b0322a9c4b6e335a_l.png
static/avatars/9037d37a43f54163b0322a9c4b6e335a_m.png
static/avatars/9037d37a43f54163b0322a9c4b6e335a_s.png
static/avatars/9139f11ea5854a6bb86d5a9f38e07ac4_raw.png
static/avatars/aed55274f1c84b52bab1b30ab3adfdb2_l.png
static/avatars/aed55274f1c84b52bab1b30ab3adfdb2_m.png
static/avatars/aed55274f1c84b52bab1b30ab3adfdb2_s.png
static/avatars/ba03d8c967ef458b9069b9367f9a7a63_raw.png
static/avatars/ba72f533f5c94dd79dde64ee5aca4fa1_l.png
static/avatars/ba72f533f5c94dd79dde64ee5aca4fa1_m.png
static/avatars/ba72f533f5c94dd79dde64ee5aca4fa1_s.png
static/avatars/ba79862152174438aff256bdab9aacc1_l.png
static/avatars/ba79862152174438aff256bdab9aacc1_m.png
static/avatars/ba79862152174438aff256bdab9aacc1_s.png
static/avatars/be2d767474e3406ba2463acd65f03158_l.png
static/avatars/be2d767474e3406ba2463acd65f03158_m.png
static/avatars/be2d767474e3406ba2463acd65f03158_s.png
static/avatars/c7b11bc3d7fd4993ad03b5e9046986c4_raw.png
static/avatars/c24f0d67bb9f4ef99fe7eac3e135d8e7_l.png
static/avatars/c24f0d67bb9f4ef99fe7eac3e135d8e7_m.png
static/avatars/c24f0d67bb9f4ef99fe7eac3e135d8e7_s.png
static/avatars/c9325959a1e940c184e4e597687211ae_l.png
static/avatars/c9325959a1e940c184e4e597687211ae_m.png
static/avatars/c9325959a1e940c184e4e597687211ae_s.png
static/avatars/de8bb1c2465345f3bc6a05c560ff743d_raw.png
static/avatars/e95939dcb19544a3aab8a88e806a09b1_raw.png
static/avatars/f6b5d00609a74f7980b0e15958783da3_raw.png
static/images/2024-03-03 225803255514-gen-image-HJpDVk.jpg
static/images/Gabbie Picture 2.png
static/versions/alpha_v1.0.zip
static/versions/latest.zip

18
database.py Normal file
View File

@ -0,0 +1,18 @@
import easySQL, pathlib
@easySQL.Table
class Uploads():
def __init__(self):
self.name = "logins"
self.columns = {
"userid": easySQL.INTEGER,
"filename": easySQL.STRING,
"author": easySQL.STRING,
"version": easySQL.STRING,
"path": easySQL.STRING
}
def add_upload(table, userid, filename, author, version, path):
easySQL.insert_into_table(table,
[userid, filename, author, version, path]
)

243
easySQL.py Normal file
View File

@ -0,0 +1,243 @@
from typing import Any
import sqlite3, pathlib
from collections import namedtuple
STRING = 'string'
INTEGER = 'integer'
UNIQUE = 'UNIQUE'
JSON = 'string' # TODO: create a function for converting lists and dict into json string and back
database = None
# TODO: add functionality to seek all entries in a filter that CONTAINS a string
def VALDATED_STRING():
return 'string'
def intergrate(database_path: pathlib.Path = None) -> sqlite3.Connection:
if not database_path:
database_path = pathlib.Path("test.sqlite")
global database
database = sqlite3.connect(database=database_path.absolute())
return database
def Table(cls):
""" easySQL decorator for table classes for easy instantiation of many of the SQL_execute strings.
This class will always need these variables defined within its __init__ method;
self.name = "foo"; This will be the name of the table in the integrated database
self.columns = {foo: dah, ...}; dictionary of foo being the column name, and dah being the columns type in the database
types for a column are:
- STRING
- INTEGER
- UNIQuE
- JSON
Returns:
Table: returns a Table class wrapped around the original class.
"""
class Table(cls):
def __init__(self, *args, **kwargs) -> None:
super(Table, self).__init__(*args, **kwargs)
self.data_object = namedtuple(f"{self.name}_row", list(self.columns.keys()))
self.columns_validation = len(self.columns)
def __repr__(self):
return f"{self.__class__.__name__} ('{self.name}')"
@property
def create_table(self):
def manufacture_create_SQL_string() -> str:
""" Takes the super()'s columns dictionary and bulds parts of the SQL_execute string.
Returns:
str: middle of create table SQL_execute string.
"""
# TODO: very crude way of doing it, research a better way.
middle_string = 'id integer PRIMARY KEY, '
current_count = 0
for column_name, column_type in self.columns.items():
if current_count == len(self.columns.items())-1:
middle_string += f"{column_name} {column_type}"
else:
middle_string += f"{column_name} {column_type}, "
current_count += 1
return middle_string
return f"CREATE TABLE {self.name} ({manufacture_create_SQL_string()});"
@property
def drop_table(self):
return f"DROP TABLE {self.name};"
def select_row(self, column: str = None, match = None):
if column:
return f"SELECT * FROM {self.name} WHERE {column}= '{match}'"
else:
return f"SELECT * FROM {self.name}"
def insert_row(self, data) -> namedtuple:
def manufacture_insert_SQL_String():
middle_string = '('
gavel_string = '('
current_count = 0
for column_name in self.columns.keys():
if current_count == len(self.columns.items())-1:
middle_string += f"{column_name})"
gavel_string += f"?)"
else:
middle_string += f"{column_name}, "
gavel_string += f"?, "
current_count += 1
return f"{middle_string} VALUES {gavel_string}"
query = namedtuple('Query', ['query', 'data'])
if len(data) == self.columns_validation:
return query(query=f"INSERT INTO {self.name}{manufacture_insert_SQL_String()}", data=data)
else:
return query(query=False, data= f"passed data to {self.name} is not the right length of entries")
def update_row_by_id(self, data: dict, id: str):
""" Update a row at {id} with {data}.
Args:
data (dict): key = column, value = data to update to
id (str): row_id in Table
"""
def manufactur_update_SQL_string(data: dict) -> str:
""" takes data and builds a SQL_execute string segment
Args:
data (dict): Key = column, value = data to update to
Returns:
_type_: middle segment of SQL_execute string
"""
# TODO: this is a very crude implementtion
middle_string = ''
current_count = 0
for key, value in data.items():
if current_count == len(data.items())-1:
middle_string += f" {key} = '{value}'"
else:
middle_string += f" {key} = '{value}', "
current_count += 1
return middle_string
return f"UPDATE {self.name} SET{manufactur_update_SQL_string(data)} WHERE id = {id}"
def convert_data(self, rows: list or tuple):
""" Takes rows returned by the tables SQL_select string and returns them as namedtuples.
Args:
rows (listortuple):
Returns:
(listortuple): returns a list of namedtuple.
"""
self.keys = list(self.columns.keys())
if isinstance(rows, list):
return [self.data_object(**{key: data[i+1] for i, key in enumerate(self.keys)}) for data in rows]
if isinstance(rows, tuple):
return [self.data_object(**{key: rows[i+1] for i, key in enumerate(self.keys)})][0]
return Table
def basic_query(query: str):
""" Used as single query functions, ex. creating tables, dropping tables, updating rows
Args:
query (str): SQL_execute string
"""
with database:
cursor = database.cursor()
cursor.execute(query)
def create_table(table: Table, drop=False):
if drop:
drop_table(table=table)
try:
with database:
cursor = database.cursor()
cursor.execute(table.create_table)
except sqlite3.OperationalError:
pass
def drop_table(table: Table):
try:
with database:
cursor = database.cursor()
cursor.execute(table.drop_table)
except sqlite3.OperationalError:
pass
def update_table_row_by_id(table: Table, row):
query = table.update_row_by_id(data=row[1], id=row[0])
with database:
cursor = database.cursor()
cursor.execute(query)
def insert_into_table(table, data):
""" Passing a query and its data as a namedtuple will insert the query into the database.
Args:
query (namedtuple): (query.query = SQL_execute string, query.data = tuple of column's data)
"""
query = table.insert_row(data)
assert query.query, query.data
with database:
cursor = database.cursor()
cursor.execute(query.query, query.data)
def fetchone_from_table(table: Table, filter: tuple([str, Any]) = None, convert_data=True) -> tuple:
if filter:
query = table.select_row(column=filter[0], match=filter[1])
else:
query = table.select_row()
with database:
cursor = database.cursor()
cursor.execute(query)
if not convert_data:
return cursor.fetchone()
return table.convert_data(cursor.fetchone())
def fetchall_from_table(table, filter: tuple([str, Any]) = None, convert_data=True) -> list:
""" Fetches all rows from the database using passed query
Args:
query (str): SQL_execute string
Returns:
list: list of rows as tuples
"""
if filter:
query = table.select_row(column=filter[0], match=filter[1])
else:
query = table.select_row()
with database:
cursor = database.cursor()
cursor.execute(query)
batch = cursor.fetchall()
if len(batch) == 1:
batch = batch[0]
if not convert_data:
return batch
return table.convert_data(batch)

483
main.py Normal file
View File

@ -0,0 +1,483 @@
from flask import Flask, Request, render_template, request, redirect, url_for, flash, send_file, send_from_directory, Response, session, jsonify, current_app
from flask_avatars import Avatars
import os, pathlib, io
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.ext.mutable import MutableList, MutableDict
from flask_login import LoginManager, current_user, login_user, logout_user, UserMixin, login_required
from werkzeug.utils import secure_filename
import random, string, bcrypt, datetime
from copy import deepcopy
from dataclasses import dataclass
from PIL import Image
import base64, json, pathlib
app = Flask(__name__)
avatars = Avatars(app)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///db.sqlite"
app.config["SECRET_KEY"] = "MYSUPERCOOLAPPKEY%#!%#!GJDAJVNEIALDOFJGNA"
app.config["UPLOAD_FOLDER"] = "media"
app.config['SECURITY_PASSWORD_HASH'] = 'bcrypt'
app.config['SECURITY_PASSWORD_SALT'] = b'$2b$12$wqKlYjmOfXPghx3FuC3Pu'
app.config['AVATARS_SAVE_PATH'] = 'static/avatars/'
db = SQLAlchemy()
login_manager = LoginManager()
login_manager.init_app(app)
@dataclass
class Users(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(250), unique=True, nullable=False)
email = db.Column(db.String(250), unique=True, nullable=False)
password = db.Column(db.String(250), nullable=False)
firstname = db.Column(db.String(250), nullable=True)
lastname = db.Column(db.String(250), nullable=True)
friendcode = db.Column(db.String(250), nullable=False)
subs = db.Column(MutableList.as_mutable(db.JSON), nullable=False)
latest = db.Column(db.Integer, nullable=True)
raw_avatar = db.Column(db.String(250), nullable=False)
s_avatar = db.Column(db.String(250), nullable=False)
m_avatar = db.Column(db.String(250), nullable=False)
l_avatar = db.Column(db.String(250), nullable=False)
class Uploads(db.Model):
id = db.Column(db.Integer, primary_key=True)
userid = db.Column(db.Integer, nullable=False)
upload_date = db.Column(db.String(250), nullable=False)
filename = db.Column(db.String(250), nullable=False)
author = db.Column(db.String(250), nullable=False)
version = db.Column(db.String(250), nullable=False)
description = db.Column(db.String(250), nullable=True)
meta_data = db.Column(MutableDict.as_mutable(db.JSON), nullable=True)
collection_json = db.Column(MutableDict.as_mutable(db.JSON), nullable=True)
character_links = db.Column(MutableDict.as_mutable(db.JSON), nullable=True)
mod_list = db.Column(MutableList.as_mutable(db.JSON), nullable=True)
path = db.Column(db.String(250), nullable=False)
db.init_app(app)
with app.app_context():
db.create_all()
def friend_code(length):
letters = string.ascii_letters
return ''.join(random.choice(letters) for i in range(length))
@login_manager.user_loader
def loader_user(user_id):
return Users.query.get(user_id)
def password_hash(input_password):
bytes = input_password.encode('utf-8')
salt = bcrypt.gensalt()
return bcrypt.hashpw(bytes, salt)
def check_password(input_password, hash):
bytes = input_password.encode('utf-8')
result = bcrypt.checkpw(bytes, hash)
return result
@app.route("/login_app/avatar", methods=["POST"])
def login_request_avatar():
if request.method == "POST":
username = request.json['username']
user = Users.query.filter_by(username=username).first()
if user.s_avatar != "default":
path = f"static/{user.s_avatar}"
filename = f"{user.username}.png"
return send_file(open(path, "rb"), download_name=filename)
return Response(status=201)
@app.route('/check_server')
def ping():
return Response(status=200)
@app.route('/update_app/<pipe>/<version>')
def update_app(pipe, version):
current_latest = "alpha_v1.0"
if pipe == "latest":
if not current_latest == version:
update_ready = True
else:
update_ready = False
data = {
'latest_version': current_latest,
'update_ready': update_ready,
'download_url': f"/download_app/{pipe}"
}
return jsonify(data=data)
return Response(status=200)
@app.route("/upload_file", methods=["POST"])
def save_chunks():
if 'file' in request.files:
uploaded_file = request.files['file']
filename = uploaded_file.filename
with open(f'{app.config['UPLOAD_FOLDER']}/{filename}', 'ab') as f:
f.write(uploaded_file.read())
return '', 200
return 404
@app.route("/upload_info", methods=['POST'])
async def upload_info():
if not request.method == 'POST':
return Response(status=415)
data = request.json
user = Users.query.filter_by(username=data['username']).first()
if not check_password(data['password'], user.password):
return Response(status=415)
filename = data['filename']
# database upload
date = datetime.date.today()
save_path = f"{app.config['UPLOAD_FOLDER']}/{filename}"
meta_data = data['meta_data']
collection_json = data['collection_json']
character_links = data['character_links']
mod_list = data['mod_list']
print(meta_data)
upload = Uploads(
userid=user.id,
upload_date = date,
filename=filename,
author=user.username,
version="test",
description="",
meta_data = meta_data,
collection_json = collection_json,
character_links = character_links,
mod_list = mod_list,
path=save_path
)
db.session.add(upload)
db.session.commit()
return Response(status=200)
@app.route("/upload_app", methods=['POST'])
async def upload_app():
data = request.files['datas'].read()
data = json.loads(data)
if not request.method == 'POST':
return Response(status=415)
user = Users.query.filter_by(username=data['username']).first()
if not check_password(data['password'], user.password):
return Response(status=415)
if 'file' not in request.files:
print(request.files)
return Response(status=415)
file = request.files['file']
if file.filename == '':
return Response(status=415)
filename = data['filename']
filename = secure_filename(filename)
# database upload
date = datetime.date.today()
save_path = f"{app.config['UPLOAD_FOLDER']}/{filename}"
meta_data = data['meta_data']
collection_json = data['collection_json']
character_links = data['character_links']
mod_list = data['mods_to_copy']
print(meta_data)
upload = Uploads(
userid=user.id,
upload_date = date,
filename=filename,
author=user.firstname,
version="test",
description="",
meta_data = meta_data,
collection_json = collection_json,
character_links = character_links,
mod_list = mod_list,
path=save_path
)
db.session.add(upload)
db.session.commit()
# saves file
print(save_path)
file.save(save_path)
return Response(status=200)
@app.route("/login_app", methods=["POST"])
def login_app():
if request.method == "POST":
username = request.json['username']
user = Users.query.filter_by(username=username).first()
data= {
"id": user.id,
"friendcode": user.friendcode,
"subs": user.subs,
}
if check_password(request.json['password'], user.password):
return data, 200
return Response(status=500)
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
user = Users.query.filter_by(username=request.form.get("username")).first()
if check_password(request.form.get("password"), user.password):
login_user(user)
return redirect(url_for("home"))
return render_template("login.html")
@app.route("/logout")
def logout():
logout_user()
return redirect(url_for("home"))
@app.route("/signup", methods=["GET", "POST"])
def signup():
if request.method == "POST":
if request.form.get('password') == request.form.get('confirm_password'):
user = Users(
username=request.form.get('username'),
email=request.form.get("email"),
password=password_hash(request.form.get("password")),
firstname=request.form.get('firstname'),
lastname=request.form.get('lastname'),
friendcode=friend_code(6),
subs=[],
raw_avatar='default',
s_avatar='default',
m_avatar='default',
l_avatar='default'
)
db.session.add(user)
db.session.commit()
return redirect(url_for("login"))
else:
return redirect(request.url)
return render_template("signup.html")
@app.route("/")
def home():
return render_template("home.html")
@app.route("/about")
def about():
return render_template("about.html")
was_upload = (False, "")
@app.route("/collections/upload", methods=["POST"])
def upload():
if request.method == "POST":
if 'file' not in request.files:
print('no file')
flash('No file part')
return redirect(url_for('collections'))
file = request.files['file']
if file.filename == '':
print("no selected file")
flash('No selected file')
return redirect(url_for('collections'))
if file:
filename= secure_filename(file.filename)
global was_upload
was_upload = (True, filename)
print(filename)
path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(path)
date = datetime.date.today()
upload = Uploads(
userid=current_user.id,
upload_date = date,
filename=filename,
author=current_user.username,
version="test",
description="",
path=path
)
db.session.add(upload)
db.session.commit()
return redirect(url_for('collections'))
def generate_file_chunks(filename):
with open(filename, 'rb') as file:
while True:
chunk = file.read(4096) # Read 4096 bytes at a time
if not chunk:
break
yield chunk
@app.route("/download_app/latest")
def download_app():
path = pathlib.Path(f"static/versions/latest.zip")
filename = "Collection Sharing App.zip"
return send_file(open(path, "rb"), download_name=filename)
@app.route("/collections/download/<id>")
def download(id):
upload = Uploads.query.filter_by(id=id).first()
abs_path = os.path.join(current_app.root_path, str(upload.path).replace('/', '\\'))
print(abs_path)
if not os.path.isfile(abs_path):
print("File not found!")
try:
with open(abs_path, "rb") as f:
print("File read success, first 10 bytes:", f.read(10))
except Exception as e:
print("Error reading file:", e)
filename = upload.filename
return send_file(abs_path, as_attachment=True, download_name=filename)
@app.route("/collections/delete/<id>", methods=["GET", "POST"])
def delete_collection(id):
upload = Uploads.query.filter_by(id=id).first()
os.remove(upload.path)
row_to_delete = db.session.query(Uploads).filter(Uploads.id == id).one()
db.session.delete(row_to_delete)
db.session.commit()
return redirect(url_for('collections'))
@app.route("/collections/update/<id>", methods=["GET", "POST"])
def update_collection(id):
print(f"this is an update call for id:{id}")
if request.method == "POST":
print(f"this is an update call for id:{id}, request is a POST")
upload = Uploads.query.filter_by(id=id).first()
upload.author = request.form.get(f"author-{id}")
upload.version = request.form.get(f"version-{id}")
upload.description = request.form.get(f"description-{id}")
print(upload.description)
db.session.commit()
if request.form.get(f"latest-{id}") == "on":
user = Users.query.filter_by(id=current_user.id).first()
user.latest = upload.id
db.session.commit()
return redirect(url_for('collections'))
return redirect(url_for('collections'))
@app.route("/collections")
def collections():
page = request.args.get('page', 1, type=int)
pagination = Uploads.query.filter_by(userid=current_user.id).order_by(Uploads.upload_date)
pagination = pagination.paginate(page=page, per_page=5, error_out=False)
data=[]
friends_data = []
for code in current_user.subs:
try:
user = Users.query.filter_by(friendcode=code).first()
upload = Uploads.query.filter_by(id=user.latest).first()
up_data = {
"id": upload.id,
"upload_date": upload.upload_date,
"name": upload.filename,
"author": upload.author,
"version": upload.version,
"description": upload.description,
"path": pathlib.Path(f"{upload.path}").absolute()
}
friends_data.append(up_data)
except:
pass
global was_upload
data=[pagination, friends_data, deepcopy(was_upload)]
was_upload = (False, "")
return render_template("collections.html", data=data)
@app.route('/profile')
def profile():
return render_template("profile.html")
@app.route('/profile/upload', methods=["POST"])
def upload_avatar():
if request.method == 'POST':
f = request.files.get('file')
print(f)
raw_filename = avatars.save_avatar(f)
user = Users.query.filter_by(id=current_user.id).first()
user.raw_avatar = f"{app.config['AVATARS_SAVE_PATH']}{raw_filename}"
db.session.commit()
session['raw_filename'] = raw_filename # you will need to store this filename in database in reality
return redirect(url_for('crop'))
return redirect(url_for('profile'))
# serve avatar image
@app.route('/avatars/<path:filename>')
def get_avatar(filename):
return send_from_directory(app.config['AVATARS_SAVE_PATH'], filename)
@app.route('/crop', methods=['GET', 'POST'])
def crop():
if request.method == 'POST':
x = request.form.get('x')
y = request.form.get('y')
w = request.form.get('w')
h = request.form.get('h')
filenames = avatars.crop_avatar(session['raw_filename'], x, y, w, h)
user = Users.query.filter_by(id=current_user.id).first()
user.s_avatar = f"avatars/{filenames[0]}"
print(user.s_avatar)
db.session.commit()
user.m_avatar = f"avatars/{filenames[1]}"
db.session.commit()
user.l_avatar = f"avatars/{filenames[2]}"
db.session.commit()
return redirect(url_for('profile'))
return render_template('crop.html')
@app.route('/profile/friends')
def friends_code():
data = []
for code in current_user.subs:
try:
user = Users.query.filter_by(friendcode=code).first()
x = {
'username': user.username,
'friendcode': user.friendcode,
'm_avatar': user.m_avatar
}
data.append(x)
except:
pass
return render_template("friends.html", data=data)
@app.route("/addcode", methods=["POST"])
def addcode():
if request.method == "POST":
if request.form.get("friendcode") == "":
return redirect(url_for('friends_code'))
user = Users.query.filter_by(id=current_user.id).first()
user.subs.append(request.form.get("friendcode"))
user.subs = user.subs
db.session.commit()
return redirect(url_for('friends_code'))
if __name__ == "__main__":
app.run(host="0.0.0.0", port="5003", debug=True)

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

16
mysite/mysite/asgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
ASGI config for mysite project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
application = get_asgi_application()

124
mysite/mysite/settings.py Normal file
View File

@ -0,0 +1,124 @@
"""
Django settings for mysite project.
Generated by 'django-admin startproject' using Django 5.0.4.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-zkb)))rp%reh1qr6ega!sn41^b(-93*)vb&u2k(h6ltk!wion9'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'catalog.apps.CatalogConfig'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'mysite.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'mysite.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

23
mysite/mysite/urls.py Normal file
View File

@ -0,0 +1,23 @@
"""
URL configuration for mysite project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('catalog/', include('catalog.urls')),
]

16
mysite/mysite/wsgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
WSGI config for mysite project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
application = get_wsgi_application()

BIN
saved_file.zip Normal file

Binary file not shown.

0
static/css/template.css Normal file
View File

24
templates/about.html Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>About Flask</title>
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<!-- Compiled and minified JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
</head>
<body>
{% extends "template.html" %}
{% block content %}
<h1> About Flask </h1>
<p> Flask is a micro web framework written in Python.</p>
<p> Applications that use the Flask framework include Pinterest,
LinkedIn, and the community web page for Flask itself.</p>
{% endblock %}
</body>
</html>

View File

@ -0,0 +1,111 @@
{% block content %}
<div class="row">
<div class="col s12">
<h3> My Collections </h3>
<table class="highlight">
<thead>
<tr>
<th>Collection Name</th>
<th>Author</th>
<th>Version</th>
<th></th>
</tr>
</thead>
<tbody>
{% for item in data[0].items %}
<tr>
<div class="modal-trigger" href="#demo-modal-{{item.id}}">
{% if item.id == current_user.latest %}
<td><i class="tiny material-icons">star</i>{{item.filename}}</td>
{% else %}
<td>{{item.filename}}</td>
{% endif %}
<td>{{item.author}}</td>
<td>{{item.version}}</td>
</div>
<div>
<td class="right-align">
<a class="waves-effect waves-light green btn-small right-align modal-trigger" data-target="modal-{{item.id}}"><i class="material-icons center">edit</i></a>
<a class="waves-effect waves-light grey btn-small right-align" href="{{ url_for('download', id=item.id)}}" download><i class="material-icons center">download</i></a>
<a class="waves-effect waves-light red btn-small right-align" href="{{ url_for('delete_collection', id=item.id)}}"><i class="material-icons center">delete</i></a>
</td>
<div id="modal-{{item.id}}" class="modal" style="width: 60%">
<div class="modal-content">
<h4>{{item.filename}}</h4>
<form action="{{ url_for('update_collection', id=item.id) }}", method="post">
<div class="row">
<div class="input-field col s12 m6">
<input value="{{item.author}}" placeholder="Foo" id="author-{{item.id}}" name="author-{{item.id}}" type="text" class="validate">
<label for="author">Author</label>
</div>
<label class="right col s2">
{% if item.id == current_user.latest %}
<input type="checkbox" class="filled-in right" checked="checked" id="latest-{{item.id}}" name="latest-{{item.id}}" />
<span>Latest</span>
{% else %}
<input type="checkbox" id="latest-{{item.id}}" name="latest-{{item.id}}" />
<span>Latest</span>
{% endif %}
</label>
</div>
<div class="row">
<div class="input-field col s12 m6">
<input value="{{item.version}}" placeholder="v2024.1.1" id="version-{{item.id}}" name="version-{{item.id}}" type="text" class="validate">
<label for="version">Version</label>
</div>
<div class="input-field col s12 m6">
<input value="{{item.upload_date}}" placeholder="1/1/2001" id="upload_date-{{item.id}}" name="upload_date-{{item.id}}" type="text" class="validate">
<label for="upload_date">Uploaded</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<textarea id="description-{{item.id}}" name="description-{{item.id}}" class="materialize-textarea">{{item.description}}</textarea>
<label for="description">Description</label>
</div>
</div>
<div class="divider"></div>
<div class="row">
<div class="input-field col s2">
<a class="waves-effect waves-light grey btn left-align" href="{{ url_for('download', id=item.id)}}" download>Download<i class="material-icons right">cloud_download</i></a>
</div>
<div class="input-field col s2 right">
<button class="btn waves-effect waves-light right" type="submit" name="action" id="submit">Save
<i class="material-icons right">save</i>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</tr>
{% endfor %}
</tbody>
</table>
<div class="row">
<ul class="pagination center">
{% if data[0].has_prev %}
<li class='waves-effect'><a class='waves-effect' href="{{ url_for('collections', page=data[0].prev_num) }}"><i class="material-icons">chevron_left</i></a></li>
{% endif %}
{% for number in data[0].iter_pages() %}
{% if data[0].page != number %}
<li class='waves-effect'><a href="{{ url_for('collections', page=number) }}">{{ number }}</a></li>
{% else %}
<li class='active'><a>{{ number }}</a></li>
{% endif %}
{% endfor %}
{% if data[0].has_next %}
<li class='waves-effect'><a href="{{ url_for('collections', page=data[0].next_num) }}"><i class="material-icons">chevron_right</i></a></li>
{% endif %}
</ul>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Treehouse - My Collections</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
</head>
<body>
{% extends "template.html" %}
{% block content %}
<div class="container" style="width: 85%; background-color: white;">
<div class="section">
<form method="post" action="{{ url_for('upload')}}" enctype="multipart/form-data">
<div class="row">
<div class="col s10 m10 l10">
<div class="file-field input-field center">
<div class="btn grey">
<span>File</span>
<input name="file" type="file">
</div>
<div class="file-path-wrapper">
<input class="file-path validate" type="text" placeholder="Upload a file...">
</div>
</div>
</div>
<div class="col s2 m2 l2">
<div class="input-field">
<button type="submit" class="btn grey" name=action>
Upload
</button>
</div>
</div>
</div>
</form>
<div class="divider"></div>
{% include "collection_list.html" %}
<div class="row">
<div class="col s12">
<h3> Friends Collections </h3>
<table>
<thead>
<tr>
<th>Collection Name</th>
<th>Author</th>
<th>Version</th>
<th></th>
</tr>
</thead>
<tbody>
{% for item in data[1] %}
<tr>
<td>{{item.name}}</td>
<td>{{item.author}}</td>
<td>{{item.version}}</td>
<td><a class="waves-effect waves-light btn-small right" href="{{ url_for('download', id=item.id)}}" download><i class="material-icons left">download</i>Download</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% if data[2][0] %}
<script> M.toast({html: "Uploaded {{data[2][1]}}!"})</script>
{% endif %}
{% endblock %}
<script>
$(document).ready(function () {
$('.modal').modal();
}
)
</script>
</body>
</html>

25
templates/crop.html Normal file
View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Flask-Avatars Demo</title>
{{ avatars.jcrop_css() }} <!-- include jcrop css -->
<style>
<!-- some css to make a better preview window -->
</style>
</head>
<body>
<h1>Step 2: Crop</h1>
{{ avatars.crop_box('get_avatar', session['raw_filename']) }} <!-- crop window -->
{{ avatars.preview_box('get_avatar', session['raw_filename']) }} <!-- preview widow -->
<form method="post">
<input type="hidden" id="x" name="x">
<input type="hidden" id="y" name="y">
<input type="hidden" id="w" name="w">
<input type="hidden" id="h" name="h">
<input type="submit" value="Crop!">
</form>
{{ avatars.jcrop_js() }} <!-- include jcrop javascript -->
{{ avatars.init_jcrop() }} <!-- init jcrop -->
</body>
</html>

45
templates/friends.html Normal file
View File

@ -0,0 +1,45 @@
{% extends "template.html" %}
{% block content %}
<div class="container">
<div class="card-panel grey lighten-5 z-depth-1">
<div class="col s12 center">
<h1>Friends</h1>
</div>
<div class="divider"></div>
<div class="section">
<div class="row">
<form action="{{url_for('addcode')}}" method="post">
<div class="offset-s4 input-field col s4">
<input placeholder="Placeholder" id="friendcode" name="friendcode" type="text" class="validate">
<label for="friendcode">Enter your FriendCode</label>
</div>
<div class="input-field col s2">
<button type="submit" class="btn waves-effect waves-light green" name=action>
Add
</button>
</div>
</form>
</div>
<ul class="collection">
{% for friend in data %}
<li class="collection-item avatar">
{% if friend.m_avatar == 'default' %}
<img src="{{ avatars.default() }}" alt="" class="circle">
{% else %}
<img src="{{url_for("static", filename=friend.m_avatar)}}" class="circle responsive-img">
{% endif %}
<h5>{{friend.username}}</h5>
<h5 class="secondary-content">{{friend.friendcode}}</h5>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endblock %}

34
templates/home.html Normal file
View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Treehouse - Home</title>
</head>
<body>
{% extends "template.html" %}
{% block content %}
<!-- <img style="object-fit: fill;" src="{{url_for('static', filename='images/2024-03-03 225803255514-gen-image-HJpDVk.jpg')}}"> -->
<div class="container section">
<div class="row">
<div class="col s12 m12 l6">
<div class="card">
<div class="card-content">
<span class="card-title">Collection Sharing App alpha_V1.0</span>
<p>Here is the latest version of the Collection Sharing App as of 11-23-2024. You can login to an account you set up under "sign-up" on this webpage
and upload directly to this site OR you can just export and import specific collections and share them through another means. That being said this is very much
an ALPHA app and will have its issues.
</p>
</div>
<div class="card-action">
<a class="waves-effect waves-light grey btn left-align" href="{{ url_for('download_app')}}" download>Latest<i class="material-icons right">cloud_download</i></a>
<p class="right">alpha_v1.0</p>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
</body>
</html>

28
templates/login.html Normal file
View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Signup</title>
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<!-- Compiled and minified JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
</head>
<body>
{% block content %}
<div class="container section">
<h1>Login to your account</h1>
<form action="#" method="post">
<label for="username">Username:</label>
<input type="text" name="username" />
<label for="password">Password:</label>
<input type="password" name="password" />
<button type="submit">Submit</button>
</form>
</div>
{% endblock %}
</body>
</html>

75
templates/profile.html Normal file
View File

@ -0,0 +1,75 @@
{% extends "template.html" %}
{% block content %}
<div class="container" style="background-color: white;">
<div class="col s12 m8">
<div class="card-panel grey lighten-5 z-depth-1">
<div class="row">
<div class="row">
<div class="col s4">
{% if current_user.l_avatar == 'default' %}
<img src="{{ avatars.default() }}" alt="" class="circle">
{% else %}
<img src="{{url_for("static", filename=current_user.l_avatar)}}" class="circle responsive-img">
{% endif %}
</div>
<div class="col s4 center">
<h1>Profile</h1>
</div>
</div>
<div class="row">
<div class="input-field col s12 valign-center">
<input value="{{current_user.username}}" placeholder="Placeholder" id="username" type="text" class="validate" disabled>
<label for="username">Username</label>
</div>
<div class="input-field col s12">
<input value="{{current_user.email}}" placeholder="" id="email" type="text" class="validate" disabled>
<label for="email">Email</label>
</div>
<div class="input-field col s12">
<input value="{{current_user.friendcode}}" placeholder="" id="friendcode" type="text" class="validate " disabled>
<label for="friendcode">Friend Code</label>
</div>
</div>
<div class="row">
<div class="input-field col s6">
<input value="{{current_user.firstname}}" placeholder="" id="firstname" type="text" class="validate ">
<label for="firstname">First Name</label>
</div>
<div class="input-field col s6">
<input value="{{current_user.lastname}}" placeholder="" id="lastname" type="text" class="validate ">
<label for="lastname">Last Name</label>
</div>
</div>
<form method="post" action="/profile/upload" id="upload_avatar" enctype="multipart/form-data">
<div class="row col s12">
<div class="col s9">
<div class="file-field input-field">
<div class="btn green" type="file">
<span>File</span>
<input name="file" type="file">
</div>
<div class="file-path-wrapper">
<input class="file-path validate" type="text" placeholder="Change/Upload Avatar...">
</div>
</div>
</div>
<div class="col s3">
<div class="input-field">
<button class="btn waves-effect waves-light green" type="submit" name=action form="upload_avatar">Submit
<i class="material-icons right">send</i>
</button>
</div>
</div>
</div>
</form>
</form>
</div>
</div>
</div>
{% endblock %}

112
templates/signup.html Normal file
View File

@ -0,0 +1,112 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Signup</title>
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<!-- Compiled and minified JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
</head>
<body>
{% block content %}
<div class="container section">
<h1 class="center">Signup</h1>
<div class="row center">
<form class="col s12" method="post">
<div class="row">
<div class="input-field col s6">
<input placeholder="Placeholder" id="first_name" type="text" class="validate" name="firstname">
<label for="first_name">First Name</label>
</div>
<div class="input-field col s6">
<input id="last_name" type="text" class="validate" name="lastname">
<label for="last_name">Last Name</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input id="username" type="text" class="validate" name="username">
<label for="username">Username</label>
</div>
</div>
<div class="row">
<div class="input-field col s5">
<input id="password" type="password" class="validate" name="password">
<label for="password">Password</label>
</div>
<div class="input-field col s5">
<input id="confirm_password" type="password" class="validate" name="confirm_password">
<label for="confirm_password">Confirm Password</label>
</div>
<div class="col s2 left-align">
<p id="message"></p>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input id="email" type="email" class="validate" name="email">
<label for="email">Email</label>
</div>
</div>
<div class="row">
<button class="btn waves-effect waves-light" type="submit" name="action" id="submit">Submit
<i class="material-icons right"></i>
</button>
</div>
</form>
</div>
</div>
{% endblock %}
<script>
let match = false
let input = document.getElementById("username")
let button = document.getElementById("submit")
let pass = document.getElementById('password')
let con_pass = document.getElementById('confirm_password')
let message = document.getElementById('message')
button.disabled = true
input.addEventListener("change", stateHandle)
pass.addEventListener("change", checkPassword)
con_pass.addEventListener("change", checkPassword)
pass.addEventListener("change", stateHandle)
con_pass.addEventListener("change", stateHandle)\
function checkPassword() {
if (pass.value == "" || con_pass.value == "") {
console.log('empty')
message.style.color = 'red';
message.innerHTML = '';
match = false
} else if (pass.value != con_pass.value) {
console.log('not')
message.style.color = 'red';
message.innerHTML = 'not matching';
match = false
} else if (pass.value ==
con_pass.value) {
console.log('equal')
message.style.color = 'green';
message.innerHTML = 'matching';
match = true
}
}
function stateHandle() {
console.log(match)
if (document.getElementById("username").value != "" && match == true) {
button.disabled = false
} else {
button.disabled = true
}
}
</script>
</body>
</html>

69
templates/template.html Normal file
View File

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Treehouse - Navbar</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<header>
<ul id='dropdown' class='dropdown-content'>
<li><a href="{{ url_for('profile')}}">Profile</a></li>
<li><a href="{{ url_for('friends_code')}}">Friend Code</a></li>
<li><a href="{{ url_for('logout') }}">Logout</a></li>
</ul>
<nav class="black-text z-depth-0" style="font-family: Arial, Helvetica, sans-serif;">
<div class="nav-wrapper green lighten-2 black-text">
<a href="#" data-target="mobile-demo" class="sidenav-trigger"><i class="material-icons">menu</i></a>
<ul id="nav-mobile" class="hide-on-med-and-down">
<li><a href="{{ url_for('home') }}">Home</a></li>
{% if current_user.is_authenticated %}
<li><a href="{{ url_for('collections') }}">Collections</a></li>
<li style="" class="right"><a style="font-size: 16px; font-family: Arial, Helvetica, sans-serif;" class="dropdown-trigger" href="#!" data-target="dropdown">
<img style="vertical-align: top; padding-right: 10px; width: 60px; padding-top: 5px" class="image circle center-valign" src="{{url_for("static", filename=current_user.m_avatar)}}">{{current_user.username}}
<i class="material-icons right">arrow_drop_down</i></a>
</li>
{% else %}
<li class="right"><a href="{{ url_for('signup') }}">Sign Up</a></li>
<li class="right"><a href="{{ url_for('login') }}">Login</a></li>
{% endif %}
</ul>
</div>
</nav>
<ul class="sidenav" id="mobile-demo">
<li><a href="{{ url_for('home') }}">Home</a></li>
{% if current_user.is_authenticated %}
<li><a href="{{ url_for('collections') }}">Collections</a></li>
<li><a class="" href="#!" data-target="">{{current_user.username}}<i class="material-icons right">arrow_drop_down</i></a></li>
{% else %}
<li><a href="{{ url_for('signup') }}">Sign Up</a></li>
<li><a href="{{ url_for('login') }}">Login</a></li>
{% endif %}
</ul>
</header>
{% block content %}
{% endblock %}
<script>
$(".dropdown-trigger").dropdown({hover: false});
</script>
<script>
$(document).ready(function(){
$('.sidenav').sidenav();
});
</script>
<script>
$(document).ready(function () {
$('.modal').modal();
}
)
</script>
</body>
</html>

36
test.py Normal file
View File

@ -0,0 +1,36 @@
import requests
from base64 import b64encode
def basic_auth(username, password):
token = b64encode(f"{username}:{password}".encode('utf-8')).decode("ascii")
return f'Basic {token}'
requests.post("https://ntfy.treehousefullofstars.com/alerts",
data="Look ma, with auth",
headers={
"Authorization": basic_auth('jadowyne', 'Jumbocarrot&001')
})
already_used = [
[(False, None), (False, None), (True, "X")],
[(False, None), (True, "Circle"), (False, None)],
[(False, None), (False, None), (False, None)]
]
already_used = [(0, 0),(0, 1),(0, 2), ...]
spot = random(len(already_used.keys()))
amount_x = 3
amount_y = 3
grid = [
[],
[],
[]
]
2, 2
state = already_used[x][y][0]