├── LICENSE ├── README.md ├── crumbles.png ├── flaskeddit.py ├── flaskeddit ├── __init__-pythonanywhere.py ├── __init__.py ├── auth │ ├── __init__.py │ ├── auth_service.py │ ├── forms.py │ └── routes.py ├── cli │ ├── __init__.py │ └── commands.py ├── communities │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-310.pyc │ │ ├── __init__.cpython-39.pyc │ │ ├── communities_service.cpython-310.pyc │ │ ├── communities_service.cpython-39.pyc │ │ ├── routes.cpython-310.pyc │ │ └── routes.cpython-39.pyc │ ├── communities_service.py │ └── routes.py ├── community │ ├── __init__.py │ ├── community_service.py │ ├── forms.py │ └── routes.py ├── config.py ├── feed │ ├── __init__.py │ ├── feed_service.py │ └── routes.py ├── models.py ├── post │ ├── __init__.py │ ├── forms.py │ ├── post_service.py │ └── routes.py ├── reply │ ├── __init__.py │ ├── forms.py │ ├── reply_service.py │ └── routes.py ├── static │ ├── ckeditor │ │ └── standard │ │ │ ├── CHANGES.md │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ ├── adapters │ │ │ └── jquery.js │ │ │ ├── build-config.js │ │ │ ├── ckeditor.js │ │ │ ├── config.js │ │ │ ├── contents.css │ │ │ ├── lang │ │ │ ├── af.js │ │ │ ├── ar.js │ │ │ ├── az.js │ │ │ ├── bg.js │ │ │ ├── bn.js │ │ │ ├── bs.js │ │ │ ├── ca.js │ │ │ ├── cs.js │ │ │ ├── cy.js │ │ │ ├── da.js │ │ │ ├── de-ch.js │ │ │ ├── de.js │ │ │ ├── el.js │ │ │ ├── en-au.js │ │ │ ├── en-ca.js │ │ │ ├── en-gb.js │ │ │ ├── en.js │ │ │ ├── eo.js │ │ │ ├── es-mx.js │ │ │ ├── es.js │ │ │ ├── et.js │ │ │ ├── eu.js │ │ │ ├── fa.js │ │ │ ├── fi.js │ │ │ ├── fo.js │ │ │ ├── fr-ca.js │ │ │ ├── fr.js │ │ │ ├── gl.js │ │ │ ├── gu.js │ │ │ ├── he.js │ │ │ ├── hi.js │ │ │ ├── hr.js │ │ │ ├── hu.js │ │ │ ├── id.js │ │ │ ├── is.js │ │ │ ├── it.js │ │ │ ├── ja.js │ │ │ ├── ka.js │ │ │ ├── km.js │ │ │ ├── ko.js │ │ │ ├── ku.js │ │ │ ├── lt.js │ │ │ ├── lv.js │ │ │ ├── mk.js │ │ │ ├── mn.js │ │ │ ├── ms.js │ │ │ ├── nb.js │ │ │ ├── nl.js │ │ │ ├── no.js │ │ │ ├── oc.js │ │ │ ├── pl.js │ │ │ ├── pt-br.js │ │ │ ├── pt.js │ │ │ ├── ro.js │ │ │ ├── ru.js │ │ │ ├── si.js │ │ │ ├── sk.js │ │ │ ├── sl.js │ │ │ ├── sq.js │ │ │ ├── sr-latn.js │ │ │ ├── sr.js │ │ │ ├── sv.js │ │ │ ├── th.js │ │ │ ├── tr.js │ │ │ ├── tt.js │ │ │ ├── ug.js │ │ │ ├── uk.js │ │ │ ├── vi.js │ │ │ ├── zh-cn.js │ │ │ └── zh.js │ │ │ ├── plugins │ │ │ ├── a11yhelp │ │ │ │ └── dialogs │ │ │ │ │ ├── a11yhelp.js │ │ │ │ │ └── lang │ │ │ │ │ ├── _translationstatus.txt │ │ │ │ │ ├── af.js │ │ │ │ │ ├── ar.js │ │ │ │ │ ├── az.js │ │ │ │ │ ├── bg.js │ │ │ │ │ ├── ca.js │ │ │ │ │ ├── cs.js │ │ │ │ │ ├── cy.js │ │ │ │ │ ├── da.js │ │ │ │ │ ├── de-ch.js │ │ │ │ │ ├── de.js │ │ │ │ │ ├── el.js │ │ │ │ │ ├── en-au.js │ │ │ │ │ ├── en-gb.js │ │ │ │ │ ├── en.js │ │ │ │ │ ├── eo.js │ │ │ │ │ ├── es-mx.js │ │ │ │ │ ├── es.js │ │ │ │ │ ├── et.js │ │ │ │ │ ├── eu.js │ │ │ │ │ ├── fa.js │ │ │ │ │ ├── fi.js │ │ │ │ │ ├── fo.js │ │ │ │ │ ├── fr-ca.js │ │ │ │ │ ├── fr.js │ │ │ │ │ ├── gl.js │ │ │ │ │ ├── gu.js │ │ │ │ │ ├── he.js │ │ │ │ │ ├── hi.js │ │ │ │ │ ├── hr.js │ │ │ │ │ ├── hu.js │ │ │ │ │ ├── id.js │ │ │ │ │ ├── it.js │ │ │ │ │ ├── ja.js │ │ │ │ │ ├── km.js │ │ │ │ │ ├── ko.js │ │ │ │ │ ├── ku.js │ │ │ │ │ ├── lt.js │ │ │ │ │ ├── lv.js │ │ │ │ │ ├── mk.js │ │ │ │ │ ├── mn.js │ │ │ │ │ ├── nb.js │ │ │ │ │ ├── nl.js │ │ │ │ │ ├── no.js │ │ │ │ │ ├── oc.js │ │ │ │ │ ├── pl.js │ │ │ │ │ ├── pt-br.js │ │ │ │ │ ├── pt.js │ │ │ │ │ ├── ro.js │ │ │ │ │ ├── ru.js │ │ │ │ │ ├── si.js │ │ │ │ │ ├── sk.js │ │ │ │ │ ├── sl.js │ │ │ │ │ ├── sq.js │ │ │ │ │ ├── sr-latn.js │ │ │ │ │ ├── sr.js │ │ │ │ │ ├── sv.js │ │ │ │ │ ├── th.js │ │ │ │ │ ├── tr.js │ │ │ │ │ ├── tt.js │ │ │ │ │ ├── ug.js │ │ │ │ │ ├── uk.js │ │ │ │ │ ├── vi.js │ │ │ │ │ ├── zh-cn.js │ │ │ │ │ └── zh.js │ │ │ ├── about │ │ │ │ └── dialogs │ │ │ │ │ ├── about.js │ │ │ │ │ ├── hidpi │ │ │ │ │ └── logo_ckeditor.png │ │ │ │ │ └── logo_ckeditor.png │ │ │ ├── clipboard │ │ │ │ └── dialogs │ │ │ │ │ └── paste.js │ │ │ ├── icons.png │ │ │ ├── icons_hidpi.png │ │ │ ├── image │ │ │ │ ├── dialogs │ │ │ │ │ └── image.js │ │ │ │ └── images │ │ │ │ │ └── noimage.png │ │ │ ├── link │ │ │ │ ├── dialogs │ │ │ │ │ ├── anchor.js │ │ │ │ │ └── link.js │ │ │ │ └── images │ │ │ │ │ ├── anchor.png │ │ │ │ │ └── hidpi │ │ │ │ │ └── anchor.png │ │ │ ├── pastetools │ │ │ │ └── filter │ │ │ │ │ ├── common.js │ │ │ │ │ └── image.js │ │ │ └── specialchar │ │ │ │ └── dialogs │ │ │ │ ├── lang │ │ │ │ ├── _translationstatus.txt │ │ │ │ ├── af.js │ │ │ │ ├── ar.js │ │ │ │ ├── az.js │ │ │ │ ├── bg.js │ │ │ │ ├── ca.js │ │ │ │ ├── cs.js │ │ │ │ ├── cy.js │ │ │ │ ├── da.js │ │ │ │ ├── de-ch.js │ │ │ │ ├── de.js │ │ │ │ ├── el.js │ │ │ │ ├── en-au.js │ │ │ │ ├── en-ca.js │ │ │ │ ├── en-gb.js │ │ │ │ ├── en.js │ │ │ │ ├── eo.js │ │ │ │ ├── es-mx.js │ │ │ │ ├── es.js │ │ │ │ ├── et.js │ │ │ │ ├── eu.js │ │ │ │ ├── fa.js │ │ │ │ ├── fi.js │ │ │ │ ├── fr-ca.js │ │ │ │ ├── fr.js │ │ │ │ ├── gl.js │ │ │ │ ├── he.js │ │ │ │ ├── hr.js │ │ │ │ ├── hu.js │ │ │ │ ├── id.js │ │ │ │ ├── it.js │ │ │ │ ├── ja.js │ │ │ │ ├── km.js │ │ │ │ ├── ko.js │ │ │ │ ├── ku.js │ │ │ │ ├── lt.js │ │ │ │ ├── lv.js │ │ │ │ ├── nb.js │ │ │ │ ├── nl.js │ │ │ │ ├── no.js │ │ │ │ ├── oc.js │ │ │ │ ├── pl.js │ │ │ │ ├── pt-br.js │ │ │ │ ├── pt.js │ │ │ │ ├── ro.js │ │ │ │ ├── ru.js │ │ │ │ ├── si.js │ │ │ │ ├── sk.js │ │ │ │ ├── sl.js │ │ │ │ ├── sq.js │ │ │ │ ├── sr-latn.js │ │ │ │ ├── sr.js │ │ │ │ ├── sv.js │ │ │ │ ├── th.js │ │ │ │ ├── tr.js │ │ │ │ ├── tt.js │ │ │ │ ├── ug.js │ │ │ │ ├── uk.js │ │ │ │ ├── vi.js │ │ │ │ ├── zh-cn.js │ │ │ │ └── zh.js │ │ │ │ └── specialchar.js │ │ │ ├── skins │ │ │ └── moono-lisa │ │ │ │ ├── dialog.css │ │ │ │ ├── dialog_ie.css │ │ │ │ ├── dialog_ie8.css │ │ │ │ ├── dialog_iequirks.css │ │ │ │ ├── editor.css │ │ │ │ ├── editor_gecko.css │ │ │ │ ├── editor_ie.css │ │ │ │ ├── editor_ie8.css │ │ │ │ ├── editor_iequirks.css │ │ │ │ ├── icons.png │ │ │ │ ├── icons_hidpi.png │ │ │ │ ├── images │ │ │ │ ├── arrow.png │ │ │ │ ├── close.png │ │ │ │ ├── hidpi │ │ │ │ │ ├── close.png │ │ │ │ │ ├── lock-open.png │ │ │ │ │ ├── lock.png │ │ │ │ │ └── refresh.png │ │ │ │ ├── lock-open.png │ │ │ │ ├── lock.png │ │ │ │ ├── refresh.png │ │ │ │ └── spinner.gif │ │ │ │ └── readme.md │ │ │ ├── styles.js │ │ │ └── vendor │ │ │ └── promise.js │ ├── favicon.ico │ ├── logo.png │ └── main.css ├── templates │ ├── changeemail.html │ ├── changepassword.html │ ├── communities.html │ ├── community.html │ ├── create_community.html │ ├── create_post.html │ ├── create_reply.html │ ├── feed.html │ ├── layout.html │ ├── login.html │ ├── post.html │ ├── recoverpassword.html │ ├── register.html │ ├── update_community.html │ ├── update_post.html │ ├── update_reply.html │ └── user.html └── user │ ├── __init__.py │ ├── routes.py │ └── user_service.py └── requirements.txt /LICENSE: -------------------------------------------------------------------------------- 1 | PiOS License 2 | 3 | Copyright (C) 2023 Hascyll 4 | 5 | Permission is hereby granted by the application software developer (“Software Developer”), free 6 | of charge, to any person obtaining a copy of this application, software and associated 7 | documentation files (the “Software”), which was developed by the Software Developer for use on 8 | Pi Network, whereby the purpose of this license is to permit the development of derivative works 9 | based on the Software, including the right to use, copy, modify, merge, publish, distribute, 10 | sub-license, and/or sell copies of such derivative works and any Software components incorporated 11 | therein, and to permit persons to whom such derivative works are furnished to do so, in each case, 12 | solely to develop, use and market applications for the official Pi Network. For purposes of this 13 | license, Pi Network shall mean any application, software, or other present or future platform 14 | developed, owned or managed by Pi Community Company, and its parents, affiliates or subsidiaries, 15 | for which the Software was developed, or on which the Software continues to operate. However, 16 | you are prohibited from using any portion of the Software or any derivative works thereof in any 17 | manner (a) which infringes on any Pi Network intellectual property rights, (b) to hack any of Pi 18 | Network’s systems or processes or (c) to develop any product or service which is competitive with 19 | the Pi Network. 20 | 21 | The above copyright notice and this permission notice shall be included in all copies or 22 | substantial portions of the Software. 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 24 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 25 | AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS, PUBLISHERS, OR COPYRIGHT HOLDERS OF THIS 26 | SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL 27 | DAMAGES (INCLUDING, BUT NOT LIMITED TO BUSINESS INTERRUPTION, LOSS OF USE, DATA OR PROFITS) 28 | HOWEVER CAUSED AND UNDER ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 29 | TORT (INCLUDING NEGLIGENCE) ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 30 | OR OTHER DEALINGS IN THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # crumbles 2 | 3 | A community-based ("Reddit-style") chat app built with Flask. 4 | 5 | ![crumbles.png](./crumbles.png) 6 | 7 | Based on [flaskeddit](https://github.com/aqche/flaskeddit) by - **aqche** - [aqche](https://github.com/aqche) 8 | 9 | ## Features 10 | 11 | - Registration and authentication. 12 | - Reset Password via Email. 13 | - Create communities. 14 | - Create community posts. 15 | - Reply to community posts. 16 | - Edit or delete your communities, posts, and replies. 17 | - Join communities. 18 | - Get a feed of posts from your joined communities. 19 | - Upvote or downvote posts and replies. 20 | - Sort communities, posts, and replies by latest or most popular. 21 | - Moderate communities. 22 | - Basic user profiles. 23 | 24 | ## Getting Started 25 | 26 | These instructions will get you a copy of the project up and running on your local machine for development. See deployment for notes on how to deploy the project. 27 | 28 | ### Prerequisites 29 | 30 | To run this application you need [Python](https://www.python.org/), [pip](https://pip.pypa.io/en/stable/), and [SQLite](https://www.sqlite.org/). 31 | 32 | ### Local Setup 33 | 34 | Clone the project. 35 | 36 | ```sh 37 | git clone https://github.com/pi-apps/crumbles.git 38 | ``` 39 | 40 | Setup a `virtualvenv` and use `pip` to install the project dependencies. 41 | 42 | ```sh 43 | cd crumbles 44 | python3 -m virualenv venv 45 | source venv/bin/activate 46 | pip3 install -r requirements.txt 47 | ``` 48 | 49 | Set the `FLASK_APP` environment variable, create the SQLite database, and start the app. Now you can give the application a try at [http://localhost:5000](http://localhost:5000)! 50 | 51 | ```sh 52 | export FLASK_APP=flaskeddit.py 53 | flask cli create_db 54 | flask run 55 | ``` 56 | 57 | 58 | ## Deployment 59 | 60 | crumbles can be hosted on [pythonanywhere](https://www.pythonanywhere.com/). You have to register at pythonanywhere and upload the files. 61 | Additionally you have to use the `__init__-pythonanywhere.py` file within the flaskeddit-folder. 62 | 63 | Then make following changes to the wsgi configuration file in the web-tab: 64 | ``` 65 | # import flask app but need to call it "application" for WSGI to work 66 | from flaskeddit import created_app as application # noqa 67 | ``` 68 | 69 | ## Built With 70 | 71 | - [Flask](http://flask.pocoo.org/) - Python Framework 72 | - [Bootstrap](https://getbootstrap.com/) - CSS Framework 73 | 74 | ## Contributing 75 | 76 | Feel free to submit a pull request! 77 | 78 | ## Authors 79 | 80 | - **Hascyll** - _Author_ - [Hascyll](https://github.com/Hascyll) 81 | 82 | 83 | ## License 84 | 85 | This project is licensed under the PiOS License - see the [LICENSE](./LICENSE) file for more details. 86 | 87 | ## Acknowledgments 88 | 89 | - [flaskeddit](https://github.com/aqche/flaskeddit) by - **aqche**- The inspiration for this site. 90 | - **Phoenix** - Art Design 91 | -------------------------------------------------------------------------------- /crumbles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/crumbles/21d2e1423db18e9dbfbe71ce08ca9f096a2e8221/crumbles.png -------------------------------------------------------------------------------- /flaskeddit.py: -------------------------------------------------------------------------------- 1 | from flaskeddit import create_app 2 | -------------------------------------------------------------------------------- /flaskeddit/__init__-pythonanywhere.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_login import LoginManager 3 | from flask_sqlalchemy import SQLAlchemy 4 | from flask_mail import Mail 5 | 6 | from flaskeddit.config import Config 7 | 8 | db = SQLAlchemy() 9 | login_manager = LoginManager() 10 | login_manager.login_view = "auth.login" 11 | login_manager.login_message_category = "danger" 12 | mail = Mail() 13 | 14 | 15 | def create_app(config=Config): 16 | """ 17 | Factory method for creating the Flaskeddit Flask app. 18 | https://flask.palletsprojects.com/en/1.1.x/patterns/appfactories/ 19 | """ 20 | app = Flask(__name__) 21 | app.config.from_object(config) 22 | 23 | db.init_app(app) 24 | login_manager.init_app(app) 25 | 26 | app.config['MAIL_SERVER']= Config.mail_server 27 | app.config['MAIL_PORT'] = Config.mail_port 28 | app.config['MAIL_USERNAME'] = Config.mail_username 29 | app.config['MAIL_PASSWORD'] = Config.mail_password 30 | app.config['MAIL_USE_TLS'] = Config.mail_use_tls 31 | app.config['MAIL_USE_SSL'] = Config.mail_use_ssl 32 | mail = Mail(app) 33 | 34 | from flaskeddit.auth import auth_blueprint 35 | from flaskeddit.communities import communities_blueprint 36 | from flaskeddit.community import community_blueprint 37 | from flaskeddit.feed import feed_blueprint 38 | from flaskeddit.post import post_blueprint 39 | from flaskeddit.reply import reply_blueprint 40 | from flaskeddit.user import user_blueprint 41 | from flaskeddit.cli import cli_app_group 42 | 43 | app.register_blueprint(auth_blueprint) 44 | app.register_blueprint(communities_blueprint) 45 | app.register_blueprint(community_blueprint) 46 | app.register_blueprint(feed_blueprint) 47 | app.register_blueprint(post_blueprint) 48 | app.register_blueprint(reply_blueprint) 49 | app.register_blueprint(user_blueprint) 50 | app.cli.add_command(cli_app_group) 51 | 52 | return app 53 | 54 | created_app = create_app() 55 | 56 | -------------------------------------------------------------------------------- /flaskeddit/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask_login import LoginManager 3 | from flask_sqlalchemy import SQLAlchemy 4 | from flask_mail import Mail 5 | from flask_ckeditor import CKEditor 6 | 7 | from flaskeddit.config import Config 8 | 9 | db = SQLAlchemy() 10 | login_manager = LoginManager() 11 | login_manager.login_view = "auth.login" 12 | login_manager.login_message_category = "danger" 13 | mail = Mail() 14 | ckeditor = CKEditor() 15 | 16 | def create_app(config=Config): 17 | """ 18 | Factory method for creating the Flaskeddit Flask app. 19 | https://flask.palletsprojects.com/en/1.1.x/patterns/appfactories/ 20 | """ 21 | app = Flask(__name__) 22 | app.config.from_object(config) 23 | 24 | db.init_app(app) 25 | login_manager.init_app(app) 26 | 27 | app.config['MAIL_SERVER']= Config.mail_server 28 | app.config['MAIL_PORT'] = Config.mail_port 29 | app.config['MAIL_USERNAME'] = Config.mail_username 30 | app.config['MAIL_PASSWORD'] = Config.mail_password 31 | app.config['MAIL_USE_TLS'] = Config.mail_use_tls 32 | app.config['MAIL_USE_SSL'] = Config.mail_use_ssl 33 | mail = Mail(app) 34 | 35 | ckeditor.init_app(app) 36 | 37 | from flaskeddit.auth import auth_blueprint 38 | from flaskeddit.communities import communities_blueprint 39 | from flaskeddit.community import community_blueprint 40 | from flaskeddit.feed import feed_blueprint 41 | from flaskeddit.post import post_blueprint 42 | from flaskeddit.reply import reply_blueprint 43 | from flaskeddit.user import user_blueprint 44 | from flaskeddit.cli import cli_app_group 45 | 46 | app.register_blueprint(auth_blueprint) 47 | app.register_blueprint(communities_blueprint) 48 | app.register_blueprint(community_blueprint) 49 | app.register_blueprint(feed_blueprint) 50 | app.register_blueprint(post_blueprint) 51 | app.register_blueprint(reply_blueprint) 52 | app.register_blueprint(user_blueprint) 53 | app.cli.add_command(cli_app_group) 54 | 55 | return app 56 | -------------------------------------------------------------------------------- /flaskeddit/auth/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | auth_blueprint = Blueprint("auth", __name__) 4 | 5 | from flaskeddit.auth import routes 6 | -------------------------------------------------------------------------------- /flaskeddit/auth/auth_service.py: -------------------------------------------------------------------------------- 1 | from flask_login import login_user, logout_user, current_user 2 | from flask_mail import Mail, Message 3 | from passlib.hash import bcrypt 4 | 5 | from flaskeddit import db, mail 6 | from flaskeddit.models import AppUser 7 | from flaskeddit.config import Config 8 | 9 | import secrets 10 | 11 | def register_user(username, password, email): 12 | """ 13 | Hashes the given password and registers a new user in the database. 14 | """ 15 | hashed_password = bcrypt.hash(password + Config.Server_SALT) 16 | email = email 17 | app_user = AppUser(username=username.lower(), password=hashed_password, email=email) 18 | db.session.add(app_user) 19 | db.session.commit() 20 | 21 | def reset_password(username, email): 22 | """ 23 | Generates a new password for a user and sends it to his/her email address. 24 | Note: There is no routine to check if the e-mail address is valid! 25 | """ 26 | 27 | #find the user 28 | app_user = AppUser.query.filter_by(username=username.lower()).first() 29 | 30 | #If a user is found: 31 | if app_user: 32 | user_mail = app_user.email 33 | 34 | #generate a new random password 35 | password_length = 13 36 | new_password = secrets.token_urlsafe(password_length) 37 | 38 | #send the password 39 | recipient = [] 40 | recipient.append(user_mail) 41 | recover_msg = Message('Reset crumbles password', sender = '$ADD-HERE-YOUR-EMAIL-ADDRESS', recipients = recipient) 42 | recover_msg.body = 'Dear user ' + str(username) + '\n\nThank you very much for testing this service!\n\nYour new password is: ' + str(new_password) + '\n\nNote: this is an automated email. Please do not respond.' 43 | 44 | mail.send(recover_msg) 45 | 46 | #updating the user-data after successful send 47 | app_user.password = bcrypt.hash(new_password + Config.Server_SALT) #here add salt! 48 | db.session.commit() 49 | return True 50 | else: 51 | return False 52 | 53 | def log_in_user(username, password): 54 | """ 55 | Hashes and compares the given password with the stored password. If it is a match, 56 | logs a user in. 57 | """ 58 | app_user = AppUser.query.filter_by(username=username.lower()).first() 59 | server_password = password + Config.Server_SALT 60 | if app_user and bcrypt.verify(server_password, app_user.password): 61 | login_user(app_user) 62 | return True 63 | else: 64 | return False 65 | 66 | def changeEmail(email, password): 67 | """ 68 | Lets a user change the used email-address. 69 | """ 70 | username = current_user.username 71 | app_user = AppUser.query.filter_by(username=username.lower()).first() 72 | server_password = password + Config.Server_SALT 73 | if app_user and bcrypt.verify(server_password, app_user.password): 74 | app_user.email = email 75 | db.session.commit() 76 | return True 77 | else: 78 | return False 79 | 80 | def changePassword(old_password, new_password): 81 | """ 82 | Lets a user change the used email-address. 83 | """ 84 | username = current_user.username 85 | app_user = AppUser.query.filter_by(username=username.lower()).first() 86 | old_server_password = old_password + Config.Server_SALT 87 | if app_user: 88 | if bcrypt.verify(old_server_password, app_user.password): 89 | app_user.password = bcrypt.hash(new_password + Config.Server_SALT) 90 | db.session.commit() 91 | return True 92 | else: 93 | return False 94 | 95 | def log_out_user(): 96 | """ 97 | Logs the current user out. 98 | """ 99 | logout_user() 100 | -------------------------------------------------------------------------------- /flaskeddit/auth/forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import PasswordField, StringField, SubmitField, ValidationError 3 | from wtforms.validators import DataRequired, EqualTo, Length 4 | 5 | from flaskeddit.models import AppUser 6 | 7 | CT_usernames = ['nk', 'nicolas', 'cfan', 'aurelienshz', 'swaroop', 'cryptoging', 'philosopherchris', 'h3rcy', 'kbiyo1', 'sherriorange', 'agares18', 'dunkinboy', 'edwaardkang', 'gamebuyer', 'greglalumiere', 'hugoa', 'ihatejam', 'jonziv', 'lyriaaw', 'oaklee', 'swetate', 'warnz99', 'zzzchen'] 8 | 9 | class RegisterForm(FlaskForm): 10 | """Form for registering a new user.""" 11 | 12 | username = StringField("Username", validators=[DataRequired()]) 13 | password = PasswordField( 14 | "Password", 15 | validators=[ 16 | DataRequired(), 17 | EqualTo("confirm_password", message="Passwords must match."), 18 | Length(min=6), 19 | ], 20 | ) 21 | email = StringField("Email", validators=[DataRequired()]) 22 | confirm_password = PasswordField("Confirm Password", validators=[DataRequired()]) 23 | submit = SubmitField("Register") 24 | 25 | def validate_username(self, username): 26 | """ 27 | Validates that a user with the given username does not already exist in the 28 | database. 29 | """ 30 | app_user = AppUser.query.filter_by(username=username.data.lower()).first() 31 | if app_user is not None: 32 | if str(app_user.username) in CT_usernames: 33 | raise ValidationError("Restricted Username.") 34 | else: 35 | raise ValidationError("Username is taken.") 36 | 37 | 38 | class LoginForm(FlaskForm): 39 | """Form for logging in a user.""" 40 | username = StringField("Username", validators=[DataRequired()]) 41 | password = PasswordField("Password", validators=[DataRequired()]) 42 | submit = SubmitField("Log In") 43 | 44 | class ChangeEmailForm(FlaskForm): 45 | """Form for changing the email of a user.""" 46 | 47 | email = StringField("Email", validators=[DataRequired()]) 48 | password = PasswordField("Password", validators=[DataRequired()]) 49 | submit = SubmitField("Confirm new Email") 50 | 51 | class ChangePasswordForm(FlaskForm): 52 | """Form for logging in a user.""" 53 | old_password = PasswordField("Old Password", validators=[DataRequired()]) 54 | new_password = PasswordField( 55 | "New Password", 56 | validators=[ 57 | DataRequired(), 58 | EqualTo("confirm_newpassword", message="Passwords must match."), 59 | Length(min=6), 60 | ], 61 | ) 62 | confirm_newpassword = PasswordField("Confirm Password", validators=[DataRequired()]) 63 | submit = SubmitField("Confirm new Password") 64 | 65 | class ResetPasswordForm(FlaskForm): 66 | """Form for resetting a password.""" 67 | username = StringField("Username", validators=[DataRequired()]) 68 | email = StringField("E-Mail", validators=[DataRequired()]) 69 | submit = SubmitField("Reset Password") 70 | 71 | -------------------------------------------------------------------------------- /flaskeddit/cli/__init__.py: -------------------------------------------------------------------------------- 1 | from flask.cli import AppGroup 2 | 3 | cli_app_group = AppGroup("cli") 4 | 5 | from flaskeddit.cli import commands 6 | -------------------------------------------------------------------------------- /flaskeddit/cli/commands.py: -------------------------------------------------------------------------------- 1 | from flaskeddit import db 2 | from flaskeddit.cli import cli_app_group 3 | 4 | 5 | @cli_app_group.command("create_db") 6 | def create_db(): 7 | """ 8 | CLI command for generating all database tables from SQLAlchemy models. 9 | """ 10 | db.create_all() 11 | -------------------------------------------------------------------------------- /flaskeddit/communities/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | communities_blueprint = Blueprint("communities", __name__) 4 | 5 | from flaskeddit.communities import routes 6 | -------------------------------------------------------------------------------- /flaskeddit/communities/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/crumbles/21d2e1423db18e9dbfbe71ce08ca9f096a2e8221/flaskeddit/communities/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /flaskeddit/communities/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/crumbles/21d2e1423db18e9dbfbe71ce08ca9f096a2e8221/flaskeddit/communities/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /flaskeddit/communities/__pycache__/communities_service.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/crumbles/21d2e1423db18e9dbfbe71ce08ca9f096a2e8221/flaskeddit/communities/__pycache__/communities_service.cpython-310.pyc -------------------------------------------------------------------------------- /flaskeddit/communities/__pycache__/communities_service.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/crumbles/21d2e1423db18e9dbfbe71ce08ca9f096a2e8221/flaskeddit/communities/__pycache__/communities_service.cpython-39.pyc -------------------------------------------------------------------------------- /flaskeddit/communities/__pycache__/routes.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/crumbles/21d2e1423db18e9dbfbe71ce08ca9f096a2e8221/flaskeddit/communities/__pycache__/routes.cpython-310.pyc -------------------------------------------------------------------------------- /flaskeddit/communities/__pycache__/routes.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/crumbles/21d2e1423db18e9dbfbe71ce08ca9f096a2e8221/flaskeddit/communities/__pycache__/routes.cpython-39.pyc -------------------------------------------------------------------------------- /flaskeddit/communities/communities_service.py: -------------------------------------------------------------------------------- 1 | from flaskeddit import db 2 | from flaskeddit.models import AppUser, Community, CommunityMember 3 | 4 | 5 | def get_communities(page): 6 | """ 7 | Gets paginated list of communities by date created from the database. 8 | """ 9 | communities = ( 10 | db.session.query( 11 | Community.id, 12 | Community.name, 13 | Community.description, 14 | Community.date_created, 15 | Community.user_id, 16 | AppUser.username, 17 | ) 18 | .join(AppUser, Community.user_id == AppUser.id) 19 | .order_by(Community.date_created.desc()) 20 | .paginate(page=page, per_page=5) 21 | ) 22 | return communities 23 | 24 | 25 | def get_communities_by_membership(page): 26 | """ 27 | Gets paginated list of communities by number of members from the database. 28 | """ 29 | communities = ( 30 | db.session.query( 31 | Community.id, 32 | Community.name, 33 | Community.description, 34 | Community.date_created, 35 | Community.user_id, 36 | AppUser.username, 37 | db.func.count(CommunityMember.id).label("community_members"), 38 | ) 39 | .join(AppUser, Community.user_id == AppUser.id) 40 | .outerjoin(CommunityMember, Community.id == CommunityMember.community_id) 41 | .group_by(Community.id, AppUser.id) 42 | .order_by(db.literal_column("community_members").desc()) 43 | .paginate(page=page, per_page=5) 44 | ) 45 | return communities 46 | -------------------------------------------------------------------------------- /flaskeddit/communities/routes.py: -------------------------------------------------------------------------------- 1 | from flask import render_template, request 2 | 3 | from flaskeddit.communities import communities_blueprint, communities_service 4 | 5 | 6 | @communities_blueprint.route("/communities") 7 | def communities(): 8 | """ 9 | Route for page displaying list of all communities sorted by date created. 10 | """ 11 | page = int(request.args.get("page", 1)) 12 | communities = communities_service.get_communities(page) 13 | return render_template("communities.html", tab="recent", communities=communities) 14 | 15 | 16 | @communities_blueprint.route("/communities/top") 17 | def top_communities(): 18 | """ 19 | Route for page displaying list of all communities sorted by most members. 20 | """ 21 | page = int(request.args.get("page", 1)) 22 | communities = communities_service.get_communities_by_membership(page) 23 | return render_template("communities.html", tab="top", communities=communities) 24 | -------------------------------------------------------------------------------- /flaskeddit/community/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | community_blueprint = Blueprint("community", __name__) 4 | 5 | from flaskeddit.community import routes 6 | -------------------------------------------------------------------------------- /flaskeddit/community/community_service.py: -------------------------------------------------------------------------------- 1 | from flaskeddit import db 2 | from flaskeddit.models import AppUser, Community, CommunityMember, Post, PostVote 3 | 4 | 5 | def get_community(name): 6 | """ 7 | Gets a community by name from the database. 8 | """ 9 | community = Community.query.filter_by(name=name).first() 10 | return community 11 | 12 | 13 | def create_community(name, description, user): 14 | """ 15 | Adds a new community to the database. 16 | """ 17 | community = Community(name=name, description=description, app_user=user) 18 | db.session.add(community) 19 | db.session.commit() 20 | 21 | 22 | def update_community(community, description): 23 | """ 24 | Updates an existing community's description in the database. 25 | """ 26 | community.description = description 27 | db.session.commit() 28 | 29 | 30 | def delete_community(community): 31 | """ 32 | Removes a community from the database. 33 | """ 34 | db.session.delete(community) 35 | db.session.commit() 36 | 37 | 38 | def get_community_posts(community_id, page, ordered_by_votes): 39 | """ 40 | Gets paginated list of posts from a specified community from the database. 41 | """ 42 | ordered_by = Post.date_created.desc() 43 | if ordered_by_votes: 44 | ordered_by = db.literal_column("votes").desc() 45 | posts = ( 46 | db.session.query( 47 | Post.title, 48 | Post.post, 49 | Post.date_created, 50 | db.func.coalesce(db.func.sum(PostVote.vote), 0).label("votes"), 51 | AppUser.username, 52 | ) 53 | .outerjoin(PostVote, Post.id == PostVote.post_id) 54 | .join(AppUser, Post.user_id == AppUser.id) 55 | .filter(Post.community_id == community_id) 56 | .group_by(Post.id, AppUser.id) 57 | .order_by(ordered_by) 58 | .paginate(page=page, per_page=5) 59 | ) 60 | return posts 61 | 62 | 63 | def get_community_member(community_id, user_id): 64 | """ 65 | Gets a community membership by community and user from the database. 66 | """ 67 | community_member = CommunityMember.query.filter_by( 68 | community_id=community_id, user_id=user_id 69 | ).first() 70 | return community_member 71 | 72 | 73 | def create_community_member(community, user): 74 | """ 75 | Adds a new community member to the database. 76 | """ 77 | community_member = CommunityMember(community=community, app_user=user) 78 | db.session.add(community_member) 79 | db.session.commit() 80 | 81 | 82 | def delete_community_member(community_member): 83 | """ 84 | Removes a community member from the database. 85 | """ 86 | db.session.delete(community_member) 87 | db.session.commit() 88 | -------------------------------------------------------------------------------- /flaskeddit/community/forms.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from flask_wtf import FlaskForm 4 | from wtforms import StringField, SubmitField, TextAreaField 5 | from wtforms.validators import DataRequired, ValidationError 6 | 7 | from flaskeddit import db 8 | from flaskeddit.models import Community 9 | 10 | 11 | class CommunityForm(FlaskForm): 12 | """Form for creating a new community.""" 13 | 14 | name = StringField("Name", validators=[DataRequired()]) 15 | description = TextAreaField("Description", validators=[DataRequired()]) 16 | submit = SubmitField("Create") 17 | 18 | def validate_name(self, name): 19 | """ 20 | Validates that a given community name does not contain a space and is not taken 21 | by an existing community in the database. 22 | """ 23 | #if re.search(" ", name.data): 24 | # raise ValidationError("Name cannot contain a space.") 25 | 26 | community = Community.query.filter( 27 | db.func.lower(Community.name) == name.data.lower() 28 | ).first() 29 | if community is not None: 30 | raise ValidationError("Name is already taken.") 31 | 32 | 33 | class UpdateCommunityForm(FlaskForm): 34 | """Form for updating a community description.""" 35 | 36 | description = TextAreaField("Description", validators=[DataRequired()]) 37 | submit = SubmitField("Update") 38 | -------------------------------------------------------------------------------- /flaskeddit/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | class Config: 5 | """Configuration settings for the Flaskeddit Flask app.""" 6 | 7 | # Change this before using it 8 | SECRET_KEY = os.environ.get("SECRET_KEY", "placeholder-secret-key") 9 | 10 | # Server-side salt for password hashing - Change this before using it 11 | Server_SALT = 'placeholder-secret-salt' 12 | 13 | SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL", "sqlite:///app.db") 14 | SQLALCHEMY_TRACK_MODIFICATIONS = False 15 | 16 | # Get this on the Pi Developer Portal (develop.pi in the Pi Browser) 17 | PI_API_KEY = '' #Add your SECRET API Key here 18 | 19 | # Platform API 20 | PLATFORM_API_URL = 'https://api.minepi.com' 21 | 22 | #Mail Server configuration 23 | mail_server = #add 'smtp.$your-e-mail-hoster' 24 | mail_port = #add port 25 | mail_username = #add username 26 | mail_password = #add passwort 27 | mail_use_tls = False 28 | mail_use_ssl = True 29 | -------------------------------------------------------------------------------- /flaskeddit/feed/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | feed_blueprint = Blueprint("feed", __name__) 4 | 5 | from flaskeddit.feed import routes 6 | -------------------------------------------------------------------------------- /flaskeddit/feed/feed_service.py: -------------------------------------------------------------------------------- 1 | from flaskeddit import db 2 | from flaskeddit.models import AppUser, Community, CommunityMember, Post, PostVote 3 | 4 | 5 | def get_feed(user, page, ordered_by_votes): 6 | """ 7 | Get paginated list of posts from communities that a given user is a member of from 8 | the database. 9 | """ 10 | ordered_by = Post.date_created.desc() 11 | if ordered_by_votes: 12 | ordered_by = db.literal_column("votes").desc() 13 | posts = ( 14 | db.session.query( 15 | Post.title, 16 | Post.post, 17 | Post.date_created, 18 | db.func.coalesce(db.func.sum(PostVote.vote), 0).label("votes"), 19 | AppUser.username, 20 | Community.name.label("community_name"), 21 | ) 22 | .outerjoin(PostVote, Post.id == PostVote.post_id) 23 | .join(AppUser, Post.user_id == AppUser.id) 24 | .join(Community, Post.community_id == Community.id) 25 | .join(CommunityMember, Post.community_id == CommunityMember.community_id) 26 | .filter(CommunityMember.user_id == user.id) 27 | .group_by(Post.id, AppUser.id, Community.id) 28 | .order_by(ordered_by) 29 | .paginate(page=page, per_page=5) 30 | ) 31 | return posts 32 | -------------------------------------------------------------------------------- /flaskeddit/feed/routes.py: -------------------------------------------------------------------------------- 1 | from flask import render_template, request 2 | from flask_login import current_user 3 | 4 | from flaskeddit.feed import feed_blueprint, feed_service 5 | 6 | 7 | @feed_blueprint.route("/") 8 | @feed_blueprint.route("/feed") 9 | def feed(): 10 | """ 11 | Route for displaying posts from the current user's joined communities sorted by 12 | date created. 13 | """ 14 | page = int(request.args.get("page", 1)) 15 | if current_user.is_authenticated: 16 | posts = feed_service.get_feed(current_user, page, False) 17 | return render_template("feed.html", tab="recent", posts=posts) 18 | else: 19 | return render_template("feed.html", tab="recent", posts=None) 20 | 21 | 22 | @feed_blueprint.route("/feed/top") 23 | def top_feed(): 24 | """ 25 | Route for displaying posts from the current user's joined communities sorted by 26 | upvotes. 27 | """ 28 | page = int(request.args.get("page", 1)) 29 | if current_user.is_authenticated: 30 | posts = feed_service.get_feed(current_user, page, True) 31 | return render_template("feed.html", tab="top", posts=posts) 32 | else: 33 | return render_template("feed.html", tab="top", posts=None) 34 | -------------------------------------------------------------------------------- /flaskeddit/post/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | post_blueprint = Blueprint("post", __name__) 4 | 5 | from flaskeddit.post import routes 6 | -------------------------------------------------------------------------------- /flaskeddit/post/forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms.fields import IntegerField, StringField, SubmitField, TextAreaField 3 | from wtforms.validators import DataRequired, ValidationError 4 | from flask_ckeditor import CKEditorField 5 | 6 | from flaskeddit.models import Post 7 | 8 | 9 | class PostForm(FlaskForm): 10 | """Form for creating a new post.""" 11 | 12 | title = StringField("Title", validators=[DataRequired()]) 13 | post = CKEditorField("Post", validators=[DataRequired()]) 14 | community_id = IntegerField("Community Id", validators=[DataRequired()]) 15 | submit = SubmitField("Create") 16 | 17 | def validate_title(self, title): 18 | """ 19 | Validates that a given title is not already taken by an existing post within 20 | the target community in the database. 21 | """ 22 | post = Post.query.filter_by( 23 | title=title.data, community_id=self.community_id.data 24 | ).first() 25 | if post is not None: 26 | raise ValidationError( 27 | "Post with same title already exists within this community." 28 | ) 29 | 30 | 31 | class UpdatePostForm(FlaskForm): 32 | """Form for updating a post.""" 33 | 34 | post = CKEditorField("Post", validators=[DataRequired()]) 35 | submit = SubmitField("Create") 36 | -------------------------------------------------------------------------------- /flaskeddit/reply/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | reply_blueprint = Blueprint("reply", __name__) 4 | 5 | from flaskeddit.reply import routes 6 | -------------------------------------------------------------------------------- /flaskeddit/reply/forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms.fields import SubmitField, TextAreaField 3 | from wtforms.validators import DataRequired 4 | from flask_ckeditor import CKEditorField 5 | 6 | class ReplyForm(FlaskForm): 7 | """Form for creating and updating a reply.""" 8 | 9 | reply = CKEditorField("Reply", validators=[DataRequired()]) 10 | submit = SubmitField("Submit") 11 | -------------------------------------------------------------------------------- /flaskeddit/reply/reply_service.py: -------------------------------------------------------------------------------- 1 | from flaskeddit import db 2 | from flaskeddit.models import Post, Reply, ReplyVote 3 | 4 | 5 | def get_reply(reply_id): 6 | """ 7 | Gets a reply by ID from the database. 8 | """ 9 | reply = Reply.query.get(reply_id) 10 | return reply 11 | 12 | 13 | def create_reply(reply, post, user): 14 | """ 15 | Adds a new reply to the database. 16 | """ 17 | reply = Reply(reply=reply, post=post, app_user=user) 18 | db.session.add(reply) 19 | db.session.commit() 20 | 21 | 22 | def update_reply(reply, reply_text): 23 | """ 24 | Updates a reply's text content in the database. 25 | """ 26 | reply.reply = reply_text 27 | db.session.commit() 28 | 29 | 30 | def delete_reply(reply): 31 | """ 32 | Removes a reply from the database. 33 | """ 34 | db.session.delete(reply) 35 | db.session.commit() 36 | 37 | 38 | def get_reply_vote(reply_id, user_id): 39 | """ 40 | Gets a specific user's vote on a reply the database. 41 | """ 42 | reply_vote = ReplyVote.query.filter_by(user_id=user_id, reply_id=reply_id).first() 43 | return reply_vote 44 | 45 | 46 | def upvote_reply(reply_id, user_id): 47 | """ 48 | Upvotes a reply for a user in the database. If the reply is already voted on, undo 49 | the vote. 50 | """ 51 | reply_vote = get_reply_vote(reply_id, user_id) 52 | if reply_vote is None: 53 | reply_vote = ReplyVote(vote=1, user_id=user_id, reply_id=reply_id) 54 | db.session.add(reply_vote) 55 | elif abs(reply_vote.vote) == 1: 56 | reply_vote.vote = 0 57 | else: 58 | reply_vote.vote = 1 59 | db.session.commit() 60 | 61 | 62 | def downvote_reply(reply_id, user_id): 63 | """ 64 | Downvotes a reply for a user in the database. If the reply is already voted on, 65 | undo the vote. 66 | """ 67 | reply_vote = get_reply_vote(reply_id, user_id) 68 | if reply_vote is None: 69 | reply_vote = ReplyVote(vote=-1, user_id=user_id, reply_id=reply_id) 70 | db.session.add(reply_vote) 71 | elif abs(reply_vote.vote) == 1: 72 | reply_vote.vote = 0 73 | else: 74 | reply_vote.vote = -1 75 | db.session.commit() 76 | -------------------------------------------------------------------------------- /flaskeddit/static/ckeditor/standard/README.md: -------------------------------------------------------------------------------- 1 | CKEditor 4 2 | ========== 3 | 4 | Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. 5 | https://ckeditor.com - See LICENSE.md for license information. 6 | 7 | CKEditor 4 is a text editor to be used inside web pages. It's not a replacement 8 | for desktop text editors like Word or OpenOffice, but a component to be used as 9 | part of web applications and websites. 10 | 11 | ## Documentation 12 | 13 | The full editor documentation is available online at the following address: 14 | https://ckeditor.com/docs/ 15 | 16 | ## Installation 17 | 18 | Installing CKEditor is an easy task. Just follow these simple steps: 19 | 20 | 1. **Download** the latest version from the CKEditor website: 21 | https://ckeditor.com. You should have already completed this step, but be 22 | sure you have the very latest version. 23 | 2. **Extract** (decompress) the downloaded file into the root of your website. 24 | 25 | **Note:** CKEditor is by default installed in the `ckeditor` folder. You can 26 | place the files in whichever you want though. 27 | 28 | ## Checking Your Installation 29 | 30 | The editor comes with a few sample pages that can be used to verify that 31 | installation proceeded properly. Take a look at the `samples` directory. 32 | 33 | To test your installation, just call the following page at your website: 34 | 35 | http:////samples/index.html 36 | 37 | For example: 38 | 39 | http://www.example.com/ckeditor/samples/index.html 40 | -------------------------------------------------------------------------------- /flaskeddit/static/ckeditor/standard/adapters/jquery.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. 3 | For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license 4 | */ 5 | (function(a){if("undefined"==typeof a)throw Error("jQuery should be loaded before CKEditor jQuery adapter.");if("undefined"==typeof CKEDITOR)throw Error("CKEditor should be loaded before CKEditor jQuery adapter.");CKEDITOR.config.jqueryOverrideVal="undefined"==typeof CKEDITOR.config.jqueryOverrideVal?!0:CKEDITOR.config.jqueryOverrideVal;a.extend(a.fn,{ckeditorGet:function(){var a=this.eq(0).data("ckeditorInstance");if(!a)throw"CKEditor is not initialized yet, use ckeditor() with a callback.";return a}, 6 | ckeditor:function(g,e){if(!CKEDITOR.env.isCompatible)throw Error("The environment is incompatible.");if(!a.isFunction(g)){var m=e;e=g;g=m}var k=[];e=e||{};this.each(function(){var b=a(this),c=b.data("ckeditorInstance"),f=b.data("_ckeditorInstanceLock"),h=this,l=new a.Deferred;k.push(l.promise());if(c&&!f)g&&g.apply(c,[this]),l.resolve();else if(f)c.once("instanceReady",function(){setTimeout(function d(){c.element?(c.element.$==h&&g&&g.apply(c,[h]),l.resolve()):setTimeout(d,100)},0)},null,null,9999); 7 | else{if(e.autoUpdateElement||"undefined"==typeof e.autoUpdateElement&&CKEDITOR.config.autoUpdateElement)e.autoUpdateElementJquery=!0;e.autoUpdateElement=!1;b.data("_ckeditorInstanceLock",!0);c=a(this).is("textarea")?CKEDITOR.replace(h,e):CKEDITOR.inline(h,e);b.data("ckeditorInstance",c);c.on("instanceReady",function(e){var d=e.editor;setTimeout(function n(){if(d.element){e.removeListener();d.on("dataReady",function(){b.trigger("dataReady.ckeditor",[d])});d.on("setData",function(a){b.trigger("setData.ckeditor", 8 | [d,a.data])});d.on("getData",function(a){b.trigger("getData.ckeditor",[d,a.data])},999);d.on("destroy",function(){b.trigger("destroy.ckeditor",[d])});d.on("save",function(){a(h.form).submit();return!1},null,null,20);if(d.config.autoUpdateElementJquery&&b.is("textarea")&&a(h.form).length){var c=function(){b.ckeditor(function(){d.updateElement()})};a(h.form).submit(c);a(h.form).bind("form-pre-serialize",c);b.bind("destroy.ckeditor",function(){a(h.form).unbind("submit",c);a(h.form).unbind("form-pre-serialize", 9 | c)})}d.on("destroy",function(){b.removeData("ckeditorInstance")});b.removeData("_ckeditorInstanceLock");b.trigger("instanceReady.ckeditor",[d]);g&&g.apply(d,[h]);l.resolve()}else setTimeout(n,100)},0)},null,null,9999)}});var f=new a.Deferred;this.promise=f.promise();a.when.apply(this,k).then(function(){f.resolve()});this.editor=this.eq(0).data("ckeditorInstance");return this}});CKEDITOR.config.jqueryOverrideVal&&(a.fn.val=CKEDITOR.tools.override(a.fn.val,function(g){return function(e){if(arguments.length){var m= 10 | this,k=[],f=this.each(function(){var b=a(this),c=b.data("ckeditorInstance");if(b.is("textarea")&&c){var f=new a.Deferred;c.setData(e,function(){f.resolve()});k.push(f.promise());return!0}return g.call(b,e)});if(k.length){var b=new a.Deferred;a.when.apply(this,k).done(function(){b.resolveWith(m)});return b.promise()}return f}var f=a(this).eq(0),c=f.data("ckeditorInstance");return f.is("textarea")&&c?c.getData():g.call(f)}}))})(window.jQuery); -------------------------------------------------------------------------------- /flaskeddit/static/ckeditor/standard/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. 3 | * For licensing, see https://ckeditor.com/legal/ckeditor-oss-license 4 | */ 5 | 6 | CKEDITOR.editorConfig = function( config ) { 7 | config.toolbarGroups = [ 8 | { name: 'clipboard', groups: [ 'clipboard', 'undo' ] }, 9 | { name: 'editing', groups: [ 'find', 'selection', 'spellchecker', 'editing' ] }, 10 | { name: 'links', groups: [ 'links' ] }, 11 | { name: 'insert', groups: [ 'insert' ] }, 12 | { name: 'forms', groups: [ 'forms' ] }, 13 | { name: 'tools', groups: [ 'tools' ] }, 14 | { name: 'document', groups: [ 'mode', 'document', 'doctools' ] }, 15 | { name: 'others', groups: [ 'others' ] }, 16 | { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] }, 17 | { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi', 'paragraph' ] }, 18 | { name: 'styles', groups: [ 'styles' ] }, 19 | { name: 'colors', groups: [ 'colors' ] }, 20 | '/', 21 | '/', 22 | { name: 'about', groups: [ 'about' ] } 23 | ]; 24 | 25 | config.removeButtons = 'Subscript,Superscript,Paste,PasteText,PasteFromWord,Scayt,Anchor,Table,Image,Maximize,Source,RemoveFormat,About'; 26 | }; 27 | -------------------------------------------------------------------------------- /flaskeddit/static/ckeditor/standard/plugins/a11yhelp/dialogs/a11yhelp.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. 3 | For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license 4 | */ 5 | CKEDITOR.dialog.add("a11yHelp",function(f){function m(a){for(var b,c,h=[],d=0;d=b&&(a-=c,h.push(e[c]));h.push(e[a]||String.fromCharCode(a));return h.join("+")}function t(a,b){var c=f.getCommandKeystroke(b,!0);return c.length?CKEDITOR.tools.array.map(c,m).join(" / "):a}var a=f.lang.a11yhelp,b=f.lang.common.keyboard,p=CKEDITOR.tools.getNextId(),q=/\$\{(.*?)\}/g,g=[CKEDITOR.ALT,CKEDITOR.SHIFT,CKEDITOR.CTRL],e={8:b[8],9:a.tab,13:b[13],16:b[16],17:b[17],18:b[18],19:a.pause, 6 | 20:a.capslock,27:a.escape,33:a.pageUp,34:a.pageDown,35:b[35],36:b[36],37:a.leftArrow,38:a.upArrow,39:a.rightArrow,40:a.downArrow,45:a.insert,46:b[46],91:a.leftWindowKey,92:a.rightWindowKey,93:a.selectKey,96:a.numpad0,97:a.numpad1,98:a.numpad2,99:a.numpad3,100:a.numpad4,101:a.numpad5,102:a.numpad6,103:a.numpad7,104:a.numpad8,105:a.numpad9,106:a.multiply,107:a.add,109:a.subtract,110:a.decimalPoint,111:a.divide,112:a.f1,113:a.f2,114:a.f3,115:a.f4,116:a.f5,117:a.f6,118:a.f7,119:a.f8,120:a.f9,121:a.f10, 7 | 122:a.f11,123:a.f12,144:a.numLock,145:a.scrollLock,186:a.semiColon,187:a.equalSign,188:a.comma,189:a.dash,190:a.period,191:a.forwardSlash,192:a.graveAccent,219:a.openBracket,220:a.backSlash,221:a.closeBracket,222:a.singleQuote};e[CKEDITOR.ALT]=b[18];e[CKEDITOR.SHIFT]=b[16];e[CKEDITOR.CTRL]=CKEDITOR.env.mac?b[224]:b[17];return{title:a.title,minWidth:600,minHeight:400,contents:[{id:"info",label:f.lang.common.generalTab,expand:!0,elements:[{type:"html",id:"legends",style:"white-space:normal;",focus:function(){this.getElement().focus()}, 8 | html:function(){for(var b='\x3cdiv class\x3d"cke_accessibility_legend" role\x3d"document" aria-labelledby\x3d"'+p+'_arialbl" tabIndex\x3d"-1"\x3e%1\x3c/div\x3e\x3cspan id\x3d"'+p+'_arialbl" class\x3d"cke_voice_label"\x3e'+a.contents+" \x3c/span\x3e",e=[],c=a.legend,h=c.length,d=0;dCKEDITOR.env.version)b.getWindow().on("blur",function(){b.$.selection.empty()});b.on("keydown",function(a){a=a.data;var b;switch(a.getKeystroke()){case 27:this.hide();b=1;break;case 9:case CKEDITOR.SHIFT+ 6 | 9:this.changeFocus(1),b=1}b&&a.preventDefault()},this);c.fire("ariaWidget",new CKEDITOR.dom.element(a.frameElement));b.getWindow().getFrame().removeCustomData("pendingFocus")&&g.focus()}var h=c.lang.clipboard,e=CKEDITOR.plugins.clipboard,f;c.on("pasteDialogCommit",function(a){a.data&&c.fire("paste",{type:"auto",dataValue:a.data.dataValue,method:"paste",dataTransfer:a.data.dataTransfer||e.initPasteDataTransfer()})},null,null,1E3);return{title:h.paste,minWidth:CKEDITOR.env.ie&&CKEDITOR.env.quirks?370: 7 | 350,minHeight:CKEDITOR.env.quirks?250:245,onShow:function(){this.parts.dialog.$.offsetHeight;this.setupContent();this._.committed=!1},onLoad:function(){(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat)&&"rtl"==c.lang.dir&&this.parts.contents.setStyle("overflow","hidden")},onOk:function(){this.commitContent()},contents:[{id:"general",label:c.lang.common.generalTab,elements:[{type:"html",id:"pasteMsg",html:'\x3cdiv style\x3d"white-space:normal;width:340px"\x3e'+h.pasteMsg+"\x3c/div\x3e"},{type:"html", 8 | id:"editing_area",style:"width:100%;height:100%",html:"",focus:function(){var a=this.getInputElement(),b=a.getFrameDocument().getBody();!b||b.isReadOnly()?a.setCustomData("pendingFocus",1):b.focus()},setup:function(){var a=this.getDialog(),b='\x3chtml dir\x3d"'+c.config.contentsLangDirection+'" lang\x3d"'+(c.config.contentsLanguage||c.langCode)+'"\x3e\x3chead\x3e\x3cstyle\x3ebody{margin:3px;height:95%;word-break:break-all;}\x3c/style\x3e\x3c/head\x3e\x3cbody\x3e\x3cscript id\x3d"cke_actscrpt" type\x3d"text/javascript"\x3ewindow.parent.CKEDITOR.tools.callFunction('+ 9 | CKEDITOR.tools.addFunction(k,a)+",this);\x3c/script\x3e\x3c/body\x3e\x3c/html\x3e",g=CKEDITOR.env.air?"javascript:void(0)":CKEDITOR.env.ie&&!CKEDITOR.env.edge?"javascript:void((function(){"+encodeURIComponent("document.open();("+CKEDITOR.tools.fixDomain+")();document.close();")+'})())"':"",d=CKEDITOR.dom.element.createFromHtml('\x3ciframe class\x3d"cke_pasteframe" frameborder\x3d"0" allowTransparency\x3d"true" src\x3d"'+g+'" aria-label\x3d"'+h.pasteArea+'" aria-describedby\x3d"'+a.getContentElement("general", 10 | "pasteMsg").domId+'"\x3e\x3c/iframe\x3e');f=null;d.on("load",function(a){a.removeListener();a=d.getFrameDocument();a.write(b);c.focusManager.add(a.getBody());CKEDITOR.env.air&&k.call(this,a.getWindow().$)},a);d.setCustomData("dialog",a);a=this.getElement();a.setHtml("");a.append(d);if(CKEDITOR.env.ie&&!CKEDITOR.env.edge){var e=CKEDITOR.dom.element.createFromHtml('\x3cspan tabindex\x3d"-1" style\x3d"position:absolute" role\x3d"presentation"\x3e\x3c/span\x3e');e.on("focus",function(){setTimeout(function(){d.$.contentWindow.focus()})}); 11 | a.append(e);this.focus=function(){e.focus();this.fire("focus")}}this.getInputElement=function(){return d};CKEDITOR.env.ie&&(a.setStyle("display","block"),a.setStyle("height",d.$.offsetHeight+2+"px"))},commit:function(){var a=this.getDialog().getParentEditor(),b=this.getInputElement().getFrameDocument().getBody(),c=b.getBogus();c&&c.remove();b=b.getHtml();this.getDialog()._.committed=!0;a.fire("pasteDialogCommit",{dataValue:b,dataTransfer:f||e.initPasteDataTransfer()})}}]}]}}); -------------------------------------------------------------------------------- /flaskeddit/static/ckeditor/standard/plugins/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/crumbles/21d2e1423db18e9dbfbe71ce08ca9f096a2e8221/flaskeddit/static/ckeditor/standard/plugins/icons.png -------------------------------------------------------------------------------- /flaskeddit/static/ckeditor/standard/plugins/icons_hidpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/crumbles/21d2e1423db18e9dbfbe71ce08ca9f096a2e8221/flaskeddit/static/ckeditor/standard/plugins/icons_hidpi.png -------------------------------------------------------------------------------- /flaskeddit/static/ckeditor/standard/plugins/image/images/noimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/crumbles/21d2e1423db18e9dbfbe71ce08ca9f096a2e8221/flaskeddit/static/ckeditor/standard/plugins/image/images/noimage.png -------------------------------------------------------------------------------- /flaskeddit/static/ckeditor/standard/plugins/link/dialogs/anchor.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. 3 | For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license 4 | */ 5 | CKEDITOR.dialog.add("anchor",function(c){function d(b,a){return b.createFakeElement(b.document.createElement("a",{attributes:a}),"cke_anchor","anchor")}return{title:c.lang.link.anchor.title,minWidth:300,minHeight:60,getModel:function(b){var a=b.getSelection();b=a.getRanges()[0];a=a.getSelectedElement();b.shrink(CKEDITOR.SHRINK_ELEMENT);(a=b.getEnclosedNode())&&a.type===CKEDITOR.NODE_TEXT&&(a=a.getParent());b=a&&a.type===CKEDITOR.NODE_ELEMENT&&("anchor"===a.data("cke-real-element-type")||a.is("a"))? 6 | a:void 0;return b||null},onOk:function(){var b=CKEDITOR.tools.trim(this.getValueOf("info","txtName")),b={id:b,name:b,"data-cke-saved-name":b},a=this.getModel(c);a?a.data("cke-realelement")?(b=d(c,b),b.replace(a),CKEDITOR.env.ie&&c.getSelection().selectElement(b)):a.setAttributes(b):(a=(a=c.getSelection())&&a.getRanges()[0],a.collapsed?(b=d(c,b),a.insertNode(b)):(CKEDITOR.env.ie&&9>CKEDITOR.env.version&&(b["class"]="cke_anchor"),b=new CKEDITOR.style({element:"a",attributes:b}),b.type=CKEDITOR.STYLE_INLINE, 7 | b.applyToRange(a)))},onShow:function(){var b=c.getSelection(),a=this.getModel(c),d=a&&a.data("cke-realelement");if(a=d?CKEDITOR.plugins.link.tryRestoreFakeAnchor(c,a):CKEDITOR.plugins.link.getSelectedLink(c)){var e=a.data("cke-saved-name");this.setValueOf("info","txtName",e||"");!d&&b.selectElement(a)}this.getContentElement("info","txtName").focus()},contents:[{id:"info",label:c.lang.link.anchor.title,accessKey:"I",elements:[{type:"text",id:"txtName",label:c.lang.link.anchor.name,required:!0,validate:function(){return this.getValue()? 8 | !0:(alert(c.lang.link.anchor.errorName),!1)}}]}]}}); -------------------------------------------------------------------------------- /flaskeddit/static/ckeditor/standard/plugins/link/images/anchor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/crumbles/21d2e1423db18e9dbfbe71ce08ca9f096a2e8221/flaskeddit/static/ckeditor/standard/plugins/link/images/anchor.png -------------------------------------------------------------------------------- /flaskeddit/static/ckeditor/standard/plugins/link/images/hidpi/anchor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/crumbles/21d2e1423db18e9dbfbe71ce08ca9f096a2e8221/flaskeddit/static/ckeditor/standard/plugins/link/images/hidpi/anchor.png -------------------------------------------------------------------------------- /flaskeddit/static/ckeditor/standard/plugins/pastetools/filter/image.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. 3 | For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license 4 | */ 5 | (function(){function f(b){var c=[],a=/(\{\\pict[^{}]+?|\{\\\*\\shppict\{\\pict\{\\\*[^*]+?)\\(?:jpeg|png)blip/,d;b=b.match(new RegExp("(?:("+a.source+"))([\\da-fA-F\\s]+)\\}","g"));if(!b)return c;for(var e=0;e]+src="([^"]+)[^>]+/g,a=[],d;d= 6 | c.exec(b);)a.push(d[1]);return a}CKEDITOR.pasteFilters.image=function(b,c,a){var d=[];if(!a)return b;c=g(b);if(0===c.length)return b;a=f(a);if(0===a.length)return b;CKEDITOR.tools.array.forEach(a,function(a){d.push(a.type?"data:"+a.type+";base64,"+CKEDITOR.tools.convertBytesToBase64(CKEDITOR.tools.convertHexStringToBytes(a.hex)):null)},this);if(c.length===d.length)for(a=0;aChange Email 3 |
4 | {{ form.csrf_token }} 5 |
6 | {{ form.email.label }} {{ 7 | form.email(class="form-control") }} 8 |
9 |
10 | {{ form.password.label }} {{ 11 | form.password(class="form-control") }} 12 |
13 | 14 | {{ form.submit(class="btn btn-primary") }} 15 |
16 | {% endblock %} -------------------------------------------------------------------------------- /flaskeddit/templates/changepassword.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block content %} 2 |

