mirror of
https://github.com/Derpy-Leggies/OnlyLegs.git
synced 2025-06-29 11:36:16 +00:00
Update to reccommended file structure
Use reccommended database structure Switch to SQLite and update scheme along with it
This commit is contained in:
parent
29d204f95e
commit
a499e6c840
41 changed files with 544 additions and 342 deletions
126
gallery/__init__.py
Normal file
126
gallery/__init__.py
Normal file
|
@ -0,0 +1,126 @@
|
|||
print("""
|
||||
___ _ _
|
||||
/ _ \\ _ __ | |_ _| | ___ __ _ ___
|
||||
| | | | '_ \\| | | | | | / _ \\/ _` / __|
|
||||
| |_| | | | | | |_| | |__| __/ (_| \\__ \\
|
||||
\\___/|_| |_|_|\\__, |_____\\___|\\__, |___/
|
||||
|___/ |___/
|
||||
Created by Fluffy Bean - Version 100123
|
||||
""")
|
||||
|
||||
# Import base packages
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Import required OnlyLegs packages
|
||||
#from packages import onlylegsDB
|
||||
#onlylegsDB = onlylegsDB.DBmanager()
|
||||
#onlylegsDB.initialize()
|
||||
|
||||
#from packages import onlylegsSass
|
||||
#onlylegsSass = onlylegsSass.Sassy('default')
|
||||
|
||||
# Import flask
|
||||
from flask import *
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
def create_app(test_config=None):
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv(os.path.join('./gallery', 'user', '.env'))
|
||||
|
||||
# create and configure the app
|
||||
app = Flask(__name__)
|
||||
|
||||
app.config.from_mapping(
|
||||
SECRET_KEY=os.environ.get('FLASK_SECRET'),
|
||||
DATABASE=os.path.join(app.instance_path, 'gallery.sqlite'),
|
||||
)
|
||||
|
||||
if test_config is None:
|
||||
# load the instance config, if it exists, when not testing
|
||||
app.config.from_pyfile('config.py', silent=True)
|
||||
else:
|
||||
# load the test config if passed in
|
||||
app.config.from_mapping(test_config)
|
||||
|
||||
# ensure the instance folder exists
|
||||
try:
|
||||
os.makedirs(app.instance_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
@app.errorhandler(405)
|
||||
def method_not_allowed(e):
|
||||
error = '405'
|
||||
msg = 'Method sussy wussy'
|
||||
return render_template('error.html', error=error, msg=msg), 404
|
||||
|
||||
@app.errorhandler(404)
|
||||
def page_not_found(e):
|
||||
error = '404'
|
||||
msg = 'Could not find what you need!'
|
||||
return render_template('error.html', error=error, msg=msg), 404
|
||||
|
||||
@app.errorhandler(403)
|
||||
def forbidden(e):
|
||||
error = '403'
|
||||
msg = 'Go away! This is no place for you!'
|
||||
return render_template('error.html', error=error, msg=msg), 403
|
||||
|
||||
@app.errorhandler(410)
|
||||
def gone(e):
|
||||
error = '410'
|
||||
msg = 'The page is no longer available! *sad face*'
|
||||
return render_template('error.html', error=error, msg=msg), 410
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_server_error(e):
|
||||
error = '500'
|
||||
msg = 'Server died inside :c'
|
||||
return render_template('error.html', error=error, msg=msg), 500
|
||||
|
||||
from . import auth
|
||||
app.register_blueprint(auth.bp)
|
||||
|
||||
from . import routes
|
||||
app.register_blueprint(routes.bp)
|
||||
app.add_url_rule('/', endpoint='index')
|
||||
|
||||
|
||||
#
|
||||
# METHODS
|
||||
#
|
||||
@app.route('/fileList/<item_type>', methods=['GET'])
|
||||
def image_list(item_type):
|
||||
if request.method != 'GET':
|
||||
abort(405)
|
||||
|
||||
cursor = onlylegsDB.database.cursor()
|
||||
cursor.execute("SELECT * FROM posts ORDER BY id DESC")
|
||||
|
||||
item_list = cursor.fetchall()
|
||||
|
||||
return jsonify(item_list)
|
||||
|
||||
@app.route('/uploads/<quality>/<request_file>', methods=['GET'])
|
||||
def uploads(quality, request_file):
|
||||
if request.method != 'GET':
|
||||
abort(405)
|
||||
|
||||
quality = secure_filename(quality)
|
||||
quality_dir = os.path.join(app.config['UPLOAD_FOLDER'], quality)
|
||||
if not os.path.isdir(quality_dir):
|
||||
abort(404)
|
||||
|
||||
request_file = secure_filename(request_file)
|
||||
|
||||
if not os.path.isfile(os.path.join(quality_dir, request_file)):
|
||||
abort(404)
|
||||
|
||||
return send_from_directory(quality_dir, request_file)
|
||||
|
||||
from . import db
|
||||
db.init_app(app)
|
||||
|
||||
return app
|
93
gallery/auth.py
Normal file
93
gallery/auth.py
Normal file
|
@ -0,0 +1,93 @@
|
|||
import functools
|
||||
from flask import (
|
||||
Blueprint, flash, g, redirect, render_template, request, session, url_for
|
||||
)
|
||||
from werkzeug.security import check_password_hash, generate_password_hash
|
||||
from gallery.db import get_db
|
||||
|
||||
bp = Blueprint('auth', __name__, url_prefix='/auth')
|
||||
|
||||
|
||||
@bp.before_app_request
|
||||
def load_logged_in_user():
|
||||
user_id = session.get('user_id')
|
||||
|
||||
if user_id is None:
|
||||
g.user = None
|
||||
else:
|
||||
g.user = get_db().execute(
|
||||
'SELECT * FROM users WHERE id = ?', (user_id,)
|
||||
).fetchone()
|
||||
|
||||
|
||||
@bp.route('/register', methods=('GET', 'POST'))
|
||||
def register():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
db = get_db()
|
||||
error = None
|
||||
|
||||
if not username:
|
||||
error = 'Username is required.'
|
||||
elif not password:
|
||||
error = 'Password is required.'
|
||||
|
||||
if error is None:
|
||||
try:
|
||||
db.execute(
|
||||
"INSERT INTO users (username, email, password) VALUES (?, ?, ?)",
|
||||
(username,'dummy@email.com' , generate_password_hash(password)),
|
||||
)
|
||||
db.commit()
|
||||
except db.IntegrityError:
|
||||
error = f"User {username} is already registered."
|
||||
else:
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
flash(error)
|
||||
|
||||
return render_template('auth/register.html')
|
||||
|
||||
|
||||
@bp.route('/login', methods=('GET', 'POST'))
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
db = get_db()
|
||||
error = None
|
||||
user = db.execute(
|
||||
'SELECT * FROM users WHERE username = ?', (username,)
|
||||
).fetchone()
|
||||
|
||||
if user is None:
|
||||
error = 'Incorrect username.'
|
||||
elif not check_password_hash(user['password'], password):
|
||||
error = 'Incorrect password.'
|
||||
|
||||
if error is None:
|
||||
session.clear()
|
||||
session['user_id'] = user['id']
|
||||
return redirect(url_for('index'))
|
||||
|
||||
flash(error)
|
||||
|
||||
return render_template('auth/login.html')
|
||||
|
||||
|
||||
@bp.route('/logout')
|
||||
def logout():
|
||||
session.clear()
|
||||
return redirect(url_for('index'))
|
||||
|
||||
|
||||
def login_required(view):
|
||||
@functools.wraps(view)
|
||||
def wrapped_view(**kwargs):
|
||||
if g.user is None:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
return view(**kwargs)
|
||||
|
||||
return wrapped_view
|
41
gallery/db.py
Normal file
41
gallery/db.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
import sqlite3
|
||||
|
||||
import click
|
||||
from flask import current_app, g
|
||||
|
||||
|
||||
def get_db():
|
||||
if 'db' not in g:
|
||||
g.db = sqlite3.connect(
|
||||
current_app.config['DATABASE'],
|
||||
detect_types=sqlite3.PARSE_DECLTYPES
|
||||
)
|
||||
g.db.row_factory = sqlite3.Row
|
||||
|
||||
return g.db
|
||||
|
||||
|
||||
def close_db(e=None):
|
||||
db = g.pop('db', None)
|
||||
|
||||
if db is not None:
|
||||
db.close()
|
||||
|
||||
|
||||
def init_db():
|
||||
db = get_db()
|
||||
|
||||
with current_app.open_resource('schema.sql') as f:
|
||||
db.executescript(f.read().decode('utf8'))
|
||||
|
||||
|
||||
@click.command('init-db')
|
||||
def init_db_command():
|
||||
"""Create tables if not already created"""
|
||||
init_db()
|
||||
click.echo('Initialized the database!')
|
||||
|
||||
|
||||
def init_app(app):
|
||||
app.teardown_appcontext(close_db)
|
||||
app.cli.add_command(init_db_command)
|
91
gallery/packages/onlylegsDB.py
Normal file
91
gallery/packages/onlylegsDB.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
import datetime
|
||||
now = datetime.datetime.now()
|
||||
import sys
|
||||
import os
|
||||
|
||||
class DBmanager():
|
||||
def __init__(self):
|
||||
try:
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
from dotenv import load_dotenv
|
||||
except ImportError:
|
||||
print("Error: could not import required packages")
|
||||
sys.exit(1)
|
||||
|
||||
env_path = os.path.join('usr', '.env')
|
||||
if not os.path.exists(env_path):
|
||||
print("Error: could not find .env file")
|
||||
sys.exit(1)
|
||||
|
||||
load_dotenv(env_path)
|
||||
print(f"{now.hour}:{now.minute}:{now.second} - Connecting to database...")
|
||||
|
||||
try:
|
||||
database = mysql.connector.connect(host=os.environ.get('DB_HOST'),
|
||||
port=os.environ.get('DB_PORT'),
|
||||
database=os.environ.get('DB_NAME'),
|
||||
user=os.environ.get('DB_USER'),
|
||||
password=os.environ.get('DB_PASS')
|
||||
)
|
||||
|
||||
if database.is_connected():
|
||||
db_Info = database.get_server_info()
|
||||
print("Connected to MySQL Server version:", db_Info)
|
||||
|
||||
cursor = database.cursor()
|
||||
cursor.execute("select database();")
|
||||
|
||||
record = cursor.fetchone()
|
||||
print("Connected to database:", record[0])
|
||||
|
||||
print(f"{now.hour}:{now.minute}:{now.second} - Done!\n")
|
||||
|
||||
except Error as e:
|
||||
print("Error while connecting to Database!\nFull error:", e)
|
||||
sys.exit(1)
|
||||
|
||||
self.database = database
|
||||
|
||||
def initialize(self):
|
||||
if not os.path.exists(os.path.join('packages', 'tables', 'generate.sql')):
|
||||
print("Error: could not find tables directory")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"{now.hour}:{now.minute}:{now.second} - Initializing tables...")
|
||||
|
||||
with open(os.path.join('packages', 'tables', 'generate.sql'), 'r') as sql:
|
||||
cursor = self.database.cursor()
|
||||
query = cursor.execute(sql.read(), multi=True)
|
||||
|
||||
i = 0
|
||||
for res in query:
|
||||
print(f"Query {i+1}: Affected {res.rowcount} rows")
|
||||
i += 1
|
||||
|
||||
if not os.path.exists(os.path.join('packages', 'tables', 'junctions.sql')):
|
||||
print("Error: could not find junctions directory")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"{now.hour}:{now.minute}:{now.second} - Initializing junctions...")
|
||||
|
||||
with open(os.path.join('packages', 'tables', 'junctions.sql'), 'r') as sql:
|
||||
cursor = self.database.cursor()
|
||||
query = cursor.execute(sql.read(), multi=True)
|
||||
|
||||
i = 0
|
||||
for res in query:
|
||||
print(f"Query {i+1}: Affected {res.rowcount} rows")
|
||||
i += 1
|
||||
|
||||
self.database.commit()
|
||||
print(f"{now.hour}:{now.minute}:{now.second} - Done!\n")
|
||||
|
||||
def getImage(self, id):
|
||||
sql = "SELECT * FROM posts WHERE id = %s"
|
||||
img = (id,)
|
||||
|
||||
cursor = self.database.cursor()
|
||||
cursor.execute(sql, img)
|
||||
|
||||
return cursor.fetchone()
|
64
gallery/packages/onlylegsSass.py
Normal file
64
gallery/packages/onlylegsSass.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
import datetime
|
||||
now = datetime.datetime.now()
|
||||
import sys
|
||||
import shutil
|
||||
import os
|
||||
|
||||
class Sassy():
|
||||
def __init__(self, theme):
|
||||
print("### OnlyLegs Theme Manager ###")
|
||||
print(f"{now.hour}:{now.minute}:{now.second} - Loading theme...")
|
||||
|
||||
try:
|
||||
import sass
|
||||
except ImportError:
|
||||
print("Could not find libsass!")
|
||||
sys.exit(1)
|
||||
|
||||
theme_path = os.path.join('usr', 'themes', theme, 'style.scss')
|
||||
|
||||
if os.path.exists(theme_path):
|
||||
print(f"Theme '{theme}' found at:", theme_path)
|
||||
self.sass = sass
|
||||
self.loadTheme(theme_path)
|
||||
else:
|
||||
print("No theme found!")
|
||||
sys.exit(1)
|
||||
|
||||
font_path = os.path.join('usr', 'themes', theme, 'fonts')
|
||||
|
||||
if os.path.exists(font_path):
|
||||
print("Fonts found at:", font_path)
|
||||
self.loadFonts(font_path)
|
||||
else:
|
||||
print("No fonts found!")
|
||||
|
||||
print(f"{now.hour}:{now.minute}:{now.second} - Done!\n")
|
||||
|
||||
def loadTheme (self, theme):
|
||||
with open('static/theme/style.css', 'w') as f:
|
||||
try:
|
||||
f.write(self.sass.compile(filename=theme, output_style='compressed'))
|
||||
print("Compiled successfully to:", f.name)
|
||||
except self.sass.CompileError as e:
|
||||
print("Failed to compile!\nFull error:", e)
|
||||
sys.exit(1)
|
||||
|
||||
def loadFonts (self, font_path):
|
||||
dest = os.path.join('static', 'theme', 'fonts')
|
||||
|
||||
if os.path.exists(dest):
|
||||
print("Removing old fonts...")
|
||||
try:
|
||||
shutil.rmtree(dest)
|
||||
print("Removed old fonts!")
|
||||
except Exception as e:
|
||||
print("Failed to remove old fonts!\nFull error:", e)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
shutil.copytree(font_path, dest)
|
||||
print("Copied fonts to:", dest)
|
||||
except Exception as e:
|
||||
print("Failed to copy fonts!\nFull error:", e)
|
||||
sys.exit(1)
|
67
gallery/packages/tables/generate.sql
Normal file
67
gallery/packages/tables/generate.sql
Normal file
|
@ -0,0 +1,67 @@
|
|||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT(69) NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
username VARCHAR(255) NOT NULL UNIQUE,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS posts (
|
||||
id INT(69) PRIMARY KEY AUTO_INCREMENT,
|
||||
file_name VARCHAR(255) NOT NULL UNIQUE,
|
||||
author_id INT(69) NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
alt TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
id INT(69) PRIMARY KEY AUTO_INCREMENT,
|
||||
author_id INT(69) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS permissions (
|
||||
id INT(69) PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INT(69) NOT NULL,
|
||||
admin BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
create_posts BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS devices (
|
||||
id INT(69) PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INT(69) NOT NULL,
|
||||
device_id VARCHAR(255) NOT NULL,
|
||||
cookie VARCHAR(255) NOT NULL,
|
||||
ip VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tokens (
|
||||
id INT(69) PRIMARY KEY AUTO_INCREMENT,
|
||||
token VARCHAR(255) NOT NULL UNIQUE,
|
||||
is_used BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS logs (
|
||||
id INT(69) PRIMARY KEY AUTO_INCREMENT,
|
||||
ip VARCHAR(255) NOT NULL,
|
||||
user_id INT(69) DEFAULT NULL,
|
||||
code INT(69) NOT NULL,
|
||||
note TEXT DEFAULT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bans (
|
||||
id INT(69) PRIMARY KEY AUTO_INCREMENT,
|
||||
ip VARCHAR(255) NOT NULL,
|
||||
code INT(69) NOT NULL,
|
||||
note TEXT DEFAULT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
5
gallery/packages/tables/junctions.sql
Normal file
5
gallery/packages/tables/junctions.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE IF NOT EXISTS group_junction (
|
||||
id INT(69) PRIMARY KEY AUTO_INCREMENT,
|
||||
group_id INT(69) NOT NULL,
|
||||
image_id INT(69) NOT NULL
|
||||
);
|
69
gallery/routes.py
Normal file
69
gallery/routes.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
from flask import (
|
||||
Blueprint, flash, g, redirect, render_template, request, url_for
|
||||
)
|
||||
from werkzeug.exceptions import abort
|
||||
|
||||
from gallery.auth import login_required
|
||||
from gallery.db import get_db
|
||||
|
||||
bp = Blueprint('routes', __name__)
|
||||
|
||||
#
|
||||
# ROUTES
|
||||
#
|
||||
@bp.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
@bp.route('/group')
|
||||
def group():
|
||||
return render_template('group.html', group_id='gwa gwa')
|
||||
|
||||
@bp.route('/group/<group_id>')
|
||||
def group_id(group_id):
|
||||
try:
|
||||
group_id = int(group_id)
|
||||
except ValueError:
|
||||
abort(404)
|
||||
|
||||
return render_template('group.html', group_id=group_id)
|
||||
|
||||
@bp.route('/upload')
|
||||
def upload():
|
||||
return render_template('upload.html')
|
||||
|
||||
@bp.route('/upload/form', methods=['POST'])
|
||||
def upload_form():
|
||||
if request.method != 'POST':
|
||||
abort(405)
|
||||
|
||||
return 'balls'
|
||||
|
||||
@bp.route('/profile')
|
||||
def profile():
|
||||
return render_template('profile.html', user_id='gwa gwa')
|
||||
|
||||
@bp.route('/profile/<user_id>')
|
||||
def profile_id(user_id):
|
||||
try:
|
||||
user_id = int(user_id)
|
||||
except ValueError:
|
||||
abort(404)
|
||||
|
||||
return render_template('profile.html', user_id=user_id)
|
||||
|
||||
@bp.route('/settings')
|
||||
def settings():
|
||||
return render_template('settings.html')
|
||||
|
||||
@bp.route('/image/<request_id>')
|
||||
def image(request_id):
|
||||
# Check if request_id is valid
|
||||
try:
|
||||
request_id = int(request_id)
|
||||
except ValueError:
|
||||
abort(404)
|
||||
|
||||
result = onlylegsDB.getImage(request_id)
|
||||
|
||||
return render_template('image.html', fileName=result[1], id=request_id)
|
78
gallery/schema.sql
Normal file
78
gallery/schema.sql
Normal file
|
@ -0,0 +1,78 @@
|
|||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
email TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS posts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_name TEXT NOT NULL UNIQUE,
|
||||
author_id INTEGER NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
alt TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (author_id) REFERENCES users (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
author_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (author_id) REFERENCES users (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS group_junction (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
group_id INTEGER NOT NULL,
|
||||
image_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (group_id) REFERENCES groups (id),
|
||||
FOREIGN KEY (image_id) REFERENCES posts (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS permissions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
admin BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
create_posts BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS devices (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
device_id TEXT NOT NULL,
|
||||
cookie TEXT NOT NULL,
|
||||
ip TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tokens (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
token TEXT NOT NULL UNIQUE,
|
||||
is_used BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
used_by INTEGER DEFAULT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (used_by) REFERENCES users (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ip TEXT NOT NULL,
|
||||
user_id INTEGER DEFAULT NULL,
|
||||
code INTEGER NOT NULL,
|
||||
note TEXT DEFAULT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bans (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
ip TEXT NOT NULL,
|
||||
code INTEGER NOT NULL,
|
||||
note TEXT DEFAULT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
BIN
gallery/static/images/leaves.jpg
Normal file
BIN
gallery/static/images/leaves.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 MiB |
24
gallery/templates/auth/login.html
Normal file
24
gallery/templates/auth/login.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
{% extends 'layout.html' %}
|
||||
|
||||
{% block header %}
|
||||
<img src="{{ url_for('static', filename='images/leaves.jpg') }}" alt="leaves" onload="imgFade(this)" style="display: none;"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="app">
|
||||
<h1>Login</h1>
|
||||
<div id="login" class="login">
|
||||
<form method="post">
|
||||
<label for="username">Username</label>
|
||||
<input name="username" id="username" required>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" name="password" id="password" required>
|
||||
<input type="submit" value="Log In">
|
||||
</form>
|
||||
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="flash">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
24
gallery/templates/auth/register.html
Normal file
24
gallery/templates/auth/register.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
{% extends 'layout.html' %}
|
||||
|
||||
{% block header %}
|
||||
<img src="{{ url_for('static', filename='images/leaves.jpg') }}" alt="leaves" onload="imgFade(this)" style="display: none;"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="app">
|
||||
<h1>register</h1>
|
||||
<div id="register" class="register">
|
||||
<form method="post">
|
||||
<label for="username">Username</label>
|
||||
<input name="username" id="username" required>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" name="password" id="password" required>
|
||||
<input type="submit" value="Register">
|
||||
</form>
|
||||
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="flash">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
12
gallery/templates/error.html
Normal file
12
gallery/templates/error.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% extends 'layout.html' %}
|
||||
|
||||
{% block header %}
|
||||
<img src="{{ url_for('static', filename='images/leaves.jpg') }}" alt="leaves" onload="imgFade(this)" style="display: none;"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="app err-warning">
|
||||
<h1>{{error}}</h1>
|
||||
<p>{{msg}}</p>
|
||||
</div>
|
||||
{% endblock %}
|
12
gallery/templates/group.html
Normal file
12
gallery/templates/group.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
{% extends 'layout.html' %}
|
||||
|
||||
{% block header %}
|
||||
<img src="{{ url_for('static', filename='images/leaves.jpg') }}" alt="leaves" onload="imgFade(this)" style="display: none;"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="app">
|
||||
<h1>Image Group</h1>
|
||||
<p>{{group_id}}</p>
|
||||
</div>
|
||||
{% endblock %}
|
17
gallery/templates/image.html
Normal file
17
gallery/templates/image.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
{% extends 'layout.html' %}
|
||||
|
||||
{% block header %}
|
||||
<img src="{{ url_for('static', filename='images/leaves.jpg') }}" alt="leaves" onload="imgFade(this)" style="display: none;"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="app">
|
||||
<div class="image__container">
|
||||
<img class="image__item" src="/uploads/original/{{ fileName }}" onload="imgFade(this)" style="display:none;"/>
|
||||
</div>
|
||||
<div class="image__info">
|
||||
<h2>{{ fileName }}</h2>
|
||||
<p>{{ id }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
55
gallery/templates/index.html
Normal file
55
gallery/templates/index.html
Normal file
|
@ -0,0 +1,55 @@
|
|||
{% extends 'layout.html' %}
|
||||
|
||||
{% block header %}
|
||||
<img src="{{ url_for('static', filename='images/leaves.jpg') }}" alt="leaves" onload="imgFade(this)" style="display: none;"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="app">
|
||||
<h1>Gallery</h1>
|
||||
<div id="gallery" class="gallery"></div>
|
||||
</div>
|
||||
<script>
|
||||
let imageList = [];
|
||||
let imageIndex = 0;
|
||||
|
||||
function loadMore(startIndex, amount = 20) {
|
||||
for (let i = startIndex; i < startIndex + amount; i++) {
|
||||
if (i < imageList.length) {
|
||||
loadImg(imageList[i][0], imageList[i][1]);
|
||||
}
|
||||
}
|
||||
imageIndex = startIndex + amount;
|
||||
}
|
||||
|
||||
function loadImg(id, fileName) {
|
||||
var imgDiv = `
|
||||
<a class="gallery__item" href="/image/${id}">
|
||||
<div class="gallery__item-info">
|
||||
<p>${id}</p>\
|
||||
<h2>${fileName}</h2>
|
||||
</div>
|
||||
<span class="gallery__item-filter"></span>
|
||||
<img class="gallery__item-image" src="https://supersecreteuploadtest.leggy.dev/usr/images/${fileName}" onload="imgFade(this)" style="display:none;">
|
||||
</a>
|
||||
`;
|
||||
|
||||
$(imgDiv).hide().appendTo('#gallery').fadeIn(250);
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/fileList/original',
|
||||
type: 'get',
|
||||
success: function(response) {
|
||||
imageList = response;
|
||||
loadMore(0, 30);
|
||||
}
|
||||
});
|
||||
|
||||
$(window).scroll(function() {
|
||||
if ($(window).height() + $(window).scrollTop() >= $(document).height() - 500) {
|
||||
loadMore(imageIndex);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
91
gallery/templates/layout.html
Normal file
91
gallery/templates/layout.html
Normal file
|
@ -0,0 +1,91 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Gallery</title>
|
||||
<link rel="stylesheet" href="{{url_for('static', filename='theme/style.css')}}" defer>
|
||||
<script
|
||||
src="https://code.jquery.com/jquery-3.6.0.min.js"
|
||||
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
|
||||
crossorigin="anonymous">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="navRoot">
|
||||
<div>
|
||||
<a href="{{url_for('index')}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" width="24" fill="currentColor">
|
||||
<path d="M2 8v10h12V8H2zm2-2V2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-2v4a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2zm2 0h8a2 2 0 0 1 2 2v4h2V2H6v4zm0 9a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"></path><path d="M7 6a3 3 0 1 1 6 0h-2a1 1 0 0 0-2 0H7zm1.864 13.518l2.725-4.672a1 1 0 0 1 1.6-.174l1.087 1.184 1.473-1.354-1.088-1.183a3 3 0 0 0-4.8.52L7.136 18.51l1.728 1.007zm6.512-12.969a2.994 2.994 0 0 1 3.285.77l1.088 1.183-1.473 1.354-1.087-1.184A1 1 0 0 0 16 8.457V8c0-.571-.24-1.087-.624-1.451z"></path>
|
||||
</svg>
|
||||
<span>Home</span>
|
||||
</a>
|
||||
|
||||
<a href="{{url_for('group')}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -4 24 24" width="24" fill="currentColor">
|
||||
<path d="M17 4H9.415l-.471-1.334A1.001 1.001 0 0 0 8 2H3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1zm-6.17-2H17a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h5c1.306 0 2.417.835 2.83 2z"></path>
|
||||
</svg>
|
||||
<span>Groups</span>
|
||||
</a>
|
||||
|
||||
<a href="{{url_for('upload')}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -5 24 24" width="24" fill="currentColor">
|
||||
<path d="M8 3.414v5.642a1 1 0 1 1-2 0V3.414L4.879 4.536A1 1 0 0 1 3.464 3.12L6.293.293a1 1 0 0 1 1.414 0l2.829 2.828A1 1 0 1 1 9.12 4.536L8 3.414zM1 12h12a1 1 0 0 1 0 2H1a1 1 0 0 1 0-2z"></path>
|
||||
</svg>
|
||||
<span>Upload</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{url_for('profile')}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" width="24" fill="currentColor">
|
||||
<path d="M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zm0-14a4 4 0 0 1 4 4v2a4 4 0 1 1-8 0V8a4 4 0 0 1 4-4zm0 2a2 2 0 0 0-2 2v2a2 2 0 1 0 4 0V8a2 2 0 0 0-2-2zM5.91 16.876a8.033 8.033 0 0 1-1.58-1.232 5.57 5.57 0 0 1 2.204-1.574 1 1 0 1 1 .733 1.86c-.532.21-.993.538-1.358.946zm8.144.022a3.652 3.652 0 0 0-1.41-.964 1 1 0 1 1 .712-1.868 5.65 5.65 0 0 1 2.284 1.607 8.032 8.032 0 0 1-1.586 1.225z"></path>
|
||||
</svg>
|
||||
<span>Profile</span>
|
||||
</a>
|
||||
|
||||
<a href="{{url_for('settings')}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -2 24 24" width="24" fill="currentColor">
|
||||
<path d="M9.815 3.094a3.467 3.467 0 0 1-2.78-1.09l-.084-.001a3.467 3.467 0 0 1-2.781 1.09 3.477 3.477 0 0 1-1.727 2.51 3.471 3.471 0 0 1 0 2.794 3.477 3.477 0 0 1 1.727 2.51 3.467 3.467 0 0 1 2.78 1.09h.084a3.467 3.467 0 0 1 2.78-1.09 3.477 3.477 0 0 1 1.727-2.51 3.471 3.471 0 0 1 0-2.794 3.477 3.477 0 0 1-1.726-2.51zM14 5.714a1.474 1.474 0 0 0 0 2.572l-.502 1.684a1.473 1.473 0 0 0-1.553 2.14l-1.443 1.122A1.473 1.473 0 0 0 8.143 14l-2.304-.006a1.473 1.473 0 0 0-2.352-.765l-1.442-1.131A1.473 1.473 0 0 0 .5 9.968L0 8.278a1.474 1.474 0 0 0 0-2.555l.5-1.69a1.473 1.473 0 0 0 1.545-2.13L3.487.77A1.473 1.473 0 0 0 5.84.005L8.143 0a1.473 1.473 0 0 0 2.358.768l1.444 1.122a1.473 1.473 0 0 0 1.553 2.14L14 5.714zm-5.812 9.198a7.943 7.943 0 0 0 2.342-.73 3.468 3.468 0 0 1-.087.215 3.477 3.477 0 0 1 1.727 2.51 3.467 3.467 0 0 1 2.78 1.09h.084a3.467 3.467 0 0 1 2.78-1.09 3.477 3.477 0 0 1 1.727-2.51 3.471 3.471 0 0 1 0-2.794 3.477 3.477 0 0 1-1.726-2.51 3.467 3.467 0 0 1-2.78-1.09h-.084l-.015.016a8.077 8.077 0 0 0 .002-2.016L16.144 6a1.473 1.473 0 0 0 2.358.768l1.444 1.122a1.473 1.473 0 0 0 1.553 2.14L22 11.714a1.474 1.474 0 0 0 0 2.572l-.502 1.684a1.473 1.473 0 0 0-1.553 2.14l-1.443 1.122a1.473 1.473 0 0 0-2.359.768l-2.304-.006a1.473 1.473 0 0 0-2.352-.765l-1.442-1.131a1.473 1.473 0 0 0-1.545-2.13l-.312-1.056zM7 10a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm8 8a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"></path>
|
||||
</svg>
|
||||
<span>Settings</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<main>
|
||||
<header>
|
||||
{% block header %}{% endblock %}
|
||||
<span></span>
|
||||
</header>
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
<i class="ph-arrow-circle-up" id="topButton"></i>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
let navToggle = true;
|
||||
|
||||
document.onscroll = function() {
|
||||
document.querySelector('header').style.opacity = `${1 - window.scrollY / 169}`;
|
||||
document.querySelector('header img').style.cssText = `object-position: center ${window.scrollY / 2}px`;
|
||||
|
||||
if (document.body.scrollTop > 300 || document.documentElement.scrollTop > 20) {
|
||||
document.querySelector('#topButton').style.opacity = 1;
|
||||
document.querySelector('#topButton').style.right = "0.75rem";
|
||||
} else {
|
||||
document.querySelector('#topButton').style.opacity = 0;
|
||||
document.querySelector('#topButton').style.right = "-3rem";
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelector('#topButton').onclick = function() {
|
||||
document.body.scrollTop = 0;
|
||||
document.documentElement.scrollTop = 0;
|
||||
}
|
||||
|
||||
function imgFade(obj) {
|
||||
$(obj).fadeIn(621);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
22
gallery/templates/profile.html
Normal file
22
gallery/templates/profile.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
{% extends 'layout.html' %}
|
||||
|
||||
{% block header %}
|
||||
<img src="{{ url_for('static', filename='images/leaves.jpg') }}" alt="leaves" onload="imgFade(this)" style="display: none;"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="app">
|
||||
<h1>User</h1>
|
||||
<p>{{user_id}}</p>
|
||||
<ul>
|
||||
{% if g.user %}
|
||||
<li><span>{{ g.user['username'] }}</span>
|
||||
<li><a href="{{ url_for('auth.logout') }}">Log Out</a>
|
||||
{% else %}
|
||||
<li><a href="{{ url_for('auth.register') }}">Register</a>
|
||||
<li><a href="{{ url_for('auth.login') }}">Log In</a>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
11
gallery/templates/settings.html
Normal file
11
gallery/templates/settings.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
{% extends 'layout.html' %}
|
||||
|
||||
{% block header %}
|
||||
<img src="{{ url_for('static', filename='images/leaves.jpg') }}" alt="leaves" onload="imgFade(this)" style="display: none;"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="app">
|
||||
<h1>Settings</h1>
|
||||
</div>
|
||||
{% endblock %}
|
100
gallery/templates/upload.html
Normal file
100
gallery/templates/upload.html
Normal file
|
@ -0,0 +1,100 @@
|
|||
{% extends 'layout.html' %}
|
||||
|
||||
{% block header %}
|
||||
<img src="{{ url_for('static', filename='images/leaves.jpg') }}" alt="leaves" onload="imgFade(this)" style="display: none;"/>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="app">
|
||||
<h1>Upload!!!!!</h1>
|
||||
<div id="upload" class="upload">
|
||||
<form method="post" enctype="multipart/form-data" id="uploadForm" style="display: flex;flex-direction: column;gap: 1rem;">
|
||||
<label>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-5 -5 24 24" width="24" fill="currentColor">
|
||||
<path d="M8 3.414v5.642a1 1 0 1 1-2 0V3.414L4.879 4.536A1 1 0 0 1 3.464 3.12L6.293.293a1 1 0 0 1 1.414 0l2.829 2.828A1 1 0 1 1 9.12 4.536L8 3.414zM1 12h12a1 1 0 0 1 0 2H1a1 1 0 0 1 0-2z"></path>
|
||||
</svg>
|
||||
<input type="file" name="file" id="file" class="input-file"/>
|
||||
</label>
|
||||
|
||||
|
||||
<label>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -2 24 24" width="24" fill="currentColor">
|
||||
<path d="M14 8.322V2H2v12h3.576l3.97-5.292A3 3 0 0 1 14 8.322zm0 3.753l-1.188-2.066a1 1 0 0 0-1.667-.101L8.076 14H14v-1.925zM14 16H2v2h12v-2zM2 0h12a2 2 0 0 1 2 2v16a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm4 9a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"></path>
|
||||
</svg>
|
||||
<input type="text" name="alt" placeholder="alt" id="alt"/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -2 24 24" width="24" fill="currentColor">
|
||||
<path d="M5.72 14.456l1.761-.508 10.603-10.73a.456.456 0 0 0-.003-.64l-.635-.642a.443.443 0 0 0-.632-.003L6.239 12.635l-.52 1.82zM18.703.664l.635.643c.876.887.884 2.318.016 3.196L8.428 15.561l-3.764 1.084a.901.901 0 0 1-1.11-.623.915.915 0 0 1-.002-.506l1.095-3.84L15.544.647a2.215 2.215 0 0 1 3.159.016zM7.184 1.817c.496 0 .898.407.898.909a.903.903 0 0 1-.898.909H3.592c-.992 0-1.796.814-1.796 1.817v10.906c0 1.004.804 1.818 1.796 1.818h10.776c.992 0 1.797-.814 1.797-1.818v-3.635c0-.502.402-.909.898-.909s.898.407.898.91v3.634c0 2.008-1.609 3.636-3.593 3.636H3.592C1.608 19.994 0 18.366 0 16.358V5.452c0-2.007 1.608-3.635 3.592-3.635h3.592z"></path>
|
||||
</svg>
|
||||
<input type="text" name="description" placeholder="description" id="description"/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -3 24 24" width="24" fill="currentColor">
|
||||
<path d="M11.586 15.071L13 13.657l1.414 1.414 6.165-6.165 1.09-3.552-2.484-2.483-1.079.336-1.598-1.598L18.591.96a2 2 0 0 1 2.008.496l2.483 2.483a2 2 0 0 1 .498 2L22.345 9.97l-7.93 7.93-2.83-2.828zM14.236.75l2.482 2.483a2 2 0 0 1 .498 2l-1.235 4.028-7.93 7.931-7.78-7.778L8.17 1.516 12.227.254a2 2 0 0 1 2.008.496zM3.1 9.414l4.95 4.95 6.164-6.165 1.09-3.552-2.484-2.483-3.585 1.115L3.1 9.414zm7.424-2.475a1.5 1.5 0 1 1 2.121-2.121 1.5 1.5 0 0 1-2.12 2.121zm6.886 1.022l.782-2.878c.45.152.755.325.917.518a1.5 1.5 0 0 1-.185 2.113c-.29.244-.795.326-1.514.247z"></path>
|
||||
</svg>
|
||||
<input type="text" name="tags" placeholder="tags" id="tags"/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-2 -4 24 24" width="24" fill="currentColor">
|
||||
<path d="M10.83 2H17a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h5c1.306 0 2.417.835 2.83 2zM17 4H9.415l-.471-1.334A1.001 1.001 0 0 0 8 2H3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1z"></path><path d="M1 5h18v2H1z"></path>
|
||||
</svg>
|
||||
<span>group1</span>
|
||||
<input type="checkbox" name="group" placeholder="group"/>
|
||||
|
||||
<span>group2</span>
|
||||
<input type="checkbox" name="group" placeholder="group"/>
|
||||
|
||||
<span>group3</span>
|
||||
<input type="checkbox" name="group" placeholder="group"/>
|
||||
|
||||
<span>group4</span>
|
||||
<input type="checkbox" name="group" placeholder="group"/>
|
||||
</label>
|
||||
|
||||
<input type="submit" value="Upload" name="submit" id="submit"/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$("#uploadForm").submit(function(event) {
|
||||
// AJAX takes control of subby form
|
||||
event.preventDefault();
|
||||
|
||||
// Check for empty upload
|
||||
if ($("#file").val() === "") {
|
||||
alert('Gwha! Pls provide image');
|
||||
} else {
|
||||
// Make form
|
||||
var formData = new FormData();
|
||||
formData.append("file", $("#file").prop("files")[0]);
|
||||
formData.append("alt", $("#alt").val());
|
||||
formData.append("description", $("#description").val());
|
||||
formData.append("tags", $("#tags").val());
|
||||
formData.append("submit", $("#submit").val());
|
||||
|
||||
// Upload the information
|
||||
$.ajax({
|
||||
url: '/upload/form',
|
||||
type: 'post',
|
||||
data: formData,
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function (response) {
|
||||
alert(response)
|
||||
}
|
||||
});
|
||||
|
||||
// Empty values
|
||||
$("#image").val("");
|
||||
$("#alt").val("");
|
||||
$("#description").val("");
|
||||
$("#tags").val("");
|
||||
$("#submit").val("");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
13
gallery/user/example.env
Normal file
13
gallery/user/example.env
Normal file
|
@ -0,0 +1,13 @@
|
|||
# DATABASE CONFIGURATION
|
||||
DB_NAME = onlylegs
|
||||
DB_USER = root
|
||||
DB_PASS =
|
||||
DB_HOST = localhost
|
||||
DB_PORT =
|
||||
|
||||
# EMAIL CONFIGURATION
|
||||
EMAIL_HOST = smtp.example.com
|
||||
EMAIL_PORT = 465
|
||||
EMAIL_HOST_USER = noreply@example.com
|
||||
EMAIL_HOST_PASSWORD = supersecurepassword
|
||||
EMAIL_USE_TLS = True
|
15
gallery/user/themes/default/buttons/btn.scss
Normal file
15
gallery/user/themes/default/buttons/btn.scss
Normal file
|
@ -0,0 +1,15 @@
|
|||
.btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
border: 1px solid rgba($white100, 0.3);
|
||||
background-color: rgba($white100, 0);
|
||||
color: $white100;
|
||||
|
||||
transition: background-color 0.3s ease-in-out, border-color 0.3s ease-in-out, color 0.3s ease-in-out;
|
||||
|
||||
&:active {
|
||||
border-color: rgba($white100, 1);
|
||||
color: $white100;
|
||||
}
|
||||
}
|
29
gallery/user/themes/default/buttons/up.scss
Normal file
29
gallery/user/themes/default/buttons/up.scss
Normal file
|
@ -0,0 +1,29 @@
|
|||
#topButton {
|
||||
margin: 0;
|
||||
padding: 0.25rem;
|
||||
|
||||
position: fixed;
|
||||
bottom: 0.75rem;
|
||||
right: -3rem;
|
||||
|
||||
font-size: 3rem;
|
||||
|
||||
display: flex; // hidden
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
border-radius: 50%;
|
||||
background-color: $black300;
|
||||
opacity: 0; // hidden
|
||||
|
||||
z-index: 2;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
transition: all 0.2s cubic-bezier(.86, 0, .07, 1);
|
||||
|
||||
&:hover {
|
||||
background-color: $black200;
|
||||
color: $green;
|
||||
}
|
||||
}
|
BIN
gallery/user/themes/default/fonts/Hubot-Sans.woff2
Normal file
BIN
gallery/user/themes/default/fonts/Hubot-Sans.woff2
Normal file
Binary file not shown.
BIN
gallery/user/themes/default/fonts/Mona-Sans.woff2
Normal file
BIN
gallery/user/themes/default/fonts/Mona-Sans.woff2
Normal file
Binary file not shown.
BIN
gallery/user/themes/default/fonts/worksans-black.woff2
Normal file
BIN
gallery/user/themes/default/fonts/worksans-black.woff2
Normal file
Binary file not shown.
BIN
gallery/user/themes/default/fonts/worksans-bold.woff2
Normal file
BIN
gallery/user/themes/default/fonts/worksans-bold.woff2
Normal file
Binary file not shown.
BIN
gallery/user/themes/default/fonts/worksans-regular.woff2
Normal file
BIN
gallery/user/themes/default/fonts/worksans-regular.woff2
Normal file
Binary file not shown.
283
gallery/user/themes/default/style.scss
Normal file
283
gallery/user/themes/default/style.scss
Normal file
|
@ -0,0 +1,283 @@
|
|||
@import 'variables/variables';
|
||||
@import 'variables/fonts';
|
||||
|
||||
@import 'ui/reset';
|
||||
@import 'ui/nav';
|
||||
@import 'ui/main';
|
||||
|
||||
@import 'buttons/btn';
|
||||
@import 'buttons/up';
|
||||
|
||||
.app {
|
||||
margin: 0 0 0 3.5rem;
|
||||
padding: 0.5rem;
|
||||
|
||||
width: auto;
|
||||
min-height: 100vh;
|
||||
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
|
||||
background-color: $black100;
|
||||
color: $white100;
|
||||
|
||||
box-sizing: border-box;
|
||||
z-index: 1;
|
||||
overflow: unset;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-family: $font-header;
|
||||
font-size: 2.5rem;
|
||||
font-stretch: ultra-expanded;
|
||||
font-weight: 600;
|
||||
|
||||
color: $green;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes imgLoading {
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.err-warning {
|
||||
min-height: 60vh;
|
||||
|
||||
gap: 0;
|
||||
|
||||
h1 {
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
|
||||
font-family: $font-header;
|
||||
font-size: 5rem;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
|
||||
color: $green;
|
||||
}
|
||||
p {
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
|
||||
font-family: $font-body;
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
|
||||
color: $white100;
|
||||
}
|
||||
}
|
||||
|
||||
.gallery {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
width: 100%;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto auto auto auto;
|
||||
gap: 0.5rem;
|
||||
|
||||
.gallery__item {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
height: auto;
|
||||
|
||||
position: relative;
|
||||
|
||||
background: linear-gradient(-45deg, $black100, $black400 40%, $black100);
|
||||
background-size: 400% 400%;
|
||||
border-radius: $rad;
|
||||
animation: imgLoading 10s ease infinite;
|
||||
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
|
||||
.gallery__item-info {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
|
||||
background-image: linear-gradient(to bottom, #00000000, rgba($black100, 0.8));
|
||||
|
||||
z-index: +1;
|
||||
|
||||
opacity: 0; // hide
|
||||
transform: scale(1.05); // scale up
|
||||
transition: all 0.5s cubic-bezier(.79, .14, .15, .86);
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
padding: 0 1rem 0.5rem;
|
||||
|
||||
font-family: $font-header;
|
||||
font-size: 1rem;
|
||||
font-stretch: ultra-expanded;
|
||||
font-weight: 600;
|
||||
|
||||
color: $green;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
||||
opacity: 0; // hide
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0 1rem 0.5rem;
|
||||
|
||||
font-family: $font-body;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
|
||||
color: $white100;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
||||
opacity: 0; // hide
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
|
||||
h2,
|
||||
p {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gallery__item-image {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
|
||||
border-radius: $rad;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.image__container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
width: 100%;
|
||||
height: auto;
|
||||
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
|
||||
border-radius: $rad;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
img {
|
||||
margin: auto;
|
||||
padding: 0;
|
||||
|
||||
max-width: 100%;
|
||||
max-height: 75vh;
|
||||
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
|
||||
border-radius: $rad;
|
||||
}
|
||||
}
|
||||
|
||||
.image__info {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
background-color: $black200;
|
||||
border-radius: calc($rad / 2);
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
padding: 0 1rem 0.5rem;
|
||||
|
||||
font-family: $font-header;
|
||||
font-size: 1rem;
|
||||
font-stretch: ultra-expanded;
|
||||
font-weight: 600;
|
||||
|
||||
color: $green;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0 1rem 0.5rem;
|
||||
|
||||
font-family: $font-body;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
|
||||
color: $white100;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
54
gallery/user/themes/default/ui/main.scss
Normal file
54
gallery/user/themes/default/ui/main.scss
Normal file
|
@ -0,0 +1,54 @@
|
|||
main {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
background-color: $black100;
|
||||
color: $white100;
|
||||
|
||||
min-height: 100vh;
|
||||
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
|
||||
header {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
|
||||
position: relative;
|
||||
|
||||
background-color: $black200;
|
||||
border-radius: $rad;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
filter: blur(0.5rem);
|
||||
|
||||
object-fit: cover;
|
||||
object-position: center 0px;
|
||||
}
|
||||
|
||||
span {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background-image: linear-gradient(to bottom, #00000000, rgba($black100, 1));
|
||||
|
||||
z-index: +1;
|
||||
}
|
||||
}
|
||||
}
|
95
gallery/user/themes/default/ui/nav.scss
Normal file
95
gallery/user/themes/default/ui/nav.scss
Normal file
|
@ -0,0 +1,95 @@
|
|||
nav {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
max-width: 100vw;
|
||||
width: 3.5rem;
|
||||
height: 100dvh;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
background-color: $black300;
|
||||
color: $white100;
|
||||
|
||||
box-sizing: border-box;
|
||||
z-index: 2;
|
||||
transition: width 0.4s cubic-bezier(.76,0,.17,1), background-color 0.3s ease-in-out;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
//gap: 0.25rem;
|
||||
|
||||
a {
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
|
||||
width: 3.5rem;
|
||||
height: 3.5rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
//gap: 0.5rem;
|
||||
|
||||
position: relative;
|
||||
|
||||
text-decoration: none;
|
||||
color: $white100;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
i, svg {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
color: $white100;
|
||||
transition: color 0.2s ease-out;
|
||||
}
|
||||
|
||||
span {
|
||||
margin: 0;
|
||||
padding: 0.5rem 0.75rem;
|
||||
|
||||
display: block;
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 3rem;
|
||||
transform: translateY(-50%);
|
||||
|
||||
font-family: $font-body;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
|
||||
background-color: $black300;
|
||||
color: $white100;
|
||||
opacity: 0;
|
||||
border-radius: $rad;
|
||||
|
||||
transition: opacity 0.2s cubic-bezier(.76,0,.17,1), left 0.2s cubic-bezier(.76,0,.17,1);
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
//background-color: $black200;
|
||||
|
||||
i, svg {
|
||||
color: $green;
|
||||
}
|
||||
|
||||
span {
|
||||
opacity: 1;
|
||||
left: 3.8rem;
|
||||
//transition-delay: 0.5s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
gallery/user/themes/default/ui/reset.scss
Normal file
16
gallery/user/themes/default/ui/reset.scss
Normal file
|
@ -0,0 +1,16 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
min-height: 100vh;
|
||||
|
||||
background-color: $black100;
|
||||
|
||||
scroll-behavior: smooth;
|
||||
}
|
35
gallery/user/themes/default/variables/fonts.scss
Normal file
35
gallery/user/themes/default/variables/fonts.scss
Normal file
|
@ -0,0 +1,35 @@
|
|||
@font-face {
|
||||
font-family: "Mona-Sans";
|
||||
src: url("fonts/Mona-Sans.woff2") format("woff2 supports variations"),
|
||||
url("fonts/Mona-Sans.woff2") format("woff2-variations");
|
||||
font-weight: 200 900;
|
||||
font-stretch: 75% 125%;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Hubot-Sans";
|
||||
src: url("fonts/Hubot-Sans.woff2") format("woff2 supports variations"),
|
||||
url("fonts/Hubot-Sans.woff2") format("woff2-variations");
|
||||
font-weight: 200 900;
|
||||
font-stretch: 75% 125%;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
src: url('fonts/worksans-regular.woff2');
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
src: url('fonts/worksans-bold.woff2');
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
src: url('fonts/worksans-black.woff2');
|
||||
font-weight: 900;
|
||||
}
|
18
gallery/user/themes/default/variables/variables.scss
Normal file
18
gallery/user/themes/default/variables/variables.scss
Normal file
|
@ -0,0 +1,18 @@
|
|||
$black100: #151515;
|
||||
$black200: #121212;
|
||||
$black300: #101010;
|
||||
$black400: #0e0e0e;
|
||||
|
||||
$white100: #e8e3e3;
|
||||
|
||||
$red: #B66467;
|
||||
$orange: #D8A657;
|
||||
$yellow: #D9BC8C;
|
||||
$green: #8C977D;
|
||||
$blue: #8DA3B9;
|
||||
$purple: #A988B0;
|
||||
|
||||
$rad: 3px;
|
||||
|
||||
$font-header: "Work Sans", sans-serif;
|
||||
$font-body: "Work Sans", sans-serif;
|
Loading…
Add table
Add a link
Reference in a new issue