aTweet (janky, messy, barely working code)

This commit is contained in:
realcgcristi 2024-09-23 22:12:41 +03:00
commit 728d481393
23 changed files with 3933 additions and 0 deletions

1
aTweet Submodule

@ -0,0 +1 @@
Subproject commit e9f513d4c94737c524698308346915e1564c8573

956
app.py Normal file
View File

@ -0,0 +1,956 @@
from flask import Flask, render_template, request, redirect, url_for, session, g, flash
import sqlite3
import os
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.utils import secure_filename
from datetime import datetime
from flask import current_app
from flask import jsonify
import markdown
import re
import bleach
def render_markdown(text):
html = markdown.markdown(text, extensions=['nl2br'])
allowed_tags = ['p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li']
allowed_attributes = {'a': ['href', 'title']}
return bleach.clean(html, tags=allowed_tags, attributes=allowed_attributes, strip=True)
def process_tweet_content(content):
content = re.sub(r'#(\w+)', r'<a href="/hashtag/\1" class="hashtag">#\1</a>', content)
content = re.sub(r'@(\w+)', r'<a href="/profile/\1" class="mention">@\1</a>', content)
return content
app = Flask(__name__)
def get_user_by_id(user_id):
db = get_db()
user = db.execute('SELECT * FROM users WHERE id = ?', (user_id,)).fetchone()
return dict(user) if user else None
def init_jinja_env(app):
app.jinja_env.globals.update(get_user_by_id=get_user_by_id)
app = Flask(__name__)
app.secret_key = 'verysecretok'
init_jinja_env(app)
@app.template_filter('replace_mentions')
def replace_mentions(content):
return bleach.linkify(re.sub(r'@(\w+)', r'<a href="/profile/\1">@\1</a>', content))
DATABASE = 'db.sqlite3'
UPLOAD_FOLDER = 'static/uploads/'
app.config['UPLOAD_FOLDER'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static', 'uploads')
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
def add_rendered_content_column():
db = get_db()
try:
db.execute('ALTER TABLE tweets ADD COLUMN rendered_content TEXT')
db.commit()
print("Added rendered_content column to tweets table")
except sqlite3.OperationalError as e:
if "duplicate column name" in str(e):
print("rendered_content column already exists")
else:
raise e
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
db.row_factory = sqlite3.Row
return db
def create_tables():
db = get_db()
db.execute('''CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
email TEXT NOT NULL,
pfp TEXT,
banner TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)''')
cursor = db.execute("PRAGMA table_info(users)")
columns = [column[1] for column in cursor.fetchall()]
if 'created_at' not in columns:
db.execute('ALTER TABLE users RENAME TO users_old')
db.execute('''CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
email TEXT NOT NULL,
pfp TEXT,
banner TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)''')
cursor = db.execute("PRAGMA table_info(users_old)")
old_columns = [column[1] for column in cursor.fetchall()]
insert_columns = ['id', 'username', 'password']
select_columns = ['id', 'username', 'password']
if 'email' in old_columns:
insert_columns.append('email')
select_columns.append('email')
else:
insert_columns.append('email')
select_columns.append("'example@email.com' AS email")
if 'pfp' in old_columns:
insert_columns.append('pfp')
select_columns.append('pfp')
if 'banner' in old_columns:
insert_columns.append('banner')
select_columns.append('banner')
db.execute(f'''
INSERT INTO users({', '.join(insert_columns)})
SELECT {', '.join(select_columns)} FROM users_old
''')
db.execute('DROP TABLE users_old')
db.commit()
db.execute('''CREATE TABLE IF NOT EXISTS tweets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
user_id INTEGER,
likes INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES users(id)
)''')
db.execute('''CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
tweet_id INTEGER,
user_id INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(tweet_id) REFERENCES tweets(id),
FOREIGN KEY(user_id) REFERENCES users(id)
)''')
db.execute('''CREATE TABLE IF NOT EXISTS likes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
tweet_id INTEGER,
FOREIGN KEY(user_id) REFERENCES users(id),
FOREIGN KEY(tweet_id) REFERENCES tweets(id),
UNIQUE(user_id, tweet_id)
)''')
db.execute('''CREATE TABLE IF NOT EXISTS group_members (
group_id INTEGER,
user_id INTEGER,
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (group_id) REFERENCES groups (id),
FOREIGN KEY (user_id) REFERENCES users (id),
PRIMARY KEY (group_id, user_id)
)''')
db.execute('''CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sender_id INTEGER,
receiver_id INTEGER,
content TEXT,
image TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (sender_id) REFERENCES users (id),
FOREIGN KEY (receiver_id) REFERENCES users (id)
)''')
db.execute('''CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
group_id INTEGER,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id),
FOREIGN KEY (group_id) REFERENCES groups (id)
)''')
db.execute('''CREATE TABLE IF NOT EXISTS groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
avatar TEXT,
vanity_url TEXT
)''')
cursor = db.execute("PRAGMA table_info(groups)")
columns = [column[1] for column in cursor.fetchall()]
if 'vanity_url' not in columns:
db.execute('''CREATE TABLE groups_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
avatar TEXT,
vanity_url TEXT UNIQUE
)''')
db.execute('INSERT INTO groups_new SELECT id, name, description, created_at, avatar, NULL FROM groups')
db.execute('DROP TABLE groups')
db.execute('ALTER TABLE groups_new RENAME TO groups')
db.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_groups_vanity_url ON groups (vanity_url)')
cursor = db.execute("PRAGMA table_info(users)")
columns = [column[1] for column in cursor.fetchall()]
if 'created_at' not in columns:
add_rendered_content_column()
update_existing_tweets()
db.commit()
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
def add_columns():
db = get_db()
try:
db.execute('ALTER TABLE users ADD COLUMN pfp TEXT')
except sqlite3.OperationalError:
pass
try:
db.execute('ALTER TABLE tweets ADD COLUMN likes INTEGER DEFAULT 0')
except sqlite3.OperationalError:
pass
try:
db.execute('ALTER TABLE users ADD COLUMN banner TEXT')
except sqlite3.OperationalError:
pass
try:
db.execute('ALTER TABLE messages ADD COLUMN image TEXT')
except sqlite3.OperationalError:
pass
try:
db.execute('''
CREATE TABLE IF NOT EXISTS followers (
follower_id INTEGER,
followed_id INTEGER,
FOREIGN KEY (follower_id) REFERENCES users (id),
FOREIGN KEY (followed_id) REFERENCES users (id),
PRIMARY KEY (follower_id, followed_id)
)
''')
except sqlite3.OperationalError:
pass
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/signup', methods=['GET', 'POST'])
def signup():
if request.method == 'POST':
username = request.form['username']
password = generate_password_hash(request.form['password'])
email = request.form['email']
restricted_usernames = ['avery', 'cgcristi', 'cg', 'ceegee']
if username.lower() in restricted_usernames:
flash('This username is not allowed. Please choose a different one.', 'error')
return render_template('signup.html')
db = get_db()
try:
db.execute('INSERT INTO users (username, password, email) VALUES (?, ?, ?)', (username, password, email))
db.commit()
flash('Account created successfully. Please log in.', 'success')
return redirect(url_for('login'))
except sqlite3.IntegrityError:
flash('Username already exists. Please choose a different one.', 'error')
return render_template('signup.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
user = db.execute('SELECT * FROM users WHERE username = ?', (username,)).fetchone()
if user and check_password_hash(user['password'], password):
session['user_id'] = user['id']
session['username'] = user['username']
if not user['pfp']:
return redirect(url_for('profile', username=user['username']))
return redirect(url_for('index'))
return render_template('login.html')
@app.route('/logout')
def logout():
session.pop('user_id', None)
session.pop('username', None)
session.pop('pfp', None)
return redirect(url_for('login'))
@app.route('/')
def index():
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
tweets = db.execute('''
SELECT t.*, u.username, u.pfp as user_pfp,
COALESCE(t.rendered_content, t.content) as displayed_content
FROM tweets t
JOIN users u ON t.user_id = u.id
ORDER BY t.created_at DESC
''').fetchall()
tweets = [dict(tweet) for tweet in tweets]
for tweet in tweets:
tweet['created_at'] = datetime.strptime(tweet['created_at'], '%Y-%m-%d %H:%M:%S')
tweet['displayed_content'] = bleach.clean(tweet['displayed_content'], strip=True)
liked_tweet_ids = {like['tweet_id'] for like in db.execute('SELECT tweet_id FROM likes WHERE user_id = ?', (session['user_id'],)).fetchall()}
user = db.execute('SELECT * FROM users WHERE id = ?', (session['user_id'],)).fetchone()
if user is None:
flash('User not found. Please log in again.', 'error')
return redirect(url_for('logout'))
user = dict(user)
user['created_at'] = datetime.strptime(user['created_at'], '%Y-%m-%d %H:%M:%S')
return render_template('index.html', tweets=tweets, liked_tweet_ids=liked_tweet_ids, user=user, get_user_by_id=get_user_by_id)
def update_existing_tweets():
db = get_db()
tweets = db.execute('SELECT id, content FROM tweets WHERE rendered_content IS NULL').fetchall()
for tweet in tweets:
rendered_content = render_markdown(tweet['content'])
db.execute('UPDATE tweets SET rendered_content = ? WHERE id = ?', (rendered_content, tweet['id']))
db.commit()
print(f"Updated {len(tweets)} existing tweets with rendered content")
@app.route('/tweet', methods=['POST'])
def tweet():
if 'user_id' in session:
content = request.form['content']
if len(content) > 280:
return redirect(url_for('index'))
processed_content = process_tweet_content(content)
rendered_content = render_markdown(processed_content)
image = request.files.get('image')
db = get_db()
if image and allowed_file(image.filename):
filename = secure_filename(image.filename)
image.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
db.execute('INSERT INTO tweets (content, rendered_content, user_id, image) VALUES (?, ?, ?, ?)',
(content, rendered_content, session['user_id'], filename))
else:
db.execute('INSERT INTO tweets (content, rendered_content, user_id) VALUES (?, ?, ?)',
(content, rendered_content, session['user_id']))
db.commit()
return redirect(url_for('index'))
@app.route('/retweet/<int:tweet_id>', methods=['POST'])
def retweet(tweet_id):
if 'user_id' in session:
db = get_db()
original_tweet = db.execute('SELECT * FROM tweets WHERE id = ?', (tweet_id,)).fetchone()
if original_tweet:
db.execute('INSERT INTO tweets (content, rendered_content, user_id, original_tweet_id) VALUES (?, ?, ?, ?)',
(original_tweet['content'], original_tweet['rendered_content'], session['user_id'], tweet_id))
db.commit()
return redirect(url_for('index'))
@app.route('/search', methods=['GET'])
def search():
query = request.args.get('q', '')
db = get_db()
users = db.execute('SELECT * FROM users WHERE username LIKE ? LIMIT 10', ('%' + query + '%',)).fetchall()
return render_template('search_results.html', users=users, query=query)
@app.route('/hashtag/<hashtag>')
def hashtag(hashtag):
db = get_db()
tweets = db.execute('''
SELECT t.*, u.username, u.pfp as user_pfp
FROM tweets t
JOIN users u ON t.user_id = u.id
WHERE t.content LIKE ?
ORDER BY t.created_at DESC
''', ('%#' + hashtag + '%',)).fetchall()
return render_template('hashtag.html', tweets=tweets, hashtag=hashtag)
@app.route('/tweet/<int:tweet_id>')
def tweet_detail(tweet_id):
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
tweet = db.execute('SELECT t.id, t.content, u.username, u.pfp, t.likes FROM tweets t JOIN users u ON t.user_id = u.id WHERE t.id = ?', (tweet_id,)).fetchone()
comments = db.execute('SELECT c.content, u.username FROM comments c JOIN users u ON c.user_id = u.id WHERE c.tweet_id = ?', (tweet_id,)).fetchall()
liked_tweet_ids = {like['tweet_id'] for like in db.execute('SELECT tweet_id FROM likes WHERE user_id = ?', (session['user_id'],)).fetchall()}
return render_template('tweet_detail.html', tweet=tweet, comments=comments, liked_tweet_ids=liked_tweet_ids)
@app.route('/comment/<int:tweet_id>', methods=['POST'])
def comment(tweet_id):
if 'user_id' not in session:
return jsonify({'success': False, 'message': 'User not logged in'})
content = request.form['content']
db = get_db()
db.execute('INSERT INTO comments (content, tweet_id, user_id) VALUES (?, ?, ?)', (content, tweet_id, session['user_id']))
db.commit()
return jsonify({'success': True})
@app.route('/like/<int:tweet_id>', methods=['POST'])
def like(tweet_id):
if 'user_id' not in session:
return jsonify({'success': False, 'message': 'User not logged in'}), 401
db = get_db()
user_id = session['user_id']
try:
existing_like = db.execute('SELECT * FROM likes WHERE user_id = ? AND tweet_id = ?', (user_id, tweet_id)).fetchone()
if existing_like:
db.execute('DELETE FROM likes WHERE user_id = ? AND tweet_id = ?', (user_id, tweet_id))
db.execute('UPDATE tweets SET likes = likes - 1 WHERE id = ?', (tweet_id,))
else:
db.execute('INSERT INTO likes (user_id, tweet_id) VALUES (?, ?)', (user_id, tweet_id))
db.execute('UPDATE tweets SET likes = likes + 1 WHERE id = ?', (tweet_id,))
db.commit()
updated_likes = db.execute('SELECT likes FROM tweets WHERE id = ?', (tweet_id,)).fetchone()['likes']
return jsonify({'success': True, 'likes': updated_likes})
except Exception as e:
db.rollback()
print(f"Error in like route: {str(e)}")
return jsonify({'success': False, 'message': 'An error occurred while processing your request'}), 500
@app.route('/profile/<username>')
def profile(username):
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
user = db.execute('SELECT * FROM users WHERE username = ?', (username,)).fetchone()
if not user:
abort(404)
user = dict(user)
if 'created_at' not in user or not user['created_at']:
first_tweet = db.execute('SELECT MIN(created_at) as first_tweet_date FROM tweets WHERE user_id = ?', (user['id'],)).fetchone()
if first_tweet and first_tweet['first_tweet_date']:
user['created_at'] = datetime.strptime(first_tweet['first_tweet_date'], '%Y-%m-%d %H:%M:%S')
else:
user['created_at'] = datetime.now()
else:
user['created_at'] = datetime.strptime(user['created_at'], '%Y-%m-%d %H:%M:%S')
tweets = db.execute('SELECT * FROM tweets WHERE user_id = ? ORDER BY created_at DESC', (user['id'],)).fetchall()
tweets = [dict(tweet) for tweet in tweets]
for tweet in tweets:
tweet['created_at'] = datetime.strptime(tweet['created_at'], '%Y-%m-%d %H:%M:%S')
return render_template('profile.html', user=user, tweets=tweets, get_user_by_id=get_user_by_id)
@app.route('/change_profile_picture', methods=['POST'])
def change_profile_picture():
if 'user_id' not in session:
return redirect(url_for('login'))
user_id = session['user_id']
if 'profile_picture' in request.files:
file = request.files['profile_picture']
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
db = get_db()
db.execute("UPDATE users SET pfp = ? WHERE id = ?", (filename, user_id))
db.commit()
flash("Profile picture updated successfully.")
else:
flash("Invalid file type. Please upload a PNG, JPG, JPEG, or GIF.")
else:
flash("No file uploaded.")
return redirect(url_for('profile'))
@app.route('/change_banner', methods=['POST'])
def change_banner():
if 'user_id' not in session:
return redirect(url_for('login'))
user_id = session['user_id']
if 'banner' in request.files:
file = request.files['banner']
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
db = get_db()
db.execute("UPDATE users SET banner = ? WHERE id = ?", (filename, user_id))
db.commit()
flash("Banner updated successfully.")
else:
flash("Invalid file type. Please upload a PNG, JPG, JPEG, or GIF.")
else:
flash("No file uploaded.")
return redirect(url_for('profile'))
@app.route('/edit_tweet/<int:tweet_id>', methods=['GET', 'POST'])
def edit_tweet(tweet_id):
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
tweet = db.execute('SELECT * FROM tweets WHERE id = ? AND user_id = ?', (tweet_id, session['user_id'])).fetchone()
if request.method == 'POST':
new_content = request.form['content']
db.execute('UPDATE tweets SET content = ? WHERE id = ?', (new_content, tweet_id))
db.commit()
return redirect(url_for('index'))
return render_template('edit_tweet.html', tweet=tweet)
@app.route('/delete_tweet/<int:tweet_id>', methods=['POST'])
def delete_tweet(tweet_id):
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
db.execute('DELETE FROM tweets WHERE id = ? AND user_id = ?', (tweet_id, session['user_id']))
db.commit()
return redirect(url_for('index'))
@app.route('/groups')
def groups():
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
search_query = request.args.get('search', '')
if search_query:
groups = db.execute('SELECT * FROM groups WHERE name LIKE ? ORDER BY name', ('%' + search_query + '%',)).fetchall()
else:
groups = db.execute('SELECT * FROM groups ORDER BY name').fetchall()
return render_template('g.html', groups=groups, get_user_by_id=get_user_by_id, search_query=search_query)
@app.route('/new_group', methods=['POST'])
def new_group():
if 'user_id' not in session:
return redirect(url_for('login'))
name = request.form['group_name']
vanity_url = request.form['vanity_url']
description = request.form['description']
db = get_db()
try:
db.execute('INSERT INTO groups (name, vanity_url, description) VALUES (?, ?, ?)', (name, vanity_url, description))
db.commit()
group_id = db.execute('SELECT last_insert_rowid()').fetchone()[0]
db.execute('INSERT INTO group_members (group_id, user_id) VALUES (?, ?)', (group_id, session['user_id']))
db.commit()
if 'group_picture' in request.files:
file = request.files['group_picture']
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
db.execute('UPDATE groups SET avatar = ? WHERE id = ?', (filename, group_id))
db.commit()
flash('Group created successfully!', 'success')
except sqlite3.IntegrityError:
flash('Group name or vanity URL already exists. Please choose a different one.', 'error')
return redirect(url_for('groups'))
@app.route('/group/<vanity_url>')
def group_detail(vanity_url):
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
group = db.execute('SELECT * FROM groups WHERE vanity_url = ?', (vanity_url,)).fetchone()
if not group:
abort(404)
posts = db.execute('''
SELECT p.*, u.username, u.pfp
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.group_id = ?
ORDER BY p.created_at DESC
''', (group['id'],)).fetchall()
is_member = db.execute('SELECT * FROM group_members WHERE group_id = ? AND user_id = ?',
(group['id'], session['user_id'])).fetchone() is not None
return render_template('group_detail.html', group=group, posts=posts, is_member=is_member, get_user_by_id=get_user_by_id)
@app.route('/join_group/<int:group_id>', methods=['POST'])
def join_group(group_id):
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
db.execute('INSERT INTO group_members (group_id, user_id) VALUES (?, ?)', (group_id, session['user_id']))
db.commit()
group = db.execute('SELECT vanity_url FROM groups WHERE id = ?', (group_id,)).fetchone()
flash('You have joined the group!', 'success')
return redirect(url_for('group_detail', vanity_url=group['vanity_url']))
@app.route('/leave_group/<int:group_id>', methods=['POST'])
def leave_group(group_id):
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
db.execute('DELETE FROM group_members WHERE user_id = ? AND group_id = ?', (session['user_id'], group_id))
db.commit()
group = db.execute('SELECT * FROM groups WHERE id = ?', (group_id,)).fetchone()
return redirect(url_for('group_detail', vanity_url=group['vanity_url']))
@app.route('/post_in_group/<int:group_id>', methods=['POST'])
def post_in_group(group_id):
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
is_member = db.execute('SELECT * FROM group_members WHERE group_id = ? AND user_id = ?',
(group_id, session['user_id'])).fetchone() is not None
if not is_member:
flash('You must be a member of the group to post.', 'error')
else:
content = request.form['content']
db.execute('INSERT INTO posts (user_id, group_id, content) VALUES (?, ?, ?)',
(session['user_id'], group_id, content))
db.commit()
flash('Your post has been added to the group!', 'success')
group = db.execute('SELECT vanity_url FROM groups WHERE id = ?', (group_id,)).fetchone()
return redirect(url_for('group_detail', vanity_url=group['vanity_url']))
@app.route('/dms')
def dms():
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
page = request.args.get('page', 1, type=int)
per_page = 10
offset = (page - 1) * per_page
conversations = db.execute('''
SELECT DISTINCT
CASE
WHEN sender_id = ? THEN receiver_id
ELSE sender_id
END AS other_user_id,
MAX(created_at) as last_message_time
FROM messages
WHERE sender_id = ? OR receiver_id = ?
GROUP BY other_user_id
ORDER BY last_message_time DESC
LIMIT ? OFFSET ?
''', (session['user_id'], session['user_id'], session['user_id'], per_page, offset)).fetchall()
dms = []
for conv in conversations:
other_user = get_user_by_id(conv['other_user_id'])
dms.append({
'username': other_user['username'],
'pfp': other_user['pfp']
})
has_more = len(dms) == per_page
return render_template('dm.html', dms=dms, page=page, has_more=has_more)
@app.route('/dm/<username>')
def dm_conversation(username):
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
other_user = db.execute('SELECT * FROM users WHERE username = ?', (username,)).fetchone()
if not other_user:
abort(404)
messages = db.execute('''
SELECT m.*, u.username, u.pfp
FROM messages m
JOIN users u ON m.sender_id = u.id
WHERE (m.sender_id = ? AND m.receiver_id = ?) OR (m.sender_id = ? AND m.receiver_id = ?)
ORDER BY m.created_at ASC
''', (session['user_id'], other_user['id'], other_user['id'], session['user_id'])).fetchall()
return render_template('dm_conversation.html', other_user=other_user, messages=messages)
@app.route('/edit_group/<int:group_id>', methods=['GET', 'POST'])
def edit_group(group_id):
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
group = db.execute('SELECT * FROM groups WHERE id = ?', (group_id,)).fetchone()
if request.method == 'POST':
name = request.form['name']
description = request.form['description']
db.execute('UPDATE groups SET name = ?, description = ? WHERE id = ?', (name, description, group_id))
db.commit()
flash('Group updated successfully!', 'success')
return redirect(url_for('group_detail', vanity_url=group['vanity_url']))
return render_template('edit_group.html', group=group)
@app.route('/dm/<username>/send', methods=['POST'])
def send_message(username):
if 'user_id' not in session:
return redirect(url_for('login'))
content = request.form['content']
image = request.files.get('image')
db = get_db()
receiver = db.execute('SELECT id FROM users WHERE username = ?', (username,)).fetchone()
if receiver:
image_filename = None
if image and allowed_file(image.filename):
image_filename = secure_filename(image.filename)
image.save(os.path.join(app.config['UPLOAD_FOLDER'], image_filename))
db.execute('INSERT INTO messages (sender_id, receiver_id, content, image, created_at) VALUES (?, ?, ?, ?, ?)',
(session['user_id'], receiver['id'], content, image_filename, datetime.now()))
db.commit()
return redirect(url_for('dm_conversation', username=username))
@app.route('/check_new_messages/<username>', methods=['GET'])
def check_new_messages(username):
if 'user_id' not in session:
return jsonify({'success': False, 'message': 'Unauthorized'})
db = get_db()
other_user = db.execute('SELECT id FROM users WHERE username = ?', (username,)).fetchone()
if not other_user:
return jsonify({'success': False, 'message': 'User not found'})
last_message_id = request.args.get('last_message_id', 0, type=int)
new_messages = db.execute('''
SELECT m.*, u.username, u.pfp
FROM messages m
JOIN users u ON m.sender_id = u.id
WHERE ((m.sender_id = ? AND m.receiver_id = ?) OR (m.sender_id = ? AND m.receiver_id = ?))
AND m.id > ?
ORDER BY m.created_at ASC
''', (session['user_id'], other_user['id'], other_user['id'], session['user_id'], last_message_id)).fetchall()
if new_messages:
return jsonify({
'success': True,
'messages': [{
'id': message['id'],
'content': message['content'],
'sender_id': message['sender_id'],
'username': message['username'],
'pfp': message['pfp'],
'created_at': message['created_at'],
'image': message['image']
} for message in new_messages]
})
else:
return jsonify({'success': False, 'message': 'No new messages'})
@app.route('/delete_group/<int:group_id>', methods=['POST'])
def delete_group(group_id):
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
db.execute('DELETE FROM groups WHERE id = ?', (group_id,))
db.commit()
return redirect(url_for('groups'))
@app.route('/start_dm', methods=['POST'])
def start_dm():
if 'user_id' not in session:
return redirect(url_for('login'))
username = request.form['username']
return redirect(url_for('dm_conversation', username=username))
@app.route('/edit_message/<int:message_id>', methods=['POST'])
def edit_message(message_id):
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
message = db.execute('SELECT * FROM messages WHERE id = ? AND sender_id = ?', (message_id, session['user_id'])).fetchone()
if not message:
abort(404)
new_content = request.form['content']
db.execute('UPDATE messages SET content = ? WHERE id = ?', (new_content, message_id))
db.commit()
return redirect(url_for('dm_conversation', username=request.form['username']))
@app.route('/delete_message/<int:message_id>', methods=['POST'])
def delete_message(message_id):
if 'user_id' not in session:
return redirect(url_for('login'))
db = get_db()
db.execute('DELETE FROM messages WHERE id = ? AND sender_id = ?', (message_id, session['user_id']))
db.commit()
return redirect(url_for('dm_conversation', username=request.form['username']))
@app.route('/admin')
def admin_panel():
if 'user_id' not in session:
return redirect(url_for('login'))
user = get_user_by_id(session['user_id'])
if not user or user['username'] != '123':
return redirect(url_for('login'))
db = get_db()
users = db.execute('SELECT * FROM users ORDER BY created_at DESC').fetchall()
tweets = db.execute('''
SELECT t.*, u.username
FROM tweets t
JOIN users u ON t.user_id = u.id
ORDER BY t.created_at DESC
''').fetchall()
groups = db.execute('''
SELECT g.*, COUNT(gm.user_id) as member_count
FROM groups g
LEFT JOIN group_members gm ON g.id = gm.group_id
GROUP BY g.id
ORDER BY g.created_at DESC
''').fetchall()
user_count = db.execute('SELECT COUNT(*) as count FROM users').fetchone()['count']
tweet_count = db.execute('SELECT COUNT(*) as count FROM tweets').fetchone()['count']
group_count = db.execute('SELECT COUNT(*) as count FROM groups').fetchone()['count']
return render_template('admin_panel.html',
users=users,
tweets=tweets,
groups=groups,
user_count=user_count,
tweet_count=tweet_count,
group_count=group_count)
def get_all_users():
db = get_db()
users = db.execute('SELECT * FROM users').fetchall()
return [dict(user) for user in users]
def get_all_tweets():
db = get_db()
tweets = db.execute('SELECT t.*, u.username FROM tweets t JOIN users u ON t.user_id = u.id ORDER BY t.created_at DESC').fetchall()
return [dict(tweet) for tweet in tweets]
def get_all_groups():
db = get_db()
groups = db.execute('SELECT * FROM groups ORDER BY created_at DESC').fetchall()
return [dict(group) for group in groups]
@app.route('/admin/delete_user/<int:user_id>', methods=['POST'])
def delete_user(user_id):
if 'user_id' not in session or get_user_by_id(session['user_id']).username != 'avery':
return jsonify({'success': False, 'message': 'Unauthorized'})
db = get_db()
db.execute('DELETE FROM users WHERE id = ?', (user_id,))
db.commit()
return jsonify({'success': True})
@app.route('/admin/delete_tweet/<int:tweet_id>', methods=['POST'])
def delete_tweet_admin(tweet_id):
if 'user_id' not in session or get_user_by_id(session['user_id']).username != 'avery':
return jsonify({'success': False, 'message': 'Unauthorized'})
db = get_db()
db.execute('DELETE FROM tweets WHERE id = ?', (tweet_id,))
db.commit()
return jsonify({'success': True})
@app.route('/admin/delete_group/<int:group_id>', methods=['POST'])
def delete_group_admin(group_id):
if 'user_id' not in session or get_user_by_id(session['user_id'])['username'] != 'avery':
return jsonify({'success': False, 'message': 'Unauthorized'})
db = get_db()
db.execute('DELETE FROM groups WHERE id = ?', (group_id,))
db.execute('DELETE FROM group_members WHERE group_id = ?', (group_id,))
db.execute('DELETE FROM posts WHERE group_id = ?', (group_id,))
db.commit()
return jsonify({'success': True})
if __name__ == '__main__':
app.run(debug=True, port=4771)

51
clean_db.py Normal file
View File

@ -0,0 +1,51 @@
import sqlite3
# Connect to the database
conn = sqlite3.connect('db.sqlite3')
cursor = conn.cursor()
# Keep users 'avery' and 'asd'
cursor.execute("SELECT id FROM users WHERE username IN ('avery', 'asd')")
keep_user_ids = [row[0] for row in cursor.fetchall()]
# Delete all users except 'avery' and 'asd'
cursor.execute("DELETE FROM users WHERE id NOT IN ({})".format(','.join('?' * len(keep_user_ids))), keep_user_ids)
# Get avery's user id
cursor.execute("SELECT id FROM users WHERE username = 'avery'")
avery_id = cursor.fetchone()[0]
# Keep only the first two tweets from 'avery' and delete all other tweets
cursor.execute("""
DELETE FROM tweets
WHERE id NOT IN (
SELECT id FROM tweets
WHERE user_id = ?
ORDER BY created_at ASC
LIMIT 2
)
""", (avery_id,))
# Delete all likes
cursor.execute("DELETE FROM likes")
# Delete all comments
cursor.execute("DELETE FROM comments")
# Delete all messages
cursor.execute("DELETE FROM messages")
# Delete all groups
cursor.execute("DELETE FROM groups")
# Delete all group members
cursor.execute("DELETE FROM group_members")
# Delete all posts
cursor.execute("DELETE FROM posts")
# Commit the changes and close the connection
conn.commit()
conn.close()
print("Database cleaned successfully.")

BIN
db.sqlite3 Normal file

Binary file not shown.

1167
static/styles.css Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

352
templates/admin_panel.html Normal file
View File

@ -0,0 +1,352 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Panel - aTweet</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
.admin-panel { max-width: 1200px; margin: 0 auto; padding: 20px; }
.admin-stats { display: flex; justify-content: space-between; margin-bottom: 30px; }
.stat-card { background-color: var(--primary-color); color: white; border-radius: 10px; padding: 20px; text-align: center; flex: 1; margin: 0 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }
.admin-section { background-color: var(--background-color); border-radius: 10px; padding: 20px; margin-bottom: 40px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }
.admin-table { width: 100%; border-collapse: collapse; }
.admin-table th, .admin-table td { padding: 12px; text-align: left; border-bottom: 1px solid var(--border-color); }
.admin-table th { background-color: var(--primary-color); color: white; }
.admin-table tr:hover { background-color: var(--hover-color); }
.admin-actions { display: flex; gap: 10px; }
.admin-button { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; transition: background-color 0.3s; color: white; }
.admin-button-view { background-color: #4CAF50; }
.admin-button-edit { background-color: #2196F3; }
.admin-button-delete { background-color: #f44336; }
.search-bar { margin-bottom: 20px; }
.search-bar input { width: 100%; padding: 10px; border: 1px solid var(--border-color); border-radius: 4px; }
.pagination { display: flex; justify-content: center; margin-top: 20px; }
.pagination button { margin: 0 5px; padding: 5px 10px; background-color: var(--primary-color); color: white; border: none; border-radius: 4px; cursor: pointer; }
.dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px; }
.dashboard-card { background-color: var(--background-color); border-radius: 10px; padding: 20px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }
.dashboard-card h3 { margin-top: 0; color: var(--primary-color); }
.quick-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 30px; }
.quick-action-button { padding: 10px 20px; background-color: var(--primary-color); color: white; border: none; border-radius: 4px; cursor: pointer; }
</style>
</head>
<body>
<div class="container admin-panel">
<h1>Admin Panel</h1>
<div class="admin-stats">
<div class="stat-card">
<h3>{{ user_count }}</h3>
<p>Total Users</p>
</div>
<div class="stat-card">
<h3>{{ tweet_count }}</h3>
<p>Total Tweets</p>
</div>
<div class="stat-card">
<h3>{{ group_count }}</h3>
<p>Total Groups</p>
</div>
</div>
<div class="dashboard">
<div class="dashboard-card">
<h3>Recent Activity</h3>
<ul id="recentActivity"></ul>
<li>Not implemented yet (lazyass)</li>
</div>
<div class="dashboard-card">
<h3>System Health</h3>
<p>Server Status: <span id="serverStatus">Operational</span></p>
<p>Database Size: <span id="databaseSize">16MB</span></p>
</div>
</div>
<div class="quick-actions">
<button class="quick-action-button" onclick="showBackupModal()">Backup Database</button>
<button class="quick-action-button" onclick="showMaintenanceModal()">Maintenance Mode</button>
<button class="quick-action-button" onclick="showBroadcastModal()">Broadcast Message</button>
</div>
<div class="admin-section">
<h2>Users</h2>
<div class="search-bar">
<input type="text" id="userSearch" placeholder="Search users...">
</div>
<table class="admin-table" id="userTable">
<thead>
<tr>
<th>Username</th>
<th>Email</th>
<th>Joined</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>{{ user.created_at }}</td>
<td class="admin-actions">
<button class="admin-button admin-button-view" onclick="viewUser({{ user.id }})">View</button>
<button class="admin-button admin-button-edit" onclick="editUser({{ user.id }})">Edit</button>
<button class="admin-button admin-button-delete" onclick="deleteUser({{ user.id }})">Delete</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<button onclick="changePage(-1, 'userTable')">Previous</button>
<span id="userCurrentPage">1</span>
<button onclick="changePage(1, 'userTable')">Next</button>
</div>
</div>
<div class="admin-section">
<h2>Tweets</h2>
<div class="search-bar">
<input type="text" id="tweetSearch" placeholder="Search tweets...">
</div>
<table class="admin-table" id="tweetTable">
<thead>
<tr>
<th>Content</th>
<th>User</th>
<th>Created At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for tweet in tweets %}
<tr>
<td>{{ tweet.content }}</td>
<td>{{ tweet.username }}</td>
<td>{{ tweet.created_at }}</td>
<td class="admin-actions">
<button class="admin-button admin-button-view" onclick="viewTweet({{ tweet.id }})">View</button>
<button class="admin-button admin-button-delete" onclick="deleteTweet({{ tweet.id }})">Delete</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<button onclick="changePage(-1, 'tweetTable')">Previous</button>
<span id="tweetCurrentPage">1</span>
<button onclick="changePage(1, 'tweetTable')">Next</button>
</div>
</div>
<div class="admin-section">
<h2>Groups</h2>
<div class="search-bar">
<input type="text" id="groupSearch" placeholder="Search groups...">
</div>
<table class="admin-table" id="groupTable">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Members</th>
<th>Created At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for group in groups %}
<tr>
<td>{{ group.name }}</td>
<td>{{ group.description }}</td>
<td>{{ group.member_count }}</td>
<td>{{ group.created_at }}</td>
<td class="admin-actions">
<button class="admin-button admin-button-view" onclick="viewGroup({{ group.id }})">View</button>
<button class="admin-button admin-button-edit" onclick="editGroup({{ group.id }})">Edit</button>
<button class="admin-button admin-button-delete" onclick="deleteGroup({{ group.id }})">Delete</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<button onclick="changePage(-1, 'groupTable')">Previous</button>
<span id="groupCurrentPage">1</span>
<button onclick="changePage(1, 'groupTable')">Next</button>
</div>
</div>
</div>
<script>
function viewUser(userId) {
window.location.href = `/profile/${userId}`;
}
function editUser(userId) {
const newUsername = prompt("Enter new username:");
const newEmail = prompt("Enter new email:");
if (newUsername && newEmail) {
fetch(`/admin/edit_user/${userId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username: newUsername, email: newEmail }),
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Failed to edit user');
}
});
}
}
function deleteUser(userId) {
if (confirm('Are you sure you want to delete this user?')) {
fetch(`/admin/delete_user/${userId}`, { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Failed to delete user');
}
});
}
}
function viewTweet(tweetId) {
window.location.href = `/tweet/${tweetId}`;
}
function deleteTweet(tweetId) {
if (confirm('Are you sure you want to delete this tweet?')) {
fetch(`/admin/delete_tweet/${tweetId}`, { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Failed to delete tweet');
}
});
}
}
function viewGroup(groupId) {
window.location.href = `/group/${groupId}`;
}
function editGroup(groupId) {
const newName = prompt("Enter new group name:");
const newDescription = prompt("Enter new group description:");
if (newName && newDescription) {
fetch(`/admin/edit_group/${groupId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: newName, description: newDescription }),
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Failed to edit group');
}
});
}
}
function deleteGroup(groupId) {
if (confirm('Are you sure you want to delete this group?')) {
fetch(`/admin/delete_group/${groupId}`, { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Failed to delete group');
}
});
}
}
function searchTable(inputId, tableId) {
const input = document.getElementById(inputId);
const filter = input.value.toUpperCase();
const table = document.getElementById(tableId);
const tr = table.getElementsByTagName("tr");
for (let i = 1; i < tr.length; i++) {
let txtValue = tr[i].textContent || tr[i].innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
document.getElementById("userSearch").addEventListener("keyup", () => searchTable("userSearch", "userTable"));
document.getElementById("tweetSearch").addEventListener("keyup", () => searchTable("tweetSearch", "tweetTable"));
document.getElementById("groupSearch").addEventListener("keyup", () => searchTable("groupSearch", "groupTable"));
const itemsPerPage = 10;
const currentPages = {
userTable: 1,
tweetTable: 1,
groupTable: 1
};
function changePage(direction, tableId) {
currentPages[tableId] += direction;
if (currentPages[tableId] < 1) currentPages[tableId] = 1;
document.getElementById(`${tableId.replace('Table', 'CurrentPage')}`).textContent = currentPages[tableId];
updateTableDisplay(tableId);
}
function updateTableDisplay(tableId) {
const table = document.getElementById(tableId);
const tr = table.getElementsByTagName("tr");
const start = (currentPages[tableId] - 1) * itemsPerPage + 1;
const end = start + itemsPerPage;
for (let i = 1; i < tr.length; i++) {
if (i >= start && i < end) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
function showBackupModal() {
alert("Database backup functionality not implemented.");
}
function showMaintenanceModal() {
alert("Maintenance mode functionality not implemented.");
}
function showBroadcastModal() {
alert("Broadcast message functionality not implemented.");
}
function updateRecentActivity() {
const recentActivity = document.getElementById("recentActivity");
recentActivity.innerHTML = "<li>not even implemented</li>";
// Initial setup
updateTableDisplay('userTable');
updateTableDisplay('tweetTable');
updateTableDisplay('groupTable');
updateRecentActivity();
</script>
</body>
</html>

View File

@ -0,0 +1,359 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Panel - aTweet</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
.admin-panel {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.admin-stats {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
}
.stat-card {
background-color: var(--primary-color);
color: white;
border-radius: 10px;
padding: 20px;
text-align: center;
flex: 1;
margin: 0 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.admin-section {
background-color: var(--background-color);
border-radius: 10px;
padding: 20px;
margin-bottom: 40px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.admin-table {
width: 100%;
border-collapse: collapse;
}
.admin-table th, .admin-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
.admin-table th {
background-color: var(--primary-color);
color: white;
}
.admin-table tr:hover {
background-color: var(--hover-color);
}
.admin-actions {
display: flex;
gap: 10px;
}
.admin-button {
padding: 6px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
color: white;
}
.admin-button-view { background-color: #4CAF50; }
.admin-button-edit { background-color: #2196F3; }
.admin-button-delete { background-color: #f44336; }
.search-bar {
margin-bottom: 20px;
}
.search-bar input {
width: 100%;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 4px;
}
.pagination {
display: flex;
justify-content: center;
margin-top: 20px;
}
.pagination button {
margin: 0 5px;
padding: 5px 10px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container admin-panel">
<h1>Admin Panel</h1>
<div class="admin-stats">
<div class="stat-card">
<h3>{{ user_count }}</h3>
<p>Total Users</p>
</div>
<div class="stat-card">
<h3>{{ tweet_count }}</h3>
<p>Total Tweets</p>
</div>
<div class="stat-card">
<h3>{{ group_count }}</h3>
<p>Total Groups</p>
</div>
</div>
<div class="admin-section">
<h2>Users</h2>
<div class="search-bar">
<input type="text" id="userSearch" placeholder="Search users...">
</div>
<table class="admin-table" id="userTable">
<thead>
<tr>
<th>Username</th>
<th>Email</th>
<th>Joined</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>{{ user.created_at }}</td>
<td class="admin-actions">
<button class="admin-button admin-button-view" onclick="viewUser({{ user.id }})">View</button>
<button class="admin-button admin-button-edit" onclick="editUser({{ user.id }})">Edit</button>
<button class="admin-button admin-button-delete" onclick="deleteUser({{ user.id }})">Delete</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<button onclick="changePage(-1)">Previous</button>
<span id="currentPage">1</span>
<button onclick="changePage(1)">Next</button>
</div>
</div>
<div class="admin-section">
<h2>Tweets</h2>
<div class="search-bar">
<input type="text" id="tweetSearch" placeholder="Search tweets...">
</div>
<table class="admin-table" id="tweetTable">
<thead>
<tr>
<th>Content</th>
<th>User</th>
<th>Created At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for tweet in tweets %}
<tr>
<td>{{ tweet.content }}</td>
<td>{{ tweet.username }}</td>
<td>{{ tweet.created_at }}</td>
<td class="admin-actions">
<button class="admin-button admin-button-view" onclick="viewTweet({{ tweet.id }})">View</button>
<button class="admin-button admin-button-delete" onclick="deleteTweet({{ tweet.id }})">Delete</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="admin-section">
<h2>Groups</h2>
<div class="search-bar">
<input type="text" id="groupSearch" placeholder="Search groups...">
</div>
<table class="admin-table" id="groupTable">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Members</th>
<th>Created At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for group in groups %}
<tr>
<td>{{ group.name }}</td>
<td>{{ group.description }}</td>
<td>{{ group.member_count }}</td>
<td>{{ group.created_at }}</td>
<td class="admin-actions">
<button class="admin-button admin-button-view" onclick="viewGroup({{ group.id }})">View</button>
<button class="admin-button admin-button-edit" onclick="editGroup({{ group.id }})">Edit</button>
<button class="admin-button admin-button-delete" onclick="deleteGroup({{ group.id }})">Delete</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<script>
function viewUser(userId) {
window.location.href = `/profile/${userId}`;
}
function editUser(userId) {
const newUsername = prompt("Enter new username:");
const newEmail = prompt("Enter new email:");
if (newUsername && newEmail) {
fetch(`/admin/edit_user/${userId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username: newUsername, email: newEmail }),
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Failed to edit user');
}
});
}
}
function deleteUser(userId) {
if (confirm('Are you sure you want to delete this user?')) {
fetch(`/admin/delete_user/${userId}`, { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Failed to delete user');
}
});
}
}
function viewTweet(tweetId) {
window.location.href = `/tweet/${tweetId}`;
}
function deleteTweet(tweetId) {
if (confirm('Are you sure you want to delete this tweet?')) {
fetch(`/admin/delete_tweet/${tweetId}`, { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Failed to delete tweet');
}
});
}
}
function viewGroup(groupId) {
window.location.href = `/group/${groupId}`;
}
function editGroup(groupId) {
const newName = prompt("Enter new group name:");
const newDescription = prompt("Enter new group description:");
if (newName && newDescription) {
fetch(`/admin/edit_group/${groupId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: newName, description: newDescription }),
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Failed to edit group');
}
});
}
}
function deleteGroup(groupId) {
if (confirm('Are you sure you want to delete this group?')) {
fetch(`/admin/delete_group/${groupId}`, { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Failed to delete group');
}
});
}
}
function searchTable(inputId, tableId) {
const input = document.getElementById(inputId);
const filter = input.value.toUpperCase();
const table = document.getElementById(tableId);
const tr = table.getElementsByTagName("tr");
for (let i = 1; i < tr.length; i++) {
let txtValue = tr[i].textContent || tr[i].innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
document.getElementById("userSearch").addEventListener("keyup", () => searchTable("userSearch", "userTable"));
document.getElementById("tweetSearch").addEventListener("keyup", () => searchTable("tweetSearch", "tweetTable"));
document.getElementById("groupSearch").addEventListener("keyup", () => searchTable("groupSearch", "groupTable"));
let currentPage = 1;
const itemsPerPage = 10;
function changePage(direction) {
currentPage += direction;
if (currentPage < 1) currentPage = 1;
document.getElementById("currentPage").textContent = currentPage;
updateTableDisplay();
}
function updateTableDisplay() {
const table = document.getElementById("userTable");
const tr = table.getElementsByTagName("tr");
const start = (currentPage - 1) * itemsPerPage + 1;
const end = start + itemsPerPage;
for (let i = 1; i < tr.length; i++) {
if (i >= start && i < end) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
updateTableDisplay();
</script>
</body>
</html>

50
templates/base.html Normal file
View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<title>{% block title %}aTweet{% endblock %}</title>
</head>
<body>
<div class="container">
<header>
<h1>aTweet</h1>
</header>
<main>
{% block content %}{% endblock %}
</main>
</div>
<footer>
<div class="footer-container">
<a href="{{ url_for('index') }}" class="footer-button">
<i class="fas fa-home"></i> Home
</a>
{% if session.username %}
<a href="{{ url_for('profile', username=session.username) }}" class="footer-button">
<i class="fas fa-user"></i> Profile
</a>
{% endif %}
<a href="{{ url_for('groups') }}" class="footer-button">
<i class="fas fa-users"></i> Groups
</a>
<a href="{{ url_for('dms') }}" class="footer-button">
<i class="fas fa-envelope"></i> DMs
</a>
{% if session.user_id and get_user_by_id(session.user_id)['username'] == 'avery' %}
<a href="{{ url_for('admin_panel') }}" class="footer-button">
<i class="fas fa-cog"></i> Admin Panel
</a>
{% endif %}
<a href="{{ url_for('logout') }}" class="footer-button">
<i class="fas fa-sign-out-alt"></i> Logout
</a>
</div>
</footer>
<script>
function toggleDarkMode() {
document.body.classList.toggle('dark-mode');
}
</script>
</body>
</html>

77
templates/dm.html Normal file
View File

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Direct Messages - aTweet</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
.messages-container {
height: 300px; /* Reduced by 25% from 400px */
overflow-y: auto;
border: 1px solid #38444d;
border-radius: 5px;
padding: 10px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>Direct Messages</h1>
<form action="{{ url_for('start_dm') }}" method="POST" class="dm-form">
<input type="text" name="username" placeholder="Enter username" required>
<button type="submit" class="btn btn-primary">Start Conversation</button>
</form>
<div class="messages-container" id="messagesContainer">
{% for dm in dms %}
<a href="{{ url_for('dm_conversation', username=dm.username) }}" class="dm-item">
<img src="{{ url_for('static', filename='uploads/' + (dm.pfp if dm.pfp else 'default_pfp.png')) }}" alt="{{ dm.username }}" class="dm-avatar">
<div class="dm-info">
<span class="dm-name">{{ dm.username }}</span>
</div>
</a>
{% endfor %}
</div>
{% if has_more %}
<a href="{{ url_for('dms', page=page+1) }}" class="btn btn-primary load-more">Load More</a>
{% endif %}
</div>
<footer>
<footer>
<div class="footer-container">
<a href="{{ url_for('index') }}" class="footer-button">
<i class="fas fa-home"></i> Home
</a>
<a href="{{ url_for('profile', username=session.username) }}" class="footer-button">
<i class="fas fa-user"></i> Profile
</a>
<a href="{{ url_for('groups') }}" class="footer-button">
<i class="fas fa-users"></i> Groups
</a>
<a href="{{ url_for('dms') }}" class="footer-button">
<i class="fas fa-envelope"></i> DMs
</a>
{% if session.user_id and get_user_by_id(session.user_id)['username'] == 'avery' %}
<a href="{{ url_for('admin_panel') }}" class="footer-button">
<i class="fas fa-cog"></i> Admin Panel
</a>
{% endif %}
<a href="{{ url_for('logout') }}" class="footer-button">
<i class="fas fa-sign-out-alt"></i> Logout
</a>
</div>
</footer>
<script>
function scrollToBottom() {
const messagesContainer = document.getElementById('messagesContainer');
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// Call this function when the page loads and after sending a new message
scrollToBottom();
</script>
</body>
</html>

View File

@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Conversation with {{ other_user.username }} - aTweet</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
.messages-container {
height: 325px;
overflow-y: auto;
border: 1px solid #38444d;
border-radius: 5px;
padding: 10px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>Conversation with {{ other_user.username }}</h1>
<div class="messages-container" id="messageList">
{% for message in messages %}
<div class="message-container {% if message.sender_id == session['user_id'] %}sent{% else %}received{% endif %}">
<img src="{{ url_for('static', filename='uploads/' + (message.pfp if message.pfp else 'default_pfp.png')) }}" alt="{{ message.username }}" class="message-avatar">
<div class="message-content">
<span class="message-username">{{ message.username }}</span>
<p>{{ message.content }}</p>
{% if message.image %}
<img src="{{ url_for('static', filename='uploads/' + message.image) }}" alt="Message image" class="message-image">
{% endif %}
</div>
</div>
{% endfor %}
</div>
<form id="messageForm" action="{{ url_for('send_message', username=other_user.username) }}" method="POST" enctype="multipart/form-data" class="message-form">
<textarea name="content" placeholder="Type your message..." required></textarea>
<input type="file" name="image" accept="image/*">
<button type="submit" class="btn btn-primary">Send</button>
</form>
</div>
<footer>
<div class="footer-container">
<a href="{{ url_for('index') }}" class="footer-button">
<i class="fas fa-home"></i> Home
</a>
<a href="{{ url_for('profile', username=session.username) }}" class="footer-button">
<i class="fas fa-user"></i> Profile
</a>
<a href="{{ url_for('groups') }}" class="footer-button">
<i class="fas fa-users"></i> Groups
</a>
<a href="{{ url_for('dms') }}" class="footer-button">
<i class="fas fa-envelope"></i> DMs
</a>
{% if session.user_id and get_user_by_id(session.user_id)['username'] == 'avery' %}
<a href="{{ url_for('admin_panel') }}" class="footer-button">
<i class="fas fa-cog"></i> Admin Panel
</a>
{% endif %}
<a href="{{ url_for('logout') }}" class="footer-button">
<i class="fas fa-sign-out-alt"></i> Logout
</a>
</div>
</footer>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
let lastMessageId = 0;
function scrollToBottom() {
const messageList = document.getElementById('messageList');
messageList.scrollTop = messageList.scrollHeight;
}
function checkNewMessages() {
$.get("{{ url_for('check_new_messages', username=other_user.username) }}?last_message_id=" + lastMessageId, function(data) {
if (data.success && data.messages && data.messages.length > 0) {
const messageList = document.getElementById('messageList');
data.messages.forEach(message => {
if (message.id > lastMessageId) {
const newMessage = `
<div class="message-container ${message.sender_id == {{ session['user_id'] }} ? 'sent' : 'received'}">
<img src="{{ url_for('static', filename='uploads/') }}${message.pfp || 'default_pfp.png'}" alt="${message.username}" class="message-avatar">
<div class="message-content">
<span class="message-username">${message.username}</span>
<p>${message.content}</p>
<span class="message-timestamp">${message.created_at}</span>
</div>
</div>
`;
messageList.insertAdjacentHTML('beforeend', newMessage);
lastMessageId = message.id;
}
});
scrollToBottom();
}
});
}
scrollToBottom();
lastMessageId = {{ messages[-1].id if messages else 0 }};
setInterval(checkNewMessages, 200);
</script>
</body>
</html>

49
templates/edit_group.html Normal file
View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit {{ group.name }} - aTweet</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<body>
<div class="container">
<h1>Edit Group</h1>
<form action="{{ url_for('edit_group', group_id=group.id) }}" method="POST" enctype="multipart/form-data" class="edit-group-form">
<input type="text" name="name" value="{{ group.name }}" required>
<textarea name="description">{{ group.description }}</textarea>
<input type="file" name="group_picture" accept="image/*">
<button type="submit" class="btn btn-primary">Save Changes</button>
</form>
<form action="{{ url_for('delete_group', group_id=group.id) }}" method="POST" class="delete-group-form">
<button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure you want to delete this group?')">Delete Group</button>
</form>
</div>
<footer>
<div class="footer-container">
<a href="{{ url_for('index') }}" class="footer-button">
<i class="fas fa-home"></i> Home
</a>
<a href="{{ url_for('profile', username=session.username) }}" class="footer-button">
<i class="fas fa-user"></i> Profile
</a>
<a href="{{ url_for('groups') }}" class="footer-button">
<i class="fas fa-users"></i> Groups
</a>
<a href="{{ url_for('dms') }}" class="footer-button">
<i class="fas fa-envelope"></i> DMs
</a>
{% if session.user_id and get_user_by_id(session.user_id)['username'] == 'avery' %}
<a href="{{ url_for('admin_panel') }}" class="footer-button">
<i class="fas fa-cog"></i> Admin Panel
</a>
{% endif %}
<a href="{{ url_for('logout') }}" class="footer-button">
<i class="fas fa-sign-out-alt"></i> Logout
</a>
</div>
</footer>
</body>
</html>

36
templates/edit_tweet.html Normal file
View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit Tweet - aTweet</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="container">
<h1>Edit Tweet</h1>
<form action="{{ url_for('edit_tweet', tweet_id=tweet.id) }}" method="POST" class="tweet-form">
<textarea name="content" required maxlength="280">{{ tweet.content }}</textarea>
<div class="form-footer">
<span class="char-count">{{ 280 - tweet.content|length }}</span>
<button type="submit">Update Tweet</button>
</div>
</form>
</div>
<script>
const textarea = document.querySelector('textarea');
const charCount = document.querySelector('.char-count');
textarea.addEventListener('input', function() {
const remaining = 280 - this.value.length;
charCount.textContent = remaining;
if (remaining < 0) {
charCount.style.color = 'red';
} else {
charCount.style.color = '';
}
});
</script>
</body>
</html>

80
templates/g.html Normal file
View File

@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Groups - aTweet</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<body>
<div class="container">
<h1>Groups</h1>
<button class="btn btn-primary" onclick="toggleNewGroupForm()">Create a new Group</button>
<div id="new-group-form" class="hidden overlay">
<div class="overlay-content">
<h2>Create a new Group</h2>
<form action="{{ url_for('new_group') }}" method="POST" enctype="multipart/form-data" onsubmit="return confirmGroupCreation()">
<input type="text" name="group_name" placeholder="Enter group name" required>
<input type="text" name="vanity_url" placeholder="Vanity URL (e.g., tech)" required>
<textarea name="description" placeholder="Group description"></textarea>
<input type="file" name="group_picture" accept="image/*">
<button type="submit" class="btn btn-success">Create Group</button>
<form action="{{ url_for('groups') }}" method="GET" class="search-form">
<input type="text" name="search" placeholder="Search groups" value="{{ search_query }}">
<button type="submit" class="btn btn-primary">Search</button>
</form>
</form>
<button class="btn btn-secondary" onclick="toggleNewGroupForm()">Cancel</button>
</div>
</div>
<div class="group-list">
{% for group in groups %}
<a href="{{ url_for('group_detail', vanity_url=group.vanity_url) }}" class="group-item">
<img src="{{ url_for('static', filename='uploads/' + (group.avatar if group.avatar else 'default_pfp.png')) }}" alt="{{ group.name }}" class="group-avatar">
<div class="group-info">
<span class="group-name">{{ group.name | safe }}</span>
<span class="group-description">{{ group.description | safe }}</span>
</div>
</a>
{% endfor %}
</div>
</div>
<footer>
<div class="footer-container">
<a href="{{ url_for('index') }}" class="footer-button">
<i class="fas fa-home"></i> Home
</a>
<a href="{{ url_for('profile', username=session.username) }}" class="footer-button">
<i class="fas fa-user"></i> Profile
</a>
<a href="{{ url_for('groups') }}" class="footer-button">
<i class="fas fa-users"></i> Groups
</a>
<a href="{{ url_for('dms') }}" class="footer-button">
<i class="fas fa-envelope"></i> DMs
</a>
{% if session.user_id and get_user_by_id(session.user_id)['username'] == 'avery' %}
<a href="{{ url_for('admin_panel') }}" class="footer-button">
<i class="fas fa-cog"></i> Admin Panel
</a>
{% endif %}
<a href="{{ url_for('logout') }}" class="footer-button">
<i class="fas fa-sign-out-alt"></i> Logout
</a>
</div>
</footer>
<script>
function toggleNewGroupForm() {
const form = document.getElementById('new-group-form');
form.classList.toggle('hidden');
}
function confirmGroupCreation() {
return confirm('Are you sure you want to create this group?');
}
</script>
</body>
</html>

View File

@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ group.name }} - aTweet</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<body>
<div class="container">
<a href="{{ url_for('groups') }}" class="btn btn-secondary back-button"><i class="fas fa-arrow-left"></i> Back to Groups</a>
<div class="group-header">
<img src="{{ url_for('static', filename='uploads/' + (group.avatar if group.avatar else 'default_pfp.png')) }}" alt="{{ group.name }}" class="group-avatar">
<div class="group-info">
<h1>{{ group.name }}</h1>
<p>{{ group.description }}</p>
{% if session.user_id == group.owner_id or get_user_by_id(session.user_id)['username'] == 'avery' %}
<a href="{{ url_for('edit_group', group_id=group.id) }}" class="btn btn-primary"><i class="fas fa-edit"></i> Edit Group</a>
{% endif %}
{% if not is_member %}
<form action="{{ url_for('join_group', group_id=group.id) }}" method="POST">
<button type="submit" class="btn btn-success"><i class="fas fa-user-plus"></i> Join Group</button>
</form>
{% else %}
<form action="{{ url_for('leave_group', group_id=group.id) }}" method="POST">
<button type="submit" class="btn btn-danger"><i class="fas fa-user-minus"></i> Leave Group</button>
</form>
{% endif %}
</div>
</div>
<div class="group-content">
<h2>Posts</h2>
{% if is_member %}
<form action="{{ url_for('post_in_group', group_id=group.id) }}" method="POST" class="tweet-form">
<textarea name="content" placeholder="Write your post here..." required></textarea>
<div class="form-footer">
<span class="char-count">280</span>
<button type="submit" class="btn btn-primary"><i class="fas fa-paper-plane"></i> Post</button>
</div>
</form>
{% else %}
<div class="profile-info-container">
<p class="info-message">Join this group to post.</p>
</div>
{% endif %}
<div class="post-list">
{% for post in posts %}
<div class="post">
<img src="{{ url_for('static', filename='uploads/' + (post.pfp if post.pfp else 'default_pfp.png')) }}" alt="{{ post.username }}" class="user-avatar">
<div class="post-content">
<span class="username">{{ post.username }}</span>
<p>{{ post.content }}</p>
<span class="timestamp">{{ post.created_at }}</span>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<footer>
<div class="footer-container">
<a href="{{ url_for('index') }}" class="footer-button">
<i class="fas fa-home"></i> Home
</a>
<a href="{{ url_for('profile', username=session.username) }}" class="footer-button">
<i class="fas fa-user"></i> Profile
</a>
<a href="{{ url_for('groups') }}" class="footer-button">
<i class="fas fa-users"></i> Groups
</a>
<a href="{{ url_for('dms') }}" class="footer-button">
<i class="fas fa-envelope"></i> DMs
</a>
<a href="{{ url_for('logout') }}" class="footer-button">
<i class="fas fa-sign-out-alt"></i> Logout
</a>
{% if session.user_id and get_user_by_id(session.user_id)['username'] == 'avery' %}
<a href="{{ url_for('admin_panel') }}" class="footer-button">
<i class="fas fa-cog"></i> Admin Panel
</a>
{% endif %}
</div>
</footer>
</body>
</html>

34
templates/hashtag.html Normal file
View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>#{{ hashtag }} - aTweet</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="container">
<h1>#{{ hashtag }}</h1>
<div class="tweets-list">
{% for tweet in tweets %}
<div class="tweet">
<div class="tweet-header">
<div class="tweet-user-info">
<img src="{{ url_for('static', filename='uploads/' + (tweet.user_pfp if tweet.user_pfp else 'default_pfp.png')) }}" alt="{{ tweet.username }}" class="tweet-pfp">
<div class="tweet-info">
<a href="{{ url_for('profile', username=tweet.username) }}" class="tweet-username">{{ tweet.username }}</a>
<span class="tweet-timestamp">{{ tweet.created_at }}</span>
</div>
</div>
</div>
<p class="tweet-content tweet-text-cull">{{ tweet.content|safe }}</p>
{% if tweet.image %}
<img src="{{ url_for('static', filename='uploads/' + tweet.image) }}" alt="Tweet image" class="tweet-image">
{% endif %}
</div>
{% endfor %}
</div>
<a href="{{ url_for('index') }}">Back to Home</a>
</div>
</body>
</html>

198
templates/index.html Normal file
View File

@ -0,0 +1,198 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>aTweet - Home</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<style>
.tweet-content a.hashtag {
color: #2ecc71;
}
.tweet-content a.mention {
color: #27ae60;
}
.tweet-content a.hashtag:hover {
color: #25a25a;
}
.tweet-content a.mention:hover {
color: #1e8449;
}
</style>
<body>
<div class="container">
<h1>aTweet</h1>
<form action="{{ url_for('search') }}" method="GET" class="search-form">
<input type="text" name="q" placeholder="Search users...">
<button type="submit">Search</button>
</form>
<form action="{{ url_for('tweet') }}" method="POST" class="tweet-form" enctype="multipart/form-data">
<textarea name="content" required placeholder="What's happening?" maxlength="280"></textarea>
<div class="form-footer">
<span class="char-count">280</span>
<button type="submit">Tweet</button>
</div>
</form>
<div class="tweets-list">
{% for tweet in tweets %}
<div class="tweet">
<div class="tweet-header">
<div class="tweet-user-info">
<img src="{{ url_for('static', filename='uploads/' + (tweet.user_pfp if tweet.user_pfp else 'default_pfp.png')) }}" alt="{{ tweet.username }}" class="tweet-pfp">
<div class="tweet-info">
<a href="{{ url_for('profile', username=tweet.username) }}" class="tweet-username">{{ tweet.username }}</a>
<span class="tweet-timestamp">{{ tweet.created_at.strftime('%b %d, %Y, %I:%M %p') }} (UTC +2)</span>
</div>
</div>
{% if tweet.user_id == session.user_id %}
<div class="tweet-options">
<button class="options-button" onclick="toggleMenu({{ tweet.id }})">
<i class="fas fa-ellipsis-h"></i>
</button>
<div id="menu-{{ tweet.id }}" class="tweet-menu">
<button onclick="editTweet({{ tweet.id }})">
<i class="fas fa-edit"></i> Edit
</button>
<button onclick="deleteTweet({{ tweet.id }})">
<i class="fas fa-trash-alt"></i> Delete
</button>
</div>
</div>
{% endif %}
</div>
<p class="tweet-content tweet-text-cull">{{ tweet.displayed_content|safe }}</p>
{% if tweet.image %}
<img src="{{ url_for('static', filename='uploads/' + tweet.image) }}" alt="Tweet image" class="tweet-image">
{% endif %}
<div class="tweet-actions">
<button class="like-button {% if tweet.id in liked_tweet_ids %}liked{% endif %}" data-tweet-id="{{ tweet.id }}">
<i class="fas fa-heart"></i> {{ tweet.likes }}
</button>
<button class="comment-button" data-tweet-id="{{ tweet.id }}">
<i class="fas fa-comment"></i> Comment
</button>
<!-- Add retweet button -->
<form action="{{ url_for('retweet', tweet_id=tweet.id) }}" method="POST" style="display: inline;">
<button type="submit" class="retweet-button">
<i class="fas fa-retweet"></i> Retweet
</button>
</form>
</div>
{% if tweet.original_tweet_id %}
<div class="retweet-info">
Retweeted from <a href="{{ url_for('tweet_detail', tweet_id=tweet.original_tweet_id) }}">original tweet</a>
</div>
{% endif %}
</div>
{% endfor %}
</div>
</div>
<footer>
<div class="footer-container">
<a href="{{ url_for('index') }}" class="footer-button">
<i class="fas fa-home"></i> Home
</a>
<a href="{{ url_for('profile', username=session.username) }}" class="footer-button">
<i class="fas fa-user"></i> Profile
</a>
<a href="{{ url_for('groups') }}" class="footer-button">
<i class="fas fa-users"></i> Groups
</a>
<a href="{{ url_for('dms') }}" class="footer-button">
<i class="fas fa-envelope"></i> DMs
</a>
{% if session.user_id and get_user_by_id(session.user_id)['username'] == 'avery' %}
<a href="{{ url_for('admin_panel') }}" class="footer-button">
<i class="fas fa-cog"></i> Admin Panel
</a>
{% endif %}
<a href="{{ url_for('logout') }}" class="footer-button">
<i class="fas fa-sign-out-alt"></i> Logout
</a>
</div>
</footer>
<script>
function toggleMenu(tweetId) {
const menu = document.getElementById(`menu-${tweetId}`);
menu.classList.toggle('show');
}
function editTweet(tweetId) {
window.location.href = `/edit_tweet/${tweetId}`;
}
function deleteTweet(tweetId) {
if (confirm('Are you sure you want to delete this tweet?')) {
fetch(`/delete_tweet/${tweetId}`, { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.success) {
const tweetElement = document.querySelector(`.tweet[data-tweet-id="${tweetId}"]`);
if (tweetElement) {
tweetElement.remove();
}
} else {
alert('Failed to delete tweet');
}
});
}
}
function commentOnTweet(tweetId) {
window.location.href = `/tweet/${tweetId}`;
}
function likeTweet(tweetId) {
fetch(`/like/${tweetId}`, { method: 'POST' })
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.success) {
const likeButton = document.querySelector(`.like-button[data-tweet-id="${tweetId}"]`);
likeButton.classList.toggle('liked');
const likeCount = likeButton.querySelector('i').nextSibling;
likeCount.textContent = ` ${data.likes}`;
} else {
throw new Error(data.message || 'Failed to like tweet');
}
})
.catch(error => {
console.error('Error:', error);
alert('Failed to like tweet: ' + error.message);
});
}
document.addEventListener('DOMContentLoaded', function() {
const likeButtons = document.querySelectorAll('.like-button');
const commentButtons = document.querySelectorAll('.comment-button');
likeButtons.forEach(button => {
button.addEventListener('click', function() {
const tweetId = this.getAttribute('data-tweet-id');
likeTweet(tweetId);
});
});
commentButtons.forEach(button => {
button.addEventListener('click', function() {
const tweetId = this.getAttribute('data-tweet-id');
commentOnTweet(tweetId);
});
});
});
</script>
</body>
</html>

15
templates/login.html Normal file
View File

@ -0,0 +1,15 @@
{% extends "base.html" %}
{% block title %}Login - aTweet{% endblock %}
{% block content %}
<div class="container">
<h1>Login to aTweet</h1>
<form action="{{ url_for('login') }}" method="POST" class="auth-form">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
<p>Don't have an account? <a href="{{ url_for('signup') }}">Sign up</a></p>
</div>
{% endblock %}

203
templates/profile.html Normal file
View File

@ -0,0 +1,203 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ user.username }}'s Profile - aTweet</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<body>
<div class="container">
<div class="profile-info-container">
<div class="banner-container">
<img class="profile-banner" src="{{ url_for('static', filename='uploads/' + user.banner) if user.banner else url_for('static', filename='default_banner.jpg') }}" alt="Profile Banner">
<div class="profile-pfp-container">
<img src="{{ url_for('static', filename='uploads/' + (user.pfp if user.pfp else 'default_pfp.png')) }}" alt="Profile Picture" class="profile-pfp">
</div>
</div>
<div class="profile-info">
<div class="profile-header">
<h2 class="profile-username">
{{ user.username }}
{% if user.username == 'avery' %}
<span class="crown-icon" title="Owner">👑</span>
<span class="badge" title="Mod">🛡️</span>
<span class="badge" title="Dev">🛠️</span>
<span class="badge" title="Verified"></span>
<span class="badge" title="Bug Finder">🪳</span>
{% elif user.username == 'asd' %}
<span class="badge" title="Test User">🧪</span>
<span class="badge" title="Verified"></span>
<span class="badge" title="Bug Finder">🪳</span>
{% elif user.username == 'sqtt' %}
<span class="badge" title="Verified"></span>
<span class="badge" title="Bug Finder">🪳</span>
{% elif user.username == 'Andrecon' %}
<span class="badge" title="Verified"></span>
{% endif %}
</h2>
</div>
<p class="join-date">Joined: {{ user.created_at.strftime('%B %d, %Y') }}</p>
</div>
</div>
{% if session.user_id == user.id %}
<div class="profile-edit-forms">
<form action="{{ url_for('change_profile_picture') }}" method="POST" enctype="multipart/form-data" class="edit-form">
<input type="file" name="profile_picture" accept="image/*" required>
<button type="submit" class="btn btn-primary">Change Profile Picture</button>
</form>
<form action="{{ url_for('change_banner') }}" method="POST" enctype="multipart/form-data" class="edit-form">
<input type="file" name="banner" accept="image/*" required>
<button type="submit" class="btn btn-primary">Change Banner</button>
</form>
</div>
{% endif %}
<h2>Tweets</h2>
<div class="tweets-list">
{% for tweet in tweets %}
<div class="tweet">
<div class="tweet-header">
<div class="tweet-user-info">
<img class="tweet-pfp" src="{{ url_for('static', filename='uploads/' + (user.pfp if user.pfp else 'default_pfp.png')) }}" alt="Profile Picture">
<div class="tweet-info">
<h3 class="tweet-username">{{ user.username }}</h3>
<span class="tweet-date">{{ tweet.created_at.strftime('%B %d, %Y at %I:%M %p') }}</span>
</div>
</div>
{% if tweet.user_id == session.user_id %}
<div class="tweet-options">
<button class="options-button" onclick="toggleMenu({{ tweet.id }})">
<i class="fas fa-ellipsis-h"></i>
</button>
<div id="menu-{{ tweet.id }}" class="tweet-menu">
<button onclick="editTweet({{ tweet.id }})">
<i class="fas fa-edit"></i> Edit
</button>
<button onclick="deleteTweet({{ tweet.id }})">
<i class="fas fa-trash-alt"></i> Delete
</button>
</div>
</div>
{% endif %}
</div>
<p class="tweet-content">{{ tweet.content }}</p>
<div class="tweet-actions">
<button class="like-button {% if tweet.id in liked_tweet_ids %}liked{% endif %}" data-tweet-id="{{ tweet.id }}">
<i class="fas fa-heart"></i> {{ tweet.likes }}
</button>
<button class="comment-button" data-tweet-id="{{ tweet.id }}">
<i class="fas fa-comment"></i> Comment
</button>
</div>
</div>
{% endfor %}
</div>
</div>
<footer>
<div class="footer-container">
<a href="{{ url_for('index') }}" class="footer-button">
<i class="fas fa-home"></i> Home
</a>
<a href="{{ url_for('profile', username=session.username) }}" class="footer-button">
<i class="fas fa-user"></i> Profile
</a>
<a href="{{ url_for('groups') }}" class="footer-button">
<i class="fas fa-users"></i> Groups
</a>
<a href="{{ url_for('dms') }}" class="footer-button">
<i class="fas fa-envelope"></i> DMs
</a>
{% if session.user_id and get_user_by_id(session.user_id)['username'] == 'avery' %}
<a href="{{ url_for('admin_panel') }}" class="footer-button">
<i class="fas fa-cog"></i> Admin Panel
</a>
{% endif %}
<a href="{{ url_for('logout') }}" class="footer-button">
<i class="fas fa-sign-out-alt"></i> Logout
</a>
</div>
</footer>
<script>
function toggleMenu(tweetId) {
const menu = document.getElementById(`menu-${tweetId}`);
menu.classList.toggle('show');
}
function editTweet(tweetId) {
window.location.href = `/edit_tweet/${tweetId}`;
}
function deleteTweet(tweetId) {
if (confirm('Are you sure you want to delete this tweet?')) {
fetch(`/delete_tweet/${tweetId}`, { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Failed to delete tweet');
}
});
}
}
function likeTweet(tweetId) {
fetch(`/like/${tweetId}`, { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.success) {
const likeButton = document.querySelector(`.like-button[data-tweet-id="${tweetId}"]`);
likeButton.classList.toggle('liked');
const likeCount = likeButton.querySelector('i').nextSibling;
likeCount.textContent = ` ${data.likes}`;
} else {
alert('Failed to like tweet');
}
});
}
document.addEventListener('DOMContentLoaded', function() {
const likeButtons = document.querySelectorAll('.like-button');
const commentButtons = document.querySelectorAll('.comment-button');
likeButtons.forEach(button => {
button.addEventListener('click', function() {
const tweetId = this.getAttribute('data-tweet-id');
likeTweet(tweetId);
});
});
commentButtons.forEach(button => {
button.addEventListener('click', function() {
const tweetId = this.getAttribute('data-tweet-id');
commentOnTweet(tweetId);
});
});
});
// Add event listeners to like and comment buttons
document.addEventListener('DOMContentLoaded', function() {
const likeButtons = document.querySelectorAll('.like-button');
const commentButtons = document.querySelectorAll('.comment-button');
likeButtons.forEach(button => {
button.addEventListener('click', function() {
const tweetId = this.getAttribute('data-tweet-id');
likeTweet(tweetId);
});
});
commentButtons.forEach(button => {
button.addEventListener('click', function() {
const tweetId = this.getAttribute('data-tweet-id');
commentOnTweet(tweetId);
});
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Search Results - aTweet</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="container">
<h1>Search Results for "{{ query }}"</h1>
<div class="user-list">
{% for user in users %}
<div class="user-item">
<img src="{{ url_for('static', filename='uploads/' + (user.pfp if user.pfp else 'default_pfp.png')) }}" alt="{{ user.username }}" class="user-pfp">
<a href="{{ url_for('profile', username=user.username) }}">{{ user.username }}</a>
</div>
{% endfor %}
</div>
<a href="{{ url_for('index') }}">Back to Home</a>
</div>
</body>
</html>

16
templates/signup.html Normal file
View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block title %}Sign Up - aTweet{% endblock %}
{% block content %}
<div class="container">
<h1>Sign Up for aTweet</h1>
<form action="{{ url_for('signup') }}" method="POST" class="auth-form">
<input type="text" name="username" placeholder="Username" required>
<input type="email" name="email" placeholder="Email" required>
<input type="password" name="password" placeholder="Password" required>
<button type="submit">Sign Up</button>
</form>
<p>Already have an account? <a href="{{ url_for('login') }}">Login</a></p>
</div>
{% endblock %}

View File

@ -0,0 +1,68 @@
{% extends "base.html" %}
{% block title %}Tweet Detail - aTweet{% endblock %}
{% block content %}
<div class="tweet-detail">
<h3>{{ tweet.username }}</h3>
<p>{{ tweet.content }}</p>
<button class="like-button {% if tweet.id in liked_tweet_ids %}liked{% endif %}" data-tweet-id="{{ tweet.id }}">
<i class="fas fa-heart"></i> <span class="like-count">{{ tweet.likes }}</span>
</button>
<h4>Comments</h4>
<form id="comment-form">
<textarea name="content" rows="2" placeholder="Add a comment" required></textarea>
<button type="submit">Comment</button>
</form>
<div id="comments-container">
{% for comment in comments %}
<p>{{ comment.username }}: {{ comment.content }}</p>
{% endfor %}
</div>
</div>
<script>
function likeTweet(tweetId) {
fetch(`/like/${tweetId}`, { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.success) {
const likeButton = document.querySelector('.like-button');
likeButton.classList.toggle('liked');
document.querySelector('.like-count').textContent = data.likes;
}
});
}
document.querySelector('.like-button').addEventListener('click', function() {
const tweetId = this.getAttribute('data-tweet-id');
likeTweet(tweetId);
});
document.getElementById('comment-form').addEventListener('submit', function(e) {
e.preventDefault();
const content = this.content.value;
const tweetId = {{ tweet.id }};
fetch(`/comment/${tweetId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `content=${encodeURIComponent(content)}`
})
.then(response => response.json())
.then(data => {
if (data.success) {
const commentsContainer = document.getElementById('comments-container');
commentsContainer.innerHTML += `<p>{{ tweet.username }}: ${content}</p>`;
this.content.value = '';
} else {
alert('Failed to add comment');
}
});
});
</script>
{% endblock %}