diff --git a/.gitignore b/.gitignore index 75b7704..c9522f1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,18 +4,5 @@ __pycache__/ uploads/ *.pyc **~ -.*.swp -__pycache__/ -venv -.env -.venv -env -data/ -conf/ -config/ -\#*\# -.\#* -node_modules/ -alembic.ini -**.egg-info -.vscode \ No newline at end of file +**/.*.swp +**/__pycache__/ diff --git a/CHANGELOG.md b/CHANGELOG.md index cfb7a8a..cae4de4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,28 +1,5 @@ # Changelog -## 0.10.0 -+ Codebase refactor (with breaking changes!) -+ Dropped support for Python<=3.9 -+ Switched database to PostgreSQL -+ Move ALL config to .env (config.py is NO MORE supported) -+ Config SITE_NAME replaced with APP_NAME -+ Add CSRF token and flask_WTF -+ Schema changes: biography and website moved to `User`; `UserProfile` table deprecated (and useless fields removed) -+ Posts can now be permanently deleted -+ Miscellaneous style changes - -## 0.9.0 - -* Website redesign: added some material icons, implemented via a `inline_svg` function, injected by default in templates and defined in `utils.py`. -* Added positive feedback mechanism: now you can +1 a message. So, `score_message_add` and `score_message_remove` API endpoints were added, and `MessageUpvote` table was created. -* Added notifications support for API. -* Added `create_account` endpoint to API. This endpoint does not require an access token. -* Added `explore`, `notifications_count`, `notifications` and `notifications_seen` endpoints. -* Added `has_more` field to feed endpoints (`feed`, `explore` and `profile_feed`). -* Added `join_date` field into `user` object of `profile_info` endpoint, for more profile transparency. -* Added `/favicon.ico`. -* Fixed some bugs when creating mentions and using offsets in feeds. - ## 0.8.0 * Added the admin dashboard, accessible from `/admin/` via basic auth. Only users with admin right can access it. Added endpoints `admin.reports` and `admin.reports_detail`. @@ -35,9 +12,9 @@ * Added `relationships_follow`, `relationships_unfollow`, `username_availability`, `edit_profile`, `request_edit` and `confirm_edit` endpoints to API. * Added `url` utility to model `Upload`. * Changed default `robots.txt`, adding report and admin-related lines. -* Released official [Android client](https://github.com/sakuragasaki46/coriplusapp/releases/tag/v0.8.0). +* Released official [Android client](https://github.com/sakuragasaki46/coriplusapp/releases/tag/v0.8.0) -## 0.7.1 +## 0.7.1-dev * Adding `messages_count`, `followers_count` and `following_count` to `profile_info` API endpoint (forgot to release). diff --git a/README.md b/README.md index 3ee8c96..b96fffc 100644 --- a/README.md +++ b/README.md @@ -16,19 +16,10 @@ This is the server. For the client, see [coriplusapp](https://github.com/sakurag * Add info to your profile * In-site notifications * Public API -* SQLite (or PostgreSQL)-based app +* SQLite-based app ## Requirements -* **Python 3.10+** with **pip**. -* **Flask** web framework. +* **Python 3** only. We don't want to support Python 2. +* **Flask** web framework (also required extension **Flask-Login**). * **Peewee** ORM. -* A \*nix-based OS. - -## Installation - -* Install dependencies: `pip install .` -* Set the `DATABASE_URL` (must be SQLite or PostgreSQL) -* Run the migrations: `sh ./genmig.sh @` -* i forgor - diff --git a/src/coriplus/__init__.py b/app/__init__.py similarity index 69% rename from src/coriplus/__init__.py rename to app/__init__.py index 09cecf6..835fcbc 100644 --- a/src/coriplus/__init__.py +++ b/app/__init__.py @@ -16,37 +16,25 @@ For other, see `app.utils`. ''' from flask import ( - Flask, g, jsonify, render_template, request, - send_from_directory, __version__ as flask_version) -import os, sys + Flask, abort, flash, g, jsonify, redirect, render_template, request, + send_from_directory, session, url_for, __version__ as flask_version) +import hashlib +import datetime, time, re, os, sys, string, json, html +from functools import wraps from flask_login import LoginManager -from flask_wtf import CSRFProtect -import dotenv -import logging -__version__ = '0.10.0-dev50' +__version__ = '0.8.0' -# we want to support Python 3.10+ only. +# we want to support Python 3 only. # Python 2 has too many caveats. -# Python <=3.9 has harder type support. -if sys.version_info[0:2] < (3, 10): - raise RuntimeError('Python 3.10+ required') - -BASEDIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) -os.chdir(BASEDIR) - -dotenv.load_dotenv() - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) +if sys.version_info[0] < 3: + raise RuntimeError('Python 3 required') app = Flask(__name__) -app.secret_key = os.environ['SECRET_KEY'] +app.config.from_pyfile('../config.py') login_manager = LoginManager(app) -CSRFProtect(app) - from .models import * from .utils import * @@ -63,43 +51,28 @@ def before_request(): try: g.db.connect() except OperationalError: - logger.error('database connected twice') + sys.stderr.write('database connected twice.\n') @app.after_request def after_request(response): - try: - g.db.close() - except Exception: - logger.error('database closed twice') + g.db.close() return response @app.context_processor def _inject_variables(): - return { - 'site_name': os.environ.get('APP_NAME', 'Cori+'), - 'locations': locations, - 'inline_svg': inline_svg - } + return {'site_name': app.config['SITE_NAME'], 'locations': locations} @login_manager.user_loader def _inject_user(userid): return User[userid] -@app.errorhandler(403) -def error_403(body): - return render_template('403.html'), 403 - @app.errorhandler(404) def error_404(body): return render_template('404.html'), 404 - -@app.route('/favicon.ico') -def favicon_ico(): - return send_from_directory(BASEDIR, 'src/favicon.ico') @app.route('/robots.txt') def robots_txt(): - return send_from_directory(BASEDIR, 'src/robots.txt') + return send_from_directory(os.getcwd(), 'robots.txt') @app.route('/uploads/.') def uploads(id, type='jpg'): diff --git a/src/coriplus/__main__.py b/app/__main__.py similarity index 100% rename from src/coriplus/__main__.py rename to app/__main__.py diff --git a/src/coriplus/admin.py b/app/admin.py similarity index 80% rename from src/coriplus/admin.py rename to app/admin.py index d8c8fb0..b8fb4b9 100644 --- a/src/coriplus/admin.py +++ b/app/admin.py @@ -4,8 +4,7 @@ Management of reports and the entire site. New in 0.8. ''' -from flask import Blueprint, abort, redirect, render_template, request, url_for -from flask_login import current_user +from flask import Blueprint, redirect, render_template, request, url_for from .models import User, Message, Report, report_reasons, REPORT_STATUS_ACCEPTED, \ REPORT_MEDIA_USER, REPORT_MEDIA_MESSAGE from .utils import pwdhash, object_list @@ -13,17 +12,21 @@ from functools import wraps bp = Blueprint('admin', __name__, url_prefix='/admin') -def _check_auth(username) -> bool: +def check_auth(username, password): try: - return User.get((User.username == username)).is_admin + return User.get((User.username == username) & (User.password == pwdhash(password)) + ).is_admin except User.DoesNotExist: return False def admin_required(f): @wraps(f) def wrapped_view(**kwargs): - if not _check_auth(current_user.username): - abort(403) + auth = request.authorization + if not (auth and check_auth(auth.username, auth.password)): + return ('Unauthorized', 401, { + 'WWW-Authenticate': 'Basic realm="Login Required"' + }) return f(**kwargs) return wrapped_view diff --git a/src/coriplus/ajax.py b/app/ajax.py similarity index 53% rename from src/coriplus/ajax.py rename to app/ajax.py index cad9c74..63dc532 100644 --- a/src/coriplus/ajax.py +++ b/app/ajax.py @@ -5,10 +5,8 @@ Warning: this is not the public API. ''' from flask import Blueprint, jsonify -from flask_login import current_user -from .models import User, Message, MessageUpvote -from .utils import locations, is_username -import datetime +from .models import User +from .utils import locations, get_current_user, is_username bp = Blueprint('ajax', __name__, url_prefix='/ajax') @@ -37,30 +35,3 @@ def location_search(name): if value.lower().startswith(name.lower()): results.append({'value': key, 'display': value}) return jsonify({'results': results}) - -@bp.route('/score//toggle', methods=['POST']) -def score_toggle(id): - user = current_user - message = Message[id] - upvoted_by_self = (MessageUpvote - .select() - .where((MessageUpvote.message == message) & (MessageUpvote.user == user)) - .exists()) - if upvoted_by_self: - (MessageUpvote - .delete() - .where( - (MessageUpvote.message == message) & - (MessageUpvote.user == user)) - .execute() - ) - else: - MessageUpvote.create( - message=message, - user=user, - created_date=datetime.datetime.now() - ) - return jsonify({ - "score": message.score, - "status": "ok" - }) diff --git a/src/coriplus/api.py b/app/api.py similarity index 66% rename from src/coriplus/api.py rename to app/api.py index 5f58c2c..0cb8719 100644 --- a/src/coriplus/api.py +++ b/app/api.py @@ -1,15 +1,11 @@ from flask import Blueprint, jsonify, request -import sys, os, datetime, re, uuid +import sys, os, datetime, re from functools import wraps from peewee import IntegrityError -from .models import User, UserProfile, Message, Upload, Relationship, Notification, \ - MessageUpvote, database, \ +from .models import User, UserProfile, Message, Upload, Relationship, database, \ MSGPRV_PUBLIC, MSGPRV_UNLISTED, MSGPRV_FRIENDS, MSGPRV_ONLYME, UPLOAD_DIRECTORY from .utils import check_access_token, Visibility, push_notification, unpush_notification, \ - create_mentions, is_username, generate_access_token, pwdhash -import logging - -logger = logging.getLogger(__name__) + create_mentions, is_username bp = Blueprint('api', __name__, url_prefix='/api/V1') @@ -19,7 +15,7 @@ def get_message_info(message): except IndexError: media = None if media: - logger.debug(media) + print(media) return { 'id': message.id, 'user': { @@ -29,9 +25,7 @@ def get_message_info(message): 'text': message.text, 'privacy': message.privacy, 'pub_date': message.pub_date.timestamp(), - 'media': media, - 'score': len(message.upvotes), - 'upvoted_by_self': message.upvoted_by_self(), + 'media': media } def validate_access(func): @@ -70,33 +64,17 @@ def feed(self): if date is None: date = datetime.datetime.now() else: - date = datetime.datetime.fromtimestamp(float(date)) + date = datetime.datetime.fromtimestamp(date) query = Visibility(Message .select() .where(((Message.user << self.following()) | (Message.user == self)) & (Message.pub_date < date)) - .order_by(Message.pub_date.desc())) - for message in query.paginate(1): + .order_by(Message.pub_date.desc()) + .limit(20)) + for message in query: timeline_media.append(get_message_info(message)) - return {'timeline_media': timeline_media, 'has_more': query.count() > len(timeline_media)} - -@bp.route('/explore') -@validate_access -def explore(self): - timeline_media = [] - date = request.args.get('offset') - if date is None: - date = datetime.datetime.now() - else: - date = datetime.datetime.fromtimestamp(float(date)) - query = Visibility(Message - .select() - .where(Message.pub_date < date) - .order_by(Message.pub_date.desc()), True) - for message in query.paginate(1): - timeline_media.append(get_message_info(message)) - return {'timeline_media': timeline_media, 'has_more': query.count() > len(timeline_media)} + return {'timeline_media': timeline_media} @bp.route('/create', methods=['POST']) @validate_access @@ -110,7 +88,7 @@ def create(self): pub_date=datetime.datetime.now(), privacy=privacy) # This API does not support files. Use create2 instead. - create_mentions(self, text, privacy) + create_mentions(self, text) return {} @bp.route('/create2', methods=['POST']) @@ -125,14 +103,14 @@ def create2(self): privacy=privacy) file = request.files.get('file') if file: - logger.info('Uploading', file.filename) + print('Uploading', file.filename) ext = file.filename.split('.')[-1] upload = Upload.create( type=ext, message=message ) file.save(os.path.join(UPLOAD_DIRECTORY, str(upload.id) + '.' + ext)) - create_mentions(self, text, privacy) + create_mentions(self, text) return {} def get_relationship_info(self, other): @@ -168,7 +146,6 @@ def profile_info(self, userid): "generation": profile.year, "instagram": profile.instagram, "facebook": profile.facebook, - "join_date": user.join_date.timestamp(), "relationships": get_relationship_info(self, user), "messages_count": len(user.messages), "followers_count": len(user.followers()), @@ -192,15 +169,16 @@ def profile_feed(self, userid): if date is None: date = datetime.datetime.now() else: - date = datetime.datetime.fromtimestamp(float(date)) + date = datetime.datetime.fromtimestamp(date) query = Visibility(Message .select() .where((Message.user == user) & (Message.pub_date < date)) - .order_by(Message.pub_date.desc())) - for message in query.paginate(1): + .order_by(Message.pub_date.desc()) + .limit(20)) + for message in query: timeline_media.append(get_message_info(message)) - return {'timeline_media': timeline_media, 'has_more': query.count() > len(timeline_media)} + return {'timeline_media': timeline_media} @bp.route('/relationships//follow', methods=['POST']) @validate_access @@ -327,110 +305,3 @@ def save_edit(self, id): data = request.get_json(True) Message.update(text=data['text'], privacy=data['privacy']).where(Message.id == id).execute() return {} - -# no validate access for this endpoint! -@bp.route('/create_account', methods=['POST']) -def create_account(): - try: - data = request.get_json(True) - try: - birthday = datetime.datetime.fromisoformat(data['birthday']) - except ValueError: - raise ValueError('invalid date format') - username = data['username'].lower() - if not is_username(username): - raise ValueError('invalid username') - with database.atomic(): - user = User.create( - username=username, - full_name=data.get('full_name') or username, - password=pwdhash(data['password']), - email=data['email'], - birthday=birthday, - join_date=datetime.datetime.now()) - UserProfile.create( - user=user - ) - - return jsonify({'access_token': generate_access_token(user), 'status': 'ok'}) - except Exception as e: - return jsonify({'message': str(e), 'status': 'fail'}) - -def get_notification_info(notification): - obj = { - "id": notification.id, - "type": notification.type, - "timestamp": notification.pub_date.timestamp(), - "seen": notification.seen - } - obj.update(json.loads(notification.detail)) - return obj - -@bp.route('/notifications/count') -@validate_access -def notifications_count(self): - count = len(Notification - .select() - .where((Notification.target == self) & (Notification.seen == 0))) - return { - 'count': count - } - -@bp.route('/notifications') -@validate_access -def notifications(self): - items = [] - query = (Notification - .select() - .where(Notification.target == self) - .order_by(Notification.pub_date.desc()) - .limit(100)) - unseen_count = len(Notification - .select() - .where((Notification.target == self) & (Notification.seen == 0))) - for notification in query: - items.append(get_notification_info(query)) - return { - "notifications": { - "items": items, - "unseen_count": unseen_count - } - } - -@bp.route('/notifications/seen', methods=['POST']) -@validate_access -def notifications_seen(self): - data = request.get_json(True) - (Notification - .update(seen=1) - .where((Notification.target == self) & (Notification.pub_date < data['offset'])) - .execute()) - return {} - -@bp.route('/score/message//add', methods=['POST']) -@validate_access -def score_message_add(self, id): - message = Message[id] - MessageUpvote.create( - message=message, - user=self, - created_date=datetime.datetime.now() - ) - return { - 'score': len(message.upvotes) - } - -@bp.route('/score/message//remove', methods=['POST']) -@validate_access -def score_message_remove(self, id): - message = Message[id] - (MessageUpvote - .delete() - .where( - (MessageUpvote.message == message) & - (MessageUpvote.user == self)) - .execute() - ) - return { - 'score': len(message.upvotes) - } diff --git a/src/coriplus/filters.py b/app/filters.py similarity index 95% rename from src/coriplus/filters.py rename to app/filters.py index 04d929a..2735d9d 100644 --- a/src/coriplus/filters.py +++ b/app/filters.py @@ -2,9 +2,9 @@ Filter functions used in the website templates. ''' -from markupsafe import Markup +from flask import Markup import html, datetime, re, time -from .utils import tokenize, inline_svg as _inline_svg +from .utils import tokenize from . import app @app.template_filter() @@ -64,4 +64,3 @@ def is_following(from_user, to_user): def locationdata(key): if key > 0: return locations[str(key)] - diff --git a/src/coriplus/models.py b/app/models.py similarity index 86% rename from src/coriplus/models.py rename to app/models.py index 07d52b7..ec37798 100644 --- a/src/coriplus/models.py +++ b/app/models.py @@ -13,43 +13,32 @@ The tables are: from flask import request from peewee import * -from playhouse.db_url import connect import os - -from . import BASEDIR # here should go `from .utils import get_current_user`, but it will cause # import errors. It's instead imported at function level. -database = connect(os.environ['DATABASE_URL']) +database = SqliteDatabase(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'coriplus.sqlite')) class BaseModel(Model): - id = AutoField(primary_key=True) - class Meta: database = database # A user. The user is separated from its page. class User(BaseModel): # The unique username. - username = CharField(30, unique=True) + username = CharField(unique=True) # The user's full name (here for better search since 0.8) - full_name = CharField(80) + full_name = TextField() # The password hash. - password = CharField(256) + password = CharField() # An email address. - email = CharField(256) + email = CharField() # 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): @@ -62,7 +51,8 @@ class User(BaseModel): return False @property def is_authenticated(self): - return True + from .utils import get_current_user + return self == get_current_user() # it often makes sense to put convenience methods on model instances, for # example, "give me all the users this user is following": @@ -118,12 +108,15 @@ 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): ''' @@ -164,22 +157,11 @@ class Message(BaseModel): # even if unlisted return not is_public_timeline elif privacy == MSGPRV_FRIENDS: - if not cur_user or cur_user.is_anonymous: + if cur_user.is_anonymous: return False return user.is_following(cur_user) and cur_user.is_following(user) else: return False - @property - def score(self): - return self.upvotes.count() - def upvoted_by_self(self): - from .utils import get_current_user - user = get_current_user() - return (MessageUpvote - .select() - .where((MessageUpvote.message == self) & (MessageUpvote.user == user)) - .exists() - ) # this model contains two foreign keys to user -- it essentially allows us to # model a "many-to-many" relationship between users. by querying and joining @@ -197,7 +179,7 @@ class Relationship(BaseModel): ) -UPLOAD_DIRECTORY = os.path.join(BASEDIR, 'uploads') +UPLOAD_DIRECTORY = os.path.join(os.path.split(os.path.dirname(__file__))[0], 'uploads') class Upload(BaseModel): # the extension of the media @@ -244,8 +226,6 @@ report_reasons = [ (REPORT_REASON_FIREARMS, "Sale or promotion of firearms"), (REPORT_REASON_DRUGS, "Sale or promotion of drugs"), (REPORT_REASON_UNDERAGE, "This user is less than 13 years old"), - (REPORT_REASON_LEAK, "Leak of sensitive information"), - (REPORT_REASON_DMCA, "Copyright violation") ] REPORT_STATUS_DELIVERED = 0 @@ -271,21 +251,10 @@ class Report(BaseModel): except DoesNotExist: return -# New in 0.9. -class MessageUpvote(BaseModel): - message = ForeignKeyField(Message, backref='upvotes') - user = ForeignKeyField(User) - created_date = DateTimeField() - - class Meta: - indexes = ( - (('message', 'user'), True), - ) - def create_tables(): with database: database.create_tables([ User, UserAdminship, UserProfile, Message, Relationship, - Upload, Notification, Report, MessageUpvote]) + Upload, Notification, Report]) if not os.path.isdir(UPLOAD_DIRECTORY): os.makedirs(UPLOAD_DIRECTORY) diff --git a/src/coriplus/reports.py b/app/reports.py similarity index 100% rename from src/coriplus/reports.py rename to app/reports.py diff --git a/src/coriplus/static/lib.js b/app/static/lib.js similarity index 80% rename from src/coriplus/static/lib.js rename to app/static/lib.js index 11a2316..538218a 100644 --- a/src/coriplus/static/lib.js +++ b/app/static/lib.js @@ -98,29 +98,3 @@ function showHideMessageOptions(id){ options.style.display = 'block'; } } - -function getCsrfToken () { - var csrf_token = document.querySelector('meta[name="csrf_token"]'); - return csrf_token?.getAttribute('content'); -} - -function toggleUpvote(id){ - var msgElem = document.getElementById(id); - //var upvoteLink = msgElem.getElementsByClassName('message-upvote')[0]; - var scoreCounter = msgElem.getElementsByClassName('message-score')[0]; - var body = "csrf_token=" + getCsrfToken(); - var xhr = new XMLHttpRequest(); - xhr.open("POST", "/ajax/score/" + id + "/toggle", true); - xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - // TODO add csrf token somewhere - xhr.onreadystatechange = function(){ - if(xhr.readyState == XMLHttpRequest.DONE){ - if(xhr.status == 200){ - console.log('liked #' + id); - var data = JSON.parse(xhr.responseText); - scoreCounter.innerHTML = data.score; - } - } - }; - xhr.send(body); -} diff --git a/app/static/style.css b/app/static/style.css new file mode 100644 index 0000000..5ade0ad --- /dev/null +++ b/app/static/style.css @@ -0,0 +1,28 @@ +body,button,input,select,textarea{font-family:'Segoe UI',Arial,Helvetica,sans-serif} +body{margin:0} +.header{padding:12px;color:white;background-color:#ff3018} +.content{padding:12px} +.header a{color:white} +.content a{color:#3399ff} +.content a.plus{color:#ff3018} +.metanav{float:right} +.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} +} +.weak{opacity:.5} +.field_desc{display:block} +.message-visual img{max-width:100%;max-height:8em} +.message-options-showhide::before{content:'\2026'} +.message-options{display:none} +.create_text{width:100%;height:8em} +.biography_text{height:4em} +.before-toggle:not(:checked) + input{display:none} +.follow_button,input[type="submit"]{background-color:#ff3018;color:white;border-radius:3px;border:1px solid #ff3018} +.follow_button.following{background-color:transparent;color:#ff3018;border-color:#ff3018} +.copyright{font-size:smaller;text-align:center;color:#808080} +.copyright a:link,.copyright a:visited{color:#31559e} +.copyright ul{list-style:none;padding:0} +.copyright ul > li{padding:0 3px} diff --git a/app/templates/404.html b/app/templates/404.html new file mode 100644 index 0000000..d434c9a --- /dev/null +++ b/app/templates/404.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} + +{% block body %} +