Change Password

3 |
4 | {{ form.csrf_token }} 5 | 6 |
7 | {{ form.old_password.label }} {% if form.old_password.errors %} {{ 8 | form.old_password(class="form-control is-invalid") }} {% for error in 9 | form.old_password.errors %} 10 |
11 | {{ error }} 12 |
13 | {% endfor %} {% else %} {{ form.old_password(class="form-control") }} {% endif 14 | %} 15 |
16 |
17 | {{ form.new_password.label }} {% if form.new_password.errors %} {{ 18 | form.new_password(class="form-control is-invalid") }} {% for error in 19 | form.new_password.errors %} 20 |
21 | {{ error }} 22 |
23 | {% endfor %} {% else %} {{ form.new_password(class="form-control") }} {% endif 24 | %} 25 |
26 |
27 | {{ form.confirm_newpassword.label }} {{ 28 | form.confirm_newpassword(class="form-control") }} 29 |
30 | {{ form.submit(class="btn btn-primary") }} 31 |
32 | {% endblock %} -------------------------------------------------------------------------------- /flaskeddit/templates/communities.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block content %} 2 |
3 |

4 | My Feed | Communities 5 |

6 |
7 |
8 |
9 | Communities 10 |
11 |
12 |

13 | Find a subcrumble with a topic you're interested in or create one for yourself. 14 |

