Compare commits
No commits in common. "c834424836eeaa9865014ec27e5a2c7af98dbc83" and "71f7bd1a3b501014eab6a08b24037a02e9d9ecdc" have entirely different histories.
c834424836
...
71f7bd1a3b
37 changed files with 136 additions and 455 deletions
|
|
@ -4,7 +4,6 @@
|
|||
+ Codebase refactor (with breaking changes!)
|
||||
+ Move ALL config to .env (config.py is NO MORE supported)
|
||||
+ Config SITE_NAME replaced with APP_NAME
|
||||
+ Add CSRF token and flask_WTF
|
||||
|
||||
## 0.9.0
|
||||
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -16,19 +16,10 @@ This is the server. For the client, see [coriplusapp](https://github.com/sakurag
|
|||
* Add info to your profile
|
||||
* In-site notifications
|
||||
* Public API
|
||||
* SQLite (or PostgreSQL)-based app
|
||||
* SQLite-based app
|
||||
|
||||
## Requirements
|
||||
|
||||
* **Python 3.10+** with **pip**.
|
||||
* **Flask** web framework.
|
||||
* **Python 3** only. We don't want to support Python 2.
|
||||
* **Flask** web framework (also required extension **Flask-Login**).
|
||||
* **Peewee** ORM.
|
||||
* A \*nix-based OS.
|
||||
|
||||
## Installation
|
||||
|
||||
* Install dependencies: `pip install .`
|
||||
* Set the `DATABASE_URL` (must be SQLite or PostgreSQL)
|
||||
* Run the migrations: `sh ./genmig.sh @`
|
||||
* i forgor
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
#!/usr/bin/bash
|
||||
# GENERATE MIGRATIONS
|
||||
|
||||
source venv/bin/activate && \
|
||||
source .env && \
|
||||
case "$1" in
|
||||
("+") pw_migrate create --auto --auto-source=coriplus.models --directory=src/migrations --database="$DATABASE_URL" "${@:2}" ;;
|
||||
("@") pw_migrate migrate --directory=src/migrations --database="$DATABASE_URL" "${@:2}" ;;
|
||||
esac
|
||||
|
|
@ -8,10 +8,7 @@ dependencies = [
|
|||
"Python-Dotenv>=1.0.0",
|
||||
"Flask",
|
||||
"Flask-Login",
|
||||
"Peewee",
|
||||
"Flask-WTF",
|
||||
"peewee-migrate",
|
||||
"PsycoPG2"
|
||||
"Peewee"
|
||||
]
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ from flask import (
|
|||
send_from_directory, __version__ as flask_version)
|
||||
import os, sys
|
||||
from flask_login import LoginManager
|
||||
from flask_wtf import CSRFProtect
|
||||
import dotenv
|
||||
import logging
|
||||
|
||||
|
|
@ -45,8 +44,6 @@ app.secret_key = os.environ['SECRET_KEY']
|
|||
|
||||
login_manager = LoginManager(app)
|
||||
|
||||
CSRFProtect(app)
|
||||
|
||||
from .models import *
|
||||
|
||||
from .utils import *
|
||||
|
|
@ -67,10 +64,7 @@ def before_request():
|
|||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
try:
|
||||
g.db.close()
|
||||
except Exception:
|
||||
logger.error('database closed twice')
|
||||
return response
|
||||
|
||||
@app.context_processor
|
||||
|
|
@ -85,10 +79,6 @@ def _inject_variables():
|
|||
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
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@ Management of reports and the entire site.
|
|||
New in 0.8.
|
||||
'''
|
||||
|
||||
from flask import Blueprint, abort, redirect, render_template, request, url_for
|
||||
from flask_login import current_user
|
||||
from flask import Blueprint, redirect, render_template, request, url_for
|
||||
from .models import User, Message, Report, report_reasons, REPORT_STATUS_ACCEPTED, \
|
||||
REPORT_MEDIA_USER, REPORT_MEDIA_MESSAGE
|
||||
from .utils import pwdhash, object_list
|
||||
|
|
@ -13,18 +12,21 @@ from functools import wraps
|
|||
|
||||
bp = Blueprint('admin', __name__, url_prefix='/admin')
|
||||
|
||||
def _check_auth(username, password) -> bool:
|
||||
def check_auth(username, password):
|
||||
try:
|
||||
return User.select().where((User.username == username) & (User.password == pwdhash(password)) & (User.is_admin)
|
||||
).exists()
|
||||
return User.get((User.username == username) & (User.password == pwdhash(password))
|
||||
).is_admin
|
||||
except User.DoesNotExist:
|
||||
return False
|
||||
|
||||
def admin_required(f):
|
||||
@wraps(f)
|
||||
def wrapped_view(**kwargs):
|
||||
if not _check_auth(current_user.username, current_user.password):
|
||||
abort(403)
|
||||
auth = request.authorization
|
||||
if not (auth and check_auth(auth.username, auth.password)):
|
||||
return ('Unauthorized', 401, {
|
||||
'WWW-Authenticate': 'Basic realm="Login Required"'
|
||||
})
|
||||
return f(**kwargs)
|
||||
return wrapped_view
|
||||
|
||||
|
|
|
|||
|
|
@ -13,14 +13,11 @@ The tables are:
|
|||
|
||||
from flask import request
|
||||
from peewee import *
|
||||
from playhouse.db_url import connect
|
||||
import os
|
||||
|
||||
from . import BASEDIR
|
||||
# here should go `from .utils import get_current_user`, but it will cause
|
||||
# import errors. It's instead imported at function level.
|
||||
|
||||
database = connect(os.environ['DATABASE_URL'])
|
||||
database = SqliteDatabase('coriplus.sqlite')
|
||||
|
||||
class BaseModel(Model):
|
||||
class Meta:
|
||||
|
|
@ -192,7 +189,7 @@ class Relationship(BaseModel):
|
|||
)
|
||||
|
||||
|
||||
UPLOAD_DIRECTORY = os.path.join(BASEDIR, 'uploads')
|
||||
UPLOAD_DIRECTORY = os.path.join(os.path.split(os.path.dirname(__file__))[0], 'uploads')
|
||||
|
||||
class Upload(BaseModel):
|
||||
# the extension of the media
|
||||
|
|
|
|||
|
|
@ -1,41 +1,19 @@
|
|||
:root {
|
||||
--accent: #f0372e;
|
||||
--link: #3399ff;
|
||||
}
|
||||
body, button, input, select, textarea {
|
||||
font-family: Inter, Roboto, sans-serif;
|
||||
line-height: 1.6;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
#site-name, h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Funnel Sans', Roboto, sans-serif;
|
||||
line-height: 1.2;
|
||||
}
|
||||
body,button,input,select,textarea{font-family:Roboto,'Segoe UI',Arial,Helvetica,sans-serif}
|
||||
body{margin:0}
|
||||
main{margin:auto; max-width: 960px;}
|
||||
a{text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
.mobile-collapse {font-size: smaller;}
|
||||
@media (max-width:640px){
|
||||
.mobile-collapse{display:none}
|
||||
}
|
||||
.header{
|
||||
padding:12px;color:white; background-color:var(--accent); box-shadow:0 0 3px 3px #ccc; display: flex; position: relative;
|
||||
}
|
||||
.centered{
|
||||
text-align: center;
|
||||
}
|
||||
.header{padding:12px;color:white;background-color:#ff3018;box-shadow:0 0 3px 3px #ccc}
|
||||
.content{padding:12px}
|
||||
.header a{color:white; }
|
||||
.header a{color:white}
|
||||
.header a svg{fill:white}
|
||||
.content a{color:var(--link)}
|
||||
.content a svg{fill:var(--link)}
|
||||
.content a.plus{color:var(--accent)}
|
||||
.leftnav, .rightnav{list-style: none; display: flex; padding: 0; margin: 0;position: absolute;}
|
||||
.leftnav {left: 0}
|
||||
.rightnav {right: 0}
|
||||
.card {background-color: #fafafa; border-radius: 12px; padding: 12px; margin-bottom: 12px; border-bottom: 2px solid #999999;}
|
||||
#site-name {text-align: center;flex: 1}
|
||||
.content a{color:#3399ff}
|
||||
.content a svg{fill:#3399ff}
|
||||
.content a.plus{color:#ff3018}
|
||||
.metanav{float:right}
|
||||
.metanav-divider{width:1px;background:white;display:inline-block}
|
||||
.header h1{margin:0;display:inline-block}
|
||||
.flash{background-color:#ff9;border:yellow 1px solid}
|
||||
.infobox{padding:12px;border:#ccc 1px solid}
|
||||
|
|
@ -45,15 +23,15 @@ a:hover{text-decoration:underline}
|
|||
.weak{opacity:.5}
|
||||
.field_desc{display:block}
|
||||
ul.timeline{padding:0;margin:auto;max-width:960px}
|
||||
ul.timeline > li{list-style:none;}
|
||||
ul.timeline > li{list-style:none;border-bottom:#808080 1px solid}
|
||||
.message-visual img{max-width:100%;margin:auto}
|
||||
.message-options-showhide::before{content:'\2026'}
|
||||
.message-options{display:none}
|
||||
.create_text{width:100%;height:8em}
|
||||
.biography_text{height:4em}
|
||||
.before-toggle:not(:checked) + input{display:none}
|
||||
.follow_button,input[type="submit"]{background-color:var(--accent);color:white;border-radius:3px;border:1px solid var(--accent)}
|
||||
.follow_button.following{background-color:transparent;color:var(--accent);border-color:var(--accent)}
|
||||
.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}
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<div class="centered">
|
||||
<h2>Forbidden</h2>
|
||||
|
||||
<p><a href="/">Back to homepage.</a></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<div class="centered">
|
||||
<h2>Not Found</h2>
|
||||
|
||||
<p><a href="/">Back to homepage.</a></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -1,15 +1,11 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<div class="card">
|
||||
<h1>About {{ site_name }}</h1>
|
||||
|
||||
<ul>
|
||||
<li>{{ site_name }} {{ version }}</li>
|
||||
<li> Python {{ python_version }}</li>
|
||||
<li>Flask {{ flask_version }}</li>
|
||||
</ul>
|
||||
<p>Copyright © 2019, 2025 Sakuragasaki46.</p>
|
||||
<p>{{ site_name }} {{ version }} – Python {{ python_version }} –
|
||||
Flask {{ flask_version }}</p>
|
||||
<p>Copyright © 2019 Sakuragasaki46.</p>
|
||||
|
||||
<h2>License</h2>
|
||||
<p>Permission is hereby granted, free of charge, to any person obtaining
|
||||
|
|
@ -35,5 +31,4 @@
|
|||
href="https://github.com/sakuragasaki46/coriplus/">
|
||||
https://github.com/sakuragasaki46/coriplus/</a>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -7,19 +7,16 @@
|
|||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1><a href="{{ url_for('admin.homepage') }}">{{ site_name }}: Admin</a></h1>
|
||||
<div class="rightnav">
|
||||
<h1><a href="{{ url_for('admin.homepage') }}">{{ site_name }} Admin</a></h1>
|
||||
<div class="metanav">
|
||||
<!-- what does it go here? -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="flash">{{ message }}</div>
|
||||
{% endfor %}
|
||||
<main>
|
||||
{% block body %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p class="copyright">© 2019 Sakuragasaki46.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
{% block body %}
|
||||
<h2>Report detail #{{ report.id }}</h2>
|
||||
<div class="card">
|
||||
<p>Type: {{ [None, 'user', 'message'][report.media_type] }}</p>
|
||||
<p>Reason: <strong>{{ report_reasons[report.reason] }}</strong></p>
|
||||
<p>Status: <strong>{{ ['Unreviewed', 'Accepted', 'Declined'][report.status] }}</strong></p>
|
||||
|
|
@ -22,9 +21,7 @@
|
|||
{% include "includes/reported_message.html" %}
|
||||
{% endif %}
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="submit" name="take_down" value="Take down">
|
||||
<input type="submit" name="discard" value="Discard">
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
{% block body %}
|
||||
<ul>
|
||||
{% for report in report_list %}
|
||||
<li class="card {% if report.status > 0 %}done{% endif %}">
|
||||
<li {% if report.status > 0 %}class="done"{% endif %}>
|
||||
<p><strong>#{{ report.id }}</strong>
|
||||
(<a href="{{ url_for('admin.reports_detail', id=report.id) }}">detail</a>)</p>
|
||||
<p>Type: {{ [None, 'user', 'message'][report.media_type] }}</p>
|
||||
|
|
|
|||
|
|
@ -6,41 +6,35 @@
|
|||
<link rel="stylesheet" type="text/css" href="/static/style.css">
|
||||
<meta name="og:title" content="Cori+">
|
||||
<meta name="og:description" content="A simple social network. Post text statuses, optionally with image.">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Funnel+Sans:ital,wght@0,300..800;1,300..800&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<ul class="leftnav">
|
||||
|
||||
</ul>
|
||||
<h1 id="site-name"><a href="{{ url_for('website.homepage') }}">{{ site_name }}</a></h1>
|
||||
<ul class="rightnav">
|
||||
<h1><a href="{{ url_for('website.homepage') }}">{{ site_name }}</a></h1>
|
||||
<div class="metanav">
|
||||
{% if current_user.is_anonymous %}
|
||||
<li><a href="{{ url_for('website.login', next=request.full_path) }}">{{ inline_svg('exit_to_app') }} <span class="mobile-collapse">log in</span></a></li>
|
||||
<li><a href="{{ url_for('website.register', next=request.full_path) }}">{{ inline_svg('person_add') }} <span class="mobile-collapse">register</span></a></li>
|
||||
<a href="{{ url_for('website.login', next=request.full_path) }}">{{ inline_svg('exit_to_app') }} <span class="mobile-collapse">log in</span></a>
|
||||
<a href="{{ url_for('website.register', next=request.full_path) }}">{{ inline_svg('person_add') }} <span class="mobile-collapse">register</span></a>
|
||||
{% else %}
|
||||
<li><a href="{{ url_for('website.user_detail', username=current_user.username) }}">{{ inline_svg('person') }} {{ current_user.username }}</a></li>
|
||||
<a href="{{ url_for('website.user_detail', username=current_user.username) }}">{{ inline_svg('person') }} {{ current_user.username }}</a>
|
||||
{% set notification_count = current_user.unseen_notification_count() %}
|
||||
<li><a href="{{ url_for('website.notifications') }}">{{ inline_svg('notifications') }} (<strong>{{ notification_count }}</strong>)</a></li>
|
||||
<li><a href="{{ url_for('website.public_timeline') }}">{{ inline_svg('explore') }} <span class="mobile-collapse">explore</span></a></li>
|
||||
<li><a href="{{ url_for('website.create') }}">{{ inline_svg('edit') }} <span class="mobile-collapse">create</span></a></li>
|
||||
<li><a href="{{ url_for('website.logout') }}">{{ inline_svg('exit_to_app') }} <span class="mobile-collapse">log out</span></a></li>
|
||||
{% if notification_count > 0 %}
|
||||
<a href="{{ url_for('website.notifications') }}">(<strong>{{ notification_count }}</strong>)</a>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<span class="metanav-divider"></span>
|
||||
<a href="{{ url_for('website.public_timeline') }}">{{ inline_svg('explore') }} <span class="mobile-collapse">explore</span></a>
|
||||
<a href="{{ url_for('website.create') }}">{{ inline_svg('edit') }} <span class="mobile-collapse">create</span></a>
|
||||
<a href="{{ url_for('website.logout') }}">{{ inline_svg('exit_to_app') }} <span class="mobile-collapse">log out</span></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="flash">{{ message }}</div>
|
||||
{% endfor %}
|
||||
<main>
|
||||
{% block body %}{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p class="copyright">© 2019, 2025 Sakuragasaki46.
|
||||
<p class="copyright">© 2019 Sakuragasaki46.
|
||||
<a href="/about/">About</a> - <a href="/terms/">Terms</a> -
|
||||
<a href="/privacy/">Privacy</a> -
|
||||
<a href="https://github.com/sakuragasaki46/coriplus">GitHub</a></p>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<div class="card">
|
||||
<h2>Change Password</h2>
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<dl>
|
||||
<dt>Old password:</dt>
|
||||
<dd><input type="password" name="old_password"></dd>
|
||||
|
|
@ -16,5 +14,4 @@
|
|||
<dd><input type="submit" value="Save"></dd>
|
||||
</dl>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<div class="card">
|
||||
<h2>Confirm Deletion</h2>
|
||||
|
||||
<p>Are you sure you want to <u>permanently delete</u> this post?
|
||||
<p>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.</p>
|
||||
|
||||
<p><small>Tip: If you only want to hide it from the public,
|
||||
you can <a href="/edit/{{ message.id }}">set its privacy</a> to "Only me".</small></p>
|
||||
<p>If you only want to hide it from the public,
|
||||
you can <a href="/edit/{{ message.id }}">set its privacy</a> to "Only me".</p>
|
||||
|
||||
<p>Here's the content of the message for reference:</p>
|
||||
|
||||
<ul>
|
||||
<li>{% include "includes/message.html" %}</li>
|
||||
</ul>
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<input type="submit" value="Delete">
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
{% block body %}
|
||||
<div class="card">
|
||||
<h2>Create</h2>
|
||||
<form action="{{ url_for('website.create') }}" method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<dl>
|
||||
<dt>Message:</dt>
|
||||
<dd><textarea name="text" placeholder="What's happening?" class="create_text">{{ request.args['preload'] }}</textarea></dd>
|
||||
|
|
@ -17,5 +15,4 @@
|
|||
<dd><input type="submit" value="Create" /></dd>
|
||||
</dl>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
{% block body %}
|
||||
<div class="card">
|
||||
<h2>Edit</h2>
|
||||
<form action="{{ url_for('website.edit', id=message.id) }}" method="POST" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<dl>
|
||||
<dt>Message:</dt>
|
||||
<dd><textarea name="text" required="" class="create_text">{{ message.text }}</textarea></dd>
|
||||
|
|
@ -16,5 +14,4 @@
|
|||
<dd><input type="submit" value="Save" /></dd>
|
||||
</dl>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<div class="card">
|
||||
<h2>Edit Profile</h2>
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<dl>
|
||||
<dt>Username:</dt>
|
||||
<dd><input type="text" class="username-input" name="username" required value="{{ current_user.username }}" autocomplete="off"></dd>
|
||||
|
|
@ -34,5 +32,4 @@
|
|||
<dd><input type="submit" value="Save"></dd>
|
||||
</dl>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% from "macros/message.html" import feed_message with context %}
|
||||
{% block body %}
|
||||
<h2>Explore</h2>
|
||||
<ul class="timeline">
|
||||
{% for message in message_list %}
|
||||
{{ feed_message(message) }}
|
||||
<li id="{{ message.id }}">{% include "includes/message.html" %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% include "includes/pagination.html" %}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% from "macros/message.html" import feed_message with context %}
|
||||
{% block body %}
|
||||
<h2>Your Timeline</h2>
|
||||
<ul class="timeline">
|
||||
{% for message in message_list %}
|
||||
{{ feed_message(message) }}
|
||||
<li id="{{ message.id }}">{% include "includes/message.html" %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% include "includes/pagination.html" %}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
{% block body %}
|
||||
<div class="card">
|
||||
<h2>Hello</h2>
|
||||
|
||||
<p>{{ site_name }} is made by people like you. <br/>
|
||||
<a href="{{ url_for('website.login') }}">Log in</a> or <a href="{{ url_for('website.register') }}">register</a> to see more.</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
{% block body %}
|
||||
<div class="card">
|
||||
<h2>Join {{ site_name }}</h2>
|
||||
<form action="{{ url_for('website.register') }}" method="POST">
|
||||
<dl>
|
||||
|
|
@ -33,5 +32,4 @@
|
|||
<dd><input type="submit" value="Join">
|
||||
</dl>
|
||||
</form>
|
||||
<div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
{% extends "base.html" %}
|
||||
{% block body %}
|
||||
<h2>Login</h2>
|
||||
{% if error %}<p class="error"><strong>Error:</strong> {{ error }}</p>{% endif %}
|
||||
<div class="card">
|
||||
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<dl>
|
||||
<dt>Username or email:
|
||||
<dd><input type="text" name="username">
|
||||
|
|
@ -20,5 +18,4 @@
|
|||
<dd><input type="submit" value="Login">
|
||||
</dl>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
{% macro feed_message(message) %}
|
||||
<li class="card" id="{{ message.id }}">
|
||||
<p class="message-content">{{ message.text|enrich }}</p>
|
||||
{% if message.uploads %}
|
||||
<div class="message-visual">
|
||||
<img src="/uploads/{{ message.uploads[0].filename() }}">
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="message-footer">
|
||||
<a href="javascript:void(0);" class="message-upvote" onclick="toggleUpvote({{ message.id }});">+</a>
|
||||
<span class="message-score">{{ message.score }}</span>
|
||||
-
|
||||
<a href="{{ url_for('website.user_detail', username=message.user.username) }}">{{ message.user.username }}</a>
|
||||
-
|
||||
{% set message_privacy = message.privacy %}
|
||||
{% if message_privacy == 0 %} Public
|
||||
{% elif message_privacy == 1 %} Unlisted
|
||||
{% elif message_privacy == 2 %} Friends
|
||||
{% elif message_privacy == 3 %} Only me
|
||||
{% endif %}
|
||||
-
|
||||
<time datetime="{{ message.pub_date.isoformat() }}" title="{{ message.pub_date.ctime() }}">{{ message.pub_date | human_date }}</time>
|
||||
-
|
||||
<a href="javascript:void(0);" onclick="showHideMessageOptions({{ message.id }});" class="message-options-showhide"></a>
|
||||
</p>
|
||||
<ul class="message-options">
|
||||
{% if message.user == current_user %}
|
||||
<li><a href="/edit/{{ message.id }}">Edit or change privacy</a></li>
|
||||
<li><a href="/delete/{{ message.id }}">Delete permanently</a></li>
|
||||
{% else %}
|
||||
<li><a href="/report/message/{{ message.id }}" target="_blank">Report</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endmacro %}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
<h2>Notifications</h2>
|
||||
<ul>
|
||||
{% for notification in notification_list %}
|
||||
<li class="card">{% include "includes/notification.html" %}</li>
|
||||
<li>{% include "includes/notification.html" %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% include "includes/pagination.html" %}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<div class="card">
|
||||
<h1>Privacy Policy</h1>
|
||||
|
||||
<p>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.</p>
|
||||
|
|
@ -16,7 +15,7 @@
|
|||
|
||||
<p>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.</p>
|
||||
|
||||
<p>You can choose to disable cookies through your individual browser options. This, however, can and will hurt Your usage of {{ site_name }}</p>
|
||||
|
||||
|
||||
<h2>Privacy Policies</h2>
|
||||
|
||||
|
|
@ -30,25 +29,19 @@
|
|||
|
||||
<p>{{ 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.</p>
|
||||
|
||||
<h2>Legal Basis</h2>
|
||||
|
||||
<p>Legal Basis for treatment is Legitimate Interest, except:</p>
|
||||
<ul>
|
||||
<li>Transactional information, such as username, email and essential cookies, are treated according to Providing a Service.</li>
|
||||
</ul>
|
||||
<p>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?</p>
|
||||
|
||||
<h2>Children's Information</h2>
|
||||
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
|
||||
<p>{{ 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.</p>
|
||||
|
||||
<h2>Online Privacy Policy Only</h2>
|
||||
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
|
||||
<h2>Consent</h2>
|
||||
|
||||
<p>By using our website, you hereby consent <u>irrevocably</u> to our Privacy Policy and agree to its Terms and Conditions.</p>
|
||||
</div>
|
||||
<p>By using our website, you hereby consent to our Privacy Policy and agree to its Terms and Conditions.</p>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
{% block body %}
|
||||
{% for reason in report_reasons %}
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<div class="item" onclick="submitReport({{ reason[0] }})">
|
||||
<h2>{{ reason[1] }}</h2>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
{% block body %}
|
||||
{% for reason in report_reasons %}
|
||||
<form method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
<div class="item" onclick="submitReport({{ reason[0] }})">
|
||||
<h2>{{ reason[1] }}</h2>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
<div class="card">
|
||||
<h1>Terms of Service</h1>
|
||||
|
||||
<p>[decline to state]</p>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
{% extends "base.html" %}
|
||||
{% from "macros/message.html" import feed_message with context %}
|
||||
{% block body %}
|
||||
{% include "includes/infobox_profile.html" %}
|
||||
<h2>Messages from {{ user.username }}</h2>
|
||||
{% if not current_user.is_anonymous %}
|
||||
{% if user.username != current_user.username %}
|
||||
{% if current_user|is_following(user) %}
|
||||
<form action="{{ url_for('website.user_unfollow', username=user.username) }}" method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<form action="{{ url_for('website.user_unfollow', username=user.username) }}" method="post">
|
||||
<input type="submit" class="follow_button following" value="- Un-follow" />
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="{{ url_for('website.user_follow', username=user.username) }}" method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<form action="{{ url_for('website.user_follow', username=user.username) }}" method="post">
|
||||
<input type="submit" class="follow_button" value="+ Follow" />
|
||||
</form>
|
||||
{% endif %}
|
||||
|
|
@ -23,7 +20,7 @@
|
|||
{% endif %}
|
||||
<ul class="timeline">
|
||||
{% for message in message_list %}
|
||||
{{ feed_message(message) }}
|
||||
<li id="{{ message.id }}">{% include "includes/message.html" %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% include "includes/pagination.html" %}
|
||||
|
|
|
|||
|
|
@ -218,6 +218,12 @@ def create_mentions(cur_user, text, privacy):
|
|||
pass
|
||||
|
||||
# New in 0.9
|
||||
# changed in 0.10
|
||||
def inline_svg(name):
|
||||
return Markup('<span class="material-icons">{}</span>').format(name)
|
||||
def inline_svg(name, width=None):
|
||||
try:
|
||||
with open('icons/' + name + '-24px.svg') as f:
|
||||
data = f.read()
|
||||
if isinstance(width, int):
|
||||
data = re.sub(r'( (?:height|width)=")\d+(")', lambda x:x.group(1) + str(width) + x.group(2), data)
|
||||
return Markup(data)
|
||||
except OSError:
|
||||
return ''
|
||||
|
|
|
|||
|
|
@ -1,171 +0,0 @@
|
|||
"""Peewee migrations -- 001_0_9_to_0_10.py.
|
||||
|
||||
Some examples (model - class or model name)::
|
||||
|
||||
> Model = migrator.orm['table_name'] # Return model in current state by name
|
||||
> Model = migrator.ModelClass # Return model in current state by name
|
||||
|
||||
> migrator.sql(sql) # Run custom SQL
|
||||
> migrator.run(func, *args, **kwargs) # Run python function with the given args
|
||||
> migrator.create_model(Model) # Create a model (could be used as decorator)
|
||||
> migrator.remove_model(model, cascade=True) # Remove a model
|
||||
> migrator.add_fields(model, **fields) # Add fields to a model
|
||||
> migrator.change_fields(model, **fields) # Change fields
|
||||
> migrator.remove_fields(model, *field_names, cascade=True)
|
||||
> migrator.rename_field(model, old_field_name, new_field_name)
|
||||
> migrator.rename_table(model, new_table_name)
|
||||
> migrator.add_index(model, *col_names, unique=False)
|
||||
> migrator.add_not_null(model, *field_names)
|
||||
> migrator.add_default(model, field_name, default)
|
||||
> migrator.add_constraint(model, name, sql)
|
||||
> migrator.drop_index(model, *col_names)
|
||||
> migrator.drop_not_null(model, *field_names)
|
||||
> migrator.drop_constraints(model, *constraints)
|
||||
|
||||
"""
|
||||
|
||||
from contextlib import suppress
|
||||
|
||||
import peewee as pw
|
||||
from peewee_migrate import Migrator
|
||||
|
||||
|
||||
with suppress(ImportError):
|
||||
import playhouse.postgres_ext as pw_pext
|
||||
|
||||
|
||||
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your migrations here."""
|
||||
|
||||
@migrator.create_model
|
||||
class BaseModel(pw.Model):
|
||||
id = pw.AutoField()
|
||||
|
||||
class Meta:
|
||||
table_name = "basemodel"
|
||||
|
||||
@migrator.create_model
|
||||
class User(pw.Model):
|
||||
id = pw.AutoField()
|
||||
username = pw.CharField(max_length=255, unique=True)
|
||||
full_name = pw.TextField()
|
||||
password = pw.CharField(max_length=255)
|
||||
email = pw.CharField(max_length=255)
|
||||
birthday = pw.DateField()
|
||||
join_date = pw.DateTimeField()
|
||||
is_disabled = pw.IntegerField()
|
||||
|
||||
class Meta:
|
||||
table_name = "user"
|
||||
|
||||
@migrator.create_model
|
||||
class Message(pw.Model):
|
||||
id = pw.AutoField()
|
||||
user = pw.ForeignKeyField(column_name='user_id', field='id', model=migrator.orm['user'])
|
||||
text = pw.TextField()
|
||||
pub_date = pw.DateTimeField()
|
||||
privacy = pw.IntegerField()
|
||||
|
||||
class Meta:
|
||||
table_name = "message"
|
||||
|
||||
@migrator.create_model
|
||||
class MessageUpvote(pw.Model):
|
||||
id = pw.AutoField()
|
||||
message = pw.ForeignKeyField(column_name='message_id', field='id', model=migrator.orm['message'])
|
||||
user = pw.ForeignKeyField(column_name='user_id', field='id', model=migrator.orm['user'])
|
||||
created_date = pw.DateTimeField()
|
||||
|
||||
class Meta:
|
||||
table_name = "messageupvote"
|
||||
indexes = [(('message', 'user'), True)]
|
||||
|
||||
@migrator.create_model
|
||||
class Notification(pw.Model):
|
||||
id = pw.AutoField()
|
||||
type = pw.TextField()
|
||||
target = pw.ForeignKeyField(column_name='target_id', field='id', model=migrator.orm['user'])
|
||||
detail = pw.TextField()
|
||||
pub_date = pw.DateTimeField()
|
||||
seen = pw.IntegerField()
|
||||
|
||||
class Meta:
|
||||
table_name = "notification"
|
||||
|
||||
@migrator.create_model
|
||||
class Relationship(pw.Model):
|
||||
id = pw.AutoField()
|
||||
from_user = pw.ForeignKeyField(column_name='from_user_id', field='id', model=migrator.orm['user'])
|
||||
to_user = pw.ForeignKeyField(column_name='to_user_id', field='id', model=migrator.orm['user'])
|
||||
created_date = pw.DateTimeField()
|
||||
|
||||
class Meta:
|
||||
table_name = "relationship"
|
||||
indexes = [(('from_user', 'to_user'), True)]
|
||||
|
||||
@migrator.create_model
|
||||
class Report(pw.Model):
|
||||
id = pw.AutoField()
|
||||
media_type = pw.IntegerField()
|
||||
media_id = pw.IntegerField()
|
||||
sender = pw.ForeignKeyField(column_name='sender_id', field='id', model=migrator.orm['user'], null=True)
|
||||
reason = pw.IntegerField()
|
||||
status = pw.IntegerField()
|
||||
created_date = pw.DateTimeField()
|
||||
|
||||
class Meta:
|
||||
table_name = "report"
|
||||
|
||||
@migrator.create_model
|
||||
class Upload(pw.Model):
|
||||
id = pw.AutoField()
|
||||
type = pw.TextField()
|
||||
message = pw.ForeignKeyField(column_name='message_id', field='id', model=migrator.orm['message'])
|
||||
|
||||
class Meta:
|
||||
table_name = "upload"
|
||||
|
||||
@migrator.create_model
|
||||
class UserAdminship(pw.Model):
|
||||
user = pw.ForeignKeyField(column_name='user_id', field='id', model=migrator.orm['user'], primary_key=True)
|
||||
|
||||
class Meta:
|
||||
table_name = "useradminship"
|
||||
|
||||
@migrator.create_model
|
||||
class UserProfile(pw.Model):
|
||||
user = pw.ForeignKeyField(column_name='user_id', field='id', model=migrator.orm['user'], primary_key=True)
|
||||
biography = pw.TextField()
|
||||
location = pw.IntegerField(null=True)
|
||||
year = pw.IntegerField(null=True)
|
||||
website = pw.TextField(null=True)
|
||||
instagram = pw.TextField(null=True)
|
||||
facebook = pw.TextField(null=True)
|
||||
telegram = pw.TextField(null=True)
|
||||
|
||||
class Meta:
|
||||
table_name = "userprofile"
|
||||
|
||||
|
||||
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your rollback migrations here."""
|
||||
|
||||
migrator.remove_model('userprofile')
|
||||
|
||||
migrator.remove_model('useradminship')
|
||||
|
||||
migrator.remove_model('upload')
|
||||
|
||||
migrator.remove_model('report')
|
||||
|
||||
migrator.remove_model('relationship')
|
||||
|
||||
migrator.remove_model('notification')
|
||||
|
||||
migrator.remove_model('messageupvote')
|
||||
|
||||
migrator.remove_model('message')
|
||||
|
||||
migrator.remove_model('user')
|
||||
|
||||
migrator.remove_model('basemodel')
|
||||
Loading…
Add table
Add a link
Reference in a new issue