Not Found

+ +

Back to homepage.

+{% endblock %} \ No newline at end of file diff --git a/app/templates/about.html b/app/templates/about.html new file mode 100644 index 0000000..e5691a8 --- /dev/null +++ b/app/templates/about.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} + +{% block body %} +

About {{ site_name }}

+ +

{{ site_name }} {{ version }} – Python {{ python_version }} – + Flask {{ flask_version }}

+

Copyright © 2019 Sakuragasaki46.

+ +

License

+

Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions:

+ +

The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software.

+ +

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ +

Source code for this site: + https://github.com/sakuragasaki46/coriplus/ + +{% endblock %} diff --git a/src/coriplus/templates/admin_base.html b/app/templates/admin_base.html similarity index 90% rename from src/coriplus/templates/admin_base.html rename to app/templates/admin_base.html index c20c146..e59e813 100644 --- a/src/coriplus/templates/admin_base.html +++ b/app/templates/admin_base.html @@ -7,19 +7,16 @@

-

{{ site_name }}: Admin

-
- {% for message in get_flashed_messages() %}
{{ message }}
{% endfor %} -
{% block body %}{% endblock %} -
{% endif %}

Join {{ site_name }}

@@ -33,5 +32,4 @@
-
{% endblock %} diff --git a/src/coriplus/templates/login.html b/app/templates/login.html similarity index 76% rename from src/coriplus/templates/login.html rename to app/templates/login.html index 21a5e93..d1dbaae 100644 --- a/src/coriplus/templates/login.html +++ b/app/templates/login.html @@ -1,10 +1,8 @@ {% extends "base.html" %} {% block body %}