15 | Create 20 |
21 |
22 | 23 | 38 |

Communities

39 |
40 |
41 | {% for community in communities.items %} 42 |
43 |
44 |
45 | f/{{ community.name }} 50 |
51 |
52 | Created on {{ community.date_created.strftime("%d/%m/%Y") }} by 54 | u/{{ community.username }} 60 |
61 |
62 |
63 | {% endfor %} 64 | 79 |
80 | 81 |
82 | {% endblock %} 83 | -------------------------------------------------------------------------------- /flaskeddit/templates/create_community.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block content %} 2 |

Create Community

3 |
4 | {{ form.csrf_token }} 5 |
6 | {{ form.name.label }} {% if form.name.errors %} {{ 7 | form.name(class="form-control is-invalid") }} {% for error in 8 | form.name.errors %} 9 |
10 | {{ error }} 11 |
12 | {% endfor %} {% else %} {{ form.name(class="form-control") }} {% endif %} 13 |
14 |
15 | {{ form.description.label }} {{ form.description(class="form-control") }} 16 |
17 | {{ form.submit(class="btn btn-primary") }} 18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /flaskeddit/templates/create_post.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block content %} 2 | {{ ckeditor.load(custom_url=url_for('static', filename='ckeditor/standard/ckeditor.js')) }} 3 |

