diff --git a/.gitignore b/.gitignore index c9522f1..75b7704 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,18 @@ __pycache__/ uploads/ *.pyc **~ -**/.*.swp -**/__pycache__/ +.*.swp +__pycache__/ +venv +.env +.venv +env +data/ +conf/ +config/ +\#*\# +.\#* +node_modules/ +alembic.ini +**.egg-info +.vscode \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index cae4de4..cfb7a8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # 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`. @@ -12,9 +35,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-dev +## 0.7.1 * 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 b96fffc..3ee8c96 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,19 @@ This is the server. For the client, see [coriplusapp](https://github.com/sakurag * Add info to your profile * In-site notifications * Public API -* SQLite-based app +* SQLite (or PostgreSQL)-based app ## Requirements -* **Python 3** only. We don't want to support Python 2. -* **Flask** web framework (also required extension **Flask-Login**). +* **Python 3.10+** with **pip**. +* **Flask** web framework. * **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/app/static/style.css b/app/static/style.css deleted file mode 100644 index 5ade0ad..0000000 --- a/app/static/style.css +++ /dev/null @@ -1,28 +0,0 @@ -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 deleted file mode 100644 index d434c9a..0000000 --- a/app/templates/404.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "base.html" %} - -{% block body %} -
{{ site_name }} {{ version }} – Python {{ python_version }} – - Flask {{ flask_version }}
-Copyright © 2019 Sakuragasaki46.
- -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/app/templates/base.html b/app/templates/base.html deleted file mode 100644 index 74ee636..0000000 --- a/app/templates/base.html +++ /dev/null @@ -1,43 +0,0 @@ - - -
-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.
- -If you only want to hide it from the public, - you can set its privacy to "Only me".
- -Here's the content of the message for reference:
- -{{ 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 %} - {% set website = website if website.startswith(('http://', 'https://')) else 'http://' + website %} -Website: {{ profile.website|urlize }}
- {% endif %} - {% if profile.instagram %} -Instagram: {{ profile.instagram }}
- {% endif %} - {% if profile.facebook %} -Facebook: {{ profile.facebook }}
- {% endif %} - {% if profile.telegram %} -Telegram: {{ profile.telegram }}
- {% endif %} -- {{ user.messages|count }} messages - - - {{ user.followers()|count }} followers - - - {{ user.following()|count }} following -
- {% if user == current_user %} - - {% endif %} -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
- -{{ 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.
- -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 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.
- -{{ 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?
- -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.
- -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.
- -By using our website, you hereby consent to our Privacy Policy and agree to its Terms and Conditions.
-{% endblock %} diff --git a/config.py b/config.py deleted file mode 100644 index a6e2b64..0000000 --- a/config.py +++ /dev/null @@ -1,3 +0,0 @@ -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 new file mode 100755 index 0000000..c141519 --- /dev/null +++ b/genmig.sh @@ -0,0 +1,10 @@ +#!/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 new file mode 100644 index 0000000..a6f23ff --- /dev/null +++ b/icons/edit-24px.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/exit_to_app-24px.svg b/icons/exit_to_app-24px.svg new file mode 100644 index 0000000..2f0decb --- /dev/null +++ b/icons/exit_to_app-24px.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/explore-24px.svg b/icons/explore-24px.svg new file mode 100644 index 0000000..9e72b8b --- /dev/null +++ b/icons/explore-24px.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/notifications-24px.svg b/icons/notifications-24px.svg new file mode 100644 index 0000000..6d5dfe6 --- /dev/null +++ b/icons/notifications-24px.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/person-24px.svg b/icons/person-24px.svg new file mode 100644 index 0000000..58b25d9 --- /dev/null +++ b/icons/person-24px.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/person_add-24px.svg b/icons/person_add-24px.svg new file mode 100644 index 0000000..40736bb --- /dev/null +++ b/icons/person_add-24px.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/shuffle-24px.svg b/icons/shuffle-24px.svg new file mode 100644 index 0000000..a3efe19 --- /dev/null +++ b/icons/shuffle-24px.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..76e1140 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[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/run_example.py b/run_example.py deleted file mode 100644 index 4ab76a9..0000000 --- a/run_example.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/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/app/__init__.py b/src/coriplus/__init__.py similarity index 69% rename from app/__init__.py rename to src/coriplus/__init__.py index 835fcbc..09cecf6 100644 --- a/app/__init__.py +++ b/src/coriplus/__init__.py @@ -16,25 +16,37 @@ For other, see `app.utils`. ''' from flask import ( - 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 + Flask, g, jsonify, render_template, request, + send_from_directory, __version__ as flask_version) +import os, sys from flask_login import LoginManager +from flask_wtf import CSRFProtect +import dotenv +import logging -__version__ = '0.8.0' +__version__ = '0.10.0-dev50' -# we want to support Python 3 only. +# we want to support Python 3.10+ only. # Python 2 has too many caveats. -if sys.version_info[0] < 3: - raise RuntimeError('Python 3 required') +# 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__) app = Flask(__name__) -app.config.from_pyfile('../config.py') +app.secret_key = os.environ['SECRET_KEY'] login_manager = LoginManager(app) +CSRFProtect(app) + from .models import * from .utils import * @@ -51,28 +63,43 @@ def before_request(): try: g.db.connect() except OperationalError: - sys.stderr.write('database connected twice.\n') + logger.error('database connected twice') @app.after_request def after_request(response): - g.db.close() + try: + g.db.close() + except Exception: + logger.error('database closed twice') return response @app.context_processor def _inject_variables(): - return {'site_name': app.config['SITE_NAME'], 'locations': locations} + return { + 'site_name': os.environ.get('APP_NAME', 'Cori+'), + 'locations': locations, + 'inline_svg': inline_svg + } @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(os.getcwd(), 'robots.txt') + return send_from_directory(BASEDIR, 'src/robots.txt') @app.route('/uploads/Copyright © 2019, 2025 Sakuragasaki46.
+ +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/ + +
Error: {{ error }}{% endif %} + {% if error %}
Error: {{ error }}
{% endif %} +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
+ +{{ 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.
+ +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 }}
+ +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.
+ +{{ 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 for treatment is Legitimate Interest, except:
+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.
+ +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.
+ +By using our website, you hereby consent irrevocably to our Privacy Policy and agree to its Terms and Conditions.
+