Login

- {% if error %}

Error: {{ error }}

{% endif %} -
+ {% if error %}

Error: {{ error }}{% endif %}

-
Username or email:
@@ -20,5 +18,4 @@
-
{% endblock %} diff --git a/src/coriplus/templates/notifications.html b/app/templates/notifications.html similarity index 74% rename from src/coriplus/templates/notifications.html rename to app/templates/notifications.html index 33ee7bb..04b61d1 100644 --- a/src/coriplus/templates/notifications.html +++ b/app/templates/notifications.html @@ -3,7 +3,7 @@

Notifications

    {% for notification in notification_list %} -
  • {% include "includes/notification.html" %}
  • +
  • {% include "includes/notification.html" %}
  • {% endfor %}
{% include "includes/pagination.html" %} diff --git a/app/templates/privacy.html b/app/templates/privacy.html new file mode 100644 index 0000000..a7b8570 --- /dev/null +++ b/app/templates/privacy.html @@ -0,0 +1,47 @@ +{% extends "base.html" %} + +{% block body %} +

Privacy Policy

+ +

At {{ site_name }}, accessible from {{ request.host }}, one of our main priorities is the privacy of our visitors. This Privacy Policy document contains types of information that is collected and recorded by {{ site_name }} and how we use it.

+ +