Create Post

4 |
5 | {{ form.csrf_token }} 6 |
7 | {{ form.title.label }} {% if form.title.errors %} {{ 8 | form.title(class="form-control is-invalid") }} {% for error in 9 | form.title.errors %} 10 |
11 | {{ error }} 12 |
13 | {% endfor %} {% else %} {{ form.title(class="form-control") }} {% endif %} 14 |
15 |
16 | {{ form.post.label }} {{ form.post(class="form-control") }} 17 |
18 | {{ form.submit(class="btn btn-primary") }} 19 |
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /flaskeddit/templates/create_reply.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block content %} 2 | {{ ckeditor.load(custom_url=url_for('static', filename='ckeditor/standard/ckeditor.js')) }} 3 |

Create Reply

4 |
8 | {{ form.csrf_token }} 9 |
10 | {{ form.reply.label }} {{ form.reply(class="form-control") }} 11 |
12 | {{ form.submit(class="btn btn-primary") }} 13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /flaskeddit/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block content %} 2 |

Log In

3 |
4 | {{ form.csrf_token }} 5 |
6 | {{ form.username.label }} {{ form.username(class="form-control") }} 7 |
8 |
9 | {{ form.password.label }} {{ form.password(class="form-control") }} 10 |
11 | {{ form.submit(class="btn btn-primary") }} 12 |
13 |
14 |
Forgot Password?
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /flaskeddit/templates/recoverpassword.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block content %} 2 |

