diff --git a/genmig.sh b/genmig.sh
index a29c9fb..c141519 100755
--- a/genmig.sh
+++ b/genmig.sh
@@ -6,4 +6,5 @@ source .env && \
case "$1" in
("+") pw_migrate create --auto --auto-source=coriplus.models --directory=src/migrations --database="$DATABASE_URL" "${@:2}" ;;
("@") pw_migrate migrate --directory=src/migrations --database="$DATABASE_URL" "${@:2}" ;;
-esac
\ No newline at end of file
+ (\\) pw_migrate rollback --directory=src/migrations --database="$DATABASE_URL" "${@:2}" ;;
+esac
diff --git a/src/coriplus/__init__.py b/src/coriplus/__init__.py
index 98b4d85..9543c60 100644
--- a/src/coriplus/__init__.py
+++ b/src/coriplus/__init__.py
@@ -24,7 +24,7 @@ from flask_wtf import CSRFProtect
import dotenv
import logging
-__version__ = '0.10.0-dev44'
+__version__ = '0.10.0-dev45'
# we want to support Python 3.10+ only.
# Python 2 has too many caveats.
diff --git a/src/coriplus/admin.py b/src/coriplus/admin.py
index a78cf1b..d8c8fb0 100644
--- a/src/coriplus/admin.py
+++ b/src/coriplus/admin.py
@@ -13,17 +13,16 @@ from functools import wraps
bp = Blueprint('admin', __name__, url_prefix='/admin')
-def _check_auth(username, password) -> bool:
+def _check_auth(username) -> bool:
try:
- return User.select().where((User.username == username) & (User.password == pwdhash(password)) & (User.is_admin)
- ).exists()
+ return User.get((User.username == username)).is_admin
except User.DoesNotExist:
return False
def admin_required(f):
@wraps(f)
def wrapped_view(**kwargs):
- if not _check_auth(current_user.username, current_user.password):
+ if not _check_auth(current_user.username):
abort(403)
return f(**kwargs)
return wrapped_view
diff --git a/src/coriplus/models.py b/src/coriplus/models.py
index 5a2ae50..f47fa65 100644
--- a/src/coriplus/models.py
+++ b/src/coriplus/models.py
@@ -29,19 +29,25 @@ class BaseModel(Model):
# A user. The user is separated from its page.
class User(BaseModel):
# The unique username.
- username = CharField(unique=True)
+ username = CharField(30, unique=True)
# The user's full name (here for better search since 0.8)
- full_name = TextField()
+ full_name = CharField(80)
# The password hash.
- password = CharField()
+ password = CharField(256)
# An email address.
- email = CharField()
+ email = CharField(256)
# The date of birth (required because of Terms of Service)
birthday = DateField()
# The date joined
join_date = DateTimeField()
# A disabled flag. 0 = active, 1 = disabled by user, 2 = banned
is_disabled = IntegerField(default=0)
+ # Short description of user.
+ biography = CharField(256, default='')
+ # Personal website.
+ website = TextField(null=True)
+
+
# Helpers for flask_login
def get_id(self):
@@ -110,15 +116,12 @@ class UserAdminship(BaseModel):
# User profile.
# Additional info for identifying users.
# New in 0.6
+# Deprecated in 0.10 and merged with User
class UserProfile(BaseModel):
user = ForeignKeyField(User, primary_key=True)
biography = TextField(default='')
location = IntegerField(null=True)
- year = IntegerField(null=True)
website = TextField(null=True)
- instagram = TextField(null=True)
- facebook = TextField(null=True)
- telegram = TextField(null=True)
@property
def full_name(self):
'''
diff --git a/src/coriplus/static/style.css b/src/coriplus/static/style.css
index c238285..cb05c09 100644
--- a/src/coriplus/static/style.css
+++ b/src/coriplus/static/style.css
@@ -2,6 +2,9 @@
--accent: #f0372e;
--link: #3399ff;
}
+* {
+ box-sizing: border-box;
+}
body, button, input, select, textarea {
font-family: Inter, Roboto, sans-serif;
line-height: 1.6;
@@ -38,13 +41,13 @@ a:hover{text-decoration:underline}
#site-name {text-align: center;flex: 1}
.header h1{margin:0;display:inline-block}
.flash{background-color:#ff9;border:yellow 1px solid}
-.infobox{padding:12px;border:#ccc 1px solid}
-@media (min-width:640px) {
- .infobox{float:right;width:320px}
+.infobox{width: 50%; float: right;}
+@media (max-width:639px) {
+ .infobox{width: 100%;}
}
.weak{opacity:.5}
.field_desc{display:block}
-ul.timeline{padding:0;margin:auto;max-width:960px}
+ul.timeline{padding:0;margin:auto;max-width:960px;clear: both}
ul.timeline > li{list-style:none;}
.message-visual img{max-width:100%;margin:auto}
.message-options-showhide::before{content:'\2026'}
diff --git a/src/coriplus/templates/includes/infobox_profile.html b/src/coriplus/templates/includes/infobox_profile.html
index 2cec0b7..bc08a20 100644
--- a/src/coriplus/templates/includes/infobox_profile.html
+++ b/src/coriplus/templates/includes/infobox_profile.html
@@ -1,26 +1,11 @@
-{% set profile = user.profile %}
-
-
{{ profile.full_name }}
-
{{ profile.biography|enrich }}
- {% if profile.location %}
-
Location: {{ profile.location|locationdata }}
- {% endif %}
- {% if profile.year %}
-
Year: {{ profile.year }}
- {% endif %}
- {% if profile.website %}
- {% set website = profile.website %}
+
+
diff --git a/src/coriplus/utils.py b/src/coriplus/utils.py
index 7a98d5b..1db7414 100644
--- a/src/coriplus/utils.py
+++ b/src/coriplus/utils.py
@@ -3,6 +3,8 @@ A list of utilities used across modules.
'''
import datetime, re, base64, hashlib, string, sys, json
+
+from flask_login import current_user
from .models import User, Message, Notification, MSGPRV_PUBLIC, MSGPRV_UNLISTED, \
MSGPRV_FRIENDS, MSGPRV_ONLYME
from flask import abort, render_template, request, session
@@ -102,15 +104,14 @@ except OSError:
# get the user from the session
# changed in 0.5 to comply with flask_login
+# DEPRECATED in 0.10; use current_user instead
def get_current_user():
# new in 0.7; need a different method to get current user id
if request.path.startswith('/api/'):
# assume token validation is already done
return User[request.args['access_token'].split(':')[0]]
- else:
- user_id = session.get('user_id')
- if user_id:
- return User[user_id]
+ elif current_user.is_authenticated:
+ return current_user
def push_notification(type, target, **kwargs):
try:
diff --git a/src/coriplus/website.py b/src/coriplus/website.py
index df43b86..14b944c 100644
--- a/src/coriplus/website.py
+++ b/src/coriplus/website.py
@@ -7,7 +7,7 @@ from .models import *
from . import __version__ as app_version
from sys import version as python_version
from flask import Blueprint, abort, flash, redirect, render_template, request, url_for, __version__ as flask_version
-from flask_login import login_required, login_user, logout_user
+from flask_login import current_user, login_required, login_user, logout_user
import json
import logging
@@ -17,7 +17,7 @@ bp = Blueprint('website', __name__)
@bp.route('/')
def homepage():
- if get_current_user():
+ if current_user and current_user.is_authenticated:
return private_timeline()
else:
return render_template('homepage.html')
@@ -26,7 +26,7 @@ def private_timeline():
# the private timeline (aka feed) exemplifies the use of a subquery -- we are asking for
# messages where the person who created the message is someone the current
# user is following. these messages are then ordered newest-first.
- user = get_current_user()
+ user = current_user
messages = Visibility(Message
.select()
.where((Message.user << user.following())
@@ -83,6 +83,9 @@ def register():
@bp.route('/login/', methods=['GET', 'POST'])
def login():
+ if current_user and current_user.is_authenticated:
+ flash('You are already logged in')
+ return redirect(request.args.get('next', '/'))
if request.method == 'POST' and request.form['username']:
try:
username = request.form['username']
@@ -134,11 +137,12 @@ def user_follow(username):
from_user=cur_user,
to_user=user,
created_date=datetime.datetime.now())
+ push_notification('follow', user, user=cur_user.id)
+ flash('You are now following %s' % user.username)
except IntegrityError:
- pass
+ flash(f'Error following {user.username}')
+
- flash('You are following %s' % user.username)
- push_notification('follow', user, user=cur_user.id)
return redirect(url_for('website.user_detail', username=user.username))
@bp.route('/+
/unfollow/', methods=['POST'])
diff --git a/src/migrations/002_move_columns_from_userprofile.py b/src/migrations/002_move_columns_from_userprofile.py
new file mode 100644
index 0000000..0b2d004
--- /dev/null
+++ b/src/migrations/002_move_columns_from_userprofile.py
@@ -0,0 +1,81 @@
+"""Peewee migrations -- 002_move_columns_from_userprofile.py.
+
+Some examples (model - class or model name)::
+
+ > Model = migrator.orm['table_name'] # Return model in current state by name
+ > Model = migrator.ModelClass # Return model in current state by name
+
+ > migrator.sql(sql) # Run custom SQL
+ > migrator.run(func, *args, **kwargs) # Run python function with the given args
+ > migrator.create_model(Model) # Create a model (could be used as decorator)
+ > migrator.remove_model(model, cascade=True) # Remove a model
+ > migrator.add_fields(model, **fields) # Add fields to a model
+ > migrator.change_fields(model, **fields) # Change fields
+ > migrator.remove_fields(model, *field_names, cascade=True)
+ > migrator.rename_field(model, old_field_name, new_field_name)
+ > migrator.rename_table(model, new_table_name)
+ > migrator.add_index(model, *col_names, unique=False)
+ > migrator.add_not_null(model, *field_names)
+ > migrator.add_default(model, field_name, default)
+ > migrator.add_constraint(model, name, sql)
+ > migrator.drop_index(model, *col_names)
+ > migrator.drop_not_null(model, *field_names)
+ > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+ import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+ """Write your migrations here."""
+
+ migrator.add_fields(
+ 'user',
+
+ biography=pw.CharField(max_length=256, default=""),
+ website=pw.TextField(null=True))
+
+ migrator.change_fields('user', username=pw.CharField(max_length=30, unique=True))
+
+ migrator.change_fields('user', full_name=pw.CharField(max_length=80))
+
+ migrator.change_fields('user', password=pw.CharField(max_length=256))
+
+ migrator.change_fields('user', email=pw.CharField(max_length=256))
+
+ migrator.sql("""
+ UPDATE "user" SET biography = (SELECT p.biography FROM userprofile p WHERE p.user_id = id LIMIT 1),
+ website = (SELECT p.website FROM userprofile p WHERE p.user_id = id LIMIT 1);
+ """)
+
+ migrator.remove_fields('userprofile', 'year', 'instagram', 'facebook', 'telegram')
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+ """Write your rollback migrations here."""
+
+ migrator.add_fields(
+ 'userprofile',
+
+ year=pw.IntegerField(null=True),
+ instagram=pw.TextField(null=True),
+ facebook=pw.TextField(null=True),
+ telegram=pw.TextField(null=True))
+
+ migrator.remove_fields('user', 'biography', 'website')
+
+ migrator.change_fields('user', username=pw.CharField(max_length=255, unique=True))
+
+ migrator.change_fields('user', full_name=pw.TextField())
+
+ migrator.change_fields('user', password=pw.CharField(max_length=255))
+
+ migrator.change_fields('user', email=pw.CharField(max_length=255))