If you have additional questions or require more information about our Privacy Policy, do not hesitate to contact us through email at sakuragasaki46@gmail.com

+ +

Log Files

+ +

{{ site_name }} follows a standard procedure of using log files. These files log visitors when they visit websites. All hosting companies do this and a part of hosting services' analytics. The information collected by log files include internet protocol (IP) addresses, browser type, Internet Service Provider (ISP), date and time stamp, referring/exit pages, and possibly the number of clicks. These are not linked to any information that is personally identifiable. The purpose of the information is for analyzing trends, administering the site, tracking users' movement on the website, and gathering demographic information.

+ +

Cookies and Web Beacons

+ +

Like any other website, {{ site_name }} uses 'cookies'. These cookies are used to store information including visitors' preferences, and the pages on the website that the visitor accessed or visited. The information is used to optimize the users' experience by customizing our web page content based on visitors' browser type and/or other information.

+ + + +

Privacy Policies

+ +

You may consult this list to find the Privacy Policy for each of the advertising partners of {{ site_name }}. Our Privacy Policy was created with the help of the Privacy Policy Generator and the Generate Privacy Policy Generator.

+ +

Third-party ad servers or ad networks uses technologies like cookies, JavaScript, or Web Beacons that are used in their respective advertisements and links that appear on {{ site_name }}, which are sent directly to users' browser. They automatically receive your IP address when this occurs. These technologies are used to measure the effectiveness of their advertising campaigns and/or to personalize the advertising content that you see on websites that you visit.