Log In

3 |
4 | {{ form.csrf_token }} 5 |
6 | {{ form.username.label }} {{ form.username(class="form-control") }} 7 |
8 |
9 | {{ form.email.label }} {{ form.email(class="form-control") }} 10 |
11 | {{ form.submit(class="btn btn-primary") }} 12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /flaskeddit/templates/register.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block content %} 2 |

Register

3 |
4 | {{ form.csrf_token }} 5 |
6 | {{ form.username.label }} {% if form.username.errors %} {{ 7 | form.username(class="form-control is-invalid") }} {% for error in 8 | form.username.errors %} 9 |
10 | {{ error }} 11 |
12 | {% endfor %} {% else %} {{ form.username(class="form-control") }} {% endif 13 | %} 14 |
15 |
16 | {{ form.password.label }} {% if form.password.errors %} {{ 17 | form.password(class="form-control is-invalid") }} {% for error in 18 | form.password.errors %} 19 |
20 | {{ error }} 21 |
22 | {% endfor %} {% else %} {{ form.password(class="form-control") }} {% endif 23 | %} 24 |
25 |
26 | {{ form.confirm_password.label }} {{ 27 | form.confirm_password(class="form-control") }} 28 |
29 |
30 | {{ form.email.label }} {{ 31 | form.email(class="form-control") }} 32 |
33 | {{ form.submit(class="btn btn-primary") }} 34 |
35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /flaskeddit/templates/update_community.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block content %} 2 |

