]> littlesong.place Git - littlesongplace.git/commitdiff
Add songs database
authorChris Fulljames <christianfulljames@gmail.com>
Sat, 4 Jan 2025 14:21:35 +0000 (09:21 -0500)
committerChris Fulljames <christianfulljames@gmail.com>
Sat, 4 Jan 2025 14:21:35 +0000 (09:21 -0500)
main.py
schema.sql
templates/profile.html
todo.txt

diff --git a/main.py b/main.py
index 8a8ed81abd793097bfca81edff16d373c069d023..d22eadf7f2795f11e15003fefb0355fb2c32471d 100644 (file)
--- a/main.py
+++ b/main.py
@@ -1,15 +1,34 @@
 import os
 import sqlite3
+import sys
 import uuid
 from pathlib import Path, PosixPath
 
 import bcrypt
 import click
-from flask import Flask, render_template, request, redirect, g, session, abort
+from flask import Flask, render_template, request, redirect, g, session, abort, \
+        send_from_directory
 from werkzeug.utils import secure_filename
 
+################################################################################
+# Check for Required Environment Variables
+################################################################################
+
+REQUIRED_VARS = ["SECRET_KEY", "DATA_DIR"]
+
+for var in REQUIRED_VARS:
+    if var not in os.environ:
+        print(f"{var} not set")
+        sys.exit(1)
+
+DATA_DIR = Path(os.environ["DATA_DIR"])
+
+################################################################################
+# Routes
+################################################################################
+
 app = Flask(__name__)
-app.secret_key = "TODO"
+app.secret_key = os.environ["SECRET_KEY"]
 
 @app.route("/")
 def index():
@@ -64,6 +83,7 @@ def login_post():
     if user_data and bcrypt.checkpw(password.encode(), user_data["password"]):
         # Successful login
         session["username"] = username
+        session["userid"] = user_data["userid"]
         session.permanent = True
         return redirect("/")
 
@@ -73,6 +93,8 @@ def login_post():
 def logout():
     if "username" in session:
         session.pop("username")
+    if "userid" in session:
+        session.pop("userid")
 
     return redirect("/")
 
@@ -81,16 +103,20 @@ def users():
     users = [row["username"] for row in query_db("select username from users")]
     return render_template("users.html", users=users)
 
-@app.get("/users/<name>")
-def users_profile(name):
+@app.get("/users/<profile_username>")
+def users_profile(profile_username):
     username = session.get("username", None)
-    songsdir = f"static/users/{username}/songs/"
-    songspath = Path(f"static/users/{username}/songs")
-    songs = []
-    if songspath.exists():
-        songs = [child.name for child in songspath.iterdir() if child.suffix.lower() == ".mp3"]
-        print(songs)
-    return render_template("profile.html", name=name, username=username, songs=songs)
+
+    # Look up user data for current profile
+    profile_data = query_db("select * from users where username = ?", [profile_username], one=True)
+    if profile_data is None:
+        abort(404)
+
+    # Get songs for current profile
+    profile_userid = profile_data["userid"]
+    profile_songs_data = query_db("select * from songs where userid = ?", [profile_userid])
+
+    return render_template("profile.html", name=profile_username, username=username, songs=profile_songs_data)
 
 @app.post("/uploadsong")
 def upload_song():
@@ -98,17 +124,35 @@ def upload_song():
         abort(401)
 
     username = session["username"]
-    userpath = Path(f"static/users/{username}/songs")
+    userid = session["userid"]
+    userpath = DATA_DIR / "songs" / str(userid)
     if not userpath.exists():
         os.makedirs(userpath)
 
     file = request.files["song"]
-    filename = secure_filename(file.filename)
-    filepath = userpath / filename
+    title = request.form["title"]
+    description = request.form["description"]
+
+    # TODO: Validate song file
+
+    song_data = query_db(
+            "insert into songs (userid, title, description) values (?, ?, ?) returning (songid)",
+            [userid, title, description], one=True)
+    get_db().commit()
+
+    filepath = userpath / (str(song_data["songid"]) + ".mp3")
     file.save(filepath)
 
     return redirect(f"/users/{username}")
 