+ +

Note that {{ site_name }} has no access to or control over these cookies that are used by third-party advertisers.

+ +

Third Party Privacy Policies

+ +

{{ site_name }}'s Privacy Policy does not apply to other advertisers or websites. Thus, we are advising you to consult the respective Privacy Policies of these third-party ad servers for more detailed information. It may include their practices and instructions about how to opt-out of certain options. You may find a complete list of these Privacy Policies and their links here: Privacy Policy Links.

+ +

You can choose to disable cookies through your individual browser options. To know more detailed information about cookie management with specific web browsers, it can be found at the browsers' respective websites. What Are Cookies?

+ +

Children's Information

+ +

Another part of our priority is adding protection for children while using the internet. We encourage parents and guardians to observe, participate in, and/or monitor and guide their online activity.

+ +

{{ site_name }} does not knowingly collect any Personal Identifiable Information from children under the age of 13. If you think that your child provided this kind of information on our website, we strongly encourage you to contact us immediately and we will do our best efforts to promptly remove such information from our records.

+ +

Online Privacy Policy Only

+ +

This Privacy Policy applies only to our online activities and is valid for visitors to our website with regards to the information that they shared and/or collect in {{ site_name }}. This policy is not applicable to any information collected offline or via channels other than this website.

+ +

Consent

+ +

By using our website, you hereby consent to our Privacy Policy and agree to its Terms and Conditions.

+{% endblock %} diff --git a/src/coriplus/templates/report_base.html b/app/templates/report_base.html similarity index 100% rename from src/coriplus/templates/report_base.html rename to app/templates/report_base.html diff --git a/src/coriplus/templates/report_done.html b/app/templates/report_done.html similarity index 100% rename from src/coriplus/templates/report_done.html rename to app/templates/report_done.html diff --git a/src/coriplus/templates/report_message.html b/app/templates/report_message.html similarity index 78% rename from src/coriplus/templates/report_message.html rename to app/templates/report_message.html index 0012c34..c17553e 100644 --- a/src/coriplus/templates/report_message.html +++ b/app/templates/report_message.html @@ -3,7 +3,6 @@ {% block body %} {% for reason in report_reasons %}
-

{{ reason[1] }}

diff --git a/src/coriplus/templates/report_user.html b/app/templates/report_user.html similarity index 78% rename from src/coriplus/templates/report_user.html rename to app/templates/report_user.html index 0012c34..c17553e 100644 --- a/src/coriplus/templates/report_user.html +++ b/app/templates/report_user.html @@ -3,7 +3,6 @@ {% block body %} {% for reason in report_reasons %} -

{{ reason[1] }}

diff --git a/src/coriplus/templates/terms.html b/app/templates/terms.html similarity index 62% rename from src/coriplus/templates/terms.html rename to app/templates/terms.html index ab8ca7f..203e44e 100644 --- a/src/coriplus/templates/terms.html +++ b/app/templates/terms.html @@ -1,9 +1,7 @@ {% extends "base.html" %} {% block body %} -

Terms of Service

-

[decline to state]

-
+ {% endblock %} diff --git a/src/coriplus/templates/user_detail.html b/app/templates/user_detail.html similarity index 72% rename from src/coriplus/templates/user_detail.html rename to app/templates/user_detail.html index 6324f1e..5a5c42f 100644 --- a/src/coriplus/templates/user_detail.html +++ b/app/templates/user_detail.html @@ -1,18 +1,15 @@ {% extends "base.html" %} -{% from "macros/message.html" import feed_message with context %} {% block body %} {% include "includes/infobox_profile.html" %}

Messages from {{ user.username }}