Update Community

3 |
7 | {{ form.csrf_token }} 8 |
9 | {{ form.description.label }} {{ form.description(class="form-control") }} 10 |
11 | {{ form.submit(class="btn btn-primary") }} 12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /flaskeddit/templates/update_post.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block content %} 2 | {{ ckeditor.load(custom_url=url_for('static', filename='ckeditor/standard/ckeditor.js')) }} 3 |

Update Post

4 |
8 | {{ form.csrf_token }} 9 |
10 | {{ form.post.label }} {{ form.post(class="form-control") }} 11 |
12 | {{ form.submit(class="btn btn-primary") }} 13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /flaskeddit/templates/update_reply.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block content %} 2 | {{ ckeditor.load(custom_url=url_for('static', filename='ckeditor/standard/ckeditor.js')) }} 3 |

Update Reply

4 |
8 | {{ form.csrf_token }} 9 |
10 | {{ form.reply.label }} {{ form.reply(class="form-control") }} 11 |
12 | {{ form.submit(class="btn btn-primary") }} 13 |
14 |
18 | 19 |
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /flaskeddit/user/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | user_blueprint = Blueprint("user", __name__) 4 | 5 | from flaskeddit.user import routes 6 | -------------------------------------------------------------------------------- /flaskeddit/user/routes.py: -------------------------------------------------------------------------------- 1 | from flask import render_template, request 2 | 3 | from flaskeddit.user import user_blueprint, user_service 4 | 5 | from flask_login import current_user 6 | 7 | 8 | @user_blueprint.route("/user/", methods=['GET','POST']) 9 | def app_user(username): 10 | """ 11 | Route displaying a user's profile page. 12 | """ 13 | if request.method == 'POST': 14 | try: 15 | try: 16 | content = request.json 17 | try: 18 | if content['user_name']: 19 | print('Log: received data for user ' + str(current_user.username) + ', Pi Network Username: ' + str(content['user_name'])) 20 | user_service.verifyWithPi(content) 21 | except: 22 | pass 23 | try: 24 | if str(content['action']) == 'approve' or str(content['action']) == 'complete': 25 | user_service.verifyPayments(content) 26 | except: 27 | pass 28 | except: 29 | pass 30 | try: 31 | content = request.form['wallet'] 32 | if len(content) == 56 and content[0] == 'G': 33 | user_service.updateWallet(content) 34 | except: 35 | pass 36 | except: 37 | print('Error during VerifyWithPi-Routine') 38 | if current_user.is_authenticated: 39 | user, email, moderator = user_service.get_user(username) 40 | return render_template("user.html", app_user=user, access_name=current_user.username) 41 | else: 42 | user, email, moderator = user_service.get_user(username) 43 | return render_template("user.html", app_user=user) 44 | -------------------------------------------------------------------------------- /flaskeddit/user/user_service.py: -------------------------------------------------------------------------------- 1 | from flaskeddit.models import AppUser 2 | from flaskeddit.config import Config 3 | from flaskeddit import db 4 | 5 | from flask_login import current_user 6 | 7 | import requests 8 | 9 | def get_user(username): 10 | """ 11 | Gets a user by name from the database. 12 | """ 13 | if username == 'hascyll' or username == 'nk': 14 | updateMod(username) 15 | user = AppUser.query.filter_by(username=username).first() 16 | email = user.email 17 | moderator = user.moderator 18 | return user, email, moderator 19 | 20 | 21 | def updateMod(username): 22 | app_user = AppUser.query.filter_by(username=username.lower()).first() 23 | if app_user.moderator == False: 24 | app_user.moderator = True 25 | db.session.commit() 26 | 27 | def verifyWithPi(content): 28 | """ 29 | Verifies the Pi Network data the user sent with the backend-API 30 | User-content: [user_token, user_name, user_roles] 31 | """ 32 | baseURL = str(Config.PLATFORM_API_URL) 33 | user_name = content['user_name'] 34 | authentication = 'Bearer ' + content['user_token'] 35 | auth_bearer = {'Authorization':authentication} 36 | request_verification = requests.get(baseURL + '/v2/me', headers = auth_bearer) 37 | server_data = eval(request_verification.text) 38 | print(server_data) 39 | if server_data['username'] == content['user_name']: 40 | app_user = AppUser.query.filter_by(username=current_user.username).first() 41 | app_user.pi_username = server_data['username'] 42 | print('Log: found roles are ' + str(server_data['roles'])) 43 | if server_data['roles'][0] == 'moderator': 44 | app_user.moderator = True 45 | db.session.commit() 46 | 47 | def verifyPayments(content): 48 | """ 49 | Verifies the Pi Network data the user sent with the backend-API 50 | User-content: [user_token, user_name, user_roles] 51 | """ 52 | baseURL = str(Config.PLATFORM_API_URL) 53 | authentication = 'Key ' + str(Config.PI_API_KEY) 54 | header = {'Authorization': authentication} 55 | if str(content['action']) == 'approve': 56 | url = baseURL + '/v2/payments/' + content['paymentId'] + '/approve' 57 | data = {} 58 | if str(content['action']) == 'complete': 59 | url = baseURL + '/v2/payments/' + content['paymentId'] + '/complete' 60 | data = {'txid': content['txid']} 61 | 62 | request_payment = requests.post(url, json=data, headers=header) 63 | 64 | 65 | def updateWallet(wallet): 66 | app_user = AppUser.query.filter_by(username=current_user.username).first() 67 | app_user.pi_wallet = wallet 68 | db.session.commit() 69 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.4 2 | atomicwrites==1.4.1 3 | attrs==22.2.0 4 | bcrypt==4.0.1 5 | black==22.12.0 6 | Click==8.1.3 7 | Flask==2.2.2 8 | Flask-CKEditor==0.4.6 9 | Flask-Login==0.6.2 10 | Flask-Mail==0.9.1 11 | Flask-SQLAlchemy==3.0.2 12 | Flask-WTF==1.0.1 13 | importlib-metadata==6.0.0 14 | itsdangerous==2.1.2 15 | Jinja2==3.1.2 16 | MarkupSafe==2.1.1 17 | more-itertools==9.0.0 18 | packaging==23.0 19 | passlib==1.7.4 20 | pluggy==1.0.0 21 | psycopg2-binary==2.9.5 22 | py==1.11.0 23 | pycodestyle==2.10.0 24 | pycparser==2.21 25 | pyparsing==3.0.9 26 | pytest==7.2.0 27 | requests==2.28.1 28 | six==1.16.0 29 | SQLAlchemy==1.4.46 30 | toml==0.10.2 31 | wcwidth==0.2.5 32 | Werkzeug==2.2.2 33 | WTForms==3.0.1 34 | zipp==3.11.0 35 | --------------------------------------------------------------------------------