+@app.get("/song/<userid>/<songid>")
+def song(userid, songid):
+    try:
+        int(userid) # Make sure userid is a valid integer
+    except ValueError:
+        abort(404)
+
+    return send_from_directory(DATA_DIR / "songs" / userid, songid + ".mp3")
 
 ################################################################################
 # Database
@@ -117,7 +161,7 @@ def upload_song():
 def get_db():
     db = getattr(g, '_database', None)
     if db is None:
-        db = g._database = sqlite3.connect("database.db")
+        db = g._database = sqlite3.connect(DATA_DIR / "database.db")
         db.row_factory = sqlite3.Row
     return db
 
@@ -142,6 +186,23 @@ def init_db():
             db.cursor().executescript(f.read())
         db.commit()
 
+
+################################################################################
+# Generate Session Key
+################################################################################
+
+@click.command("gen-key")
+def gen_key():
+    """Generate a secret key for session cookie encryption"""
+    import secrets
+    print(secrets.token_hex())
+
+
+################################################################################
+# App Configuration
+################################################################################
+
 app.teardown_appcontext(close_db)
 app.cli.add_command(init_db)
+app.cli.add_command(gen_key)
 
index 2be86014ff96764f8ea2fe1b9bc497da04c0b80c..331c5be87881983170e9f9dbf043de49558b3d4a 100644 (file)
@@ -1,7 +1,16 @@
 DROP TABLE IF EXISTS users;
+DROP TABLE IF EXISTS songs;
 
 CREATE TABLE users (
-  id INTEGER PRIMARY KEY AUTOINCREMENT,
-  username TEXT UNIQUE NOT NULL,
-  password TEXT NOT NULL
+    userid INTEGER PRIMARY KEY AUTOINCREMENT,
+    username TEXT UNIQUE NOT NULL,
+    password TEXT NOT NULL
+);
+
+CREATE TABLE songs (
+    songid INTEGER PRIMARY KEY AUTOINCREMENT,
+    userid INTEGER NOT NULL,
+    title TEXT NOT NULL,
+    description TEXT,
+    FOREIGN KEY(userid) REFERENCES users(userid)
 );
index 7112ab544f71c23b148c197097807183192179e2..db643875cae5f535e3b19678997f53d50c7a8f61 100644 (file)
@@ -4,13 +4,19 @@
 
 {% block body %}
 
-<h1 class="name">{{ name }}</h1>
+<h1 class="profile-name">{{ name }}</h1>
 
 {% if name == username %}
 <form action="/uploadsong" method="post" enctype="multipart/form-data">
     <div class="upload-form">
         <input type="file" name="song" accept=".mp3"></input>
     </div>
+    <div class="upload-form">
+        <input type="text" name="title"></input>
+    </div>
+    <div class="upload-form">
+        <input type="text" name="description"></input>
+    </div>
     <div class="upload-form">
         <input type="submit" value="Upload"></input>
     </div>
@@ -19,8 +25,9 @@
 
 {% for song in songs %}
 <div class="song">
-    {{song}}
-    <audio src="/static/users/{{ name }}/songs/{{ song }}" controls></audio>
+    <div class="song-title">{{ song["title"] }}</div>
+    <audio src="/song/{{ song["userid"] }}/{{ song["songid"] }}" controls></audio>
+    <div class="song-description">{{ song["description"] }}</div>
 </div>
 {% endfor %}
 
index b419060bcb4f8c1681a9c3ffa62d694983c44b60..ba3f530842721897e7eb994fc84e6ab9d883b794 100644 (file)
--- a/todo.txt
+++ b/todo.txt
@@ -1,7 +1,14 @@
-- file upload
-- file download
-- user file list
-- user profile
+- song database
+- user folders outside of static tree (custom route)
+- user folders use id instead of name
+- save songs with id for name
+- validate song files
+- song collaborators
+- song tags
+- delete song
+
+- admin account(s)
+- logging
 
 URL
 secrethideout.net