{% if not current_user.is_anonymous %} {% if user.username != current_user.username %} {% if current_user|is_following(user) %} - - +
{% else %} -
- +
{% endif %} @@ -21,9 +18,9 @@ Create a message {% endif %} {% endif %} -
    +
      {% for message in message_list %} - {{ feed_message(message) }} +
    • {% include "includes/message.html" %}
    • {% endfor %}
    {% include "includes/pagination.html" %} diff --git a/src/coriplus/templates/user_list.html b/app/templates/user_list.html similarity index 100% rename from src/coriplus/templates/user_list.html rename to app/templates/user_list.html diff --git a/src/coriplus/utils.py b/app/utils.py similarity index 94% rename from src/coriplus/utils.py rename to app/utils.py index 1db7414..f459bf1 100644 --- a/src/coriplus/utils.py +++ b/app/utils.py @@ -3,12 +3,9 @@ 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 -from markupsafe import Markup _forbidden_extensions = 'com net org txt'.split() _username_characters = frozenset(string.ascii_letters + string.digits + '_') @@ -85,7 +82,7 @@ class Visibility(object): def get_locations(): data = {} - with open('locations.txt', encoding='utf-8') as f: + with open('locations.txt') as f: for line in f: line = line.rstrip() if line.startswith('#'): @@ -104,14 +101,15 @@ 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]] - elif current_user.is_authenticated: - return current_user + else: + user_id = session.get('user_id') + if user_id: + return User[user_id] def push_notification(type, target, **kwargs): try: @@ -200,7 +198,7 @@ def check_access_token(token): if h.hexdigest()[:32] == hh: return user -def create_mentions(cur_user, text, privacy): +def create_mentions(cur_user, text): # create mentions mention_usernames = set() for mo in re.finditer(r'\+([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*)', text): @@ -217,8 +215,3 @@ def create_mentions(cur_user, text, privacy): push_notification('mention', mention_user, user=user.id) except User.DoesNotExist: pass - -# New in 0.9 -# changed in 0.10 -def inline_svg(name): - return Markup('{}').format(name) diff --git a/src/coriplus/website.py b/app/website.py similarity index 91% rename from src/coriplus/website.py rename to app/website.py index ab9ab53..9d7d13d 100644 --- a/src/coriplus/website.py +++ b/app/website.py @@ -7,17 +7,14 @@ 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 current_user, login_required, login_user, logout_user +from flask_login import login_required, login_user, logout_user import json -import logging - -logger = logging.getLogger(__name__) bp = Blueprint('website', __name__) @bp.route('/') def homepage(): - if current_user and current_user.is_authenticated: + if get_current_user(): return private_timeline() else: return render_template('homepage.html') @@ -26,7 +23,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 = current_user + user = get_current_user() messages = Visibility(Message .select() .where((Message.user << user.following()) @@ -83,9 +80,6 @@ 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'] @@ -137,12 +131,11 @@ 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: - flash(f'Error following {user.username}') - + pass + 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']) @@ -188,14 +181,29 @@ def create(): privacy=privacy) file = request.files.get('file') if file: - logger.info('Uploading', file.filename) + print('Uploading', file.filename) ext = file.filename.split('.')[-1] upload = Upload.create( type=ext, message=message ) file.save(UPLOAD_DIRECTORY + str(upload.id) + '.' + ext) - create_mentions(user, text, privacy) + # create mentions + mention_usernames = set() + for mo in re.finditer(r'\+([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*)', text): + mention_usernames.add(mo.group(1)) + # to avoid self mention + mention_usernames.difference_update({user.username}) + for u in mention_usernames: + try: + mention_user = User.get(User.username == u) + if privacy in (MSGPRV_PUBLIC, MSGPRV_UNLISTED) or \ + (privacy == MSGPRV_FRIENDS and + mention_user.is_following(user) and + user.is_following(mention_user)): + push_notification('mention', mention_user, user=user.id) + except User.DoesNotExist: + pass flash('Your message has been posted successfully') return redirect(url_for('website.user_detail', username=user.username)) return render_template('create.html') @@ -239,15 +247,12 @@ def edit(id): @bp.route('/delete/', methods=['GET', 'POST']) def confirm_delete(id): - user: User = current_user - message: Message = get_object_or_404(Message, Message.id == id) + user = get_current_user() + message = get_object_or_404(Message, Message.id == id) if message.user != user: abort(404) if request.method == 'POST': - if message.user == user: - message.delete_instance() - flash('Your message has been deleted forever') - return redirect(request.args.get('next', '/')) + abort(501, 'CSRF-Token missing.') return render_template('confirm_delete.html', message=message) # Workaround for problems related to invalid data. diff --git a/config.py b/config.py new file mode 100644 index 0000000..a6e2b64 --- /dev/null +++ b/config.py @@ -0,0 +1,3 @@ +DEBUG = True +SECRET_KEY = 'hin6bab8ge25*r=x&+5$0kn=-#log$pt^#@vrqjld!^2ci@g*b' +SITE_NAME = 'Cori+' diff --git a/genmig.sh b/genmig.sh deleted file mode 100755 index c141519..0000000 --- a/genmig.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/bash -# GENERATE MIGRATIONS - -source venv/bin/activate && \ -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}" ;; - (\\) pw_migrate rollback --directory=src/migrations --database="$DATABASE_URL" "${@:2}" ;; -esac diff --git a/icons/edit-24px.svg b/icons/edit-24px.svg deleted file mode 100644 index a6f23ff..0000000 --- a/icons/edit-24px.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/icons/exit_to_app-24px.svg b/icons/exit_to_app-24px.svg deleted file mode 100644 index 2f0decb..0000000 --- a/icons/exit_to_app-24px.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/icons/explore-24px.svg b/icons/explore-24px.svg deleted file mode 100644 index 9e72b8b..0000000 --- a/icons/explore-24px.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/icons/notifications-24px.svg b/icons/notifications-24px.svg deleted file mode 100644 index 6d5dfe6..0000000 --- a/icons/notifications-24px.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/icons/person-24px.svg b/icons/person-24px.svg deleted file mode 100644 index 58b25d9..0000000 --- a/icons/person-24px.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/icons/person_add-24px.svg b/icons/person_add-24px.svg deleted file mode 100644 index 40736bb..0000000 --- a/icons/person_add-24px.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/icons/shuffle-24px.svg b/icons/shuffle-24px.svg deleted file mode 100644 index a3efe19..0000000 --- a/icons/shuffle-24px.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/old_migrations/migrate_0_4_to_0_5.py b/migrate_0_4_to_0_5.py similarity index 100% rename from src/old_migrations/migrate_0_4_to_0_5.py rename to migrate_0_4_to_0_5.py diff --git a/src/old_migrations/migrate_0_6_to_0_7.py b/migrate_0_6_to_0_7.py similarity index 100% rename from src/old_migrations/migrate_0_6_to_0_7.py rename to migrate_0_6_to_0_7.py diff --git a/src/old_migrations/migrate_0_7_to_0_8.py b/migrate_0_7_to_0_8.py similarity index 100% rename from src/old_migrations/migrate_0_7_to_0_8.py rename to migrate_0_7_to_0_8.py diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 76e1140..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,24 +0,0 @@ -[project] -name = "sakuragasaki46_coriplus" -authors = [ - { name = "Sakuragasaki46" } -] -dynamic = ["version"] -dependencies = [ - "Python-Dotenv>=1.0.0", - "Flask", - "Flask-Login", - "Peewee", - "Flask-WTF", - "peewee-migrate", - "PsycoPG2" -] -requires-python = ">=3.10" -classifiers = [ - "Private :: X" -] - -[tool.setuptools.dynamic] -version = { attr = "coriplus.__version__" } - - diff --git a/src/robots.txt b/robots.txt similarity index 100% rename from src/robots.txt rename to robots.txt diff --git a/run_example.py b/run_example.py new file mode 100644 index 0000000..4ab76a9 --- /dev/null +++ b/run_example.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +import sys +sys.path.insert(0, '../..') + +import argparse +parser = argparse.ArgumentParser() +parser.add_argument('-p', '--port', type=int, default=5000, + help='An alternative port where to run the server.') + +from app import app, create_tables + +if __name__ == '__main__': + args = parser.parse_args() + create_tables() + app.run(port=args.port) diff --git a/src/coriplus/static/style.css b/src/coriplus/static/style.css deleted file mode 100644 index cb05c09..0000000 --- a/src/coriplus/static/style.css +++ /dev/null @@ -1,63 +0,0 @@ -:root { - --accent: #f0372e; - --link: #3399ff; -} -* { - box-sizing: border-box; -} -body, button, input, select, textarea { - font-family: Inter, Roboto, sans-serif; - line-height: 1.6; - background-color: #eeeeee; -} -#site-name, h1, h2, h3, h4, h5, h6 { - font-family: 'Funnel Sans', Roboto, sans-serif; - line-height: 1.2; -} -body{margin:0} -main{margin:auto; max-width: 960px;} -a{text-decoration:none} -a:hover{text-decoration:underline} -.mobile-collapse {font-size: smaller;} -@media (max-width:640px){ - .mobile-collapse{display:none} -} -.header{ - padding:12px;color:white; background-color:var(--accent); box-shadow:0 0 3px 3px #ccc; display: flex; position: relative; -} -.centered{ - text-align: center; -} -.content{padding:12px} -.header a{color:white; } -.header a svg{fill:white} -.content a{color:var(--link)} -.content a svg{fill:var(--link)} -.content a.plus{color:var(--accent)} -.leftnav, .rightnav{list-style: none; display: flex; padding: 0; margin: 0;position: absolute;} -.leftnav {left: 0} -.rightnav {right: 0} -.card {background-color: #fafafa; border-radius: 12px; padding: 12px; margin-bottom: 12px; border-bottom: 2px solid #999999;} -#site-name {text-align: center;flex: 1} -.header h1{margin:0;display:inline-block} -.flash{background-color:#ff9;border:yellow 1px solid} -.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;clear: both} -ul.timeline > li{list-style:none;} -.message-visual img{max-width:100%;margin:auto} -.message-options-showhide::before{content:'\2026'} -.message-options{display:none} -.create_text{width:100%;height:8em} -.biography_text{height:4em} -.before-toggle:not(:checked) + input{display:none} -.follow_button,input[type="submit"]{background-color:var(--accent);color:white;border-radius:3px;border:1px solid var(--accent)} -.follow_button.following{background-color:transparent;color:var(--accent);border-color:var(--accent)} -.copyright{font-size:smaller;text-align:center;color:#808080} -.copyright a:link,.copyright a:visited{color:#31559e} -.copyright ul{list-style:none;padding:0} -.copyright ul > li{padding:0 3px} diff --git a/src/coriplus/templates/403.html b/src/coriplus/templates/403.html deleted file mode 100644 index 5cdbf3b..0000000 --- a/src/coriplus/templates/403.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "base.html" %} - -{% block body %} -
    -

    Forbidden

    - -

    Back to homepage.

    -
    -{% endblock %} \ No newline at end of file diff --git a/src/coriplus/templates/404.html b/src/coriplus/templates/404.html deleted file mode 100644 index 4acfb0b..0000000 --- a/src/coriplus/templates/404.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "base.html" %} - -{% block body %} -
    -

    Not Found

    - -

    Back to homepage.

    -
    -{% endblock %} \ No newline at end of file diff --git a/src/coriplus/templates/about.html b/src/coriplus/templates/about.html deleted file mode 100644 index 905eb8d..0000000 --- a/src/coriplus/templates/about.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends "base.html" %} - -{% block body %} -
    -

    About {{ site_name }}

    - -
      -
    • {{ site_name }} {{ version }}
    • -
    • Python {{ python_version }}
    • -
    • Flask {{ flask_version }}
    • -
    -

    Copyright © 2019, 2025 Sakuragasaki46.

    - -

    License

    -

    Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files - (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, - publish, distribute, sublicense, and/or sell copies of the Software, - and to permit persons to whom the Software is furnished to do so, - subject to the following conditions:

    - -

    The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software.

    - -

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

    - -

    Source code for this site: - https://github.com/sakuragasaki46/coriplus/ - -

    -{% endblock %} diff --git a/src/coriplus/templates/base.html b/src/coriplus/templates/base.html deleted file mode 100644 index 60667ea..0000000 --- a/src/coriplus/templates/base.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - {{ site_name }} - - - - - - - - - - - - -
    - {% for message in get_flashed_messages() %} -
    {{ message }}
    - {% endfor %} -
    - {% block body %}{% endblock %} -
    -
    - - - - diff --git a/src/coriplus/templates/change_password.html b/src/coriplus/templates/change_password.html deleted file mode 100644 index 22fa5bb..0000000 --- a/src/coriplus/templates/change_password.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "base.html" %} - -{% block body %} -
    -

    Change Password

    - -
    - -
    -
    Old password:
    -
    -
    New password:
    -
    -
    New password, again:
    -
    -
    -
    -
    -
    -{% endblock %} diff --git a/src/coriplus/templates/confirm_delete.html b/src/coriplus/templates/confirm_delete.html deleted file mode 100644 index 7343dad..0000000 --- a/src/coriplus/templates/confirm_delete.html +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "base.html" %} - -{% block body %} -
    -

    Confirm Deletion

    - -

    Are you sure you want to permanently delete this post? - Neither you nor others will be able to see it; - you cannot recover a post after it's deleted.

    - -

    Tip: If you only want to hide it from the public, - you can set its privacy to "Only me".

    - -
      -
    • {% include "includes/message.html" %}
    • -
    - -
    - - -
    -
    -{% endblock %} diff --git a/src/coriplus/templates/includes/infobox_profile.html b/src/coriplus/templates/includes/infobox_profile.html deleted file mode 100644 index bc08a20..0000000 --- a/src/coriplus/templates/includes/infobox_profile.html +++ /dev/null @@ -1,20 +0,0 @@ - -
    -

    {{ user.full_name }}

    -

    {{ user.biography|enrich }}

    - {% if user.website %} - {% set website = user.website %} - {% set website = website if website.startswith(('http://', 'https://')) else 'http://' + website %} -

    Website: {{ website|urlize }}

    - {% endif %} -

    - {{ user.messages|count }} messages - - - {{ user.followers()|count }} followers - - - {{ user.following()|count }} following -

    - {% if user == current_user %} -

    {{ inline_svg('edit') }} Edit profile

    - {% endif %} -
    diff --git a/src/coriplus/templates/macros/message.html b/src/coriplus/templates/macros/message.html deleted file mode 100644 index b4ef93a..0000000 --- a/src/coriplus/templates/macros/message.html +++ /dev/null @@ -1,35 +0,0 @@ -{% macro feed_message(message) %} -
  • -

    {{ message.text|enrich }}

    -{% if message.uploads %} -
    - -
    -{% endif %} - - -
  • -{% endmacro %} \ No newline at end of file diff --git a/src/coriplus/templates/privacy.html b/src/coriplus/templates/privacy.html deleted file mode 100644 index df4248a..0000000 --- a/src/coriplus/templates/privacy.html +++ /dev/null @@ -1,54 +0,0 @@ -{% extends "base.html" %} - -{% block body %} -
    -

    Privacy Policy

    - -

    At {{ site_name }}, accessible from {{ request.host }}, one of our main priorities is the privacy of our visitors. This Privacy Policy document contains types of information that is collected and recorded by {{ site_name }} and how we use it.

    - -

    If you have additional questions or require more information about our Privacy Policy, do not hesitate to contact us through email at sakuragasaki46@gmail.com

    - -

    Log Files

    - -

    {{ site_name }} follows a standard procedure of using log files. These files log visitors when they visit websites. All hosting companies do this and a part of hosting services' analytics. The information collected by log files include internet protocol (IP) addresses, browser type, Internet Service Provider (ISP), date and time stamp, referring/exit pages, and possibly the number of clicks. These are not linked to any information that is personally identifiable. The purpose of the information is for analyzing trends, administering the site, tracking users' movement on the website, and gathering demographic information.

    - -

    Cookies and Web Beacons

    - -

    Like any other website, {{ site_name }} uses 'cookies'. These cookies are used to store information including visitors' preferences, and the pages on the website that the visitor accessed or visited. The information is used to optimize the users' experience by customizing our web page content based on visitors' browser type and/or other information.

    - -

    You can choose to disable cookies through your individual browser options. This, however, can and will hurt Your usage of {{ site_name }}

    - -

    Privacy Policies

    - -

    You may consult this list to find the Privacy Policy for each of the advertising partners of {{ site_name }}. Our Privacy Policy was created with the help of the Privacy Policy Generator and the Generate Privacy Policy Generator.

    - -

    Third-party ad servers or ad networks uses technologies like cookies, JavaScript, or Web Beacons that are used in their respective advertisements and links that appear on {{ site_name }}, which are sent directly to users' browser. They automatically receive your IP address when this occurs. These technologies are used to measure the effectiveness of their advertising campaigns and/or to personalize the advertising content that you see on websites that you visit.

    - -

    Note that {{ site_name }} has no access to or control over these cookies that are used by third-party advertisers.

    - -

    Third Party Privacy Policies

    - -

    {{ site_name }}'s Privacy Policy does not apply to other advertisers or websites. Thus, we are advising you to consult the respective Privacy Policies of these third-party ad servers for more detailed information. It may include their practices and instructions about how to opt-out of certain options. You may find a complete list of these Privacy Policies and their links here: Privacy Policy Links.

    - -

    Legal Basis

    - -

    Legal Basis for treatment is Legitimate Interest, except:

    -
      -
    • Transactional information, such as username, email and essential cookies, are treated according to Providing a Service.
    • -
    - -

    Children's Information

    - -

    Another part of our priority is adding protection for children while using the internet. We encourage parents and guardians to observe, participate in, monitor, guide and/or exercise total control on their online activity.

    - -

    {{ site_name }} does not knowingly collect any Personal Identifiable Information from children under the age of 13. If you think that your child provided this kind of information on our website, we strongly encourage you to contact us immediately and we will do our best efforts to promptly remove such information from our records.

    - -

    Online Privacy Policy Only

    - -

    This Privacy Policy applies only to our online activities and is valid for visitors to our website with regards to the information that they shared and/or collect in {{ site_name }}. This policy is not applicable to any information collected via channels other than this website.

    - -

    Consent

    - -

    By using our website, you hereby consent irrevocably to our Privacy Policy and agree to its Terms and Conditions.

    -
    -{% endblock %} diff --git a/src/favicon.ico b/src/favicon.ico deleted file mode 100644 index 89a3407..0000000 Binary files a/src/favicon.ico and /dev/null differ diff --git a/src/migrations/001_0_9_to_0_10.py b/src/migrations/001_0_9_to_0_10.py deleted file mode 100644 index 17a8884..0000000 --- a/src/migrations/001_0_9_to_0_10.py +++ /dev/null @@ -1,171 +0,0 @@ -"""Peewee migrations -- 001_0_9_to_0_10.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.create_model - class BaseModel(pw.Model): - id = pw.AutoField() - - class Meta: - table_name = "basemodel" - - @migrator.create_model - class User(pw.Model): - id = pw.AutoField() - username = pw.CharField(max_length=255, unique=True) - full_name = pw.TextField() - password = pw.CharField(max_length=255) - email = pw.CharField(max_length=255) - birthday = pw.DateField() - join_date = pw.DateTimeField() - is_disabled = pw.IntegerField() - - class Meta: - table_name = "user" - - @migrator.create_model - class Message(pw.Model): - id = pw.AutoField() - user = pw.ForeignKeyField(column_name='user_id', field='id', model=migrator.orm['user']) - text = pw.TextField() - pub_date = pw.DateTimeField() - privacy = pw.IntegerField() - - class Meta: - table_name = "message" - - @migrator.create_model - class MessageUpvote(pw.Model): - id = pw.AutoField() - message = pw.ForeignKeyField(column_name='message_id', field='id', model=migrator.orm['message']) - user = pw.ForeignKeyField(column_name='user_id', field='id', model=migrator.orm['user']) - created_date = pw.DateTimeField() - - class Meta: - table_name = "messageupvote" - indexes = [(('message', 'user'), True)] - - @migrator.create_model - class Notification(pw.Model): - id = pw.AutoField() - type = pw.TextField() - target = pw.ForeignKeyField(column_name='target_id', field='id', model=migrator.orm['user']) - detail = pw.TextField() - pub_date = pw.DateTimeField() - seen = pw.IntegerField() - - class Meta: - table_name = "notification" - - @migrator.create_model - class Relationship(pw.Model): - id = pw.AutoField() - from_user = pw.ForeignKeyField(column_name='from_user_id', field='id', model=migrator.orm['user']) - to_user = pw.ForeignKeyField(column_name='to_user_id', field='id', model=migrator.orm['user']) - created_date = pw.DateTimeField() - - class Meta: - table_name = "relationship" - indexes = [(('from_user', 'to_user'), True)] - - @migrator.create_model - class Report(pw.Model): - id = pw.AutoField() - media_type = pw.IntegerField() - media_id = pw.IntegerField() - sender = pw.ForeignKeyField(column_name='sender_id', field='id', model=migrator.orm['user'], null=True) - reason = pw.IntegerField() - status = pw.IntegerField() - created_date = pw.DateTimeField() - - class Meta: - table_name = "report" - - @migrator.create_model - class Upload(pw.Model): - id = pw.AutoField() - type = pw.TextField() - message = pw.ForeignKeyField(column_name='message_id', field='id', model=migrator.orm['message']) - - class Meta: - table_name = "upload" - - @migrator.create_model - class UserAdminship(pw.Model): - user = pw.ForeignKeyField(column_name='user_id', field='id', model=migrator.orm['user'], primary_key=True) - - class Meta: - table_name = "useradminship" - - @migrator.create_model - class UserProfile(pw.Model): - user = pw.ForeignKeyField(column_name='user_id', field='id', model=migrator.orm['user'], primary_key=True) - biography = pw.TextField() - location = pw.IntegerField(null=True) - year = pw.IntegerField(null=True) - website = pw.TextField(null=True) - instagram = pw.TextField(null=True) - facebook = pw.TextField(null=True) - telegram = pw.TextField(null=True) - - class Meta: - table_name = "userprofile" - - -def rollback(migrator: Migrator, database: pw.Database, *, fake=False): - """Write your rollback migrations here.""" - - migrator.remove_model('userprofile') - - migrator.remove_model('useradminship') - - migrator.remove_model('upload') - - migrator.remove_model('report') - - migrator.remove_model('relationship') - - migrator.remove_model('notification') - - migrator.remove_model('messageupvote') - - migrator.remove_model('message') - - migrator.remove_model('user') - - migrator.remove_model('basemodel') diff --git a/src/migrations/002_move_columns_from_userprofile.py b/src/migrations/002_move_columns_from_userprofile.py deleted file mode 100644 index d8637f7..0000000 --- a/src/migrations/002_move_columns_from_userprofile.py +++ /dev/null @@ -1,86 +0,0 @@ -"""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.sql(""" - UPDATE "userprofile" SET biography = (SELECT p.biography FROM user p WHERE p.user_id = id LIMIT 1), - website = (SELECT p.website FROM user p WHERE p.user_id = id LIMIT 1); - """) - - 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))