mirror of https://git.sr.ht/~sircmpwn/fosspay
commit
1059a4d62d
@ -0,0 +1,14 @@ |
|||||||
|
*.pyc |
||||||
|
bin/ |
||||||
|
config.ini |
||||||
|
alembic.ini |
||||||
|
include/ |
||||||
|
local/ |
||||||
|
lib/ |
||||||
|
static/ |
||||||
|
*.swp |
||||||
|
*.rdb |
||||||
|
storage/ |
||||||
|
pip-selfcheck.json |
||||||
|
.sass-cache/ |
||||||
|
overrides/ |
@ -0,0 +1,19 @@ |
|||||||
|
Copyright (c) 2015 Drew DeVault |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of |
||||||
|
this software and associated documentation files (the "Software"), to deal in |
||||||
|
the Software without restriction, including without limitation the rights to |
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do |
||||||
|
so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
@ -0,0 +1,51 @@ |
|||||||
|
# Builds static assets
|
||||||
|
# Depends on:
|
||||||
|
# - scss
|
||||||
|
# - coffeescript
|
||||||
|
# - inotify-tools
|
||||||
|
# Run `make` to compile static assets
|
||||||
|
# Run `make watch` to recompile whenever a change is made
|
||||||
|
|
||||||
|
.PHONY: all static watch clean |
||||||
|
|
||||||
|
STYLES:=$(patsubst styles/%.scss,static/%.css,$(wildcard styles/*.scss))
|
||||||
|
STYLES+=$(patsubst styles/%.css,static/%.css,$(wildcard styles/*.css))
|
||||||
|
SCRIPTS:=$(patsubst scripts/%.coffee,static/%.js,$(wildcard scripts/*.coffee))
|
||||||
|
SCRIPTS+=$(patsubst scripts/%.js,static/%.js,$(wildcard scripts/*.js))
|
||||||
|
_STATIC:=$(patsubst _static/%,static/%,$(wildcard _static/*))
|
||||||
|
|
||||||
|
static/%: _static/% |
||||||
|
@mkdir -p static/
|
||||||
|
cp $< $@
|
||||||
|
|
||||||
|
static/%.css: styles/%.css |
||||||
|
@mkdir -p static/
|
||||||
|
cp $< $@
|
||||||
|
|
||||||
|
static/%.css: styles/%.scss |
||||||
|
@mkdir -p static/
|
||||||
|
scss -I styles/ $< $@
|
||||||
|
|
||||||
|
static/%.js: scripts/%.js |
||||||
|
@mkdir -p static/
|
||||||
|
cp $< $@
|
||||||
|
|
||||||
|
static/%.js: scripts/%.coffee |
||||||
|
@mkdir -p static/
|
||||||
|
coffee -m -o static/ -c $<
|
||||||
|
|
||||||
|
static: $(STYLES) $(SCRIPTS) $(_STATIC) |
||||||
|
|
||||||
|
all: static |
||||||
|
echo $(STYLES)
|
||||||
|
echo $(SCRIPTS)
|
||||||
|
|
||||||
|
clean: |
||||||
|
rm -rf static
|
||||||
|
|
||||||
|
watch: |
||||||
|
while inotifywait \
|
||||||
|
-e close_write scripts/ \
|
||||||
|
-e close_write styles/ \
|
||||||
|
-e close_write _static/; \
|
||||||
|
do make; done
|
@ -0,0 +1,107 @@ |
|||||||
|
# fosspay |
||||||
|
|
||||||
|
Helps you get paid for your open source work. |
||||||
|
|
||||||
|
[![](https://img.shields.io/badge/Donations-fosspay-brightgreen.svg)](https://drewdevault.com/donate) |
||||||
|
|
||||||
|
## Rationale |
||||||
|
|
||||||
|
I write a ton of open source software, but almost none of it is on the scale |
||||||
|
that I can expect reliable income from donations, or the sorts of projects that |
||||||
|
a business would be likely to fund. It's very unlikely that I'd receive enough |
||||||
|
donations from random folks to support full time open source work, but full time |
||||||
|
is the best way to make serious progress on your projects. |
||||||
|
|
||||||
|
So - here's how this works: supporters give you one-time or recurring donations, |
||||||
|
and after a while you get enough to take a week off from work to spend on open |
||||||
|
source work. Since I have several projects, I also ask supporters to tell me |
||||||
|
what project they're donating towards, and I distribute the load based on which |
||||||
|
projects receive the most support. |
||||||
|
|
||||||
|
## Before you start |
||||||
|
|
||||||
|
Talk to your employer. The way that this is designed to work is that you |
||||||
|
continue working full-time at your job, and collect donations. After a while, |
||||||
|
you should have enough donations to take some period of unpaid leave - a week, a |
||||||
|
month, or whatever works. |
||||||
|
|
||||||
|
* You keep your current job and job security |
||||||
|
* You get paid to work on FOSS even with flaky or inconsistent donations |
||||||
|
* Everyone wins |
||||||
|
|
||||||
|
There are a few things you need to talk about with your employer: |
||||||
|
|
||||||
|
1. Make sure you own the IP for the things you write during your open source |
||||||
|
sprints. |
||||||
|
1. Make sure that you have a job to come back to afterwards. |
||||||
|
1. Research the tax implications of accepting these donations. |
||||||
|
|
||||||
|
### Stripe |
||||||
|
|
||||||
|
Payments are taken through Stripe, which is pretty headache-free for you to use. |
||||||
|
You need to set up an approved Stripe account, which you can get from here: |
||||||
|
|
||||||
|
https://stripe.com/ |
||||||
|
|
||||||
|
### Mandrill |
||||||
|
|
||||||
|
You will need a mail server of some sort. If you don't want to go through the |
||||||
|
trouble of setting one up, you can use Mandrill: |
||||||
|
|
||||||
|
http://mandrill.com/ |
||||||
|
|
||||||
|
You can probably also use your existing mail server, which is what I do, which |
||||||
|
makes it easy for people to email you questions and such. |
||||||
|
|
||||||
|
### SSL |
||||||
|
|
||||||
|
You will need an SSL certificate for your website (you also need a domain name). |
||||||
|
You can get a free SSL certificate from [StartSSL](http://www.startssl.com/), |
||||||
|
but they've always felt pretty... bad to me. You can pay for one instead at |
||||||
|
[RapidSSL](https://www.rapidssl.com/), which is what I use personally. You can |
||||||
|
also get one for free from [Let's Encrypt](https://letsencrypt.org/) if that |
||||||
|
ever happens. |
||||||
|
|
||||||
|
If you need a domain, you can use my referral link for |
||||||
|
[Namecheap](http://www.namecheap.com/?aff=84838) and that'd be super nice of |
||||||
|
you. Here's a link to Namecheap without the referral link: |
||||||
|
[Namecheap](http://www.namecheap.com). |
||||||
|
|
||||||
|
## Installation |
||||||
|
|
||||||
|
Install these things (Arch Linux packages in parenthesis): |
||||||
|
|
||||||
|
* Python 3 (python) |
||||||
|
* PostgreSQL (postgresql) |
||||||
|
* scss (ruby-sass) |
||||||
|
* Flask (python-flask) |
||||||
|
* SQLAlchemy (python-sqlalchemy) |
||||||
|
* Flask-Login (python-flask-login) |
||||||
|
* psycopg2 (python-psycopg2) |
||||||
|
* bcrypt (python-bcrypt) |
||||||
|
|
||||||
|
You'll have to configure PostgreSQL yourself and get a connection string that |
||||||
|
fosspay can use. Then you can clone this repository to wherever you want to run |
||||||
|
it from (I suggest making an unprivledged user account on the server you want to |
||||||
|
host this on). |
||||||
|
|
||||||
|
### Configuration |
||||||
|
|
||||||
|
Copy config.ini.example to config.ini and edit it to your liking. Then, you can |
||||||
|
run this command to try the site in development mode: |
||||||
|
|
||||||
|
python app.py |
||||||
|
|
||||||
|
[Click here](http://localhost:5000) to visit your donation site and further |
||||||
|
instructions will be provided there. |
||||||
|
|
||||||
|
### Production Deployment |
||||||
|
|
||||||
|
To deploy this to production, copy the systemd unit from `contrib/` to your |
||||||
|
server at `/etc/systemd/system/` (or whatever's appropriate for your distro). |
||||||
|
Use `sytsemctl enable fosspay` and `systemctl start fosspay` to run the site on |
||||||
|
`127.0.0.1:8000` (you can change this port by editing the unit file). You should |
||||||
|
configure nginx to proxy through to fosspay from whatever other website you |
||||||
|
already have. My nginx config is provided in `contrib/` for you to take a look |
||||||
|
at - it proxies most requests to Github pages (my blog), and `/donate` to |
||||||
|
fosspay. |
@ -0,0 +1,11 @@ |
|||||||
|
from fosspay.app import app |
||||||
|
from fosspay.config import _cfg, _cfgi |
||||||
|
|
||||||
|
import os |
||||||
|
|
||||||
|
app.static_folder = os.path.join(os.getcwd(), "static") |
||||||
|
|
||||||
|
import os |
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
app.run(host=_cfg("debug-host"), port=_cfgi('debug-port'), debug=True) |
@ -0,0 +1,24 @@ |
|||||||
|
[dev] |
||||||
|
# Change this to the actual location of your site |
||||||
|
protocol=http |
||||||
|
domain=localhost:5000 |
||||||
|
# Change this value to something random and secret |
||||||
|
secret-key=hello world |
||||||
|
|
||||||
|
# On the debug server, this lets you choose what to bind to |
||||||
|
debug-host=0.0.0.0 |
||||||
|
debug-port=5000 |
||||||
|
|
||||||
|
# Fill out these details with your mail server |
||||||
|
smtp-host=mail.you.com |
||||||
|
smtp-port=587 |
||||||
|
smtp-user=you |
||||||
|
smtp-password=password |
||||||
|
smtp-from=donate@you.com |
||||||
|
|
||||||
|
# Your information |
||||||
|
your_name=Joe Bloe |
||||||
|
your_email=joe@bloe.com |
||||||
|
|
||||||
|
# SQL connection string |
||||||
|
connection-string=postgresql://postgres@localhost/fosspay |
@ -0,0 +1,74 @@ |
|||||||
|
from flask import Flask, render_template, request, g, Response, redirect, url_for |
||||||
|
from flask.ext.login import LoginManager, current_user |
||||||
|
from jinja2 import FileSystemLoader, ChoiceLoader |
||||||
|
|
||||||
|
import sys |
||||||
|
import os |
||||||
|
import locale |
||||||
|
|
||||||
|
from fosspay.config import _cfg, _cfgi |
||||||
|
from fosspay.database import db, init_db |
||||||
|
from fosspay.objects import User |
||||||
|
from fosspay.common import * |
||||||
|
from fosspay.network import * |
||||||
|
|
||||||
|
from fosspay.blueprints.html import html |
||||||
|
|
||||||
|
app = Flask(__name__) |
||||||
|
app.secret_key = _cfg("secret-key") |
||||||
|
app.jinja_env.cache = None |
||||||
|
init_db() |
||||||
|
login_manager = LoginManager() |
||||||
|
login_manager.init_app(app) |
||||||
|
|
||||||
|
app.jinja_loader = ChoiceLoader([ |
||||||
|
FileSystemLoader("overrides"), |
||||||
|
FileSystemLoader("templates"), |
||||||
|
]) |
||||||
|
|
||||||
|
@login_manager.user_loader |
||||||
|
def load_user(email): |
||||||
|
return User.query.filter(User.email == email).first() |
||||||
|
|
||||||
|
login_manager.anonymous_user = lambda: None |
||||||
|
|
||||||
|
app.register_blueprint(html) |
||||||
|
|
||||||
|
try: |
||||||
|
locale.setlocale(locale.LC_ALL, 'en_US') |
||||||
|
except: |
||||||
|
pass |
||||||
|
|
||||||
|
if not app.debug: |
||||||
|
@app.errorhandler(500) |
||||||
|
def handle_500(e): |
||||||
|
# shit |
||||||
|
try: |
||||||
|
db.rollback() |
||||||
|
db.close() |
||||||
|
except: |
||||||
|
# shit shit |
||||||
|
print("We're very borked, letting init system kick us back up") |
||||||
|
sys.exit(1) |
||||||
|
return render_template("internal_error.html"), 500 |
||||||
|
|
||||||
|
@app.errorhandler(404) |
||||||
|
def handle_404(e): |
||||||
|
return render_template("not_found.html"), 404 |
||||||
|
|
||||||
|
@app.context_processor |
||||||
|
def inject(): |
||||||
|
return { |
||||||
|
'root': _cfg("protocol") + "://" + _cfg("domain"), |
||||||
|
'domain': _cfg("domain"), |
||||||
|
'protocol': _cfg("protocol"), |
||||||
|
'len': len, |
||||||
|
'any': any, |
||||||
|
'request': request, |
||||||
|
'locale': locale, |
||||||
|
'url_for': url_for, |
||||||
|
'file_link': file_link, |
||||||
|
'user': current_user, |
||||||
|
'_cfg': _cfg, |
||||||
|
'debug': app.debug |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
from flask import Blueprint, render_template, abort, request, redirect, session, url_for, send_file, Response |
||||||
|
from flask.ext.login import current_user, login_user, logout_user |
||||||
|
from fosspay.objects import * |
||||||
|
from fosspay.database import db |
||||||
|
from fosspay.common import * |
||||||
|
from fosspay.config import _cfg, load_config |
||||||
|
|
||||||
|
import locale |
||||||
|
|
||||||
|
encoding = locale.getdefaultlocale()[1] |
||||||
|
html = Blueprint('html', __name__, template_folder='../../templates') |
||||||
|
|
||||||
|
@html.route("/") |
||||||
|
def index(): |
||||||
|
if User.query.count() == 0: |
||||||
|
load_config() |
||||||
|
return render_template("setup.html") |
||||||
|
return render_template("index.html") |
||||||
|
|
||||||
|
@html.route("/setup", methods=["POST"]) |
||||||
|
def setup(): |
||||||
|
if not User.query.count() == 0: |
||||||
|
abort(400) |
||||||
|
email = request.form.get("email") |
||||||
|
password = request.form.get("password") |
||||||
|
if not email or not password: |
||||||
|
return redirect("/") # TODO: Tell them what they did wrong (i.e. being stupid) |
||||||
|
user = User(email, password) |
||||||
|
user.admin = True |
||||||
|
db.add(user) |
||||||
|
db.commit() |
||||||
|
login_user(user) |
||||||
|
return redirect("/admin?first-run=1") |
||||||
|
|
||||||
|
@html.route("/admin") |
||||||
|
@adminrequired |
||||||
|
def admin(): |
||||||
|
first=bool(request.args.get("first-run")) |
||||||
|
return render_template("admin.html", first=first) |
@ -0,0 +1,104 @@ |
|||||||
|
from flask import session, jsonify, redirect, request, Response, abort |
||||||
|
from flask.ext.login import current_user |
||||||
|
from werkzeug.utils import secure_filename |
||||||
|
from functools import wraps |
||||||
|
from fosspay.objects import User |
||||||
|
from fosspay.database import db, Base |
||||||
|
from fosspay.config import _cfg |
||||||
|
|
||||||
|
import json |
||||||
|
import urllib |
||||||
|
import requests |
||||||
|
import xml.etree.ElementTree as ET |
||||||
|
import hashlib |
||||||
|
|
||||||
|
def firstparagraph(text): |
||||||
|
try: |
||||||
|
para = text.index("\n\n") |
||||||
|
return text[:para + 2] |
||||||
|
except: |
||||||
|
try: |
||||||
|
para = text.index("\r\n\r\n") |
||||||
|
return text[:para + 4] |
||||||
|
except: |
||||||
|
return text |
||||||
|
|
||||||
|
def with_session(f): |
||||||
|
@wraps(f) |
||||||
|
def go(*args, **kw): |
||||||
|
try: |
||||||
|
ret = f(*args, **kw) |
||||||
|
db.commit() |
||||||
|
return ret |
||||||
|
except: |
||||||
|
db.rollback() |
||||||
|
db.close() |
||||||
|
raise |
||||||
|
return go |
||||||
|
|
||||||
|
def loginrequired(f): |
||||||
|
@wraps(f) |
||||||
|
def wrapper(*args, **kwargs): |
||||||
|
if not current_user: |
||||||
|
return redirect("/login?return_to=" + urllib.parse.quote_plus(request.url)) |
||||||
|
else: |
||||||
|
return f(*args, **kwargs) |
||||||
|
return wrapper |
||||||
|
|
||||||
|
def adminrequired(f): |
||||||
|
@wraps(f) |
||||||
|
def wrapper(*args, **kwargs): |
||||||
|
if not current_user: |
||||||
|
return redirect("/login?return_to=" + urllib.parse.quote_plus(request.url)) |
||||||
|
else: |
||||||
|
if not current_user.admin: |
||||||
|
abort(401) |
||||||
|
return f(*args, **kwargs) |
||||||
|
return wrapper |
||||||
|
|
||||||
|
def json_output(f): |
||||||
|
@wraps(f) |
||||||
|
def wrapper(*args, **kwargs): |
||||||
|
def jsonify_wrap(obj): |
||||||
|
jsonification = json.dumps(obj) |
||||||
|
return Response(jsonification, mimetype='application/json') |
||||||
|
|
||||||
|
result = f(*args, **kwargs) |
||||||
|
if isinstance(result, tuple): |
||||||
|
return jsonify_wrap(result[0]), result[1] |
||||||
|
if isinstance(result, dict): |
||||||
|
return jsonify_wrap(result) |
||||||
|
if isinstance(result, list): |
||||||
|
return jsonify_wrap(result) |
||||||
|
|
||||||
|
# This is a fully fleshed out response, return it immediately |
||||||
|
return result |
||||||
|
|
||||||
|
return wrapper |
||||||
|
|
||||||
|
def cors(f): |
||||||
|
@wraps(f) |
||||||
|
def wrapper(*args, **kwargs): |
||||||
|
res = f(*args, **kwargs) |
||||||
|
if request.headers.get('x-cors-status', False): |
||||||
|
if isinstance(res, tuple): |
||||||
|
json_text = res[0].data |
||||||
|
code = res[1] |
||||||
|
else: |
||||||
|
json_text = res.data |
||||||
|
code = 200 |
||||||
|
|
||||||
|
o = json.loads(json_text) |
||||||
|
o['x-status'] = code |
||||||
|
|
||||||
|
return jsonify(o) |
||||||
|
|
||||||
|
return res |
||||||
|
|
||||||
|
return wrapper |
||||||
|
|
||||||
|
def file_link(path): |
||||||
|
return _cfg("protocol") + "://" + _cfg("domain") + "/" + path |
||||||
|
|
||||||
|
def disown_link(path): |
||||||
|
return _cfg("protocol") + "://" + _cfg("domain") + "/disown?filename=" + path |
@ -0,0 +1,33 @@ |
|||||||
|
import logging |
||||||
|
|
||||||
|
try: |
||||||
|
from configparser import ConfigParser |
||||||
|
except ImportError: |
||||||
|
# Python 2 support |
||||||
|
from ConfigParser import ConfigParser |
||||||
|
|
||||||
|
logger = logging.getLogger("fosspay") |
||||||
|
logger.setLevel(logging.DEBUG) |
||||||
|
|
||||||
|
sh = logging.StreamHandler() |
||||||
|
sh.setLevel(logging.DEBUG) |
||||||
|
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") |
||||||
|
sh.setFormatter(formatter) |
||||||
|
|
||||||
|
logger.addHandler(sh) |
||||||
|
|
||||||
|
# scss logger |
||||||
|
logging.getLogger("scss").addHandler(sh) |
||||||
|
|
||||||
|
env = 'dev' |
||||||
|
config = None |
||||||
|
|
||||||
|
def load_config(): |
||||||
|
global config |
||||||
|
config = ConfigParser() |
||||||
|
config.readfp(open('config.ini')) |
||||||
|
|
||||||
|
load_config() |
||||||
|
|
||||||
|
_cfg = lambda k: config.get(env, k) |
||||||
|
_cfgi = lambda k: int(_cfg(k)) |
@ -0,0 +1,14 @@ |
|||||||
|
from sqlalchemy import create_engine |
||||||
|
from sqlalchemy.orm import scoped_session, sessionmaker |
||||||
|
from sqlalchemy.ext.declarative import declarative_base |
||||||
|
|
||||||
|
from .config import _cfg, _cfgi |
||||||
|
engine = create_engine(_cfg('connection-string')) |
||||||
|
db = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine)) |
||||||
|
|
||||||
|
Base = declarative_base() |
||||||
|
Base.query = db.query_property() |
||||||
|
|
||||||
|
def init_db(): |
||||||
|
import fosspay.objects |
||||||
|
Base.metadata.create_all(bind=engine) |
@ -0,0 +1,19 @@ |
|||||||
|
def makeMask(n): |
||||||
|
"return a mask of n bits as a long integer" |
||||||
|
return (2 << n - 1) - 1 |
||||||
|
|
||||||
|
|
||||||
|
def dottedQuadToNum(ip): |
||||||
|
"convert decimal dotted quad string to long integer" |
||||||
|
parts = ip.split(".") |
||||||
|
return int(parts[0]) | (int(parts[1]) << 8) | (int(parts[2]) << 16) | (int(parts[3]) << 24) |
||||||
|
|
||||||
|
|
||||||
|
def networkMask(ip, bits): |
||||||
|
"Convert a network address to a long integer" |
||||||
|
return dottedQuadToNum(ip) & makeMask(bits) |
||||||
|
|
||||||
|
|
||||||
|
def addressInNetwork(ip, net): |
||||||
|
"Is an address in a network" |
||||||
|
return ip & net == net |
@ -0,0 +1,42 @@ |
|||||||
|
from sqlalchemy import Column, Integer, String, Unicode, Boolean, DateTime |
||||||
|
from sqlalchemy import ForeignKey, Table, UnicodeText, Text, text |
||||||
|
from sqlalchemy.orm import relationship, backref |
||||||
|
from .database import Base |
||||||
|
|
||||||
|
from datetime import datetime |
||||||
|
import bcrypt |
||||||
|
import os |
||||||
|
import hashlib |
||||||
|
|
||||||
|
class User(Base): |
||||||
|
__tablename__ = 'user' |
||||||
|
id = Column(Integer, primary_key = True) |
||||||
|
email = Column(String(256), nullable = False, index = True) |
||||||
|
admin = Column(Boolean()) |
||||||
|
password = Column(String) |
||||||
|
created = Column(DateTime) |
||||||
|
passwordReset = Column(String(128)) |
||||||
|
passwordResetExpiry = Column(DateTime) |
||||||
|
|
||||||
|
def set_password(self, password): |
||||||
|
self.password = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8") |
||||||
|
|
||||||
|
def __init__(self, email, password): |
||||||
|
self.email = email |
||||||
|
self.admin = False |
||||||
|
self.created = datetime.now() |
||||||
|
self.set_password(password) |
||||||
|
|
||||||
|
def __repr__(self): |
||||||
|
return '<User %r>' % self.username |
||||||
|
|
||||||
|
# Flask.Login stuff |
||||||
|
# We don't use most of these features |
||||||
|
def is_authenticated(self): |
||||||
|
return True |
||||||
|
def is_active(self): |
||||||
|
return True |
||||||
|
def is_anonymous(self): |
||||||
|
return False |
||||||
|
def get_id(self): |
||||||
|
return self.email |
@ -0,0 +1,5 @@ |
|||||||
|
stripe |
||||||
|
Flask |
||||||
|
Jinja2 |
||||||
|
Flask-Misaka |
||||||
|
gunicorn |
@ -0,0 +1,64 @@ |
|||||||
|
{% extends "layout.html" %} |
||||||
|
{% block body %} |
||||||
|
<h1>Fosspay Admin</h1> |
||||||
|
{% if first %} |
||||||
|
<div class="well"> |
||||||
|
<p> |
||||||
|
You're set up and ready to go! This is your admin panel. |
||||||
|
Yeah, it's not pretty. Next steps: |
||||||
|
</p> |
||||||
|
<ol> |
||||||
|
<li> |
||||||
|
Add some projects. Donors can tell you which project they want to support |
||||||
|
when they donate. |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
Customize the look & feel. Look at the contents of the <code>templates</code> |
||||||
|
directory - you can copy and paste any of these templates into the |
||||||
|
<code>overrides</code> directory and change it to suit your needs. |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<a href="https://drewdevault.com/donate?project=fosspay">Donate to fosspay upstream?</a> |
||||||
|
</li> |
||||||
|
<li> |
||||||
|
<a href="https://github.com/SirCmpwn/fosspay">Contribute code to fosspay upstream?</a> |
||||||
|
</li> |
||||||
|
</ol> |
||||||
|
</div> |
||||||
|
{% endif %} |
||||||
|
<h2>Projects</h2> |
||||||
|
<div class="row"> |
||||||
|
<div class="col-md-6"> |
||||||
|
<table class="table"> |
||||||
|
<thead> |
||||||
|
<tr> |
||||||
|
<th>Project Name</th> |
||||||
|
<th>One-off donations</th> |
||||||
|
<th>Recurring donations</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
<div class="col-md-6"> |
||||||
|
<h4>Add Project</h4> |
||||||
|
<form method="POST" action="/create-project"> |
||||||
|
<div class="form-group"> |
||||||
|
<input class="form-control" type="text" placeholder="Project name" name="name" /> |
||||||
|
</div> |
||||||
|
<input type="submit" value="Add" /> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<h2>Donation History</h2> |
||||||
|
<table class="table"> |
||||||
|
<thead> |
||||||
|
<tr> |
||||||
|
<th>Email</th> |
||||||
|
<th>Project</th> |
||||||
|
<th>Comment</th> |
||||||
|
<th>Amount</th> |
||||||
|
<th style="width: 10%">Recurring</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
</table> |
||||||
|
{% endblock %} |
@ -0,0 +1,14 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
{% block title %} |
||||||
|
<title>Donate to {{_cfg("your-name")}}</title> |
||||||
|
{% endblock %} |
||||||
|
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" /> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div class="container"> |
||||||
|
{% block body %}{% endblock %} |
||||||
|
</div> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,73 @@ |
|||||||
|
{% extends "layout.html" %} |
||||||
|
{% block body %} |
||||||
|
<h1>FossPay Setup</h1> |
||||||
|
<p>Congrats! You have FossPay up and running.</p> |
||||||
|
|
||||||
|
<h2>config.ini</h2> |
||||||
|
<ul class="list-unstyled"> |
||||||
|
<li> |
||||||
|
{% if _cfg("secret-key") == "hello world" %} |
||||||
|
<span class="glyphicon glyphicon-remove text-danger"></span> |
||||||
|
You need to change the secret key to something other than "hello world". |
||||||
|
{% else %} |
||||||
|
<span class="glyphicon glyphicon-ok text-success"></span> |
||||||
|
Your secret key looks good. |
||||||
|
{% endif %} |
||||||
|
</li> |
||||||
|
|
||||||
|
<li> |
||||||
|
{% if _cfg("domain") == "localhost:5000" %} |
||||||
|
<span class="glyphicon glyphicon-remove text-danger"></span> |
||||||
|
You should change your domain to something other than localhost. |
||||||
|
{% else %} |
||||||
|
<span class="glyphicon glyphicon-ok text-success"></span> |
||||||
|
Your domain is set to "{{_cfg("domain")}}". |
||||||
|
{% endif %} |
||||||
|
</li> |
||||||
|
|
||||||
|
<li> |
||||||
|
{% if _cfg("protocol") != "https" %} |
||||||
|
<span class="glyphicon glyphicon-remove text-danger"></span> |
||||||
|
Stripe requires your website to use HTTPS. |
||||||
|
{% else %} |
||||||
|
<span class="glyphicon glyphicon-ok text-success"></span> |
||||||
|
Stripe requires your website to use HTTPS. |
||||||
|
{% endif %} |
||||||
|
</li> |
||||||
|
|
||||||
|
<li> |
||||||
|
{% if not _cfg("smtp-host") %} |
||||||
|
<span class="glyphicon glyphicon-remove text-danger"></span> |
||||||
|
You should configure an SMTP server to send emails with. |
||||||
|
{% else %} |
||||||
|
<span class="glyphicon glyphicon-ok text-success"></span> |
||||||
|
Your email configuration looks good. |
||||||
|
{% endif %} |
||||||
|
</li> |
||||||
|
|
||||||
|
<li> |
||||||
|
{% if not _cfg("stripe-secret") or not _cfg("stripe-publish") %} |
||||||
|
<span class="glyphicon glyphicon-remove text-danger"></span> |
||||||
|
Your Stripe API keys are not in your config file. |
||||||
|
{% else %} |
||||||
|
<span class="glyphicon glyphicon-ok text-success"></span> |
||||||
|
Your Stripe API keys look good. |
||||||
|
{% endif %} |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
<p>You can make changes and refresh this page if you like.</p> |
||||||
|
|
||||||
|
<h2>Admin Account</h2> |
||||||
|
<p>Enter your details for the admin account:</p> |
||||||
|
<form class="form" action="/setup" method="POST"> |
||||||
|
<div class="form-group"> |
||||||
|
<input type="text" class="form-control" name="email" |
||||||
|
placeholder="Email" value="{{_cfg("your-email")}}" /> |
||||||
|
</div> |
||||||
|
<div class="form-group"> |
||||||
|
<input type="password" class="form-control" name="password" placeholder="Password" /> |
||||||
|
</div> |
||||||
|
<input type="submit" value="Continue" class="btn btn-primary" /> |
||||||
|
</form> |
||||||
|
|
||||||
|
{% endblock %} |
Loading…
Reference in new issue