rename, clean up, adapt, internationalize
This commit is contained in:
parent
22a57a7b9a
commit
e0f03b3b30
@ -1,5 +1,5 @@
|
||||
[extractors]
|
||||
spt = gratipay.utils.i18n:extract_spt
|
||||
spt = liberapay.utils.i18n:extract_spt
|
||||
|
||||
[python: **.py]
|
||||
[jinja2: **.html]
|
||||
|
@ -10,8 +10,3 @@ trim_trailing_whitespace = true
|
||||
# Makefiles require us to use tabs, so we're gonna keep tabs with Makefiles
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
# These files are generated programmatically, so we're going to keep with the
|
||||
# program
|
||||
[{package,bower}.json]
|
||||
indent_size = 2
|
||||
|
17
.gitignore
vendored
17
.gitignore
vendored
@ -1,24 +1,11 @@
|
||||
env/
|
||||
gratipay.egg-info/
|
||||
*.egg-info/
|
||||
*~
|
||||
*.pyc
|
||||
local.env
|
||||
tests/env
|
||||
*.idea/
|
||||
tests/local.env
|
||||
*.egg-info/
|
||||
nosetests.xml
|
||||
.cache
|
||||
.coverage
|
||||
.noseids
|
||||
.deps
|
||||
.sass-cache/
|
||||
.vagrant
|
||||
node_modules/
|
||||
.DS_Store
|
||||
docs/_build
|
||||
docs/gratipay
|
||||
docs/gratipay.rst
|
||||
_vimrc_local.vim
|
||||
.transifexrc
|
||||
npm-debug.log
|
||||
phantomjsdriver.log
|
||||
|
@ -1,6 +0,0 @@
|
||||
{ "asi": true
|
||||
, "laxbreak": true
|
||||
, "laxcomma": true
|
||||
, "boss": true
|
||||
, "shadow": true
|
||||
}
|
@ -1,6 +1,3 @@
|
||||
# Files removed before slug
|
||||
# compilation on Heroku
|
||||
# Files removed before slug compilation on Heroku
|
||||
# See: https://devcenter.heroku.com/articles/slug-compiler#slug-size
|
||||
img-src/
|
||||
tests/
|
||||
jstests/
|
||||
|
11
.travis.yml
11
.travis.yml
@ -4,27 +4,22 @@ addons:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
before_install:
|
||||
- node --version
|
||||
cache:
|
||||
directories:
|
||||
- env/bin
|
||||
- env/lib/python2.7/site-packages
|
||||
install:
|
||||
- if [ "${TRAVIS_BRANCH}" = "master" -a "${TRAVIS_PULL_REQUEST}" = "false" ]; then rm -rf env; fi
|
||||
- touch requirements.txt package.json
|
||||
- make env
|
||||
- make node_modules
|
||||
- touch requirements.txt && make env
|
||||
before_script:
|
||||
- echo "DATABASE_URL=dbname=gratipay" | tee -a tests/local.env local.env
|
||||
- psql -U postgres -c 'CREATE DATABASE "gratipay";'
|
||||
- psql -U postgres -c 'CREATE DATABASE liberapay_tests;'
|
||||
- if [ "${TRAVIS_BRANCH}" = "master" -a "${TRAVIS_PULL_REQUEST}" = "false" ]; then rm -rfv tests/py/fixtures; fi
|
||||
script: make test
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
channels:
|
||||
- "chat.freenode.net#gratipay"
|
||||
- "chat.freenode.net#liberapay"
|
||||
on_success: change
|
||||
on_failure: always
|
||||
template:
|
||||
|
@ -1,8 +0,0 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[gratipay.core]
|
||||
source_file = i18n/core.pot
|
||||
source_lang = en
|
||||
file_filter = i18n/core/<lang>.po
|
||||
type = PO
|
@ -1,14 +0,0 @@
|
||||
## Contributing to Gratipay
|
||||
|
||||
If you are opening a new issue or submitting a pull request, **go for it!**
|
||||
Don't be afraid that it's a dumb idea or a duplicate of another issue or an
|
||||
unwanted change or whatever. Maybe it is! We're still glad to have you! :^)
|
||||
|
||||
**License.** Gratipay is [licensed under
|
||||
CC0](https://github.com/gratipay/gratipay.com/tree/master/COPYING), i.e.,
|
||||
it's public domain.
|
||||
|
||||
**More info.** If you want to really get involved, then check out our full
|
||||
documentation for contributors:
|
||||
|
||||
http://inside.gratipay.com/
|
151
COPYING
151
COPYING
@ -1,151 +0,0 @@
|
||||
Gratipay is in the public domain, via the "CC0 1.0 Universal - Public Domain
|
||||
Dedication" from the Creative Commons.
|
||||
|
||||
|
||||
## Summary of "CC0 1.0 Universal - Public Domain Dedication"
|
||||
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
No Copyright
|
||||
|
||||
The person who associated a work with this deed has dedicated the work to the
|
||||
public domain by waiving all of his or her rights to the work worldwide under
|
||||
copyright law, including all related and neighboring rights, to the extent
|
||||
allowed by law.
|
||||
|
||||
You can copy, modify, distribute and perform the work, even for commercial
|
||||
purposes, all without asking permission. See Other Information below.
|
||||
|
||||
|
||||
Other Information
|
||||
|
||||
- In no way are the patent or trademark rights of any person affected by
|
||||
CC0, nor are the rights that other persons may have in the work or in
|
||||
how the work is used, such as publicity or privacy rights.
|
||||
|
||||
- Unless expressly stated otherwise, the person who associated a work
|
||||
with this deed makes no warranties about the work, and disclaims
|
||||
liability for all uses of the work, to the fullest extent permitted by
|
||||
applicable law.
|
||||
|
||||
- When using or citing the work, you should not imply endorsement by the
|
||||
author or the affirmer.
|
||||
|
||||
|
||||
## CC0 1.0 Universal - Public Domain Dedication
|
||||
|
||||
http://creativecommons.org/publicdomain/zero/1.0/legalcode
|
||||
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator and
|
||||
subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||
purpose of contributing to a commons of creative, cultural and scientific works
|
||||
("Commons") that the public can reliably and without fear of later claims of
|
||||
infringement build upon, modify, incorporate in other works, reuse and
|
||||
redistribute as freely as possible in any form whatsoever and for any purposes,
|
||||
including without limitation commercial purposes. These owners may contribute
|
||||
to the Commons to promote the ideal of a free culture and the further
|
||||
production of creative, cultural and scientific works, or to gain reputation or
|
||||
greater distribution for their Work in part through the use and efforts of
|
||||
others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any expectation of
|
||||
additional consideration or compensation, the person associating CC0 with a
|
||||
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
|
||||
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and
|
||||
publicly distribute the Work under its terms, with knowledge of his or her
|
||||
Copyright and Related Rights in the Work and the meaning and intended legal
|
||||
effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not limited to,
|
||||
the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
|
||||
iii. publicity and privacy rights pertaining to a person's image or likeness
|
||||
depicted in a Work;
|
||||
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation thereof,
|
||||
including any amended or successor version of such directive); and
|
||||
|
||||
vii. other similar, equivalent or corresponding rights throughout the world
|
||||
based on applicable law or treaty, and any national implementations
|
||||
thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
|
||||
and Related Rights and associated claims and causes of action, whether now
|
||||
known or unknown (including existing as well as future claims and causes of
|
||||
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||
duration provided by applicable law or treaty (including future time
|
||||
extensions), (iii) in any current or future medium and for any number of
|
||||
copies, and (iv) for any purpose whatsoever, including without limitation
|
||||
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
|
||||
the Waiver for the benefit of each member of the public at large and to the
|
||||
detriment of Affirmer's heirs and successors, fully intending that such Waiver
|
||||
shall not be subject to revocation, rescission, cancellation, termination, or
|
||||
any other legal or equitable action to disrupt the quiet enjoyment of the Work
|
||||
by the public as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||
shall be preserved to the maximum extent permitted taking into account
|
||||
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
|
||||
is so judged Affirmer hereby grants to each affected person a royalty-free, non
|
||||
transferable, non sublicensable, non exclusive, irrevocable and unconditional
|
||||
license to exercise Affirmer's Copyright and Related Rights in the Work (i) in
|
||||
all territories worldwide, (ii) for the maximum duration provided by applicable
|
||||
law or treaty (including future time extensions), (iii) in any current or
|
||||
future medium and for any number of copies, and (iv) for any purpose
|
||||
whatsoever, including without limitation commercial, advertising or promotional
|
||||
purposes (the "License"). The License shall be deemed effective as of the date
|
||||
CC0 was applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder of the
|
||||
License, and in such case Affirmer hereby affirms that he or she will not (i)
|
||||
exercise any of his or her remaining Copyright and Related Rights in the Work
|
||||
or (ii) assert any associated claims and causes of action with respect to the
|
||||
Work, in either case contrary to Affirmer's express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied, statutory
|
||||
or otherwise, including without limitation warranties of title,
|
||||
merchantability, fitness for a particular purpose, non infringement, or
|
||||
the absence of latent or other defects, accuracy, or the present or
|
||||
absence of errors, whether or not discoverable, all to the greatest
|
||||
extent permissible under applicable law.
|
||||
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the Work.
|
||||
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
54
Dockerfile
54
Dockerfile
@ -1,54 +0,0 @@
|
||||
# Dockerfile to build and run Gratipay
|
||||
# Version 0.2 (March 10, 2015)
|
||||
|
||||
################################################## General Information ##################################################
|
||||
|
||||
FROM ubuntu:14.04
|
||||
MAINTAINER Mihir Singh (@citruspi)
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
################################################## Install Dependencies #################################################
|
||||
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main" > /etc/apt/sources.list.d/pgdg.list
|
||||
|
||||
RUN apt-get -y install wget
|
||||
|
||||
RUN wget --quiet --no-check-certificate https://www.postgresql.org/media/keys/ACCC4CF8.asc
|
||||
RUN apt-key add ACCC4CF8.asc
|
||||
|
||||
RUN apt-get -y update
|
||||
|
||||
RUN apt-get -y install \
|
||||
git \
|
||||
gcc \
|
||||
make \
|
||||
g++ \
|
||||
libpq-dev \
|
||||
python-dev \
|
||||
python-pip \
|
||||
postgresql-9.3 \
|
||||
postgresql-contrib-9.3 \
|
||||
language-pack-en
|
||||
|
||||
################################################## Configure Postgres #################################################
|
||||
|
||||
RUN /etc/init.d/postgresql start && su postgres -c "createuser --superuser root" && su postgres -c "createdb gratipay"
|
||||
|
||||
################################################# Copy files + Setup Gratipay ##########################################
|
||||
|
||||
COPY ./ /srv/gratipay.com/
|
||||
WORKDIR /srv/gratipay.com
|
||||
RUN make env && /etc/init.d/postgresql start && make schema && make schema data
|
||||
|
||||
################################################ Create a Launch Script ###############################################
|
||||
|
||||
RUN echo "#!/bin/bash" >> /usr/bin/gratipay && \
|
||||
echo "/etc/init.d/postgresql start" >> /usr/bin/gratipay && \
|
||||
echo "cd /srv/gratipay.com && make run" >> /usr/bin/gratipay && \
|
||||
chmod +x /usr/bin/gratipay
|
||||
|
||||
################################################### Launch command #####################################################
|
||||
|
||||
CMD ["/usr/bin/gratipay"]
|
||||
|
132
Gruntfile.js
132
Gruntfile.js
@ -1,132 +0,0 @@
|
||||
var http = require('http');
|
||||
var spawn = require('child_process').spawn;
|
||||
var fs = require('fs');
|
||||
var ini = require('ini');
|
||||
var yaml = require('js-yaml');
|
||||
var path = require('path');
|
||||
|
||||
// Add node_modules/.bin to PATH. We'll need it later for phantomjs
|
||||
process.env.PATH = process.env.PATH + path.delimiter +
|
||||
path.join(process.cwd(), 'node_modules', '.bin');
|
||||
|
||||
module.exports = function(grunt) {
|
||||
'use strict';
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
env: ini.parse(
|
||||
fs.readFileSync('defaults.env', 'utf8') +
|
||||
fs.readFileSync('tests/test.env', 'utf8') +
|
||||
(fs.existsSync('tests/local.env') ?
|
||||
fs.readFileSync('tests/local.env', 'utf8') : '')
|
||||
),
|
||||
|
||||
watch: {
|
||||
gruntfile: {
|
||||
files: '<%= jshint.gruntfile %>',
|
||||
tasks: 'jshint:gruntfile'
|
||||
},
|
||||
|
||||
js: {
|
||||
files: '<%= jshint.js %>',
|
||||
tasks: ['jshint:js', 'webdriver']
|
||||
},
|
||||
|
||||
tests: {
|
||||
files: '<%= jshint.tests %>',
|
||||
tasks: ['jshint:tests', 'webdriver']
|
||||
}
|
||||
},
|
||||
|
||||
jshint: {
|
||||
gruntfile: 'Gruntfile.js',
|
||||
js: 'js/**/*.{js,json}',
|
||||
tests: 'tests/js/**/*.js',
|
||||
|
||||
options: {
|
||||
jshintrc: '.jshintrc',
|
||||
|
||||
globals: {
|
||||
Gratipay: true,
|
||||
_gttp: true,
|
||||
gttpURI: true,
|
||||
alert: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
webdriver: {
|
||||
tests: {
|
||||
tests: 'tests/js/test_*.js'
|
||||
},
|
||||
|
||||
options: {
|
||||
desiredCapabilities: {
|
||||
browserName: 'phantomjs'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-webdriver');
|
||||
|
||||
grunt.registerTask('default', ['test']);
|
||||
grunt.registerTask('test', ['jshint', 'aspen:start', 'webdriver']);
|
||||
|
||||
grunt.registerTask('aspen:start', 'Start Aspen (if necessary)', function() {
|
||||
var done = this.async();
|
||||
|
||||
grunt.config.requires('env.CANONICAL_HOST');
|
||||
var canonicalHost = grunt.config.get('env.CANONICAL_HOST') || 'localhost:8537';
|
||||
|
||||
var port = parseInt(canonicalHost.split(':').pop());
|
||||
|
||||
http.get('http://' + canonicalHost + '/', function(res) {
|
||||
grunt.log.writeln('Aspen seems to be running already. Doing nothing.');
|
||||
done();
|
||||
})
|
||||
.on('error', function(e) {
|
||||
grunt.log.write('Starting Aspen...');
|
||||
|
||||
var started = false;
|
||||
var aspen_out = [];
|
||||
|
||||
var bin = 'env/' + (process.platform == 'win32' ? 'Scripts' : 'bin');
|
||||
var child = spawn(
|
||||
bin + '/gunicorn',
|
||||
['--bind', ':' + port, '--workers', '1', 'gratipay.main:website'],
|
||||
{ env: grunt.config.get('env') }
|
||||
);
|
||||
|
||||
child.stdout.setEncoding('utf8');
|
||||
child.stderr.setEncoding('utf8');
|
||||
|
||||
child.stdout.on('data', function(data) { aspen_out.push(data); });
|
||||
|
||||
child.stderr.on('data', function(data) {
|
||||
aspen_out.push(data);
|
||||
|
||||
if (!started && /Starting gunicorn /.test(data)) {
|
||||
grunt.log.writeln(' started.');
|
||||
setTimeout(function() {
|
||||
started = true;
|
||||
done();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
child.on('exit', function() {
|
||||
if (!started) {
|
||||
grunt.log.writeln(' failed!');
|
||||
grunt.log.write(aspen_out);
|
||||
grunt.fail.fatal('Something went wrong when starting Aspen :<');
|
||||
}
|
||||
});
|
||||
|
||||
process.on('exit', child.kill.bind(child));
|
||||
});
|
||||
});
|
||||
};
|
87
Makefile
87
Makefile
@ -2,14 +2,13 @@ python := "$(shell { command -v python2.7 || command -v python; } 2>/dev/null)"
|
||||
|
||||
# Set the relative path to installed binaries under the project virtualenv.
|
||||
# NOTE: Creating a virtualenv on Windows places binaries in the 'Scripts' directory.
|
||||
bin_dir := $(shell $(python) -c 'import sys; bin = "Scripts" if sys.platform == "win32" else "bin"; print(bin)')
|
||||
bin_dir := $(shell $(python) -c 'import sys; print("Scripts" if sys.platform == "win32" else "bin")')
|
||||
env_bin := env/$(bin_dir)
|
||||
venv := "./vendor/virtualenv-12.0.7.py"
|
||||
test_env_files := defaults.env,tests/test.env,tests/local.env
|
||||
pip := $(env_bin)/pip
|
||||
honcho := $(env_bin)/honcho
|
||||
honcho_run := $(honcho) run -e defaults.env,local.env
|
||||
py_test := $(honcho) run -e $(test_env_files) $(env_bin)/py.test
|
||||
with_local_env := $(env_bin)/honcho run -e defaults.env,local.env
|
||||
with_tests_env := $(env_bin)/honcho run -e $(test_env_files)
|
||||
py_test := $(with_tests_env) $(env_bin)/py.test
|
||||
|
||||
ifdef PYTEST
|
||||
pytest = ./tests/py/$(PYTEST)
|
||||
@ -17,87 +16,65 @@ else
|
||||
pytest = ./tests/py/
|
||||
endif
|
||||
|
||||
env: requirements.txt requirements_tests.txt setup.py
|
||||
$(python) $(venv) \
|
||||
--prompt="[gratipay] " \
|
||||
--extra-search-dir=./vendor/ \
|
||||
--always-copy \
|
||||
./env/
|
||||
$(pip) install --no-index -f ./vendor/ -r requirements.txt
|
||||
$(pip) install --no-index -f ./vendor/ -r requirements_tests.txt
|
||||
$(pip) install -e ./
|
||||
env: requirements.txt requirements_tests.txt
|
||||
$(python) -m virtualenv ./env/
|
||||
$(pip) install -r requirements.txt
|
||||
$(pip) install -r requirements_tests.txt
|
||||
@touch env
|
||||
|
||||
clean:
|
||||
rm -rf env *.egg *.egg-info
|
||||
find . -name \*.pyc -delete
|
||||
|
||||
schema: env
|
||||
$(honcho_run) ./recreate-schema.sh
|
||||
$(with_local_env) ./recreate-schema.sh
|
||||
|
||||
schema-diff: test-schema
|
||||
pg_dump -sO `heroku config:get DATABASE_URL -a gratipay` >prod.sql
|
||||
$(honcho) run -e $(test_env_files) sh -c 'pg_dump -sO "$$DATABASE_URL"' >local.sql
|
||||
pg_dump -sO `heroku config:get DATABASE_URL -a liberapay` >prod.sql
|
||||
$(with_tests_env) sh -c 'pg_dump -sO "$$DATABASE_URL"' >local.sql
|
||||
diff -uw prod.sql local.sql
|
||||
rm prod.sql local.sql
|
||||
|
||||
data:
|
||||
$(honcho_run) $(env_bin)/fake_data fake_data
|
||||
data: env
|
||||
$(with_local_env) $(env_bin)/python -m liberapay.utils.fake_data
|
||||
|
||||
run: env
|
||||
PATH=$(env_bin):$(PATH) $(honcho_run) web
|
||||
$(with_local_env) make --no-print-directory run_
|
||||
|
||||
stop:
|
||||
pkill gunicorn
|
||||
run_:
|
||||
$(env_bin)/$(shell grep -E '^web: ' Procfile | cut -d' ' -f2-)
|
||||
|
||||
py: env
|
||||
$(honcho_run) $(env_bin)/python -i gratipay/main.py
|
||||
$(with_local_env) $(env_bin)/python -i liberapay/main.py
|
||||
|
||||
test-schema: env
|
||||
$(honcho) run -e $(test_env_files) ./recreate-schema.sh
|
||||
$(with_tests_env) ./recreate-schema.sh test
|
||||
|
||||
pyflakes: env
|
||||
$(env_bin)/pyflakes *.py bin gratipay tasks tests
|
||||
|
||||
test: test-schema pytest jstest
|
||||
|
||||
pytest: env
|
||||
$(py_test) --cov gratipay $(pytest)
|
||||
@$(MAKE) --no-print-directory pyflakes
|
||||
|
||||
retest: env
|
||||
$(py_test) ./tests/py/ --lf
|
||||
@$(MAKE) --no-print-directory pyflakes
|
||||
|
||||
test-cov: env
|
||||
$(py_test) --cov-report html --cov gratipay ./tests/py/
|
||||
$(env_bin)/pyflakes liberapay tests
|
||||
|
||||
test: test-schema pytest
|
||||
tests: test
|
||||
|
||||
node_modules: package.json
|
||||
npm install --no-bin-links
|
||||
@if [ -d node_modules ]; then touch node_modules; fi
|
||||
pytest: env
|
||||
PYTHONPATH=. $(py_test) --cov liberapay $(pytest)
|
||||
@$(MAKE) --no-print-directory pyflakes
|
||||
|
||||
jstest: node_modules
|
||||
node_modules/grunt-cli/bin/grunt test
|
||||
pytest-cov: env
|
||||
PYTHONPATH=. $(py_test) --cov-report html --cov liberapay ./tests/py/
|
||||
|
||||
transifexrc:
|
||||
@echo '[https://www.transifex.com]' >.transifexrc
|
||||
@echo 'hostname = https://www.transifex.com' >>.transifexrc
|
||||
@echo "password = $$TRANSIFEX_PASS" >>.transifexrc
|
||||
@echo 'token = ' >>.transifexrc
|
||||
@echo "username = $$TRANSIFEX_USER" >>.transifexrc
|
||||
pytest-re: env
|
||||
PYTHONPATH=. $(py_test) --lf ./tests/py/
|
||||
@$(MAKE) --no-print-directory pyflakes
|
||||
|
||||
tx:
|
||||
@if [ ! -x $(env_bin)/tx ]; then $(env_bin)/pip install transifex-client; fi
|
||||
|
||||
i18n: env tx
|
||||
$(env_bin)/pybabel extract -F .babel_extract --no-wrap -o i18n/core.pot emails gratipay templates www
|
||||
i18n: env
|
||||
$(env_bin)/pybabel extract -F .babel_extract --no-wrap -o i18n/core.pot emails liberapay templates www
|
||||
|
||||
i18n_upload: i18n
|
||||
$(env_bin)/tx push -s
|
||||
rm i18n/*.pot
|
||||
|
||||
i18n_download: env tx
|
||||
i18n_download: env
|
||||
$(env_bin)/tx pull -a -f --mode=reviewed --minimum-perc=50
|
||||
@for f in i18n/*/*.po; do \
|
||||
sed -E -e '/^"POT?-[^-]+-Date: /d' \
|
||||
|
2
Procfile
2
Procfile
@ -1 +1 @@
|
||||
web: gunicorn gratipay.main:website --bind :$PORT $GUNICORN_OPTS
|
||||
web: gunicorn liberapay.main:website --bind :$PORT $GUNICORN_OPTS
|
||||
|
565
README.md
565
README.md
@ -1,555 +1,76 @@
|
||||
# Welcome to Gratipay [<img height="26px" src="https://raw.githubusercontent.com/gratipay/gratipay.com/master/www/assets/gratipay.opengraph.png"/>](https://gratipay.com/)
|
||||
[Liberapay](http://liberapay.com) is a recurrent donations platform, forked from
|
||||
[Gratipay](http://gratipay.com).
|
||||
|
||||
[](https://travis-ci.org/gratipay/gratipay.com)
|
||||
[](https://coveralls.io/r/gratipay/gratipay.com?branch=master)
|
||||
[](https://huboard.com/gratipay/gratipay.com)
|
||||
[](https://www.bountysource.com/teams/gratipay/issues)
|
||||
|
||||
[Gratipay](http://gratipay.com) is a weekly gift exchange, helping to create a culture of generosity.
|
||||
If you'd like to learn more, check out <https://gratipay.com/about>.
|
||||
If you'd like to contribute to Gratipay, check out <http://inside.gratipay.com>.
|
||||
## Contributing
|
||||
|
||||
Quick Start
|
||||
===========
|
||||
### Installation
|
||||
|
||||
Local
|
||||
-----
|
||||
Firstly, make sure you have the following dependencies installed:
|
||||
|
||||
Given Python 2.7, Postgres 9.3, and a C/make toolchain:
|
||||
- python 2.7
|
||||
- virtualenv
|
||||
- postgresql
|
||||
- make
|
||||
|
||||
```
|
||||
$ git clone git@github.com:gratipay/gratipay.com.git
|
||||
$ cd gratipay.com
|
||||
$ sudo -u postgres createuser --superuser $USER
|
||||
$ createdb gratipay
|
||||
$ make schema data
|
||||
$ make run
|
||||
```
|
||||
Then run:
|
||||
|
||||
And/or:
|
||||
make env
|
||||
|
||||
```
|
||||
$ make test
|
||||
```
|
||||
Now you'll need to create two postgres databases, here's the simplest way of doing it:
|
||||
|
||||
[Read more](#table-of-contents).
|
||||
sudo -u postgres createuser --superuser $USER
|
||||
createdb liberapay
|
||||
createdb liberapay_tests
|
||||
|
||||
Then you can set up the DB:
|
||||
|
||||
Vagrant
|
||||
-------
|
||||
make schema
|
||||
|
||||
Given VirtualBox 4.3 and Vagrant 1.6.x:
|
||||
### Configuration
|
||||
|
||||
```
|
||||
$ vagrant up
|
||||
```
|
||||
Environment variables are used for configuration, the default values are in
|
||||
`defaults.env` and `tests/test.env`. You can override them in
|
||||
`local.env` and `tests/local.env` respectively.
|
||||
|
||||
[Read more](#vagrant-1).
|
||||
### Running
|
||||
|
||||
Once you've installed everything and set up the database, you can run the app:
|
||||
|
||||
Docker
|
||||
-------
|
||||
make run
|
||||
|
||||
Given some version(?) of Docker:
|
||||
It should now be accessible at [http://localhost:8339/](http://localhost:8339/).
|
||||
|
||||
```
|
||||
$ docker build -t gratipay .
|
||||
$ docker run -p 8537:8537 gratipay
|
||||
```
|
||||
You can create some fake users to make it look more like the real site:
|
||||
|
||||
[Read more](#docker-1).
|
||||
make data
|
||||
|
||||
### Modifying the database schema
|
||||
|
||||
Table of Contents
|
||||
=================
|
||||
The DB schema is in `sql/schema.sql`, but don't modify that file directly,
|
||||
instead put the changes in `sql/branch.sql`. During deployment that script will
|
||||
be run on the production DB and the changes will be merged into `sql/schema.sql`.
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Dependencies](#dependencies)
|
||||
- [Building](#building)
|
||||
- [Launching](#launching)
|
||||
- [Configuring](#configuring)
|
||||
- [Vagrant](#vagrant)
|
||||
- [Docker](#docker)
|
||||
- [Help!](#help)
|
||||
- [Configuration](https://github.com/gratipay/gratipay.com/wiki/Configuration)
|
||||
- [Modifying CSS](#modifying-css)
|
||||
- [Modifying the Database](#modifying-the-database)
|
||||
- [Testing](#testing-)
|
||||
- [Setting up a Database](#local-database-setup)
|
||||
- [API](#api)
|
||||
- [Implementations](#api-implementations)
|
||||
- [Glossary](#glossary)
|
||||
That process is semi-automated by `release.sh`.
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
Thanks for hacking on Gratipay! Be sure to review
|
||||
[CONTRIBUTING](https://github.com/gratipay/gratipay.com/blob/master/CONTRIBUTING.md#readme)
|
||||
as well if that's what you're planning to do.
|
||||
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
Building `gratipay.com` requires [Python
|
||||
2.7](http://python.org/download/releases/2.7.4/), and a gcc/make toolchain.
|
||||
|
||||
All Python library dependencies are bundled in the repo (under `vendor/`). If
|
||||
you are receiving issues from `psycopg2`, please [ensure that its needs are
|
||||
met](http://initd.org/psycopg/docs/faq.html#problems-compiling-and-deploying-psycopg2).
|
||||
|
||||
On Debian or Ubuntu you will need the following packages:
|
||||
|
||||
$ sudo apt-get install postgresql-9.3 postgresql-contrib libpq-dev python-dev
|
||||
|
||||
On OS X you can [download Postgres directly](http://www.postgresql.org/download/macosx/) or install through [Homebrew](http://brew.sh/):
|
||||
|
||||
$ brew install postgresql
|
||||
|
||||
To configure local Postgres create default role (if it hasn’t been created already) and database.
|
||||
|
||||
$ sudo -u postgres createuser --superuser $USER
|
||||
$ createdb gratipay
|
||||
|
||||
If you are getting an error about `unknown argument: '-mno-fused-madd'` when
|
||||
running `make`, then add
|
||||
`Wno-error=unused-command-line-argument-hard-error-in-future` to your
|
||||
`ARCHFLAGS` environment variable and run `make clean env` again (see [this Stack Overflow answer
|
||||
for more information](http://stackoverflow.com/a/22355874/347246)):
|
||||
|
||||
$ ARCHFLAGS=-Wno-error=unused-command-line-argument-hard-error-in-future make clean env
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
All Python dependencies (including virtualenv) are bundled with Gratipay in the
|
||||
vendor/ directory. Gratipay is designed so that you don't manage its
|
||||
virtualenv directly and you don't download its dependencies at build
|
||||
time.
|
||||
|
||||
The included `Makefile` contains several targets. Configuration options
|
||||
are stored in default_local.env file while overrides are in local.env.
|
||||
|
||||
To create virtualenv enviroment with all python dependencies installed
|
||||
in a sandbox:
|
||||
|
||||
$ make env
|
||||
|
||||
If you haven't run Gratipay for a while, you can reinstall the dependencies:
|
||||
|
||||
$ make clean env
|
||||
|
||||
Add the necessary schemas and insert dummy data into postgres:
|
||||
|
||||
$ make schema
|
||||
$ make data
|
||||
|
||||
|
||||
Launching
|
||||
---------
|
||||
|
||||
Once you've installed Python and Postgres and set up a database, you can use
|
||||
make to build and launch Gratipay:
|
||||
|
||||
$ make run
|
||||
|
||||
If you don't have make, look at the Makefile to see what steps you need
|
||||
to perform to build and launch Gratipay. The Makefile is pretty simple and
|
||||
straightforward.
|
||||
|
||||
If Gratipay launches successfully it will look like this:
|
||||
|
||||
```
|
||||
$ make run
|
||||
PATH=env/bin:{lots-more-of-your-own-PATH} env/bin/honcho run -e defaults.env,local.env web
|
||||
2014-07-22 14:53:09 [1258] [INFO] Starting gunicorn 18.0
|
||||
2014-07-22 14:53:09 [1258] [INFO] Listening at: http://0.0.0.0:8537 (1258)
|
||||
2014-07-22 14:53:09 [1258] [INFO] Using worker: sync
|
||||
2014-07-22 14:53:09 [1261] [INFO] Booting worker with pid: 1261
|
||||
pid-1261 thread-140735191843600 (MainThread) Reading configuration from defaults, environment, and command line.
|
||||
pid-1261 thread-140735191843600 (MainThread) changes_reload False default
|
||||
pid-1261 thread-140735191843600 (MainThread) changes_reload True environment variable ASPEN_CHANGES_RELOAD=yes
|
||||
pid-1261 thread-140735191843600 (MainThread) charset_dynamic UTF-8 default
|
||||
pid-1261 thread-140735191843600 (MainThread) charset_static None default
|
||||
pid-1261 thread-140735191843600 (MainThread) configuration_scripts [] default
|
||||
pid-1261 thread-140735191843600 (MainThread) indices [u'index.html', u'index.json', u'index', u'index.html.spt', u'index.json.spt', u'index.spt'] default
|
||||
pid-1261 thread-140735191843600 (MainThread) list_directories False default
|
||||
pid-1261 thread-140735191843600 (MainThread) logging_threshold 0 default
|
||||
pid-1261 thread-140735191843600 (MainThread) media_type_default text/plain default
|
||||
pid-1261 thread-140735191843600 (MainThread) media_type_json application/json default
|
||||
pid-1261 thread-140735191843600 (MainThread) project_root None default
|
||||
pid-1261 thread-140735191843600 (MainThread) project_root . environment variable ASPEN_PROJECT_ROOT=.
|
||||
pid-1261 thread-140735191843600 (MainThread) renderer_default stdlib_percent default
|
||||
pid-1261 thread-140735191843600 (MainThread) show_tracebacks False default
|
||||
pid-1261 thread-140735191843600 (MainThread) show_tracebacks True environment variable ASPEN_SHOW_TRACEBACKS=yes
|
||||
pid-1261 thread-140735191843600 (MainThread) www_root None default
|
||||
pid-1261 thread-140735191843600 (MainThread) www_root www/ environment variable ASPEN_WWW_ROOT=www/
|
||||
pid-1261 thread-140735191843600 (MainThread) project_root is relative to CWD: '.'.
|
||||
pid-1261 thread-140735191843600 (MainThread) project_root set to /Users/whit537/personal/gratipay/gratipay.com.
|
||||
pid-1261 thread-140735191843600 (MainThread) Found plugin for renderer 'jinja2'
|
||||
pid-1261 thread-140735191843600 (MainThread) Won't log to Sentry (SENTRY_DSN is empty).
|
||||
pid-1261 thread-140735191843600 (MainThread) Renderers (*ed are unavailable, CAPS is default):
|
||||
pid-1261 thread-140735191843600 (MainThread) stdlib_percent
|
||||
pid-1261 thread-140735191843600 (MainThread) json_dump
|
||||
pid-1261 thread-140735191843600 (MainThread) stdlib_format
|
||||
pid-1261 thread-140735191843600 (MainThread) JINJA2
|
||||
pid-1261 thread-140735191843600 (MainThread) stdlib_template
|
||||
```
|
||||
|
||||
You should then find this in your browser at
|
||||
[http://localhost:8537/](http://localhost:8537/):
|
||||
|
||||

|
||||
|
||||
Congratulations! Sign in using Twitter or GitHub and you're off and
|
||||
running. At some point, try [running the test suite](#testing-).
|
||||
|
||||
Configuring
|
||||
-----------
|
||||
|
||||
Gratipay's default configuration lives in [`defaults.env`](https://github.com/gratipay/gratipay.com/blob/master/defaults.env).
|
||||
If you'd like to override some settings, create a file named `local.env` to store them.
|
||||
|
||||
The following explains some of the content of that file:
|
||||
|
||||
The `BALANCED_API_SECRET` is a test marketplace. To generate a new secret for
|
||||
your own testing run this command:
|
||||
|
||||
curl -X POST https://api.balancedpayments.com/v1/api_keys | grep secret
|
||||
|
||||
Grab that secret and also create a new marketplace to test against:
|
||||
|
||||
curl -X POST https://api.balancedpayments.com/v1/marketplaces -u <your_secret>:
|
||||
|
||||
The site works without this, except for the credit card page. Visit the
|
||||
[Balanced Documentation](https://www.balancedpayments.com/docs) if you want to
|
||||
know more about creating marketplaces.
|
||||
|
||||
The `GITHUB_*` keys are for a gratipay-dev application in the Gratipay
|
||||
organization on Github. It points back to localhost:8537, which is where
|
||||
Gratipay will be running if you start it locally with `make run`. Similarly
|
||||
with the `TWITTER_*` keys, but there they required us to spell it `127.0.0.1`.
|
||||
|
||||
If you wish to use a different username or database name for the database, you
|
||||
should override the `DATABASE_URL` in `local.env` using the following format:
|
||||
|
||||
DATABASE_URL=postgres://<username>@localhost/<database name>
|
||||
|
||||
The `MANDRILL_KEY` value in `defaults.env` is for a test mail server, which
|
||||
won't actually send email to you. If you need to receive email during
|
||||
development then sign up for an account of your own at
|
||||
[Mandrill](http://mandrill.com/) and override `MANDRILL_KEY` in your
|
||||
`local.env`.
|
||||
|
||||
|
||||
Vagrant
|
||||
-------
|
||||
If you have Vagrant installed, you can run Gratipay by running `vagrant up` from the project directory. Please note that if you ever switch between running Gratipay on your own machine to Vagrant or vice versa, you will need to run `make clean`.
|
||||
|
||||
If you're using Vagrant for the first time you'll need [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) installed. If you're on Linux you'll need to install `nfs-kernel-server`.
|
||||
|
||||
The `Vagrantfile` will download a custom made image from the Internet. If you have a slow internet connection, you can download a local copy of this file, by running:
|
||||
|
||||
`curl https://downloads.gratipay.com/gratipay.box`
|
||||
|
||||
Once downloaded into the top of the project tree, our Vagrantfile will use this local file automatically when you run `vagrant up`. Vagrant is setup to use key based SSH authentication, if you're prompted for a password when logging in please use `vagrant`.
|
||||
|
||||
**Mac users:** If you're prompted for a password during initial installation, it's sudo and you should enter your Mac OS password.
|
||||
|
||||
**Ubuntu users:** If you experience problems, please see [this
|
||||
issue](https://github.com/gratipay/gratipay.com/pull/2321#issuecomment-41455169).
|
||||
As mentioned, you will also need to be wary of projects that are nested
|
||||
in encrypted directories.
|
||||
|
||||
Docker
|
||||
------------
|
||||
|
||||
You can also install/run Gratipay with Docker.
|
||||
|
||||
Build it with the included Dockerfile:
|
||||
|
||||
```
|
||||
$ git clone git@github.com:gratipay/gratipay.com.git
|
||||
$ cd gratipay.com
|
||||
$ docker build -t gratipay .
|
||||
```
|
||||
|
||||
Once you've built the image, you can launch a container:
|
||||
|
||||
|
||||
```
|
||||
$ docker run -d -p 8537:8537 gratipay
|
||||
```
|
||||
|
||||
Check it out at [localhost:8537](http://localhost:8537/)!
|
||||
|
||||
|
||||
To edit files and have those changes reflect in the running container, mount your local folder when you execute the run command:
|
||||
|
||||
```
|
||||
$ docker run -d -v $PWD:/srv/gratipay.com -p 8537:8537 gratipay
|
||||
```
|
||||
|
||||
You can get the running container's ID with `docker ps`. With that, you can
|
||||
|
||||
- view the logs:
|
||||
|
||||
```
|
||||
$ docker logs [container_id]
|
||||
```
|
||||
|
||||
- run commands within the project root:
|
||||
|
||||
```
|
||||
$ docker exec [container_id] make schema
|
||||
$ docker exec [container_id] make data
|
||||
```
|
||||
|
||||
Once you're done, kill the running container:
|
||||
|
||||
```
|
||||
$ docker kill [container_id]
|
||||
```
|
||||
|
||||
Help!
|
||||
-----
|
||||
|
||||
If you get stuck somewhere along the way, you can find help in the #gratipay
|
||||
channel on [Freenode](http://webchat.freenode.net/) or in the [issue
|
||||
tracker](/gratipay/gratipay.com/issues/new) here on GitHub.
|
||||
|
||||
Thanks for installing Gratipay! :smiley:
|
||||
|
||||
|
||||
Modifying CSS
|
||||
=============
|
||||
|
||||
We use SCSS, with files stored in `scss/`. All of the individual files are
|
||||
combined in `scss/gratipay.scss` which itself is compiled by `libsass` in
|
||||
`www/assets/%version/gratipay.css.spt` on each request.
|
||||
|
||||
|
||||
Modifying the Database
|
||||
======================
|
||||
|
||||
We write SQL, specifically the [PostgreSQL
|
||||
variant](http://www.postgresql.org/docs/9.3/static/). We keep our database
|
||||
schema in
|
||||
[`schema.sql`](https://github.com/gratipay/gratipay.com/blob/master/sql/schema.sql),
|
||||
and we write schema changes for each PR branch in a `sql/branch.sql` file, which
|
||||
then gets run against production and merged into `sql/schema.sql` during
|
||||
deployment.
|
||||
|
||||
|
||||
Testing [](https://travis-ci.org/gratipay/gratipay.com)
|
||||
=======
|
||||
|
||||
Please write unit tests for all new code and all code you change. Gratipay's
|
||||
test suite uses the py.test test runner, which will be installed into the
|
||||
virtualenv you get by running `make env`. As a rule of thumb, each test case
|
||||
should perform one assertion.
|
||||
### Testing [](https://travis-ci.org/liberapay/liberapay.com)
|
||||
|
||||
The easiest way to run the test suite is:
|
||||
|
||||
$ make test
|
||||
make test
|
||||
|
||||
However, the test suite deletes data in all tables in the public schema of the
|
||||
database configured in your testing environment.
|
||||
That recreates the test DB's schema and runs all the tests. To speed things up
|
||||
you can also use the following commands:
|
||||
|
||||
To invoke py.test directly you should use the `honcho` utility that comes
|
||||
with the install. First `make tests/env`, activate the virtualenv and then:
|
||||
- `make pytest` only runs the python tests without recreating the test DB
|
||||
- `make pytest-re` does the same but only runs the tests that failed in the previous run
|
||||
|
||||
[gratipay] $ cd tests/
|
||||
[gratipay] $ honcho run -e defaults.env,local.env py.test
|
||||
### Help
|
||||
|
||||
Local Database Setup
|
||||
--------------------
|
||||
|
||||
For the best development experience, you need a local
|
||||
installation of [Postgres](http://www.postgresql.org/download/). The best
|
||||
version of Postgres to use is 9.3.2, because that's what we're using in
|
||||
production at Heroku. You need at least 9.2, because we depend on being able to
|
||||
specify a URI to `psql`, and that was added in 9.2.
|
||||
|
||||
+ Mac: use Homerew: `brew install postgres`
|
||||
+ Ubuntu: use Apt: `apt-get install postgresql postgresql-contrib libpq-dev`
|
||||
|
||||
To setup the instance for gratipay's needs run:
|
||||
|
||||
$ sudo -u postgres createuser --superuser $USER
|
||||
$ createdb gratipay
|
||||
$ createdb gratipay-test
|
||||
|
||||
You can speed up the test suite when using a regular HDD by running:
|
||||
|
||||
$ psql -q gratipay-test -c 'alter database "gratipay-test" set synchronous_commit to off'
|
||||
|
||||
### Schema
|
||||
|
||||
Once Postgres is set up, run:
|
||||
|
||||
$ make schema
|
||||
|
||||
Which populates the database named by `DATABASE_URL` with the schema from `sql/schema.sql`.
|
||||
|
||||
### Example data
|
||||
|
||||
The gratipay database created in the last step is empty. To populate it with
|
||||
some fake data, so that more of the site is functional, run this command:
|
||||
|
||||
$ make data
|
||||
If there is something you can't seem to figure out by yourself, come ask us in
|
||||
the IRC channel #liberapay on [Freenode](http://webchat.freenode.net/).
|
||||
|
||||
|
||||
API
|
||||
===
|
||||
## License
|
||||
|
||||
The Gratipay API is comprised of these six endpoints:
|
||||
|
||||
**[/about/charts.json](https://gratipay.com/about/charts.json)**
|
||||
([source](https://github.com/gratipay/gratipay.com/tree/master/www/about/charts.json.spt))—<i>public</i>—Returns
|
||||
an array of objects, one per week, showing aggregate numbers over time. The
|
||||
[stats](https://gratipay.com/about/stats) page uses this.
|
||||
|
||||
**[/about/paydays.json](https://gratipay.com/about/paydays.json)**
|
||||
([source](https://github.com/gratipay/gratipay.com/tree/master/www/about/paydays.json.spt))—<i>public</i>—Returns
|
||||
an array of objects, one per week, showing aggregate numbers over time. The old
|
||||
charts page used to use this.
|
||||
|
||||
**[/about/stats.json](https://gratipay.com/about/stats.json)**
|
||||
([source](https://github.com/gratipay/gratipay.com/tree/master/www/about/stats.spt))—<i>public</i>—Returns
|
||||
an object giving a point-in-time snapshot of Gratipay. The
|
||||
[stats](https://gratipay.com/about/stats.html) page displays the same info.
|
||||
|
||||
**/`%username`/charts.json**
|
||||
([example](https://gratipay.com/Gratipay/charts.json),
|
||||
[source](https://github.com/gratipay/gratipay.com/tree/master/www/%25username/charts.json.spt))—<i>public</i>—Returns
|
||||
an array of objects, one per week, showing aggregate numbers over time for the
|
||||
given user.
|
||||
|
||||
**/`%username`/public.json**
|
||||
([example](https://gratipay.com/Gratipay/public.json),
|
||||
[source](https://github.com/gratipay/gratipay.com/tree/master/www/%25username/public.json.spt))—<i>public</i>—Returns an object with these keys:
|
||||
|
||||
- "receiving"—an estimate of the amount the given participant will
|
||||
receive this week
|
||||
|
||||
- "my_tip"—logged-in user's tip to the Gratipay participant in
|
||||
question; possible values are:
|
||||
|
||||
- `undefined` (key not present)—there is no logged-in user
|
||||
- "self"—logged-in user is the participant in question
|
||||
- `null`—user has never tipped this participant
|
||||
- "0.00"—user used to tip this participant
|
||||
- "3.00"—user tips this participant the given amount
|
||||
<br><br>
|
||||
|
||||
- "goal"—funding goal of the given participant; possible values are:
|
||||
|
||||
- `undefined` (key not present)—participant is a patron (or has 0 as the goal)
|
||||
- `null`—participant is grateful for gifts, but doesn't have a specific funding goal
|
||||
- "100.00"—participant's goal is to receive the given amount per week
|
||||
<br><br>
|
||||
|
||||
- "elsewhere"—participant's connected accounts elsewhere; returns an object with these keys:
|
||||
|
||||
- "bitbucket"—participant's Bitbucket account; possible values are:
|
||||
- `undefined` (key not present)—no Bitbucket account connected
|
||||
- `https://bitbucket.org/api/1.0/users/%bitbucket_username`
|
||||
- "github"—participant's GitHub account; possible values are:
|
||||
- `undefined` (key not present)—no GitHub account connected
|
||||
- `https://api.github.com/users/%github_username`
|
||||
- "twitter"—participant's Twitter account; possible values are:
|
||||
- `undefined` (key not present)—no Twitter account connected
|
||||
- `https://api.twitter.com/1.1/users/show.json?id=%twitter_immutable_id&include_entities=1`
|
||||
- "openstreetmap"—participant's OpenStreetMap account; possible values are:
|
||||
- `undefined` (key not present)—no OpenStreetMap account connected
|
||||
- `http://www.openstreetmap.org/user/%openstreetmap_username`
|
||||
|
||||
|
||||
**/`%username`/tips.json**
|
||||
([source](https://github.com/gratipay/gratipay.com/tree/master/www/%25username/tips.json.spt))—<i>private</i>—Responds
|
||||
to `GET` with an array of objects representing your current tips. `POST` the
|
||||
same structure back in order to update tips in bulk (be sure to set
|
||||
`Content-Type` to `application/json` instead of
|
||||
`application/x-www-form-urlencoded`). You can `POST` a partial array to update
|
||||
a subset of your tips. The response to a `POST` will be only the subset you
|
||||
updated. If the `amount` is `"error"` then there will also be an `error`
|
||||
attribute with a one-word error code. If you include an `also_prune` key in the
|
||||
querystring (not the body!) with a value of `yes`, `true`, or `1`, then any
|
||||
tips not in the array you `POST` will be zeroed out.
|
||||
|
||||
NOTE: The amounts must be encoded as a string (rather than a number).
|
||||
Additionally, currently, the only supported platform is 'gratipay' ('gittip'
|
||||
still works for backwards-compatibility).
|
||||
|
||||
This endpoint requires authentication. Look for your user ID and API key on your
|
||||
[account page](https://gratipay.com/about/me/settings/), and pass them using basic
|
||||
auth. E.g.:
|
||||
|
||||
```
|
||||
curl https://gratipay.com/foobar/tips.json \
|
||||
-u $userid:$api_key \
|
||||
-X POST \
|
||||
-d'[{"username":"bazbuz", "platform":"gratipay", "amount": "1.00"}]' \
|
||||
-H"Content-Type: application/json"
|
||||
```
|
||||
|
||||
API Implementations
|
||||
-------------------
|
||||
|
||||
Below are some projects that use the Gratipay APIs, that can serve as inspiration
|
||||
for your project!
|
||||
|
||||
### Renamed to Gratipay
|
||||
|
||||
- [Ruby: gratitude](https://github.com/JohnKellyFerguson/gratitude): a simple
|
||||
ruby wrapper for the Gratipay API
|
||||
|
||||
- [php-curl-class](https://github.com/php-curl-class/php-curl-class/blob/master/examples/gratipay_send_tip.php): a php class to tip using the Gratipay API
|
||||
|
||||
- [gratipay-twisted](https://github.com/TigerND/gratipay-twisted): Gratipay client
|
||||
for the Twisted framework
|
||||
|
||||
- [WordPress: WP-Gratipay](https://github.com/KakersUK/WP-Gratipay): a simple way to show a Gratipay widget on your WordPress site
|
||||
|
||||
|
||||
### Still Using Gittip
|
||||
|
||||
These probably still work, but are using our [old name](https://medium.com/gratipay-blog/gratitude-gratipay-ef24ad5e41f9):
|
||||
|
||||
- [Drupal: Gittip](https://drupal.org/project/gittip): Includes a Gittip
|
||||
giving field type to let you implement the Khan academy model for users on
|
||||
your Drupal site. ([ticket](https://www.drupal.org/node/2332131))
|
||||
|
||||
- [Node.js: Node-Gittip](https://npmjs.org/package/gittip) (also see [Khan
|
||||
Academy's setup](http://ejohn.org/blog/gittip-at-khan-academy/)) ([ticket](https://github.com/KevinTCoughlin/node-gittip/issues/1))
|
||||
|
||||
- [hubot-gittip](https://github.com/myplanetdigital/hubot-gittip): A Hubot
|
||||
script for interacting with a shared Gratipay account. ([ticket](https://github.com/myplanetdigital/hubot-gittip/issues/6))
|
||||
|
||||
- [gittip-collab](https://github.com/engineyard/gittip-collab): A Khan-style
|
||||
tool for managing a Gittip account as a team. ([ticket](https://github.com/engineyard/gittip-collab/issues/1))
|
||||
|
||||
- [WWW::Gittip](https://metacpan.org/pod/WWW::Gittip): A Perl module
|
||||
implementing the Gittip API more or less ([ticket](https://rt.cpan.org/Public/Bug/Display.html?id=101103))
|
||||
|
||||
|
||||
Glossary
|
||||
========
|
||||
|
||||
**Account Elsewhere** - An entity's registration on a platform other than
|
||||
Gratipay (e.g., Twitter).
|
||||
|
||||
**Entity** - An entity.
|
||||
|
||||
**Participant** - An entity registered with Gratipay.
|
||||
|
||||
**User** - A person using the Gratipay website. Can be authenticated or
|
||||
anonymous. If authenticated, the user is guaranteed to also be a participant.
|
||||
|
||||
License
|
||||
========
|
||||
|
||||
Gratipay is dedicated to public domain. See the text of [CC0 1.0 Universal](http://creativecommons.org/publicdomain/zero/1.0/) dedication in [COPYING](COPYING) here.
|
||||
[CC0 Public Domain Dedication](http://creativecommons.org/publicdomain/zero/1.0/)
|
||||
|
69
Vagrantfile
vendored
69
Vagrantfile
vendored
@ -1,69 +0,0 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
|
||||
VAGRANTFILE_API_VERSION = "2"
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
|
||||
# We use multi-machine config here. First one to rebuild the box from scratch
|
||||
# (it is normally commented) and second (faster) that download built version.
|
||||
|
||||
# Previous configs are:
|
||||
# - https://github.com/gratipay/gratipay.com/blob/83312e60c6b31c298ffca61036baa9849044c75e/Vagrantfile
|
||||
# - https://github.com/gratipay/gratipay.com/blob/fc0b4395e85259cdd17d9fe8560bf654abd756ce/Vagrantfile
|
||||
|
||||
|
||||
# --- [ common access parameters ] ---
|
||||
|
||||
# Gratipay app is accessible at http://localhost:8537/ from host
|
||||
#
|
||||
config.vm.network :forwarded_port, guest: 8537, host: 8537
|
||||
|
||||
# Current folder is available as /vagrant from guest.
|
||||
# We speed up access by installing and using NFS as described here:
|
||||
# https://docs.vagrantup.com/v2/synced-folders/nfs.html
|
||||
config.vm.synced_folder ".", "/vagrant", type: "nfs"
|
||||
config.vm.network "private_network", ip: "172.27.36.119"
|
||||
|
||||
# http://serverfault.com/questions/453185/vagrant-virtualbox-dns-10-0-2-3-not-working
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
# VirtualBox DNS proxy is enabled by default, but it fails to refresh
|
||||
# DHCP leases after resume from sleep or WiFi network change
|
||||
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
|
||||
end
|
||||
|
||||
# --- [ boxes ] ---
|
||||
#
|
||||
# after you bootstrap this box, saving it into a gratipay.box is as easy as
|
||||
# $ vagrant package --output gratipay.box
|
||||
#
|
||||
config.vm.define "basebox" do |base|
|
||||
# using Ubuntu 14.04, because it is what our hosting (Heroku) uses
|
||||
# (search cedar-14 for details)
|
||||
base.vm.box = "ubuntu/trusty64"
|
||||
|
||||
# --- install prerequisites ---
|
||||
# [ ] use the same package versions as Heroku
|
||||
# [ ] figure out how to fetch Heroku versions
|
||||
base.vm.provision :shell, :path => "scripts/vagrant-debian.sh" # install system prerequisites
|
||||
base.vm.provision :shell, :path => "scripts/vagrant-setup.sh" # apply user specific settings
|
||||
end
|
||||
|
||||
#config.vm.define "gratipay" do |box|
|
||||
# box.vm.box = "gratipay"
|
||||
# box.vm.box_url = File.exist?("gratipay.box") ? "file://gratipay.box" : "https://downloads.gratipay.com/gratipay.box"
|
||||
#end
|
||||
|
||||
config.vm.post_up_message = '
|
||||
----[ Gratipay Vagrant VM ]--------------------
|
||||
|
||||
vagrant ssh
|
||||
|
||||
$ make run - start local Gratipay server
|
||||
$ make test - run tests
|
||||
|
||||
-----------------------------------------------
|
||||
|
||||
'
|
||||
end
|
83
backup.sh
83
backup.sh
@ -1,83 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Fail on error.
|
||||
# ==============
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
# Be somewhere predictable.
|
||||
# =========================
|
||||
|
||||
cd "`dirname $0`"
|
||||
|
||||
|
||||
# Helpers
|
||||
# =======
|
||||
|
||||
confirm () {
|
||||
proceed=""
|
||||
while [ "$proceed" != "y" ]; do
|
||||
read -p"$1 (y/N) " proceed
|
||||
if [ "$proceed" == "n" -o "$proceed" == "N" -o "$proceed" == "" ]
|
||||
then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
require () {
|
||||
if [ ! `which $1` ]; then
|
||||
echo "The '$1' command was not found."
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
get_filepath () {
|
||||
TODAY="`date +%F`"
|
||||
CANDIDATE="$DIRPATH/$TODAY.psql"
|
||||
if [ -f "$CANDIDATE" ]
|
||||
then
|
||||
echo " $CANDIDATE" >&2
|
||||
for x in {a..z}
|
||||
do
|
||||
CANDIDATE="$DIRPATH/$TODAY$x.psql"
|
||||
if [ ! -f "$CANDIDATE" ]
|
||||
then
|
||||
break
|
||||
fi
|
||||
echo " $CANDIDATE" >&2
|
||||
done
|
||||
if [ -f "$CANDIDATE" ]
|
||||
then
|
||||
CANDIDATE=""
|
||||
else
|
||||
echo "----> $CANDIDATE" >&2
|
||||
echo >&2
|
||||
fi
|
||||
fi
|
||||
echo "$CANDIDATE"
|
||||
}
|
||||
|
||||
|
||||
# Work
|
||||
# ====
|
||||
|
||||
require heroku
|
||||
require pg_dump
|
||||
|
||||
DIRPATH="../backups"
|
||||
FILEPATH=$(get_filepath)
|
||||
|
||||
if [ "$FILEPATH" = "" ]
|
||||
then
|
||||
exit "Too many backups!"
|
||||
fi
|
||||
|
||||
confirm "Backup the Gratipay database to $FILEPATH?"
|
||||
if [ $? -eq 0 ]; then
|
||||
export PGSSLMODE=require
|
||||
pg_dump `heroku config:get DATABASE_URL -a gratipay` > $FILEPATH
|
||||
fi
|
@ -1,56 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e # exit when any command fails
|
||||
|
||||
# Install base dependencies
|
||||
sudo apt-get install -y g++ git language-pack-en libpq-dev make python-dev wget
|
||||
|
||||
# Get Postgres installed and running
|
||||
pkgmissing() {
|
||||
if dpkg -l | grep 'ii '$1' ' > /dev/null;
|
||||
then
|
||||
return 1;
|
||||
fi;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if pkgmissing postgresql-9.3; then
|
||||
if ! dpkg -l postgresql-9.3
|
||||
then
|
||||
echo " configuring apt for postgres-9.3"
|
||||
codename=`lsb_release -cs`
|
||||
aptlist="deb http://apt.postgresql.org/pub/repos/apt/ $codename-pgdg main"
|
||||
echo $aptlist | sudo tee /etc/apt/sources.list.d/pgdg.list > /dev/null
|
||||
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
|
||||
|
||||
sudo apt-get update
|
||||
fi
|
||||
sudo apt-get install -y postgresql-9.3 postgresql-contrib
|
||||
sudo service postgresql start
|
||||
fi
|
||||
|
||||
if [ -z ""`psql template1 -tAc "select usename from pg_user where usename='$USER'"` ];
|
||||
then
|
||||
sudo -u postgres createuser --superuser $USER
|
||||
fi;
|
||||
|
||||
db_exists() {
|
||||
if [ -n ""`psql template1 -tAc "select datname from pg_database where datname='$1'"` ];
|
||||
then
|
||||
return 0;
|
||||
fi
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ! db_exists gratipay-test;
|
||||
then
|
||||
createdb gratipay-test
|
||||
psql -q gratipay-test -c 'alter database "gratipay-test" set synchronous_commit to off'
|
||||
fi
|
||||
|
||||
if ! db_exists gratipay;
|
||||
then
|
||||
createdb gratipay
|
||||
fi
|
||||
|
||||
echo "done"
|
294
bin/masspay.py
294
bin/masspay.py
@ -1,294 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""This is a script for managing MassPay each week.
|
||||
|
||||
Most of our payouts are handled by Balanced, but they're limited to people in
|
||||
the U.S. We need to payout to people outside the U.S. (#126), and while we work
|
||||
on a long-term solution, we are using PayPal. However, we've grown past the
|
||||
point that PayPal's Instant Transfer feature is workable. This script is for
|
||||
interfacing with PayPal's MassPay feature.
|
||||
|
||||
This script provides for:
|
||||
|
||||
1. Computing an input CSV by hitting the Gratipay database directly.
|
||||
2. Computing two output CSVs (one to upload to PayPal, the second to use for POSTing
|
||||
the exchanges back to Gratipay)
|
||||
3. POSTing the exchanges back to Gratipay via the HTTP API.
|
||||
|
||||
The idea is that you run steps 1 and 2, then run through the MassPay UI on the
|
||||
PayPal website using the appropriate CSV from step 2, then run step 3.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
import csv
|
||||
import datetime
|
||||
import getpass
|
||||
import os
|
||||
import sys
|
||||
from decimal import Decimal as D, ROUND_HALF_UP
|
||||
|
||||
import requests
|
||||
from gratipay import wireup
|
||||
from httplib import IncompleteRead
|
||||
|
||||
|
||||
os.chdir('../masspay')
|
||||
ts = datetime.datetime.now().strftime('%Y-%m-%d')
|
||||
INPUT_CSV = '{}.input.csv'.format(ts)
|
||||
PAYPAL_CSV = '{}.output.paypal.csv'.format(ts)
|
||||
GRATIPAY_CSV = '{}.output.gratipay.csv'.format(ts)
|
||||
REPORT_CSV = '{}.report.paypal.csv'.format(ts)
|
||||
|
||||
|
||||
def round_(d):
|
||||
return d.quantize(D('0.01'), rounding=ROUND_HALF_UP)
|
||||
|
||||
def print_rule(w=80):
|
||||
print("-" * w)
|
||||
|
||||
|
||||
class Payee(object):
|
||||
username = None
|
||||
email = None
|
||||
gross = None
|
||||
gross_perc = None
|
||||
fee = None
|
||||
net = None
|
||||
additional_note = ""
|
||||
|
||||
def __init__(self, rec):
|
||||
self.username, self.email, fee_cap, amount = rec
|
||||
self.gross = D(amount)
|
||||
self.fee = D(0)
|
||||
self.fee_cap = D(fee_cap)
|
||||
self.net = self.gross
|
||||
|
||||
def assess_fee(self):
|
||||
|
||||
# In order to avoid slowly leaking escrow, we need to be careful about
|
||||
# how we compute the fee. It's complicated, but it goes something like
|
||||
# this:
|
||||
#
|
||||
# 1. We want to pass PayPal's fees through to each payee.
|
||||
#
|
||||
# 2. With MassPay there is no option to have the receiver pay the fee,
|
||||
# as there is with Instant Transfer.
|
||||
#
|
||||
# 3. We have to subtract the fee before uploading the spreadsheet
|
||||
# to PayPal.
|
||||
#
|
||||
# 4. If we upload 15.24, PayPal upcharges to 15.54.
|
||||
#
|
||||
# 6. If we upload 15.25, PayPal upcharges to 15.56.
|
||||
#
|
||||
# 7. They only accept whole cents. We can't upload 15.245.
|
||||
#
|
||||
# 8. What if we want to hit 15.55?
|
||||
#
|
||||
# 9. We can't.
|
||||
#
|
||||
# 10. Our solution is to leave a penny behind in Gratipay for
|
||||
# affected payees.
|
||||
#
|
||||
# 11. BUT ... if we upload 1.25, PayPal upcharges to 1.28. Think about
|
||||
# it.
|
||||
#
|
||||
# See also: https://github.com/gratipay/gratipay.com/issues/1673
|
||||
# https://github.com/gratipay/gratipay.com/issues/2029
|
||||
# https://github.com/gratipay/gratipay.com/issues/2198
|
||||
# https://github.com/gratipay/gratipay.com/pull/2209
|
||||
# https://github.com/gratipay/gratipay.com/issues/2296
|
||||
|
||||
target = net = self.gross
|
||||
while 1:
|
||||
net -= D('0.01')
|
||||
fee = round_(net * D('0.02'))
|
||||
fee = min(fee, self.fee_cap)
|
||||
gross = net + fee
|
||||
if gross <= target:
|
||||
break
|
||||
self.gross = gross
|
||||
self.net = net
|
||||
self.fee = fee
|
||||
|
||||
remainder = target - gross
|
||||
if remainder > 0:
|
||||
n = "{:.2} remaining due to PayPal rounding limitation.".format(remainder)
|
||||
self.additional_note = n
|
||||
|
||||
return fee
|
||||
|
||||
|
||||
def compute_input_csv():
|
||||
db = wireup.db(wireup.env())
|
||||
participants = db.all("""
|
||||
|
||||
SELECT p.*, r.address AS paypal_email, r.fee_cap AS paypal_fee_cap
|
||||
FROM exchange_routes r
|
||||
JOIN participants p ON p.id = r.participant
|
||||
WHERE r.network = 'paypal'
|
||||
AND p.balance > 0
|
||||
ORDER BY p.balance DESC
|
||||
|
||||
""")
|
||||
writer = csv.writer(open(INPUT_CSV, 'w+'))
|
||||
print_rule(88)
|
||||
headers = "username", "email", "fee cap", "balance", "tips", "amount"
|
||||
print("{:<24}{:<32} {:^7} {:^7} {:^7} {:^7}".format(*headers))
|
||||
print_rule(88)
|
||||
total_gross = 0
|
||||
for participant in participants:
|
||||
total = participant.giving + participant.pledging
|
||||
amount = participant.balance - total
|
||||
if amount < 0.50:
|
||||
# Minimum payout of 50 cents. I think that otherwise PayPal upcharges to a penny.
|
||||
# See https://github.com/gratipay/gratipay.com/issues/1958.
|
||||
continue
|
||||
total_gross += amount
|
||||
print("{:<24}{:<32} {:>7} {:>7} {:>7} {:>7}".format( participant.username
|
||||
, participant.paypal_email
|
||||
, participant.paypal_fee_cap
|
||||
, participant.balance
|
||||
, total
|
||||
, amount
|
||||
))
|
||||
row = (participant.username, participant.paypal_email, participant.paypal_fee_cap, amount)
|
||||
writer.writerow(row)
|
||||
print(" "*80, "-"*7)
|
||||
print("{:>88}".format(total_gross))
|
||||
|
||||
|
||||
def compute_output_csvs():
|
||||
payees = [Payee(rec) for rec in csv.reader(open(INPUT_CSV))]
|
||||
payees.sort(key=lambda o: o.gross, reverse=True)
|
||||
|
||||
total_fees = sum([payee.assess_fee() for payee in payees]) # side-effective!
|
||||
total_net = sum([p.net for p in payees])
|
||||
total_gross = sum([p.gross for p in payees])
|
||||
assert total_fees + total_net == total_gross
|
||||
|
||||
paypal_csv = csv.writer(open(PAYPAL_CSV, 'w+'))
|
||||
gratipay_csv = csv.writer(open(GRATIPAY_CSV, 'w+'))
|
||||
print_rule()
|
||||
print("{:<24}{:<32} {:^7} {:^7} {:^7}".format("username", "email", "gross", "fee", "net"))
|
||||
print_rule()
|
||||
for payee in payees:
|
||||
paypal_csv.writerow((payee.email, payee.net, "usd"))
|
||||
gratipay_csv.writerow(( payee.username
|
||||
, payee.email
|
||||
, payee.gross
|
||||
, payee.fee
|
||||
, payee.net
|
||||
, payee.additional_note
|
||||
))
|
||||
print("{username:<24}{email:<32} {gross:>7} {fee:>7} {net:>7}".format(**payee.__dict__))
|
||||
|
||||
print(" "*56, "-"*23)
|
||||
print("{:>64} {:>7} {:>7}".format(total_gross, total_fees, total_net))
|
||||
|
||||
|
||||
def load_statuses():
|
||||
_status_map = {'Completed': 'succeeded', 'Denied': 'failed'} # PayPal -> Gratipay
|
||||
statuses = {}
|
||||
fp = open(REPORT_CSV)
|
||||
for line in fp:
|
||||
if line.startswith('Transaction ID,Recipient'):
|
||||
break
|
||||
for rec in csv.reader(fp):
|
||||
statuses[rec[1]] = _status_map[rec[5]]
|
||||
return statuses
|
||||
|
||||
|
||||
def post_back_to_gratipay():
|
||||
|
||||
try:
|
||||
gratipay_api_key = os.environ['GRATIPAY_API_KEY']
|
||||
except KeyError:
|
||||
gratipay_api_key = getpass.getpass("Gratipay API key: ")
|
||||
|
||||
try:
|
||||
gratipay_base_url = os.environ['GRATIPAY_BASE_URL']
|
||||
except KeyError:
|
||||
gratipay_base_url = 'https://gratipay.com'
|
||||
|
||||
statuses = load_statuses()
|
||||
|
||||
nposts = 0
|
||||
for username, email, gross, fee, net, additional_note in csv.reader(open(GRATIPAY_CSV)):
|
||||
url = '{}/{}/history/record-an-exchange'.format(gratipay_base_url, username)
|
||||
note = 'PayPal MassPay to {}.'.format(email)
|
||||
if additional_note:
|
||||
note += " " + additional_note
|
||||
print(note)
|
||||
status = statuses[email]
|
||||
|
||||
data = {'amount': '-' + net, 'fee': fee, 'note': note, 'status': status}
|
||||
try:
|
||||
response = requests.post(url, auth=(gratipay_api_key, ''), data=data)
|
||||
except IncompleteRead:
|
||||
print('IncompleteRead, proceeding (but double-check!)')
|
||||
else:
|
||||
if response.status_code == 200:
|
||||
nposts += 1
|
||||
else:
|
||||
if response.status_code == 404:
|
||||
print('Got 404, is your API key good? {}'.format(gratipay_api_key))
|
||||
else:
|
||||
print('... resulted in a {} response:'.format(response.status_code))
|
||||
print(response.text)
|
||||
raise SystemExit
|
||||
print("POSTed MassPay back to Gratipay for {} users.".format(nposts))
|
||||
|
||||
|
||||
def run_report():
|
||||
"""Print a report to help Determine how much escrow we should store in PayPal.
|
||||
"""
|
||||
totals = []
|
||||
max_masspay = max_weekly_growth = D(0)
|
||||
for filename in os.listdir('.'):
|
||||
if not filename.endswith('.input.csv'):
|
||||
continue
|
||||
|
||||
datestamp = filename.split('.')[0]
|
||||
|
||||
totals.append(D(0))
|
||||
for rec in csv.reader(open(filename)):
|
||||
amount = rec[-1]
|
||||
totals[-1] += D(amount)
|
||||
|
||||
max_masspay = max(max_masspay, totals[-1])
|
||||
if len(totals) == 1:
|
||||
print("{} {:8}".format(datestamp, totals[-1]))
|
||||
else:
|
||||
weekly_growth = totals[-1] / totals[-2]
|
||||
max_weekly_growth = max(max_weekly_growth, weekly_growth)
|
||||
print("{} {:8} {:4.1f}".format(datestamp, totals[-1], weekly_growth))
|
||||
|
||||
print()
|
||||
print("Max Withdrawal: ${:9,.2f}".format(max_masspay))
|
||||
print("Max Weekly Growth: {:8.1f}".format(max_weekly_growth))
|
||||
print("5x Current: ${:9,.2f}".format(5 * totals[-1]))
|
||||
|
||||
|
||||
def main():
|
||||
if not sys.argv[1:]:
|
||||
print("Looking for files for {} ...".format(ts))
|
||||
for filename in (INPUT_CSV, PAYPAL_CSV, GRATIPAY_CSV):
|
||||
print(" [{}] {}".format('x' if os.path.exists(filename) else ' ', filename))
|
||||
print("Rerun with one of these options:")
|
||||
print(" -i - hits db to generate input CSV (needs envvars via heroku + honcho)")
|
||||
print(" -o - computes output CSVs (doesn't need anything but input CSV)")
|
||||
print(" -p - posts back to Gratipay (prompts for API key)")
|
||||
elif '-i' in sys.argv:
|
||||
compute_input_csv()
|
||||
elif '-o' in sys.argv:
|
||||
compute_output_csvs()
|
||||
elif '-p' in sys.argv:
|
||||
post_back_to_gratipay()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print()
|
@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# see https://github.com/gratipay/gratipay.com/issues/1771
|
||||
|
||||
dump=${1-`ls -r ~/gratipay-dumps/????-??-??.dump | head -1`}
|
||||
cores=`grep -c ^processor /proc/cpuinfo`
|
||||
echo "Clearing '$USER' database..."
|
||||
psql -q -c "drop schema public cascade; create schema public;" 2> /dev/null
|
||||
echo "Restoring $dump..."
|
||||
pg_restore --jobs $cores --no-acl --no-owner -d $USER $dump
|
||||
|
@ -1,69 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""This is a command line utility for managing Gratipay backups.
|
||||
|
||||
Running this script gets you a `snapper> ` prompt with commands to take backups
|
||||
and load them locally. Backups are managed as *.psql files in ../backups/, and
|
||||
they're loaded into a local gratipay-bak database. Type 'help' or '?' at the
|
||||
prompt for help.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
import cmd
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
||||
class Snapper(cmd.Cmd):
|
||||
|
||||
prompt = 'snapper> '
|
||||
root = '../backups'
|
||||
dbname = 'gratipay-bak'
|
||||
|
||||
def do_EOF(self, line):
|
||||
raise KeyboardInterrupt
|
||||
|
||||
def do_quit(self, line):
|
||||
raise SystemExit
|
||||
do_exit = do_quit
|
||||
|
||||
def do_new(self, line):
|
||||
"""Take a new backup.
|
||||
"""
|
||||
subprocess.call('./backup.sh')
|
||||
do_n = do_new
|
||||
|
||||
def do_list(self, line):
|
||||
"""List available backups.
|
||||
"""
|
||||
filenames = self.get_filenames()
|
||||
for i, filename in enumerate(filenames):
|
||||
print('{:>2} {}'.format(i, filename[:-len('.psql')]))
|
||||
do_l = do_list
|
||||
|
||||
def get_filenames(self):
|
||||
return sorted([f for f in os.listdir(self.root) if f.endswith('.psql')])
|
||||
|
||||
def do_load(self, line):
|
||||
"""Load a backup based on its number per `list`..
|
||||
"""
|
||||
try:
|
||||
i = int(line)
|
||||
filename = self.get_filenames()[i]
|
||||
except (ValueError, KeyError):
|
||||
print('\x1b[31;1mBad backup number!\x1b[0m')
|
||||
print('\x1b[32;1mPick one of these:\x1b[0m')
|
||||
self.do_list('')
|
||||
else:
|
||||
if subprocess.call(['dropdb', self.dbname]) == 0:
|
||||
if subprocess.call(['createdb', self.dbname]) == 0:
|
||||
subprocess.call( 'psql {} < {}/{}'.format(self.dbname, self.root, filename)
|
||||
, shell=True
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
Snapper().cmdloop()
|
||||
except KeyboardInterrupt:
|
||||
print()
|
@ -1,180 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""This is a one-off script to update user_info for #1936.
|
||||
|
||||
This could be generalized for #900.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
import os
|
||||
import time
|
||||
import sys
|
||||
|
||||
import requests
|
||||
from gratipay import wireup
|
||||
from requests_oauthlib import OAuth1
|
||||
|
||||
db = wireup.db(wireup.env())
|
||||
|
||||
def update_twitter():
|
||||
oauth = OAuth1( os.environ['TWITTER_CONSUMER_KEY']
|
||||
, os.environ['TWITTER_CONSUMER_SECRET']
|
||||
, os.environ['TWITTER_ACCESS_TOKEN']
|
||||
, os.environ['TWITTER_ACCESS_TOKEN_SECRET']
|
||||
)
|
||||
elsewhere = db.all("SELECT user_id FROM ELSEWHERE WHERE platform='twitter' ORDER BY id;")
|
||||
url = "https://api.twitter.com/1.1/users/lookup.json"
|
||||
|
||||
while elsewhere:
|
||||
batch = elsewhere[:100]
|
||||
elsewhere = elsewhere[100:]
|
||||
user_ids = ','.join([str(user_id) for user_id in batch])
|
||||
|
||||
response = requests.post(url, data={'user_id': user_ids}, auth=oauth)
|
||||
|
||||
|
||||
# Log the rate-limit.
|
||||
# ===================
|
||||
|
||||
nremaining = int(response.headers['X-RATE-LIMIT-REMAINING'])
|
||||
reset = int(response.headers['X-RATE-LIMIT-RESET'])
|
||||
print(nremaining, reset, time.time())
|
||||
|
||||
|
||||
if response.status_code != 200:
|
||||
|
||||
# Who knows what happened?
|
||||
# ========================
|
||||
# Supposedly we shouldn't hit 429, at least.
|
||||
|
||||
print(response.status_code, response.text)
|
||||
|
||||
else:
|
||||
|
||||
# Update!
|
||||
# =======
|
||||
|
||||
users = response.json()
|
||||
|
||||
with db.get_cursor() as c:
|
||||
|
||||
for user_info in users:
|
||||
|
||||
# flatten per upsert method in gratipay/elsewhere/__init__.py
|
||||
for k, v in user_info.items():
|
||||
user_info[k] = unicode(v)
|
||||
|
||||
user_id = user_info['id']
|
||||
|
||||
c.one("""
|
||||
UPDATE elsewhere
|
||||
SET user_info=%s
|
||||
WHERE user_id=%s
|
||||
AND platform='twitter'
|
||||
RETURNING id
|
||||
""", (user_info, user_id))
|
||||
|
||||
print("updated {} ({})".format(user_info['screen_name'], user_id))
|
||||
|
||||
# find deleted users
|
||||
existing = set(u['id'] for u in users)
|
||||
deleted = existing - set(batch)
|
||||
|
||||
for user_id in deleted:
|
||||
|
||||
c.one("""
|
||||
UPDATE elsewhere
|
||||
SET user_info=NULL
|
||||
WHERE user_id=%s
|
||||
AND platform='twitter'
|
||||
RETURNING id
|
||||
""", (user_id,))
|
||||
|
||||
print("orphan found: {}".format(user_id))
|
||||
|
||||
|
||||
# Stay under our rate limit.
|
||||
# =========================
|
||||
# We get 180 per 15 minutes for the users/lookup endpoint, per:
|
||||
#
|
||||
# https://dev.twitter.com/docs/rate-limiting/1.1/limits
|
||||
|
||||
sleep_for = 5
|
||||
if nremaining == 0:
|
||||
sleep_for = reset - time.time()
|
||||
sleep_for += 10 # Account for potential clock skew between us and Twitter.
|
||||
time.sleep(sleep_for)
|
||||
|
||||
def update_github():
|
||||
elsewhere = db.all("SELECT user_id FROM ELSEWHERE WHERE platform='github' ORDER BY id;")
|
||||
url = "https://api.github.com/user/%s"
|
||||
client_id = os.environ.get('GITHUB_CLIENT_ID')
|
||||
client_secret = os.environ.get('GITHUB_CLIENT_SECRET')
|
||||
|
||||
for user_id in elsewhere:
|
||||
response = requests.get(url % user_id, params={
|
||||
'client_id': client_id,
|
||||
'client_secret': client_secret
|
||||
})
|
||||
|
||||
# Log the rate-limit.
|
||||
# ===================
|
||||
|
||||
nremaining = int(response.headers['X-RATELIMIT-REMAINING'])
|
||||
reset = int(response.headers['X-RATELIMIT-RESET'])
|
||||
# https://developer.github.com/v3/#rate-limiting
|
||||
now = time.time()
|
||||
print(nremaining, reset, now, reset-now, end=' ')
|
||||
|
||||
status = response.status_code
|
||||
|
||||
if status == 200:
|
||||
|
||||
user_info = response.json()
|
||||
|
||||
# flatten per upsert method in gratipay/elsewhere/__init__.py
|
||||
for k, v in user_info.items():
|
||||
user_info[k] = unicode(v)
|
||||
|
||||
assert user_id == user_info['id']
|
||||
|
||||
db.one("""
|
||||
UPDATE elsewhere
|
||||
SET user_info=%s
|
||||
WHERE user_id=%s
|
||||
AND platform='github'
|
||||
RETURNING id
|
||||
""", (user_info, user_id))
|
||||
|
||||
print("updated {} ({})".format(user_info['login'], user_id))
|
||||
|
||||
elif status == 404:
|
||||
|
||||
db.one("""
|
||||
UPDATE elsewhere
|
||||
SET user_info=NULL
|
||||
WHERE user_id=%s
|
||||
AND platform='github'
|
||||
RETURNING id
|
||||
""", (user_id,))
|
||||
|
||||
print("orphan found: {}".format(user_id))
|
||||
else:
|
||||
# some other problem
|
||||
print(response.status_code, response.text)
|
||||
|
||||
sleep_for = 0.5
|
||||
if nremaining == 0:
|
||||
sleep_for = reset - time.time()
|
||||
sleep_for += 10 # Account for potential clock skew between us and them
|
||||
time.sleep(sleep_for)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) == 1:
|
||||
platform = raw_input("twitter or github?: ")
|
||||
else:
|
||||
platform = sys.argv[1]
|
||||
|
||||
if platform == 'twitter':
|
||||
update_twitter()
|
||||
elif platform == 'github':
|
||||
update_github()
|
69
defaults.env
69
defaults.env
@ -1,68 +1,63 @@
|
||||
DATABASE_URL="dbname=gratipay"
|
||||
DATABASE_URL="dbname=liberapay"
|
||||
|
||||
PYTHONDONTWRITEBYTECODE=true
|
||||
PORT=8537
|
||||
CANONICAL_HOST=localhost:8537
|
||||
PORT=8339
|
||||
CANONICAL_HOST=localhost:8339
|
||||
CANONICAL_SCHEME=http
|
||||
DATABASE_MAXCONN=10
|
||||
|
||||
GRATIPAY_ASSET_URL=/assets/
|
||||
GRATIPAY_CACHE_STATIC=no
|
||||
GRATIPAY_COMPRESS_ASSETS=no
|
||||
ASSET_URL=/assets/
|
||||
CACHE_STATIC=no
|
||||
COMPRESS_ASSETS=no
|
||||
|
||||
BALANCED_API_SECRET=ak-test-2zFgxfVijBzn4xC9dLRtLkxoB38iNKNKR
|
||||
|
||||
COINBASE_API_KEY=uETKVUrnPuXzVaVj
|
||||
COINBASE_API_SECRET=32zAkQCcHHYkGGn29VkvEZvn21PM1lgO
|
||||
BALANCED_API_SECRET=
|
||||
|
||||
OAUTHLIB_INSECURE_TRANSPORT=1
|
||||
OAUTHLIB_RELAX_TOKEN_SCOPE=1
|
||||
|
||||
GITHUB_CLIENT_ID=3785a9ac30df99feeef5
|
||||
GITHUB_CLIENT_SECRET=5f3ba73babf40b59ce5cc34f909136208f3b81b6
|
||||
GITHUB_CALLBACK=http://127.0.0.1:8537/on/github/associate
|
||||
GITHUB_CLIENT_ID=18891d01e40e5aef93b8
|
||||
GITHUB_CLIENT_SECRET=46f75669895e96029d57b64832d6f2c8e6291a0e
|
||||
GITHUB_CALLBACK=http://127.0.0.1:8339/on/github/associate
|
||||
|
||||
BITBUCKET_CONSUMER_KEY=pp8wEHmS43qVE6yHj5
|
||||
BITBUCKET_CONSUMER_SECRET=nmqwaZtwQWkpLE89Tc6XYKkWMxD84TbE
|
||||
BITBUCKET_CALLBACK=http://127.0.0.1:8537/on/bitbucket/associate
|
||||
BITBUCKET_CONSUMER_KEY=Fu9aQHB64WdVR7UxY4
|
||||
BITBUCKET_CONSUMER_SECRET=Y5G6A9BaWDxn2gLKZwwkrGtVE3Zjd7y7
|
||||
BITBUCKET_CALLBACK=http://127.0.0.1:8339/on/bitbucket/associate
|
||||
|
||||
TWITTER_CONSUMER_KEY=E9Jdz8gEa2jvJxeY8I9CVKjSi
|
||||
TWITTER_CONSUMER_SECRET=1gbEU3sqBVzadrLl1zlUoJDZV0RcbOBbUb0D4Q04QWEwRJq7t0
|
||||
TWITTER_CALLBACK=http://127.0.0.1:8537/on/twitter/associate
|
||||
TWITTER_CONSUMER_KEY=h8bBZtoPNz63S5RkZdbo9R5zb
|
||||
TWITTER_CONSUMER_SECRET=Jye64vkWxa2dQu64feTnk0BM3j4JO8ZlTa4EQvMDwrweLkwPaw
|
||||
TWITTER_CALLBACK=http://127.0.0.1:8339/on/twitter/associate
|
||||
|
||||
FACEBOOK_APP_ID=522653061204254
|
||||
FACEBOOK_APP_SECRET=d1cd7715dad7bc82c984d75cc2ece73f
|
||||
FACEBOOK_CALLBACK=http://localhost:8537/on/facebook/associate
|
||||
FACEBOOK_APP_ID=1418954898427187
|
||||
FACEBOOK_APP_SECRET=3bcb5dc6ce821e5202870c1e6ef5bbc4
|
||||
FACEBOOK_CALLBACK=http://localhost:8339/on/facebook/associate
|
||||
|
||||
GOOGLE_CLIENT_ID=536476350570-aigraucpr7s30auc031ef7hf1mcepumd.apps.googleusercontent.com
|
||||
GOOGLE_CLIENT_SECRET=0V0vEns6cNyI8Uv-hxt7Ft2Q
|
||||
GOOGLE_CALLBACK=http://localhost:8537/on/google/associate
|
||||
GOOGLE_CLIENT_ID=547044792904-jikvtdgeigpot9ek7tsb5660t8rp9p77.apps.googleusercontent.com
|
||||
GOOGLE_CLIENT_SECRET=7AkgPekyWr6stQWdM6y1TtV6
|
||||
GOOGLE_CALLBACK=http://localhost:8339/on/google/associate
|
||||
|
||||
BOUNTYSOURCE_API_SECRET=e2BbqjNY60kC7V-Uq1dv2oHgGavbWm9pUJmiRHCApFZHDiY9aZyAspInhZaZ94x9
|
||||
BOUNTYSOURCE_CALLBACK=http://127.0.0.1:8537/on/bountysource/associate
|
||||
BOUNTYSOURCE_API_SECRET=
|
||||
BOUNTYSOURCE_CALLBACK=http://127.0.0.1:8339/on/bountysource/associate
|
||||
BOUNTYSOURCE_API_HOST=https://staging-api.bountysource.com
|
||||
BOUNTYSOURCE_WWW_HOST=https://staging.bountysource.com
|
||||
|
||||
VENMO_CLIENT_ID=2031
|
||||
VENMO_CLIENT_SECRET=YaBbmZQVmQjfgL7NFekpyJv4PayUhwhb
|
||||
VENMO_CALLBACK=http://localhost:8537/on/venmo/associate
|
||||
VENMO_CLIENT_ID=
|
||||
VENMO_CLIENT_SECRET=
|
||||
VENMO_CALLBACK=http://localhost:8339/on/venmo/associate
|
||||
|
||||
OPENSTREETMAP_CONSUMER_KEY=2pHsNzigNVLXcqa3Vq6QOolb26HL62DtOxw6TT5E
|
||||
OPENSTREETMAP_CONSUMER_SECRET=vdNq2XeqNHOPOJ2rOABNbrKUv7GqHRgy7x46Brga
|
||||
OPENSTREETMAP_CALLBACK=http://127.0.0.1:8537/on/openstreetmap/associate
|
||||
OPENSTREETMAP_CONSUMER_KEY=
|
||||
OPENSTREETMAP_CONSUMER_SECRET=
|
||||
OPENSTREETMAP_CALLBACK=http://127.0.0.1:8339/on/openstreetmap/associate
|
||||
OPENSTREETMAP_API_URL=http://www.openstreetmap.org/api/0.6
|
||||
OPENSTREETMAP_AUTH_URL=http://www.openstreetmap.org
|
||||
|
||||
UPDATE_GLOBAL_STATS_EVERY=300
|
||||
CHECK_DB_EVERY=600
|
||||
DEQUEUE_EMAILS_EVERY=60
|
||||
OPTIMIZELY_ID=
|
||||
INCLUDE_PIWIK=no
|
||||
SENTRY_DSN=
|
||||
LOG_METRICS=0
|
||||
|
||||
ASPEN_CHANGES_RELOAD=yes
|
||||
ASPEN_NETWORK_ADDRESS=:8537
|
||||
ASPEN_NETWORK_ADDRESS=:8339
|
||||
ASPEN_PROJECT_ROOT=.
|
||||
ASPEN_SHOW_TRACEBACKS=yes
|
||||
ASPEN_WWW_ROOT=www/
|
||||
@ -70,6 +65,6 @@ ASPEN_WWW_ROOT=www/
|
||||
# https://github.com/benoitc/gunicorn/issues/186
|
||||
GUNICORN_OPTS="--workers=1 --timeout=99999999"
|
||||
|
||||
MANDRILL_KEY=Phh_Lm3RdPT5blqOPY4dVQ
|
||||
MANDRILL_KEY=
|
||||
|
||||
RAISE_SIGNIN_NOTIFICATIONS=no
|
||||
|
@ -1,49 +0,0 @@
|
||||
# Makefile for Sphinx documentation
|
||||
# Trimmed up from the
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = ../env/bin/swaddle ../tests/env ../env/bin/sphinx-build
|
||||
BUILDDIR = _build
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
#ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
#$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
#endif
|
||||
|
||||
# Internal variables.
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html linkcheck doctest
|
||||
|
||||
default: html
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
rm -rf gratipay* $(BUILDDIR)/*
|
||||
|
||||
rst:
|
||||
AUTOLIB_LIBRARY_ROOT=../gratipay ./autolib.py
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""Generate *.rst files to mirror *.py files in a Python library.
|
||||
|
||||
This script is conceptually similar to the sphinx-apidoc script bundled with
|
||||
Sphinx:
|
||||
|
||||
http://sphinx-doc.org/man/sphinx-apidoc.html
|
||||
|
||||
We produce different *.rst output, however.
|
||||
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals
|
||||
import os
|
||||
|
||||
|
||||
w = lambda f, s, *a, **kw: print(s.format(*a, **kw), file=f)
|
||||
|
||||
|
||||
def rst_for_module(toc_path):
|
||||
"""Given a toc_path, write rst and return a file object.
|
||||
"""
|
||||
|
||||
f = open(toc_path + '.rst', 'w+')
|
||||
|
||||
heading = ":mod:`{}`".format(os.path.basename(toc_path))
|
||||
dotted = toc_path.replace('/', '.')
|
||||
|
||||
w(f, heading)
|
||||
w(f, "=" * len(heading))
|
||||
w(f, ".. automodule:: {}", dotted)
|
||||
|
||||
return f
|
||||
|
||||
|
||||
def rst_for_package(root, dirs, files):
|
||||
"""Given ../mylib/path/to/package and lists of dir/file names, write rst.
|
||||
"""
|
||||
|
||||
doc_path = root[3:]
|
||||
if not os.path.isdir(doc_path):
|
||||
os.mkdir(doc_path)
|
||||
|
||||
|
||||
# Start a rst doc for this package.
|
||||
# =================================
|
||||
|
||||
f = rst_for_module(doc_path)
|
||||
|
||||
|
||||
# Add a table of contents.
|
||||
# ========================
|
||||
|
||||
w(f, ".. toctree::")
|
||||
|
||||
def toc(doc_path, name):
|
||||
parent = os.path.dirname(doc_path)
|
||||
toc_path = os.path.join(doc_path[len(parent):].lstrip('/'), name)
|
||||
if toc_path.endswith('.py'):
|
||||
toc_path = toc_path[:-len('.py')]
|
||||
w(f, " {}", toc_path)
|
||||
return os.path.join(parent, toc_path)
|
||||
|
||||
for name in sorted(dirs + files):
|
||||
if name in dirs:
|
||||
toc(doc_path, name)
|
||||
else:
|
||||
if not name.endswith('.py'): continue
|
||||
if name == '__init__.py': continue
|
||||
|
||||
toc_path = toc(doc_path, name)
|
||||
|
||||
|
||||
# Write a rst file for each module.
|
||||
# =================================
|
||||
|
||||
rst_for_module(toc_path)
|
||||
|
||||
|
||||
def main():
|
||||
library_root = os.environ['AUTOLIB_LIBRARY_ROOT']
|
||||
for root, dirs, files in os.walk(library_root):
|
||||
rst_for_package(root, dirs, files)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
267
docs/conf.py
267
docs/conf.py
@ -1,267 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Gratipay documentation build configuration file, created by
|
||||
# sphinx-quickstart on Thu Aug 8 23:20:15 2013.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Gratipay'
|
||||
copyright = u'2014, Gratipay, LLC'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '-'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '-'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
|
||||
# -- Generate RST files -------------------------------------------------------
|
||||
|
||||
# We do this in here instead of in the Makefile so that RTD picks this up.
|
||||
os.environ['AUTOLIB_LIBRARY_ROOT'] = '../gratipay'
|
||||
os.system("./autolib.py")
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Gratipaydoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Gratipay.tex', u'Gratipay Documentation',
|
||||
u'Gratipay, LLC', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'gratipay', u'Gratipay Documentation',
|
||||
[u'Gratipay, LLC'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Gratipay', u'Gratipay Documentation',
|
||||
u'Gratipay, LLC', 'Gratipay', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'http://docs.python.org/': None}
|
||||
|
||||
autodoc_default_flags = ['members', 'member-order: bysource']
|
||||
|
||||
import mock
|
||||
|
||||
MOCK_MODULES = ['scipy', 'scipy.sparse']
|
||||
for mod_name in MOCK_MODULES:
|
||||
sys.modules[mod_name] = mock.Mock()
|
@ -1,36 +0,0 @@
|
||||
gratipay.com
|
||||
==============
|
||||
|
||||
Welcome! This is the documentation for programmers working on `gratipay.com`_
|
||||
(not to be confused with programmers working with Gratipay's `web API`_).
|
||||
|
||||
.. _gratipay.com: https://github.com/gratipay/gratipay.com
|
||||
.. _web API: https://github.com/gratipay/gratipay.com#api
|
||||
|
||||
|
||||
DB Schema
|
||||
---------
|
||||
|
||||
is_suspipicous on participant can be None, True or False. It represents unknown,
|
||||
blacklisted or whitelisted user.
|
||||
|
||||
* whitelisted can transfer money out of gratipay
|
||||
* unknown can move money within gratipay
|
||||
* blacklisted cannot do anything
|
||||
|
||||
|
||||
The exchanges table records movements of money into and out of Gratipay. The
|
||||
``amount`` column shows a positive amount for payins and a negative amount for
|
||||
payouts. The ``fee`` column is always positive. For both payins and payouts,
|
||||
the ``amount`` does not include the ``fee`` (e.g., a $10 payin would result in
|
||||
an ``amount`` of ``9.41`` and a ``fee`` of ``0.59``, and a $100 payout with a
|
||||
2% fee would result in an ``amount`` of ``-98.04`` and a fee of ``1.96``).
|
||||
|
||||
|
||||
Contents
|
||||
--------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
gratipay Python library <gratipay>
|
242
docs/make.bat
242
docs/make.bat
@ -1,242 +0,0 @@
|
||||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. xml to make Docutils-native XML files
|
||||
echo. pseudoxml to make pseudoxml-XML files for display purposes
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
|
||||
%SPHINXBUILD% 2> nul
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Gratipay.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Gratipay.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdf" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf
|
||||
cd %BUILDDIR%/..
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdfja" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf-ja
|
||||
cd %BUILDDIR%/..
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "xml" (
|
||||
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The XML files are in %BUILDDIR%/xml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pseudoxml" (
|
||||
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
@ -1,6 +1,6 @@
|
||||
[---] text/html
|
||||
<div style="text-align: center; padding: 20px 0; margin: 0;">
|
||||
<img src="https://downloads.gratipay.com/email/gratipay.png" alt="Gratipay">
|
||||
<img src="https://static.liberapay.com/logo/banner.png" alt="Liberapay">
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; font: normal 14px/21px Arial, sans-serif; color: #333; padding: 0 20%;">
|
||||
@ -11,12 +11,9 @@ $body
|
||||
<div style="font: normal 14px/21px Arial, sans-serif;">
|
||||
{{ _("Something not right? Reply to this email for help.") }}
|
||||
</div>
|
||||
<div style="font: normal 10px/21px Arial, sans-serif;">
|
||||
Sent by <a href="https://gratipay.com/" style="color: #999; text-decoration: underline;">Gratipay, LLC</a> | 716 Park Road, Ambridge, PA, 15003, USA
|
||||
</div>
|
||||
{% if include_unsubscribe %}
|
||||
<div style="font: normal 10px/21px Arial, sans-serif; padding-top: 10px;">
|
||||
To stop receiving emails like this, change your <a href="https://gratipay.com/about/me/settings/#notifications" style="color: #999; text-decoration: underline;">notification preferences</a>.
|
||||
To stop receiving emails like this, change your <a href="https://liberapay.com/about/me/settings/#notifications" style="color: #999; text-decoration: underline;">notification preferences</a>.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -30,9 +27,6 @@ $body
|
||||
|
||||
----
|
||||
|
||||
Sent by Gratipay, LLC, https://gratipay.com/
|
||||
716 Park Road, Ambridge, PA, 15003, USA
|
||||
|
||||
{% if include_unsubscribe %}
|
||||
To stop receiving emails like this, change your notification preferences by visiting https://gratipay.com/about/me/settings/#notifications
|
||||
To stop receiving emails like this, change your notification preferences by visiting https://liberapay.com/about/me/settings/#notifications
|
||||
{% endif %}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{{ _("Thanks for supporting {0}!", top_tippee) }}
|
||||
|
||||
[---] text/html
|
||||
{{ _("We charged your credit card {0} today, to fund your ongoing support for {1}. Thanks for using Gratipay!",
|
||||
{{ _("We charged your credit card {0} today, to fund your ongoing support for {1}. Thanks for using Liberapay!",
|
||||
format_currency(exchange.amount + exchange.fee, 'USD'),
|
||||
('<b><a href="{0}">{1}</a></b>'|safe).format(
|
||||
participant.profile_url+'giving/',
|
||||
@ -15,7 +15,7 @@
|
||||
style="{{ button_style }}">{{ _("View Receipt") }}</a>
|
||||
|
||||
[---] text/plain
|
||||
{{ _("We charged your credit card {0} today, to fund your ongoing support for {1}. Thanks for using Gratipay!",
|
||||
{{ _("We charged your credit card {0} today, to fund your ongoing support for {1}. Thanks for using Liberapay!",
|
||||
format_currency(exchange.amount + exchange.fee, 'USD'),
|
||||
top_tippee if ntippees == 1 else ngettext('{0} and {n} other',
|
||||
'{0} and {n} others',
|
||||
|
@ -1,21 +0,0 @@
|
||||
{{ _("Connect to {0} on Gratipay?", username) }}
|
||||
|
||||
[---] text/html
|
||||
A while ago we received a request to connect <b>{{ escape(email) }}</b> to the
|
||||
{{ '<b><a href="https://gratipay.com/{0}/">{0}</a></b>'.format(username) }}
|
||||
account on Gratipay (<a
|
||||
href="https://medium.com/gratipay-blog/gratitude-gratipay-ef24ad5e41f9">formerly</a>
|
||||
Gittip). Now we're finally sending a verification email! Ring a bell?
|
||||
<br>
|
||||
<br>
|
||||
<a href="{{ link }}" style="color: #fff; text-decoration:none; display:inline-block; padding: 0 15px; background: #396; font: normal 14px/40px Arial, sans-serif; white-space: nowrap; border-radius: 3px">Yes, proceed!</a>
|
||||
|
||||
[---] text/plain
|
||||
|
||||
A while ago we received a request to connect `{{ email }}`
|
||||
to the `{{ username }}` account on Gratipay (formerly Gittip).
|
||||
Now we're finally sending a verification email!
|
||||
|
||||
Ring a bell? Follow this link to finish connecting your email:
|
||||
|
||||
{{ link }}
|
@ -1,11 +1,11 @@
|
||||
{{ _("{0} from {1} has joined Gratipay!", user_name, platform) }}
|
||||
{{ _("{0} from {1} has joined Liberapay!", user_name, platform) }}
|
||||
|
||||
[---] text/html
|
||||
{{ _("Your pledge to give {0} every week to {1} will be turned into action now that they have joined Gratipay. Huzzah!",
|
||||
{{ _("Your pledge to give {0} every week to {1} will be turned into action now that they have joined Liberapay. Huzzah!",
|
||||
format_currency(amount, 'USD'),
|
||||
('<b><a href="{0}">{1}</a></b>'|safe).format(profile_url, user_name)) }}
|
||||
[---] text/plain
|
||||
{{ _("Your pledge to give {0} every week to {1} will be turned into action now that they have joined Gratipay. Huzzah!",
|
||||
{{ _("Your pledge to give {0} every week to {1} will be turned into action now that they have joined Liberapay. Huzzah!",
|
||||
format_currency(amount, 'USD'),
|
||||
user_name) }}
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
{{ _("Connect to {0} on Gratipay?", username) }}
|
||||
{{ _("Connect to {0} on Liberapay?", username) }}
|
||||
|
||||
[---] text/html
|
||||
{{ _("We've received a request to connect {0} to the {1} account on Gratipay. Sound familiar?",
|
||||
{{ _("We've received a request to connect {0} to the {1} account on Liberapay. Sound familiar?",
|
||||
('<b>%s</b>'|safe) % email,
|
||||
('<b><a href="https://gratipay.com/{0}">{0}</a></b>'|safe).format(username)) }}
|
||||
('<b><a href="https://liberapay.com/{0}">{0}</a></b>'|safe).format(username)) }}
|
||||
<br>
|
||||
<br>
|
||||
<a href="{{ link }}" style="{{ button_style }}">{{ _("Yes, proceed!") }}</a>
|
||||
|
||||
[---] text/plain
|
||||
{{ _("We've received a request to connect {0} to the {1} account on Gratipay. Sound familiar?",
|
||||
{{ _("We've received a request to connect {0} to the {1} account on Liberapay. Sound familiar?",
|
||||
email, username) }}
|
||||
|
||||
{{ _("Follow this link to finish connecting your email:") }}
|
||||
|
@ -1,13 +1,13 @@
|
||||
{{ _("Connecting {0} to {1} on Gratipay.", new_email, username) }}
|
||||
{{ _("Connecting {0} to {1} on Liberapay.", new_email, username) }}
|
||||
|
||||
[---] text/html
|
||||
{{ _("We are connecting {0} to the {1} account on Gratipay. This is a notification "
|
||||
{{ _("We are connecting {0} to the {1} account on Liberapay. This is a notification "
|
||||
"sent to {2} because that is the primary email address we have on file.",
|
||||
('<b>%s</b>'|safe) % new_email,
|
||||
('<b><a href="https://gratipay.com/{0}">{0}</a></b>'|safe).format(username),
|
||||
('<b><a href="https://liberapay.com/{0}">{0}</a></b>'|safe).format(username),
|
||||
('<b>%s</b>'|safe) % email) }}
|
||||
|
||||
[---] text/plain
|
||||
{{ _("We are connecting {0} to the {1} account on Gratipay. This is a notification "
|
||||
{{ _("We are connecting {0} to the {1} account on Liberapay. This is a notification "
|
||||
"sent to {2} because that is the primary email address we have on file.",
|
||||
new_email, username, email) }}
|
||||
|
@ -2,8 +2,8 @@ from __future__ import absolute_import, division, print_function, unicode_litera
|
||||
|
||||
from aspen.http import status_strings
|
||||
|
||||
from gratipay.utils import LazyResponse
|
||||
from gratipay.utils.i18n import HTTP_ERRORS
|
||||
from liberapay.utils import LazyResponse
|
||||
from liberapay.utils.i18n import HTTP_ERRORS
|
||||
|
||||
[----------------------------------------]
|
||||
|
||||
|
122
gratipay.py
122
gratipay.py
@ -1,122 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""\
|
||||
Gratipay
|
||||
~~~~~~
|
||||
|
||||
A personal funding platform.
|
||||
|
||||
Dependencies:
|
||||
- Python 2.7
|
||||
- Postgresql 9.2
|
||||
|
||||
To run:
|
||||
$ gratipay.py
|
||||
|
||||
This will also initialize a local environment on the first run.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
from subprocess import check_call, check_output, STDOUT, CalledProcessError
|
||||
|
||||
|
||||
is_win = sys.platform.startswith('win')
|
||||
bin_dir = 'Scripts' if is_win else 'bin'
|
||||
ext = '.exe' if is_win else ''
|
||||
|
||||
default_port = 8537
|
||||
vendor_path = 'vendor'
|
||||
env_path = 'env'
|
||||
requirements_installed_path = os.path.join(env_path, '.requirements_installed')
|
||||
bin_path = os.path.join(env_path, bin_dir)
|
||||
default_config_path = 'default_local.env'
|
||||
config_path = 'local.env'
|
||||
virtualenv_path = os.path.join(vendor_path, 'virtualenv-12.0.7.py')
|
||||
pip_path = os.path.join(bin_path, 'pip' + ext)
|
||||
swaddle_path = os.path.join(bin_path, 'swaddle' + ext)
|
||||
aspen_path = os.path.join(bin_path, 'aspen' + ext)
|
||||
|
||||
|
||||
def main():
|
||||
# TODO: Handle command-line arguments to override default config values
|
||||
# e.g. the address and port to serve on, whether to run tests, etc
|
||||
|
||||
try:
|
||||
bootstrap_environment()
|
||||
except CalledProcessError as ex:
|
||||
print ex.output
|
||||
except EnvironmentError as ex:
|
||||
print 'Error:', ex
|
||||
return 1
|
||||
|
||||
run_server()
|
||||
|
||||
|
||||
def bootstrap_environment():
|
||||
ensure_dependencies()
|
||||
init_config()
|
||||
init_virtualenv()
|
||||
install_requirements()
|
||||
|
||||
|
||||
def ensure_dependencies():
|
||||
if not shell('python', '--version', capture=True).startswith('Python 2.7'):
|
||||
raise EnvironmentError('Python 2.7 is required.')
|
||||
|
||||
try:
|
||||
shell('pg_config' + ext, capture=True)
|
||||
except OSError as e:
|
||||
if e.errno != os.errno.ENOENT:
|
||||
raise
|
||||
raise EnvironmentError('Postgresql is required. (Make sure pg_config is on your PATH.)')
|
||||
|
||||
|
||||
def init_config():
|
||||
if os.path.exists(config_path):
|
||||
return
|
||||
|
||||
print 'Creating a %s file...' % config_path
|
||||
shutil.copyfile(default_config_path, config_path)
|
||||
|
||||
|
||||
def init_virtualenv():
|
||||
if os.path.exists(env_path):
|
||||
return
|
||||
|
||||
print 'Initializing virtualenv at %s...' % env_path
|
||||
|
||||
shell('python', virtualenv_path,
|
||||
'--prompt="[gratipay] "',
|
||||
'--extra-search-dir=' + vendor_path,
|
||||
'--always-copy',
|
||||
env_path)
|
||||
|
||||
|
||||
def install_requirements():
|
||||
# TODO: Detect when requirements.txt changes instead of checking for a file
|
||||
if os.path.exists(requirements_installed_path):
|
||||
return
|
||||
|
||||
print 'Installing requirements...'
|
||||
|
||||
shell(pip_path, 'install', '-r', 'requirements.txt')
|
||||
shell(pip_path, 'install', os.path.join(vendor_path, 'nose-1.1.2.tar.gz'))
|
||||
shell(pip_path, 'install', '-e', '.')
|
||||
|
||||
open(requirements_installed_path, 'w').close()
|
||||
|
||||
|
||||
def run_server():
|
||||
# TODO: Wait for Aspen to quit before exiting
|
||||
shell(swaddle_path, config_path, aspen_path)
|
||||
|
||||
|
||||
def shell(*args, **kwargs):
|
||||
if kwargs.pop('capture', None):
|
||||
return check_output(args, stderr=STDOUT, **kwargs)
|
||||
return check_call(args, **kwargs)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
@ -1,3 +0,0 @@
|
||||
The files in this directory are not meant to be modified by Pull Requests. If
|
||||
you want to contribute to the internationalization of Gratipay please join our
|
||||
translation teams on Transifex: https://www.transifex.com/projects/p/gratipay/
|
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="2048" height="2048" viewBox="-256 -384 2048 2048">
|
||||
<rect x="-256" y="-384" width="2048" height="2048" fill="#D7E4DE" />
|
||||
<circle cx="767" cy="640" r="767" fill="#BBB1A7" />
|
||||
<path d="M 763,1046 413,573 q -16,-22 -24.5,-59 -8.5,-37 6,-85 14.5,-48 61.5,-79 40,-26 83,-25.5 43,0.5 73.5,17.5 30.5,17 54.5,45 36,40 96,40 59,0 95,-40 24,-28 54.5,-45 30.5,-17 73.5,-17.5 43,-0.5 84,25.5 46,31 60.5,79 14.5,48 6,85 -8.5,37 -24.5,59 z" fill="#FFFFFF" />
|
||||
</svg>
|
Before Width: | Height: | Size: 559 B |
Binary file not shown.
Before Width: | Height: | Size: 165 KiB |
@ -1,32 +1,26 @@
|
||||
/* Main namespace.
|
||||
* ===============
|
||||
* Individual modules are in the gratipay/ directory.
|
||||
*/
|
||||
Liberapay = {};
|
||||
|
||||
Gratipay = {};
|
||||
|
||||
Gratipay.getCookie = function(key) {
|
||||
Liberapay.getCookie = function(key) {
|
||||
var o = new RegExp("(?:^|; ?)" + escape(key) + "=([^;]+)").exec(document.cookie);
|
||||
return o && unescape(o[1]);
|
||||
}
|
||||
|
||||
Gratipay.init = function() {
|
||||
Gratipay.forms.initCSRF();
|
||||
Gratipay.signIn();
|
||||
Gratipay.signOut();
|
||||
Gratipay.tips.initSupportGratipay();
|
||||
Liberapay.init = function() {
|
||||
Liberapay.forms.initCSRF();
|
||||
Liberapay.signIn();
|
||||
Liberapay.signOut();
|
||||
};
|
||||
|
||||
Gratipay.error = function(jqXHR, textStatus, errorThrown) {
|
||||
Liberapay.error = function(jqXHR, textStatus, errorThrown) {
|
||||
var msg = null;
|
||||
try {
|
||||
msg = JSON.parse(jqXHR.responseText).error_message_long;
|
||||
} catch(exc) {}
|
||||
if(!msg) {
|
||||
msg = "An error occurred (" + (errorThrown || textStatus) + ").\n" +
|
||||
"Please contact support@gratipay.com if the problem persists.";
|
||||
"Please contact support@liberapay.com if the problem persists.";
|
||||
}
|
||||
Gratipay.notification(msg, 'error', -1);
|
||||
Liberapay.notification(msg, 'error', -1);
|
||||
}
|
||||
|
||||
|
||||
@ -34,12 +28,12 @@ Gratipay.error = function(jqXHR, textStatus, errorThrown) {
|
||||
// ===================
|
||||
// yanked from gttp.co/v1/api.js
|
||||
|
||||
Gratipay.each = function(a, fn) {
|
||||
Liberapay.each = function(a, fn) {
|
||||
for (var i=0; i<a.length; i++)
|
||||
fn(a[i], i, length);
|
||||
};
|
||||
|
||||
Gratipay.jsoncss = function(jsoncss) {
|
||||
Liberapay.jsoncss = function(jsoncss) {
|
||||
var out = '';
|
||||
|
||||
this.each(jsoncss, function(selector) {
|
||||
@ -59,7 +53,7 @@ Gratipay.jsoncss = function(jsoncss) {
|
||||
return this.jsonml(['style', out]);
|
||||
};
|
||||
|
||||
Gratipay.jsonml = function(jsonml) {
|
||||
Liberapay.jsonml = function(jsonml) {
|
||||
var node = document.createElement(jsonml[0]),
|
||||
_ = this;
|
||||
|
||||
@ -85,7 +79,7 @@ Gratipay.jsonml = function(jsonml) {
|
||||
return node;
|
||||
};
|
||||
|
||||
Gratipay.signIn = function() {
|
||||
Liberapay.signIn = function() {
|
||||
$('.sign-in > .dropdown').mouseenter(function(e) {
|
||||
clearTimeout($(this).data('timeoutId'));
|
||||
$(this).addClass('open');
|
||||
@ -113,7 +107,7 @@ Gratipay.signIn = function() {
|
||||
});
|
||||
};
|
||||
|
||||
Gratipay.signOut = function() {
|
||||
Liberapay.signOut = function() {
|
||||
$('a#sign-out').click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
@ -124,7 +118,7 @@ Gratipay.signOut = function() {
|
||||
success: function() {
|
||||
window.location.href = window.location.href;
|
||||
},
|
||||
error: Gratipay.error
|
||||
error: Liberapay.error
|
||||
});
|
||||
});
|
||||
};
|
@ -1,13 +1,13 @@
|
||||
Gratipay.charts = {};
|
||||
Liberapay.charts = {};
|
||||
|
||||
// After retrieving the JSON data, wait for document ready event.
|
||||
Gratipay.charts.make = function(series) {
|
||||
Liberapay.charts.make = function(series) {
|
||||
$(document).ready(function() {
|
||||
Gratipay.charts._make(series);
|
||||
Liberapay.charts._make(series);
|
||||
});
|
||||
};
|
||||
|
||||
Gratipay.charts._make = function(series) {
|
||||
Liberapay.charts._make = function(series) {
|
||||
if (!series.length) {
|
||||
$('.chart-wrapper').remove();
|
||||
return;
|
27
js/communities.js
Normal file
27
js/communities.js
Normal file
@ -0,0 +1,27 @@
|
||||
Liberapay.communities = {};
|
||||
|
||||
Liberapay.communities.update = function(name, is_member, success_callback, error_callback) {
|
||||
jQuery.ajax(
|
||||
{ type: 'POST'
|
||||
, url: '/for/communities.json'
|
||||
, data: {name: name, is_member: is_member}
|
||||
, dataType: 'json'
|
||||
, success: success_callback
|
||||
, error: error_callback
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Liberapay.communities.jumpTo = function(slug) {
|
||||
window.location.href = "/for/" + slug + "/";
|
||||
};
|
||||
|
||||
Liberapay.communities.join = function(name, success_callback, error_callback) {
|
||||
Liberapay.communities.update(name, true, success_callback, error_callback);
|
||||
};
|
||||
|
||||
Liberapay.communities.leave = function(name, callback) {
|
||||
if (confirm("Are you sure you want to leave the " + name + " community?"))
|
||||
Liberapay.communities.update(name, false, callback);
|
||||
|
||||
};
|
@ -1,20 +0,0 @@
|
||||
// Degrade the console obj where not present.
|
||||
// ==========================================
|
||||
// http://fbug.googlecode.com/svn/branches/firebug1.2/lite/firebugx.js
|
||||
// Relaxed to allow for Chrome's console.
|
||||
|
||||
function mock_console()
|
||||
{
|
||||
var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
|
||||
"dirxml", "group", "groupEnd", "time", "timeEnd", "count",
|
||||
"trace", "profile", "profileEnd"];
|
||||
window.console = {};
|
||||
var mock = function() {};
|
||||
for (var i=0, name; name = names[i]; i++)
|
||||
window.console[name] = mock;
|
||||
}
|
||||
|
||||
if (!window.console)
|
||||
{
|
||||
mock_console();
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
// Form Generics
|
||||
// =============
|
||||
|
||||
Gratipay.forms = {};
|
||||
Liberapay.forms = {};
|
||||
|
||||
Gratipay.forms.initCSRF = function() { // https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax
|
||||
Liberapay.forms.initCSRF = function() { // https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax
|
||||
jQuery(document).ajaxSend(function(event, xhr, settings) {
|
||||
function sameOrigin(url) {
|
||||
// url could be relative or scheme relative or absolute
|
||||
@ -23,13 +23,12 @@ Gratipay.forms.initCSRF = function() { // https://docs.djangoproject.com/en/de
|
||||
|
||||
if (!safeMethod(settings.type) && sameOrigin(settings.url)) {
|
||||
// We have to avoid httponly on the csrf_token cookie because of this.
|
||||
// https://github.com/gratipay/gratipay.com/issues/3030
|
||||
xhr.setRequestHeader("X-CSRF-TOKEN", Gratipay.getCookie('csrf_token'));
|
||||
xhr.setRequestHeader("X-CSRF-TOKEN", Liberapay.getCookie('csrf_token'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Gratipay.forms.jsEdit = function(params) {
|
||||
Liberapay.forms.jsEdit = function(params) {
|
||||
|
||||
var $root = $(params.root);
|
||||
var $form = $root.find('form.edit');
|
||||
@ -92,7 +91,7 @@ Gratipay.forms.jsEdit = function(params) {
|
||||
},
|
||||
error: params.error || [
|
||||
function () { $inputs.prop('disabled', false); },
|
||||
Gratipay.error,
|
||||
Liberapay.error,
|
||||
],
|
||||
});
|
||||
}
|
||||
@ -101,14 +100,14 @@ Gratipay.forms.jsEdit = function(params) {
|
||||
|
||||
};
|
||||
|
||||
Gratipay.forms.clearInvalid = function($form) {
|
||||
Liberapay.forms.clearInvalid = function($form) {
|
||||
$form.find('.invalid').removeClass('invalid');
|
||||
};
|
||||
|
||||
Gratipay.forms.focusInvalid = function($form) {
|
||||
Liberapay.forms.focusInvalid = function($form) {
|
||||
$form.find('.invalid').eq(0).focus();
|
||||
};
|
||||
|
||||
Gratipay.forms.setInvalid = function($input, invalid) {
|
||||
Liberapay.forms.setInvalid = function($input, invalid) {
|
||||
$input.toggleClass('invalid', invalid);
|
||||
};
|
@ -1,27 +0,0 @@
|
||||
Gratipay.communities = {};
|
||||
|
||||
Gratipay.communities.update = function(name, is_member, success_callback, error_callback) {
|
||||
jQuery.ajax(
|
||||
{ type: 'POST'
|
||||
, url: '/for/communities.json'
|
||||
, data: {name: name, is_member: is_member}
|
||||
, dataType: 'json'
|
||||
, success: success_callback
|
||||
, error: error_callback
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
Gratipay.communities.jumpTo = function(slug) {
|
||||
window.location.href = "/for/" + slug + "/";
|
||||
};
|
||||
|
||||
Gratipay.communities.join = function(name, success_callback, error_callback) {
|
||||
Gratipay.communities.update(name, true, success_callback, error_callback);
|
||||
};
|
||||
|
||||
Gratipay.communities.leave = function(name, callback) {
|
||||
if (confirm("Are you sure you want to leave the " + name + " community?"))
|
||||
Gratipay.communities.update(name, false, callback);
|
||||
|
||||
};
|
@ -1,2 +0,0 @@
|
||||
// Localize gratipayURI for gttp.co widgets
|
||||
gratipayURI = '/';
|
@ -3,8 +3,8 @@ $(document).ready(function() {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var platform = Gratipay.trim($('#jump select').val()),
|
||||
val = Gratipay.trim($('#jump input').val());
|
||||
var platform = Liberapay.trim($('#jump select').val()),
|
||||
val = Liberapay.trim($('#jump input').val());
|
||||
if (val) window.location = '/on/' + platform + '/' + val + '/';
|
||||
});
|
||||
});
|
@ -2,14 +2,14 @@
|
||||
* Display a notification
|
||||
* Valid notification types are: "notice", "error", and "success".
|
||||
*/
|
||||
Gratipay.notification = function(text, type, timeout, closeCallback) {
|
||||
Liberapay.notification = function(text, type, timeout, closeCallback) {
|
||||
var type = type || 'notice';
|
||||
var timeout = timeout || (type == 'error' ? 10000 : 5000);
|
||||
|
||||
var dialog = ['div', { 'class': 'notification notification-' + type }, [ 'div', text ]];
|
||||
var $dialog = $([
|
||||
Gratipay.jsonml(dialog),
|
||||
Gratipay.jsonml(dialog)
|
||||
Liberapay.jsonml(dialog),
|
||||
Liberapay.jsonml(dialog)
|
||||
]);
|
||||
|
||||
// Close if we're on the page the notification links to.
|
||||
@ -33,15 +33,15 @@ Gratipay.notification = function(text, type, timeout, closeCallback) {
|
||||
if (timeout > 0) setTimeout(close, timeout);
|
||||
};
|
||||
|
||||
Gratipay.initNotifications = function(notifs) {
|
||||
Liberapay.initNotifications = function(notifs) {
|
||||
jQuery.each(notifs, function(k, notif) {
|
||||
Gratipay.notification(notif.jsonml, notif.type, -1, function() {
|
||||
Liberapay.notification(notif.jsonml, notif.type, -1, function() {
|
||||
jQuery.ajax({
|
||||
url: '/'+Gratipay.username+'/notifications.json',
|
||||
url: '/'+Liberapay.username+'/notifications.json',
|
||||
type: 'POST',
|
||||
data: {remove: notif.name},
|
||||
dataType: 'json',
|
||||
error: Gratipay.error,
|
||||
error: Liberapay.error,
|
||||
});
|
||||
});
|
||||
});
|
@ -1,29 +1,29 @@
|
||||
/* Bank Account and Credit Card forms
|
||||
*
|
||||
* These two forms share some common wiring under the Gratipay.payments
|
||||
* namespace, and each has unique code under the Gratipay.payments.{cc,ba}
|
||||
* These two forms share some common wiring under the Liberapay.payments
|
||||
* namespace, and each has unique code under the Liberapay.payments.{cc,ba}
|
||||
* namespaces. Each form gets its own page so we only instantiate one of these
|
||||
* at a time.
|
||||
*
|
||||
*/
|
||||
|
||||
Gratipay.payments = {};
|
||||
Liberapay.payments = {};
|
||||
|
||||
|
||||
// Common code
|
||||
// ===========
|
||||
|
||||
Gratipay.payments.init = function() {
|
||||
$('#delete').submit(Gratipay.payments.deleteRoute);
|
||||
Liberapay.payments.init = function() {
|
||||
$('#delete').submit(Liberapay.payments.deleteRoute);
|
||||
|
||||
// Lazily depend on Balanced.
|
||||
var balanced_js = "https://js.balancedpayments.com/1.1/balanced.min.js";
|
||||
jQuery.getScript(balanced_js, function() {
|
||||
$('input[type!="hidden"]').eq(0).focus();
|
||||
}).fail(Gratipay.error);
|
||||
}).fail(Liberapay.error);
|
||||
}
|
||||
|
||||
Gratipay.payments.deleteRoute = function(e) {
|
||||
Liberapay.payments.deleteRoute = function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
@ -33,32 +33,32 @@ Gratipay.payments.deleteRoute = function(e) {
|
||||
return false;
|
||||
}
|
||||
jQuery.ajax(
|
||||
{ url: "/" + Gratipay.username + "/routes/delete.json"
|
||||
{ url: "/" + Liberapay.username + "/routes/delete.json"
|
||||
, data: {network: $this.data('network'), address: $this.data('address')}
|
||||
, type: "POST"
|
||||
, success: function() { window.location.reload(); }
|
||||
, error: Gratipay.error
|
||||
, error: Liberapay.error
|
||||
}
|
||||
);
|
||||
return false;
|
||||
};
|
||||
|
||||
Gratipay.payments.onError = function(response) {
|
||||
Liberapay.payments.onError = function(response) {
|
||||
$('button#save').prop('disabled', false);
|
||||
var msg = response.status_code + ": " +
|
||||
$.map(response.errors, function(obj) { return obj.description }).join(', ');
|
||||
Gratipay.notification(msg, 'error', -1);
|
||||
Liberapay.notification(msg, 'error', -1);
|
||||
return msg;
|
||||
};
|
||||
|
||||
Gratipay.payments.onSuccess = function(data) {
|
||||
Liberapay.payments.onSuccess = function(data) {
|
||||
$('button#save').prop('disabled', false);
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
Gratipay.payments.associate = function (network) { return function (response) {
|
||||
Liberapay.payments.associate = function (network) { return function (response) {
|
||||
if (response.status_code !== 201) {
|
||||
return Gratipay.payments.onError(response);
|
||||
return Liberapay.payments.onError(response);
|
||||
}
|
||||
|
||||
/* The request to tokenize the thing succeeded. Now we need to associate it
|
||||
@ -75,9 +75,9 @@ Gratipay.payments.associate = function (network) { return function (response) {
|
||||
type: "POST",
|
||||
data: data,
|
||||
dataType: "json",
|
||||
success: Gratipay.payments.onSuccess,
|
||||
success: Liberapay.payments.onSuccess,
|
||||
error: [
|
||||
Gratipay.error,
|
||||
Liberapay.error,
|
||||
function() { $('button#save').prop('disabled', false); },
|
||||
],
|
||||
});
|
||||
@ -87,18 +87,18 @@ Gratipay.payments.associate = function (network) { return function (response) {
|
||||
// Bank Accounts
|
||||
// =============
|
||||
|
||||
Gratipay.payments.ba = {};
|
||||
Liberapay.payments.ba = {};
|
||||
|
||||
Gratipay.payments.ba.init = function() {
|
||||
Gratipay.payments.init();
|
||||
$('form#bank-account').submit(Gratipay.payments.ba.submit);
|
||||
Liberapay.payments.ba.init = function() {
|
||||
Liberapay.payments.init();
|
||||
$('form#bank-account').submit(Liberapay.payments.ba.submit);
|
||||
};
|
||||
|
||||
Gratipay.payments.ba.submit = function (e) {
|
||||
Liberapay.payments.ba.submit = function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
$('button#save').prop('disabled', true);
|
||||
Gratipay.forms.clearInvalid($(this));
|
||||
Liberapay.forms.clearInvalid($(this));
|
||||
|
||||
var bankAccount = {
|
||||
name: $('#account_name').val(),
|
||||
@ -110,8 +110,8 @@ Gratipay.payments.ba.submit = function (e) {
|
||||
// Validate routing number.
|
||||
if (bankAccount.routing_number) {
|
||||
if (!balanced.bankAccount.validateRoutingNumber(bankAccount.routing_number)) {
|
||||
Gratipay.forms.setInvalid($('#routing_number'));
|
||||
Gratipay.forms.focusInvalid($(this));
|
||||
Liberapay.forms.setInvalid($('#routing_number'));
|
||||
Liberapay.forms.focusInvalid($(this));
|
||||
$('button#save').prop('disabled', false);
|
||||
return false
|
||||
}
|
||||
@ -119,7 +119,7 @@ Gratipay.payments.ba.submit = function (e) {
|
||||
|
||||
// Okay, send the data to Balanced.
|
||||
balanced.bankAccount.create( bankAccount
|
||||
, Gratipay.payments.associate('balanced-ba')
|
||||
, Liberapay.payments.associate('balanced-ba')
|
||||
);
|
||||
};
|
||||
|
||||
@ -127,12 +127,12 @@ Gratipay.payments.ba.submit = function (e) {
|
||||
// Credit Cards
|
||||
// ============
|
||||
|
||||
Gratipay.payments.cc = {};
|
||||
Liberapay.payments.cc = {};
|
||||
|
||||
Gratipay.payments.cc.init = function() {
|
||||
Gratipay.payments.init();
|
||||
$('form#credit-card').submit(Gratipay.payments.cc.submit);
|
||||
Gratipay.payments.cc.formatInputs(
|
||||
Liberapay.payments.cc.init = function() {
|
||||
Liberapay.payments.init();
|
||||
$('form#credit-card').submit(Liberapay.payments.cc.submit);
|
||||
Liberapay.payments.cc.formatInputs(
|
||||
$('#card_number'),
|
||||
$('#expiration_month'),
|
||||
$('#expiration_year'),
|
||||
@ -143,7 +143,7 @@ Gratipay.payments.cc.init = function() {
|
||||
|
||||
/* Most of the following code is taken from https://github.com/wangjohn/creditly */
|
||||
|
||||
Gratipay.payments.cc.formatInputs = function (cardNumberInput, expirationMonthInput, expirationYearInput, cvvInput) {
|
||||
Liberapay.payments.cc.formatInputs = function (cardNumberInput, expirationMonthInput, expirationYearInput, cvvInput) {
|
||||
function getInputValue(e, element) {
|
||||
var inputValue = element.val().trim();
|
||||
inputValue = inputValue + String.fromCharCode(e.which);
|
||||
@ -264,12 +264,12 @@ Gratipay.payments.cc.formatInputs = function (cardNumberInput, expirationMonthIn
|
||||
});
|
||||
}
|
||||
|
||||
Gratipay.payments.cc.submit = function(e) {
|
||||
Liberapay.payments.cc.submit = function(e) {
|
||||
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$('button#save').prop('disabled', true);
|
||||
Gratipay.forms.clearInvalid($(this));
|
||||
Liberapay.forms.clearInvalid($(this));
|
||||
|
||||
// Adapt our form lingo to balanced nomenclature.
|
||||
|
||||
@ -312,16 +312,16 @@ Gratipay.payments.cc.submit = function(e) {
|
||||
var is_cvv_invalid = !balanced.card.isSecurityCodeValid(credit_card.number,
|
||||
credit_card.cvv);
|
||||
|
||||
Gratipay.forms.setInvalid($('#card_number'), is_card_number_invalid);
|
||||
Gratipay.forms.setInvalid($('#expiration_month'), is_expiry_invalid);
|
||||
Gratipay.forms.setInvalid($('#cvv'), is_cvv_invalid);
|
||||
Liberapay.forms.setInvalid($('#card_number'), is_card_number_invalid);
|
||||
Liberapay.forms.setInvalid($('#expiration_month'), is_expiry_invalid);
|
||||
Liberapay.forms.setInvalid($('#cvv'), is_cvv_invalid);
|
||||
|
||||
if (is_card_number_invalid || is_expiry_invalid || is_cvv_invalid) {
|
||||
$('button#save').prop('disabled', false);
|
||||
Gratipay.forms.focusInvalid($(this));
|
||||
Liberapay.forms.focusInvalid($(this));
|
||||
return false;
|
||||
}
|
||||
|
||||
balanced.card.create(credit_card, Gratipay.payments.associate('balanced-cc'));
|
||||
balanced.card.create(credit_card, Liberapay.payments.associate('balanced-cc'));
|
||||
return false;
|
||||
};
|
@ -1,13 +1,13 @@
|
||||
Gratipay.profile = {};
|
||||
Liberapay.profile = {};
|
||||
|
||||
Gratipay.profile.init = function() {
|
||||
Liberapay.profile.init = function() {
|
||||
|
||||
// Wire up textarea for statement.
|
||||
// ===============================
|
||||
|
||||
$('textarea').focus();
|
||||
|
||||
Gratipay.forms.jsEdit({
|
||||
Liberapay.forms.jsEdit({
|
||||
confirmBeforeUnload: true,
|
||||
root: $('.statement.js-edit'),
|
||||
});
|
||||
@ -45,7 +45,7 @@ Gratipay.profile.init = function() {
|
||||
},
|
||||
error: [
|
||||
function () { $inputs.prop('disabled', false); },
|
||||
Gratipay.error,
|
||||
Liberapay.error,
|
||||
]
|
||||
});
|
||||
});
|
||||
@ -62,7 +62,7 @@ Gratipay.profile.init = function() {
|
||||
// Wire up goal knob.
|
||||
// ==================
|
||||
|
||||
Gratipay.forms.jsEdit({
|
||||
Liberapay.forms.jsEdit({
|
||||
root: $('.goal.js-edit'),
|
||||
success: function(d) {
|
||||
var goal = $('input[name=goal]:checked');
|
||||
@ -78,7 +78,7 @@ Gratipay.profile.init = function() {
|
||||
|
||||
$('tr.cryptocoin.js-edit').each(function() {
|
||||
var $root = $(this);
|
||||
Gratipay.forms.jsEdit({
|
||||
Liberapay.forms.jsEdit({
|
||||
root: $root,
|
||||
success: function(){
|
||||
var addr = $root.find('[name="address"]').val();
|
||||
@ -87,7 +87,7 @@ Gratipay.profile.init = function() {
|
||||
$root.addClass('not-empty');
|
||||
},
|
||||
});
|
||||
$root.find('button.delete').click(Gratipay.payments.deleteRoute);
|
||||
$root.find('button.delete').click(Liberapay.payments.deleteRoute);
|
||||
});
|
||||
|
||||
|
||||
@ -104,7 +104,7 @@ Gratipay.profile.init = function() {
|
||||
success: function ( ) {
|
||||
location.reload();
|
||||
},
|
||||
error: Gratipay.error,
|
||||
error: Liberapay.error,
|
||||
data: { platform: this.dataset.platform, user_id: this.dataset.user_id }
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
Gratipay.settings = {};
|
||||
Liberapay.settings = {};
|
||||
|
||||
Gratipay.settings.post_email = function(e) {
|
||||
Liberapay.settings.post_email = function(e) {
|
||||
e.preventDefault();
|
||||
var $this = $(this);
|
||||
var action = this.className;
|
||||
@ -17,7 +17,7 @@ Gratipay.settings.post_email = function(e) {
|
||||
dataType: 'json',
|
||||
success: function (msg) {
|
||||
if (msg) {
|
||||
Gratipay.notification(msg, 'success');
|
||||
Liberapay.notification(msg, 'success');
|
||||
}
|
||||
if (action == 'add-email') {
|
||||
$('input.add-email').val('');
|
||||
@ -33,17 +33,17 @@ Gratipay.settings.post_email = function(e) {
|
||||
},
|
||||
error: [
|
||||
function () { $inputs.prop('disabled', false); },
|
||||
Gratipay.error
|
||||
Liberapay.error
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
Gratipay.settings.init = function() {
|
||||
Liberapay.settings.init = function() {
|
||||
|
||||
// Wire up username knob.
|
||||
// ======================
|
||||
|
||||
Gratipay.forms.jsEdit({
|
||||
Liberapay.forms.jsEdit({
|
||||
hideEditButton: true,
|
||||
root: $('.username.js-edit'),
|
||||
success: function(d) {
|
||||
@ -76,11 +76,11 @@ Gratipay.settings.init = function() {
|
||||
}
|
||||
if (data.number) {
|
||||
$input.prop('checked', true);
|
||||
Gratipay.notification(data.msg || "Success", 'success');
|
||||
Liberapay.notification(data.msg || "Success", 'success');
|
||||
$('li.members').toggleClass('hidden', data.number !== 'plural');
|
||||
}
|
||||
},
|
||||
error: Gratipay.error,
|
||||
error: Liberapay.error,
|
||||
});
|
||||
}
|
||||
post();
|
||||
@ -103,11 +103,11 @@ Gratipay.settings.init = function() {
|
||||
, dataType: 'json'
|
||||
, success: function(data) {
|
||||
if (data.msg) {
|
||||
Gratipay.notification(data.msg, 'success');
|
||||
Liberapay.notification(data.msg, 'success');
|
||||
}
|
||||
$(e.target).attr('checked', data[field] ^ neg);
|
||||
}
|
||||
, error: Gratipay.error
|
||||
, error: Liberapay.error
|
||||
});
|
||||
});
|
||||
|
||||
@ -123,11 +123,11 @@ Gratipay.settings.init = function() {
|
||||
, data: {toggle: field, bits: bits}
|
||||
, dataType: 'json'
|
||||
, success: function(data) {
|
||||
Gratipay.notification(data.msg, 'success');
|
||||
Liberapay.notification(data.msg, 'success');
|
||||
$(e.target).attr('checked', data.new_value & bits)
|
||||
}
|
||||
, error: [
|
||||
Gratipay.error,
|
||||
Liberapay.error,
|
||||
function(){ $(e.target).attr('checked', !$(e.target).attr('checked')) },
|
||||
]
|
||||
});
|
||||
@ -166,8 +166,8 @@ Gratipay.settings.init = function() {
|
||||
// =============================
|
||||
|
||||
$('.emails button, .emails input').prop('disabled', false);
|
||||
$('.emails button[class]').on('click', Gratipay.settings.post_email);
|
||||
$('form.add-email').on('submit', Gratipay.settings.post_email);
|
||||
$('.emails button[class]').on('click', Liberapay.settings.post_email);
|
||||
$('form.add-email').on('submit', Liberapay.settings.post_email);
|
||||
|
||||
|
||||
// Wire up close knob.
|
@ -1,4 +1,4 @@
|
||||
Gratipay.team = (function() {
|
||||
Liberapay.team = (function() {
|
||||
function init() {
|
||||
$('#lookup-container form').submit(add);
|
||||
$('#lookup-results').on('click', 'li', selectLookupResult);
|
||||
@ -43,7 +43,7 @@ Gratipay.team = (function() {
|
||||
var rows = [];
|
||||
|
||||
if (nmembers === 0) {
|
||||
rows.push(Gratipay.jsonml(
|
||||
rows.push(Liberapay.jsonml(
|
||||
[ 'tr'
|
||||
, ['td', {'colspan': '6', 'class': 'no-members'}, "No members"]
|
||||
]
|
||||
@ -62,7 +62,7 @@ Gratipay.team = (function() {
|
||||
increase = 'max';
|
||||
|
||||
if (i < nmembers)
|
||||
rows.push(Gratipay.jsonml(
|
||||
rows.push(Liberapay.jsonml(
|
||||
[ 'tr'
|
||||
, ['td', {'class': 'n'}, (i === nmembers ? '' : nmembers - i)]
|
||||
, ['td', ['a', {'href': '/'+member.username+'/'}, member.username]]
|
||||
@ -73,7 +73,7 @@ Gratipay.team = (function() {
|
||||
]
|
||||
));
|
||||
else if (nmembers > 0)
|
||||
rows.push(Gratipay.jsonml(
|
||||
rows.push(Liberapay.jsonml(
|
||||
[ 'tr'
|
||||
, ['td']
|
||||
, ['td']
|
||||
@ -106,7 +106,7 @@ Gratipay.team = (function() {
|
||||
var items = [];
|
||||
for (var i=0, len=results.length; i<len; i++) {
|
||||
var result = results[i];
|
||||
items.push(Gratipay.jsonml(
|
||||
items.push(Liberapay.jsonml(
|
||||
['li', {"data-id": result.id}, result.username]
|
||||
));
|
||||
}
|
||||
@ -185,11 +185,11 @@ Gratipay.team = (function() {
|
||||
}
|
||||
}
|
||||
if(d.success) {
|
||||
Gratipay.notification(d.success, 'success');
|
||||
Liberapay.notification(d.success, 'success');
|
||||
}
|
||||
drawRows(d.members);
|
||||
}
|
||||
, error: [resetTake, Gratipay.error]
|
||||
, error: [resetTake, Liberapay.error]
|
||||
});
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
Gratipay.tips = {};
|
||||
Liberapay.tips = {};
|
||||
|
||||
Gratipay.tips.init = function() {
|
||||
Liberapay.tips.init = function() {
|
||||
|
||||
Gratipay.forms.jsEdit({
|
||||
Liberapay.forms.jsEdit({
|
||||
confirmBeforeUnload: true,
|
||||
hideEditButton: true,
|
||||
root: $('.your-tip.js-edit'),
|
||||
success: function(data) {
|
||||
Gratipay.notification(data.msg, 'success');
|
||||
Gratipay.tips.afterTipChange(data);
|
||||
Liberapay.notification(data.msg, 'success');
|
||||
Liberapay.tips.afterTipChange(data);
|
||||
}
|
||||
});
|
||||
|
||||
@ -33,32 +33,7 @@ Gratipay.tips.init = function() {
|
||||
};
|
||||
|
||||
|
||||
Gratipay.tips.initSupportGratipay = function() {
|
||||
$('.support-gratipay button').click(function() {
|
||||
var amount = parseFloat($(this).attr('data-amount'), 10);
|
||||
Gratipay.tips.set('Gratipay', amount, function(data) {
|
||||
Gratipay.notification(data.msg, 'success');
|
||||
$('.support-gratipay').slideUp();
|
||||
|
||||
// If you're on your own giving page ...
|
||||
var tip_on_giving = $('.your-tip[data-tippee="Gratipay"]');
|
||||
if (tip_on_giving.length > 0) {
|
||||
tip_on_giving[0].defaultValue = amount;
|
||||
tip_on_giving.attr('value', amount.toFixed(2));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('.support-gratipay .no-thanks').click(function(event) {
|
||||
event.preventDefault();
|
||||
jQuery.post('/ride-free.json')
|
||||
.success(function() { $('.support-gratipay').slideUp(); })
|
||||
.fail(Gratipay.error)
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Gratipay.tips.afterTipChange = function(data) {
|
||||
Liberapay.tips.afterTipChange = function(data) {
|
||||
$('.my-total-giving').text(data.total_giving_l);
|
||||
$('.total-receiving[data-tippee="'+data.tippee_id+'"]').text(data.total_receiving_tippee_l);
|
||||
$('#payment-prompt').toggleClass('needed', data.amount > 0);
|
||||
@ -75,12 +50,12 @@ Gratipay.tips.afterTipChange = function(data) {
|
||||
};
|
||||
|
||||
|
||||
Gratipay.tips.set = function(tippee, amount, callback) {
|
||||
Liberapay.tips.set = function(tippee, amount, callback) {
|
||||
|
||||
// send request to change tip
|
||||
$.post('/' + tippee + '/tip.json', { amount: amount }, function(data) {
|
||||
if (callback) callback(data);
|
||||
Gratipay.tips.afterTipChange(data);
|
||||
Liberapay.tips.afterTipChange(data);
|
||||
})
|
||||
.fail(Gratipay.error);
|
||||
.fail(Liberapay.error);
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
// strips Unicode & non-printable characters, then leading/trailing whitespace
|
||||
Gratipay.trim = function(s) {
|
||||
Liberapay.trim = function(s) {
|
||||
return s.replace(/[^\x20-\x7F]/g, '').replace(/^\s+|\s+$/g, '');
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
Gratipay.upgrade = {};
|
||||
Liberapay.upgrade = {};
|
||||
|
||||
Gratipay.upgrade.init = function () {
|
||||
Liberapay.upgrade.init = function () {
|
||||
|
||||
var userAgent = navigator.userAgent.toLowerCase();
|
||||
var browser = (userAgent.indexOf('msie') != -1) ? parseInt(userAgent.split('msie')[1], 10) : -1;
|
||||
@ -17,4 +17,4 @@ Gratipay.upgrade.init = function () {
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(Gratipay.upgrade.init);
|
||||
$(document).ready(Liberapay.upgrade.init);
|
@ -1,56 +1,11 @@
|
||||
"""This is the Python library behind gratipay.com.
|
||||
"""
|
||||
import datetime
|
||||
import locale
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
try: # XXX This can't be right.
|
||||
locale.setlocale(locale.LC_ALL, "en_US.utf8")
|
||||
except locale.Error:
|
||||
import sys
|
||||
if sys.platform == 'win32':
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
else:
|
||||
locale.setlocale(locale.LC_ALL, "en_US.UTF-8")
|
||||
BIRTHDAY = datetime.date(2015, 5, 22)
|
||||
|
||||
|
||||
BIRTHDAY = datetime.date(2012, 6, 1)
|
||||
CARDINALS = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven',
|
||||
'eight', 'nine']
|
||||
ORDINALS = ['zeroth', 'first', 'second', 'third', 'fourth', 'fifth', 'sixth',
|
||||
'seventh', 'eighth', 'ninth', 'tenth']
|
||||
MONTHS = [None, 'January', 'February', 'March', 'April', 'May', 'June', 'July',
|
||||
'August', 'September', 'October', 'November', 'December']
|
||||
|
||||
def age():
|
||||
today = datetime.date.today()
|
||||
nmonths = (12 - BIRTHDAY.month) \
|
||||
+ (12 * (today.year - BIRTHDAY.year - 1)) \
|
||||
+ (today.month)
|
||||
plural = 's' if nmonths != 1 else ''
|
||||
if nmonths < 10:
|
||||
nmonths = CARDINALS[nmonths]
|
||||
else:
|
||||
nmonths = str(nmonths)
|
||||
return "%s month%s" % (nmonths, plural)
|
||||
|
||||
|
||||
class NotSane(Exception):
|
||||
"""This is used when a sanity check fails.
|
||||
|
||||
A sanity check is when it really seems like the logic shouldn't allow the
|
||||
condition to arise, but you never know.
|
||||
|
||||
"""
|
||||
|
||||
db = None # This global is wired in wireup. It's an instance of
|
||||
# gratipay.postgres.PostgresManager.
|
||||
|
||||
|
||||
MAX_TIP_SINGULAR = Decimal('100.00')
|
||||
MAX_TIP_PLURAL = Decimal('1000.00')
|
||||
MIN_TIP = Decimal('0.00')
|
||||
MAX_TIP = Decimal('100.00')
|
||||
MIN_TIP = Decimal('0.01')
|
||||
|
||||
RESTRICTED_IDS = None
|
||||
|
||||
@ -65,7 +20,7 @@ canonical_scheme = None
|
||||
canonical_host = None
|
||||
|
||||
def canonize(request):
|
||||
"""Enforce a certain scheme and hostname. Store these on request as well.
|
||||
"""Enforce a certain scheme and hostname.
|
||||
"""
|
||||
scheme = request.headers.get('X-Forwarded-Proto', 'http') # per Heroku
|
||||
host = request.headers['Host']
|
||||
@ -83,7 +38,3 @@ def canonize(request):
|
||||
# For non-idempotent methods, redirect to homepage.
|
||||
url += '/'
|
||||
request.redirect(url)
|
||||
|
||||
|
||||
def set_version_header(response, website):
|
||||
response.headers['X-Gratipay-Version'] = website.version
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Functions for moving money between Gratipay and the outside world.
|
||||
"""Functions for moving money between Liberapay and the outside world.
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
@ -8,10 +8,10 @@ import balanced
|
||||
|
||||
from aspen import log
|
||||
from aspen.utils import typecheck
|
||||
from gratipay.exceptions import NegativeBalance, NotWhitelisted
|
||||
from gratipay.models import check_db
|
||||
from gratipay.models.participant import Participant
|
||||
from gratipay.models.exchange_route import ExchangeRoute
|
||||
from liberapay.exceptions import NegativeBalance, NotWhitelisted
|
||||
from liberapay.models import check_db
|
||||
from liberapay.models.participant import Participant
|
||||
from liberapay.models.exchange_route import ExchangeRoute
|
||||
|
||||
|
||||
BALANCED_CLASSES = {
|
||||
@ -88,7 +88,7 @@ def ach_credit(db, participant, withhold, minimum_credit=MINIMUM_CREDIT):
|
||||
|
||||
# Compute the amount to credit them.
|
||||
# ==================================
|
||||
# Leave money in Gratipay to cover their obligations next week (as these
|
||||
# Leave money in the wallet to cover their obligations next week (as these
|
||||
# currently stand).
|
||||
|
||||
balance = participant.balance
|
||||
@ -155,8 +155,8 @@ def ach_credit(db, participant, withhold, minimum_credit=MINIMUM_CREDIT):
|
||||
def create_card_hold(db, participant, amount):
|
||||
"""Create a hold on the participant's credit card.
|
||||
|
||||
Amount should be the nominal amount. We'll compute Gratipay's fee below
|
||||
this function and add it to amount to end up with charge_amount.
|
||||
Amount should be the nominal amount. We'll compute fees below this function
|
||||
and add it to amount to end up with charge_amount.
|
||||
|
||||
"""
|
||||
typecheck(amount, Decimal)
|
||||
@ -272,7 +272,7 @@ def record_exchange(db, route, amount, fee, participant, status, error=None):
|
||||
Records in the exchanges table have these characteristics:
|
||||
|
||||
amount It's negative for credits (representing an outflow from
|
||||
Gratipay to you) and positive for charges.
|
||||
Liberapay to you) and positive for charges.
|
||||
The sign is how we differentiate the two in, e.g., the
|
||||
history page.
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
"""This is Gratipay's payday algorithm.
|
||||
"""This is Liberapay's payday algorithm.
|
||||
|
||||
Exchanges (moving money between Gratipay and the outside world) and transfers
|
||||
(moving money amongst Gratipay users) happen within an isolated event called
|
||||
Exchanges (moving money between Liberapay and the outside world) and transfers
|
||||
(moving money amongst Liberapay users) happen within an isolated event called
|
||||
payday. This event has duration (it's not punctiliar).
|
||||
|
||||
Payday is designed to be crash-resistant. Everything that can be rolled back
|
||||
@ -14,16 +14,16 @@ from __future__ import unicode_literals
|
||||
import itertools
|
||||
from multiprocessing.dummy import Pool as ThreadPool
|
||||
|
||||
from balanced import CardHold
|
||||
|
||||
import aspen.utils
|
||||
from aspen import log
|
||||
from gratipay.billing.exchanges import (
|
||||
from balanced import CardHold
|
||||
from psycopg2 import IntegrityError
|
||||
|
||||
from liberapay.billing.exchanges import (
|
||||
ach_credit, cancel_card_hold, capture_card_hold, create_card_hold, upcharge
|
||||
)
|
||||
from gratipay.exceptions import NegativeBalance
|
||||
from gratipay.models import check_db
|
||||
from psycopg2 import IntegrityError
|
||||
from liberapay.exceptions import NegativeBalance
|
||||
from liberapay.models import check_db
|
||||
|
||||
|
||||
with open('sql/fake_payday.sql') as f:
|
||||
@ -57,31 +57,6 @@ class NoPayday(Exception):
|
||||
|
||||
|
||||
class Payday(object):
|
||||
"""Represent an abstract event during which money is moved.
|
||||
|
||||
On Payday, we want to use a participant's Gratipay balance to settle their
|
||||
tips due (pulling in more money via credit card as needed), but we only
|
||||
want to use their balance at the start of Payday. Balance changes should be
|
||||
atomic globally per-Payday.
|
||||
|
||||
Here's the call structure of the Payday.run method:
|
||||
|
||||
run
|
||||
payin
|
||||
prepare
|
||||
create_card_holds
|
||||
transfer_tips
|
||||
transfer_takes
|
||||
settle_card_holds
|
||||
update_balances
|
||||
take_over_balances
|
||||
payout
|
||||
update_stats
|
||||
update_cached_amounts
|
||||
end
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@classmethod
|
||||
def start(cls):
|
||||
@ -115,7 +90,6 @@ class Payday(object):
|
||||
payday.__dict__.update(d)
|
||||
return payday
|
||||
|
||||
|
||||
def run(self):
|
||||
"""This is the starting point for payday.
|
||||
|
||||
@ -148,7 +122,6 @@ class Payday(object):
|
||||
fmt_past = "Script ran for %%(age)s (%s)." % _delta
|
||||
log(aspen.utils.to_age(_start, fmt_past=fmt_past))
|
||||
|
||||
|
||||
def payin(self):
|
||||
"""The first stage of payday where we charge credit cards and transfer
|
||||
money internally between participants.
|
||||
@ -181,7 +154,6 @@ class Payday(object):
|
||||
DROP FUNCTION transfer(text, text, numeric, context_type);
|
||||
""")
|
||||
|
||||
|
||||
@staticmethod
|
||||
def prepare(cursor, ts_start):
|
||||
"""Prepare the DB: we need temporary tables with indexes and triggers.
|
||||
@ -385,7 +357,6 @@ class Payday(object):
|
||||
""", dict(ts_start=ts_start))
|
||||
log('Prepared the DB.')
|
||||
|
||||
|
||||
@staticmethod
|
||||
def fetch_card_holds(participant_ids):
|
||||
holds = {}
|
||||
@ -408,7 +379,6 @@ class Payday(object):
|
||||
cancel_card_hold(hold)
|
||||
return holds
|
||||
|
||||
|
||||
def create_card_holds(self, cursor):
|
||||
|
||||
# Get the list of participants to create card holds for
|
||||
@ -464,7 +434,6 @@ class Payday(object):
|
||||
|
||||
return holds
|
||||
|
||||
|
||||
@staticmethod
|
||||
def transfer_tips(cursor):
|
||||
cursor.run("""
|
||||
@ -479,7 +448,6 @@ class Payday(object):
|
||||
|
||||
""")
|
||||
|
||||
|
||||
@staticmethod
|
||||
def transfer_takes(cursor, ts_start):
|
||||
cursor.run("""
|
||||
@ -507,7 +475,6 @@ class Payday(object):
|
||||
|
||||
""", dict(ts_start=ts_start))
|
||||
|
||||
|
||||
def settle_card_holds(self, cursor, holds):
|
||||
participants = cursor.all("""
|
||||
SELECT *
|
||||
@ -527,7 +494,6 @@ class Payday(object):
|
||||
threaded_map(cancel_card_hold, holds.values())
|
||||
log("Canceled %i card holds." % len(holds))
|
||||
|
||||
|
||||
@staticmethod
|
||||
def update_balances(cursor):
|
||||
participants = cursor.all("""
|
||||
@ -557,7 +523,6 @@ class Payday(object):
|
||||
""")
|
||||
log("Updated the balances of %i participants." % len(participants))
|
||||
|
||||
|
||||
def take_over_balances(self):
|
||||
"""If an account that receives money is taken over during payin we need
|
||||
to transfer the balance to the absorbing account.
|
||||
@ -597,7 +562,6 @@ class Payday(object):
|
||||
|
||||
""")
|
||||
|
||||
|
||||
def payout(self):
|
||||
"""This is the second stage of payday in which we send money out to the
|
||||
bank accounts of participants.
|
||||
@ -626,7 +590,6 @@ class Payday(object):
|
||||
self.db.self_check()
|
||||
log("Checked the DB.")
|
||||
|
||||
|
||||
def update_stats(self):
|
||||
self.db.run("""\
|
||||
|
||||
@ -689,24 +652,19 @@ class Payday(object):
|
||||
""", {'ts_start': self.ts_start})
|
||||
log("Updated payday stats.")
|
||||
|
||||
|
||||
def update_cached_amounts(self):
|
||||
with self.db.get_cursor() as cursor:
|
||||
cursor.execute(FAKE_PAYDAY)
|
||||
log("Updated receiving amounts.")
|
||||
|
||||
|
||||
def end(self):
|
||||
self.ts_end = self.db.one("""\
|
||||
|
||||
self.ts_end = self.db.one("""
|
||||
UPDATE paydays
|
||||
SET ts_end=now()
|
||||
WHERE ts_end='1970-01-01T00:00:00+00'::timestamptz
|
||||
RETURNING ts_end AT TIME ZONE 'UTC'
|
||||
|
||||
""", default=NoPayday).replace(tzinfo=aspen.utils.utc)
|
||||
|
||||
|
||||
def notify_participants(self):
|
||||
ts_start, ts_end = self.ts_start, self.ts_end
|
||||
exchanges = self.db.all("""
|
||||
@ -756,27 +714,21 @@ class Payday(object):
|
||||
top_tippee=top_tippee,
|
||||
)
|
||||
|
||||
|
||||
# Record-keeping.
|
||||
# ===============
|
||||
# Record-keeping
|
||||
# ==============
|
||||
|
||||
def mark_ach_failed(self):
|
||||
self.db.one("""\
|
||||
|
||||
self.db.one("""
|
||||
UPDATE paydays
|
||||
SET nach_failing = nach_failing + 1
|
||||
WHERE ts_end='1970-01-01T00:00:00+00'::timestamptz
|
||||
RETURNING id
|
||||
|
||||
""", default=NoPayday)
|
||||
|
||||
|
||||
def mark_stage_done(self):
|
||||
self.db.one("""\
|
||||
|
||||
self.db.one("""
|
||||
UPDATE paydays
|
||||
SET stage = stage + 1
|
||||
WHERE ts_end='1970-01-01T00:00:00+00'::timestamptz
|
||||
RETURNING id
|
||||
|
||||
""", default=NoPayday)
|
||||
|
@ -1,6 +1,4 @@
|
||||
"""This is installed as `payday`.
|
||||
"""
|
||||
from gratipay import wireup
|
||||
from liberapay import wireup
|
||||
|
||||
|
||||
def payday():
|
||||
@ -16,11 +14,9 @@ def payday():
|
||||
|
||||
# Lazily import the billing module.
|
||||
# =================================
|
||||
# This dodges a problem where db in billing is None if we import it from
|
||||
# gratipay before calling wireup.billing.
|
||||
|
||||
from gratipay.billing.exchanges import sync_with_balanced
|
||||
from gratipay.billing.payday import Payday
|
||||
from liberapay.billing.exchanges import sync_with_balanced
|
||||
from liberapay.billing.payday import Payday
|
||||
|
||||
try:
|
||||
sync_with_balanced(db)
|
||||
|
@ -15,8 +15,8 @@ from aspen.utils import to_age, utc
|
||||
from oauthlib.oauth2 import TokenExpiredError
|
||||
from requests_oauthlib import OAuth1Session, OAuth2Session
|
||||
|
||||
from gratipay.elsewhere._extractors import not_available
|
||||
from gratipay.utils import LazyResponse
|
||||
from liberapay.elsewhere._extractors import not_available
|
||||
from liberapay.utils import LazyResponse
|
||||
|
||||
|
||||
ACTIONS = {'opt-in', 'connect', 'lock', 'unlock'}
|
||||
@ -27,7 +27,7 @@ class UnknownAccountElsewhere(Exception): pass
|
||||
|
||||
|
||||
class PlatformRegistry(object):
|
||||
"""Registry of platforms we support connecting to Gratipay accounts.
|
||||
"""Registry of platforms we support.
|
||||
"""
|
||||
def __init__(self, platforms):
|
||||
self.__dict__ = OrderedDict((p.name, p) for p in platforms)
|
||||
|
@ -1,9 +1,9 @@
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from aspen import Response
|
||||
from gratipay.elsewhere import PlatformOAuth1
|
||||
from gratipay.elsewhere._extractors import any_key, key, not_available
|
||||
from gratipay.elsewhere._paginators import keys_paginator
|
||||
from liberapay.elsewhere import PlatformOAuth1
|
||||
from liberapay.elsewhere._extractors import any_key, key, not_available
|
||||
from liberapay.elsewhere._paginators import keys_paginator
|
||||
|
||||
|
||||
class Bitbucket(PlatformOAuth1):
|
||||
|
@ -10,8 +10,8 @@ from urlparse import parse_qs, urlparse
|
||||
import requests
|
||||
|
||||
from aspen import Response
|
||||
from gratipay.elsewhere import Platform
|
||||
from gratipay.elsewhere._extractors import key, not_available
|
||||
from liberapay.elsewhere import Platform
|
||||
from liberapay.elsewhere._extractors import key, not_available
|
||||
|
||||
|
||||
class Bountysource(Platform):
|
||||
@ -49,7 +49,7 @@ class Bountysource(Platform):
|
||||
redirect_url=self.callback_url+'?query_id='+query_id,
|
||||
external_access_token=token
|
||||
)
|
||||
url = self.auth_url+'/auth/gratipay/confirm?'+urlencode(params)
|
||||
url = self.auth_url+'/auth/liberapay/confirm?'+urlencode(params)
|
||||
return url, query_id, ''
|
||||
|
||||
def get_query_id(self, querystring):
|
||||
|
@ -1,8 +1,8 @@
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from gratipay.elsewhere import PlatformOAuth2
|
||||
from gratipay.elsewhere._extractors import key
|
||||
from gratipay.elsewhere._paginators import keys_paginator
|
||||
from liberapay.elsewhere import PlatformOAuth2
|
||||
from liberapay.elsewhere._extractors import key
|
||||
from liberapay.elsewhere._paginators import keys_paginator
|
||||
|
||||
|
||||
class Facebook(PlatformOAuth2):
|
||||
|
@ -1,8 +1,8 @@
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from gratipay.elsewhere import PlatformOAuth2
|
||||
from gratipay.elsewhere._extractors import key
|
||||
from gratipay.elsewhere._paginators import header_links_paginator
|
||||
from liberapay.elsewhere import PlatformOAuth2
|
||||
from liberapay.elsewhere._extractors import key
|
||||
from liberapay.elsewhere._paginators import header_links_paginator
|
||||
|
||||
|
||||
class GitHub(PlatformOAuth2):
|
||||
|
@ -1,8 +1,8 @@
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from gratipay.elsewhere import PlatformOAuth2
|
||||
from gratipay.elsewhere._extractors import any_key, key
|
||||
from gratipay.elsewhere._paginators import query_param_paginator
|
||||
from liberapay.elsewhere import PlatformOAuth2
|
||||
from liberapay.elsewhere._extractors import any_key, key
|
||||
from liberapay.elsewhere._paginators import query_param_paginator
|
||||
|
||||
|
||||
class Google(PlatformOAuth2):
|
||||
|
@ -1,7 +1,7 @@
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from gratipay.elsewhere import PlatformOAuth1
|
||||
from gratipay.elsewhere._extractors import not_available, xpath
|
||||
from liberapay.elsewhere import PlatformOAuth1
|
||||
from liberapay.elsewhere._extractors import not_available, xpath
|
||||
|
||||
|
||||
class OpenStreetMap(PlatformOAuth1):
|
||||
|
@ -1,8 +1,8 @@
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from gratipay.elsewhere import PlatformOAuth1
|
||||
from gratipay.elsewhere._extractors import key, not_available
|
||||
from gratipay.elsewhere._paginators import query_param_paginator
|
||||
from liberapay.elsewhere import PlatformOAuth1
|
||||
from liberapay.elsewhere._extractors import key, not_available
|
||||
from liberapay.elsewhere._paginators import query_param_paginator
|
||||
|
||||
|
||||
class Twitter(PlatformOAuth1):
|
||||
|
@ -1,7 +1,7 @@
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from gratipay.elsewhere import PlatformOAuth2
|
||||
from gratipay.elsewhere._extractors import key
|
||||
from liberapay.elsewhere import PlatformOAuth2
|
||||
from liberapay.elsewhere._extractors import key
|
||||
|
||||
|
||||
class Venmo(PlatformOAuth2):
|
||||
|
@ -32,7 +32,7 @@ class ProblemChangingEmail(Response):
|
||||
Response.__init__(self, 400, self.msg.format(*args))
|
||||
|
||||
class EmailAlreadyTaken(ProblemChangingEmail):
|
||||
msg = "{} is already connected to a different Gratipay account."
|
||||
msg = "{} is already connected to a different Liberapay account."
|
||||
|
||||
class CannotRemovePrimaryEmail(ProblemChangingEmail):
|
||||
msg = "You cannot remove your primary email address."
|
||||
@ -44,14 +44,6 @@ class TooManyEmailAddresses(ProblemChangingEmail):
|
||||
msg = "You've reached the maximum number of email addresses we allow."
|
||||
|
||||
|
||||
class ProblemChangingNumber(Exception):
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
|
||||
class HasBigTips(ProblemChangingNumber):
|
||||
msg = "You receive tips too large for an individual. Please contact support@gratipay.com."
|
||||
|
||||
|
||||
class TooGreedy(Exception): pass
|
||||
class NoSelfTipping(Exception): pass
|
||||
class NoTippee(Exception): pass
|
||||
|
@ -2,15 +2,12 @@ from __future__ import division
|
||||
|
||||
import base64
|
||||
|
||||
import gratipay
|
||||
import gratipay.wireup
|
||||
from gratipay import canonize, utils
|
||||
from gratipay.cron import Cron
|
||||
from gratipay.models.participant import Participant
|
||||
from gratipay.security import authentication, csrf, x_frame_options
|
||||
from gratipay.utils import erase_cookie, http_caching, i18n, set_cookie, timer
|
||||
from gratipay.version import get_version
|
||||
from gratipay.renderers import csv_dump, jinja2_htmlescaped
|
||||
from liberapay import canonize, utils, wireup
|
||||
from liberapay.cron import Cron
|
||||
from liberapay.models.participant import Participant
|
||||
from liberapay.security import authentication, csrf, x_frame_options
|
||||
from liberapay.utils import erase_cookie, http_caching, i18n, set_cookie, timer
|
||||
from liberapay.renderers import csv_dump, jinja2_htmlescaped
|
||||
|
||||
import aspen
|
||||
from aspen.website import Website
|
||||
@ -49,28 +46,17 @@ website.renderer_factories['jinja2'].Renderer.global_context = {
|
||||
# Wireup Algorithm
|
||||
# ================
|
||||
|
||||
exc = None
|
||||
try:
|
||||
website.version = get_version()
|
||||
except Exception, e:
|
||||
exc = e
|
||||
website.version = 'x'
|
||||
|
||||
|
||||
env = website.env = gratipay.wireup.env()
|
||||
tell_sentry = website.tell_sentry = gratipay.wireup.make_sentry_teller(env)
|
||||
gratipay.wireup.canonical(env)
|
||||
website.db = gratipay.wireup.db(env)
|
||||
website.mailer = gratipay.wireup.mail(env, website.project_root)
|
||||
gratipay.wireup.billing(env)
|
||||
gratipay.wireup.username_restrictions(website)
|
||||
gratipay.wireup.load_i18n(website.project_root, tell_sentry)
|
||||
gratipay.wireup.other_stuff(website, env)
|
||||
gratipay.wireup.accounts_elsewhere(website, env)
|
||||
gratipay.wireup.cryptocoin_networks(website)
|
||||
|
||||
if exc:
|
||||
tell_sentry(exc, {})
|
||||
env = website.env = wireup.env()
|
||||
tell_sentry = website.tell_sentry = wireup.make_sentry_teller(env)
|
||||
wireup.canonical(env)
|
||||
website.db = wireup.db(env)
|
||||
website.mailer = wireup.mail(env, website.project_root)
|
||||
wireup.billing(env)
|
||||
wireup.username_restrictions(website)
|
||||
wireup.load_i18n(website.project_root, tell_sentry)
|
||||
wireup.other_stuff(website, env)
|
||||
wireup.accounts_elsewhere(website, env)
|
||||
wireup.cryptocoin_networks(website)
|
||||
|
||||
|
||||
# Periodic jobs
|
||||
@ -102,8 +88,8 @@ algorithm.functions = [
|
||||
|
||||
algorithm['dispatch_request_to_filesystem'],
|
||||
|
||||
http_caching.get_etag_for_file if website.cache_static else noop,
|
||||
http_caching.try_to_serve_304 if website.cache_static else noop,
|
||||
http_caching.get_etag_for_file if env.cache_static else noop,
|
||||
http_caching.try_to_serve_304 if env.cache_static else noop,
|
||||
|
||||
algorithm['apply_typecasters_to_path'],
|
||||
algorithm['get_resource_for_request'],
|
||||
@ -113,7 +99,6 @@ algorithm.functions = [
|
||||
tell_sentry,
|
||||
algorithm['get_response_for_exception'],
|
||||
|
||||
gratipay.set_version_header,
|
||||
authentication.add_auth_to_response,
|
||||
csrf.add_token_to_response,
|
||||
http_caching.add_caching_to_response,
|
||||
|
@ -1,11 +1,3 @@
|
||||
"""
|
||||
|
||||
The most important object in the Gratipay object model is Participant, and the
|
||||
second most important one is Ccommunity. There are a few others, but those are
|
||||
the most important two. Participant, in particular, is at the center of
|
||||
everything on Gratipay.
|
||||
|
||||
"""
|
||||
from contextlib import contextmanager
|
||||
|
||||
from postgres import Postgres
|
||||
@ -17,14 +9,14 @@ def just_yield(obj):
|
||||
yield obj
|
||||
|
||||
|
||||
class GratipayDB(Postgres):
|
||||
class DB(Postgres):
|
||||
|
||||
def get_cursor(self, cursor=None, **kw):
|
||||
if cursor:
|
||||
if kw:
|
||||
raise ValueError('cannot change options when reusing a cursor')
|
||||
return just_yield(cursor)
|
||||
return super(GratipayDB, self).get_cursor(**kw)
|
||||
return super(DB, self).get_cursor(**kw)
|
||||
|
||||
def self_check(self):
|
||||
with self.get_cursor() as cursor:
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Teams on Gratipay are plural participants with members.
|
||||
"""Teams are plural participants with members.
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
from decimal import Decimal
|
||||
@ -8,19 +8,11 @@ from aspen.utils import typecheck
|
||||
|
||||
class MemberLimitReached(Exception): pass
|
||||
|
||||
|
||||
class StubParticipantAdded(Exception): pass
|
||||
|
||||
|
||||
class MixinTeam(object):
|
||||
"""This class provides methods for working with a Participant as a Team.
|
||||
|
||||
:param Participant participant: the underlying :py:class:`~gratipay.participant.Participant` object for this team
|
||||
|
||||
"""
|
||||
|
||||
# XXX These were all written with the ORM and need to be converted.
|
||||
|
||||
def __init__(self, participant):
|
||||
self.participant = participant
|
||||
|
||||
def show_as_team(self, user):
|
||||
"""Return a boolean, whether to show this participant as a team.
|
||||
@ -112,8 +104,8 @@ class MixinTeam(object):
|
||||
assert self.IS_PLURAL
|
||||
|
||||
# lazy import to avoid circular import
|
||||
from gratipay.security.user import User
|
||||
from gratipay.models.participant import Participant
|
||||
from liberapay.security.user import User
|
||||
from liberapay.models.participant import Participant
|
||||
|
||||
typecheck( member, Participant
|
||||
, take, Decimal
|
||||
|
@ -12,10 +12,10 @@ from postgres.orm import Model
|
||||
from psycopg2 import IntegrityError
|
||||
import xmltodict
|
||||
|
||||
import gratipay
|
||||
from gratipay.exceptions import ProblemChangingUsername
|
||||
from gratipay.security.crypto import constant_time_compare
|
||||
from gratipay.utils.username import safely_reserve_a_username
|
||||
import liberapay
|
||||
from liberapay.exceptions import ProblemChangingUsername
|
||||
from liberapay.security.crypto import constant_time_compare
|
||||
from liberapay.utils.username import safely_reserve_a_username
|
||||
|
||||
|
||||
CONNECT_TOKEN_TIMEOUT = timedelta(hours=24)
|
||||
@ -185,15 +185,15 @@ class AccountElsewhere(Model):
|
||||
return self.platform_data.get_auth_session(**params)
|
||||
|
||||
@property
|
||||
def gratipay_slug(self):
|
||||
def liberapay_slug(self):
|
||||
return self.user_name or ('~' + self.user_id)
|
||||
|
||||
@property
|
||||
def gratipay_url(self):
|
||||
scheme = gratipay.canonical_scheme
|
||||
host = gratipay.canonical_host
|
||||
def liberapay_url(self):
|
||||
scheme = liberapay.canonical_scheme
|
||||
host = liberapay.canonical_host
|
||||
platform = self.platform
|
||||
slug = self.gratipay_slug
|
||||
slug = self.liberapay_slug
|
||||
return "{scheme}://{host}/on/{platform}/{slug}/".format(**locals())
|
||||
|
||||
@property
|
||||
@ -225,7 +225,7 @@ class AccountElsewhere(Model):
|
||||
def opt_in(self, desired_username):
|
||||
"""Given a desired username, return a User object.
|
||||
"""
|
||||
from gratipay.security.user import User
|
||||
from liberapay.security.user import User
|
||||
user = User.from_username(self.participant.username)
|
||||
assert not user.ANON, self.participant # sanity check
|
||||
if self.participant.is_claimed:
|
||||
|
@ -40,8 +40,6 @@ def get_list_for(db, participant_id):
|
||||
""", (participant_id,))
|
||||
|
||||
class Community(Model):
|
||||
"""Model a community on Gratipay.
|
||||
"""
|
||||
|
||||
typname = "communities"
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
"""*Participant* is the name Gratipay gives to people and groups that are known
|
||||
to Gratipay. We've got a ``participants`` table in the database, and a
|
||||
:py:class:`Participant` class that we define here. We distinguish several kinds
|
||||
of participant, based on certain properties.
|
||||
"""*Participant* is the name we give to people and groups that are known to us.
|
||||
We've got a `participants` table in the database, and a `Participant` class
|
||||
that we define here. We distinguish two kinds of participants:
|
||||
|
||||
- *Stub* participants
|
||||
- *Organizations* are plural participants
|
||||
- *Teams* are plural participants with members
|
||||
- "stub" participants who haven't actually joined yet
|
||||
- "real" participants who have signed in
|
||||
|
||||
"""
|
||||
from __future__ import print_function, unicode_literals
|
||||
@ -24,10 +22,8 @@ from markupsafe import escape as htmlescape
|
||||
from postgres.orm import Model
|
||||
from psycopg2 import IntegrityError
|
||||
|
||||
import gratipay
|
||||
from gratipay import NotSane
|
||||
from gratipay.exceptions import (
|
||||
HasBigTips,
|
||||
import liberapay
|
||||
from liberapay.exceptions import (
|
||||
UsernameIsEmpty,
|
||||
UsernameTooLong,
|
||||
UsernameContainsInvalidCharacters,
|
||||
@ -43,28 +39,26 @@ from gratipay.exceptions import (
|
||||
TooManyEmailAddresses,
|
||||
)
|
||||
|
||||
from gratipay.models import add_event
|
||||
from gratipay.models._mixin_team import MixinTeam
|
||||
from gratipay.models.account_elsewhere import AccountElsewhere
|
||||
from gratipay.models.exchange_route import ExchangeRoute
|
||||
from gratipay.security.crypto import constant_time_compare
|
||||
from gratipay.utils import i18n, is_card_expiring, emails, notifications, pricing
|
||||
from gratipay.utils.username import safely_reserve_a_username
|
||||
from liberapay.models import add_event
|
||||
from liberapay.models._mixin_team import MixinTeam
|
||||
from liberapay.models.account_elsewhere import AccountElsewhere
|
||||
from liberapay.models.exchange_route import ExchangeRoute
|
||||
from liberapay.security.crypto import constant_time_compare
|
||||
from liberapay.utils import i18n, is_card_expiring, emails, notifications
|
||||
from liberapay.utils.username import safely_reserve_a_username
|
||||
|
||||
|
||||
ASCII_ALLOWED_IN_USERNAME = set("0123456789"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
".,-_:@ ")
|
||||
# We use | in Sentry logging, so don't make that allowable. :-)
|
||||
"-_")
|
||||
|
||||
EMAIL_HASH_TIMEOUT = timedelta(hours=24)
|
||||
|
||||
USERNAME_MAX_SIZE = 32
|
||||
|
||||
|
||||
class Participant(Model, MixinTeam):
|
||||
"""Represent a Gratipay participant.
|
||||
"""
|
||||
|
||||
typname = 'participants'
|
||||
|
||||
@ -199,12 +193,6 @@ class Participant(Model, MixinTeam):
|
||||
|
||||
def update_number(self, number):
|
||||
assert number in ('singular', 'plural')
|
||||
if number == 'singular':
|
||||
nbigtips = self.db.one("""\
|
||||
SELECT count(*) FROM current_tips WHERE tippee=%s AND amount > %s
|
||||
""", (self.username, gratipay.MAX_TIP_SINGULAR))
|
||||
if nbigtips > 0:
|
||||
raise HasBigTips
|
||||
with self.db.get_cursor() as c:
|
||||
add_event(c, 'participant', dict(action='set', id=self.id, values=dict(number=number)))
|
||||
self.remove_all_members(c)
|
||||
@ -270,11 +258,7 @@ class Participant(Model, MixinTeam):
|
||||
|
||||
@property
|
||||
def suggested_payment(self):
|
||||
return pricing.suggested_payment(self.usage)
|
||||
|
||||
@property
|
||||
def suggested_payment_low_high(self):
|
||||
return pricing.suggested_payment_low_high(self.usage)
|
||||
return (self.usage * Decimal('0.05')).quantize(Decimal('.01'))
|
||||
|
||||
|
||||
# API Key
|
||||
@ -295,8 +279,6 @@ class Participant(Model, MixinTeam):
|
||||
|
||||
# Claiming
|
||||
# ========
|
||||
# An unclaimed Participant is a stub that's created when someone pledges to
|
||||
# give to an AccountElsewhere that's not been connected on Gratipay yet.
|
||||
|
||||
def resolve_unclaimed(self):
|
||||
"""Given a username, return an URL path.
|
||||
@ -353,7 +335,7 @@ class Participant(Model, MixinTeam):
|
||||
class BankWithdrawalFailed(Exception): pass
|
||||
|
||||
def withdraw_balance_to_bank_account(self):
|
||||
from gratipay.billing.exchanges import ach_credit
|
||||
from liberapay.billing.exchanges import ach_credit
|
||||
error = ach_credit( self.db
|
||||
, self
|
||||
, Decimal('0.00') # don't withhold anything
|
||||
@ -541,8 +523,8 @@ class Participant(Model, MixinTeam):
|
||||
if not nonce:
|
||||
return self.add_email(email)
|
||||
|
||||
scheme = gratipay.canonical_scheme
|
||||
host = gratipay.canonical_host
|
||||
scheme = liberapay.canonical_scheme
|
||||
host = liberapay.canonical_host
|
||||
username = self.username_lower
|
||||
quoted_email = quote(email)
|
||||
link = "{scheme}://{host}/{username}/emails/verify.html?email={quoted_email}&nonce={nonce}"
|
||||
@ -648,8 +630,8 @@ class Participant(Model, MixinTeam):
|
||||
b = base_spt[t].render(context).strip()
|
||||
return b.replace('$body', spt[t].render(context).strip())
|
||||
message = {}
|
||||
message['from_email'] = 'support@gratipay.com'
|
||||
message['from_name'] = 'Gratipay Support'
|
||||
message['from_email'] = 'support@liberapay.com'
|
||||
message['from_name'] = 'Liberapay Support'
|
||||
message['to'] = [{'email': email, 'name': self.username}]
|
||||
message['subject'] = spt['subject'].render(context)
|
||||
message['html'] = render('text/html', context_html)
|
||||
@ -668,7 +650,7 @@ class Participant(Model, MixinTeam):
|
||||
user_name=elsewhere.user_name,
|
||||
platform=elsewhere.platform_data.display_name,
|
||||
amount=t.amount,
|
||||
profile_url=elsewhere.gratipay_url,
|
||||
profile_url=elsewhere.liberapay_url,
|
||||
)
|
||||
|
||||
def queue_email(self, spt_name, **context):
|
||||
@ -792,8 +774,8 @@ class Participant(Model, MixinTeam):
|
||||
|
||||
@property
|
||||
def profile_url(self):
|
||||
scheme = gratipay.canonical_scheme
|
||||
host = gratipay.canonical_host
|
||||
scheme = liberapay.canonical_scheme
|
||||
host = liberapay.canonical_host
|
||||
username = self.username
|
||||
return '{scheme}://{host}/{username}/'.format(**locals())
|
||||
|
||||
@ -856,7 +838,7 @@ class Participant(Model, MixinTeam):
|
||||
|
||||
lowercased = suggested.lower()
|
||||
|
||||
if lowercased in gratipay.RESTRICTED_USERNAMES:
|
||||
if lowercased in liberapay.RESTRICTED_USERNAMES:
|
||||
raise UsernameIsRestricted(suggested)
|
||||
|
||||
if suggested != self.username:
|
||||
@ -1017,18 +999,6 @@ class Participant(Model, MixinTeam):
|
||||
new_takes = self.compute_actual_takes(cursor=cursor)
|
||||
self.update_taking(old_takes, new_takes, cursor=cursor)
|
||||
|
||||
def update_is_free_rider(self, is_free_rider, cursor=None):
|
||||
with self.db.get_cursor(cursor) as cursor:
|
||||
cursor.run( "UPDATE participants SET is_free_rider=%(is_free_rider)s "
|
||||
"WHERE username=%(username)s"
|
||||
, dict(username=self.username, is_free_rider=is_free_rider)
|
||||
)
|
||||
add_event( cursor
|
||||
, 'participant'
|
||||
, dict(id=self.id, action='set', values=dict(is_free_rider=is_free_rider))
|
||||
)
|
||||
self.set_attributes(is_free_rider=is_free_rider)
|
||||
|
||||
|
||||
def set_tip_to(self, tippee, amount, update_self=True, update_tippee=True, cursor=None):
|
||||
"""Given a Participant or username, and amount as str, returns a dict.
|
||||
@ -1056,8 +1026,7 @@ class Participant(Model, MixinTeam):
|
||||
raise NoSelfTipping
|
||||
|
||||
amount = Decimal(amount) # May raise InvalidOperation
|
||||
max_tip = gratipay.MAX_TIP_PLURAL if tippee.IS_PLURAL else gratipay.MAX_TIP_SINGULAR
|
||||
if (amount < gratipay.MIN_TIP) or (amount > max_tip):
|
||||
if amount != 0 and amount < liberapay.MIN_TIP or amount > liberapay.MAX_TIP:
|
||||
raise BadAmount
|
||||
|
||||
if not tippee.accepts_tips and amount != 0:
|
||||
@ -1088,9 +1057,6 @@ class Participant(Model, MixinTeam):
|
||||
if update_tippee:
|
||||
# Update receiving amount of tippee
|
||||
tippee.update_receiving(cursor)
|
||||
if tippee.username == 'Gratipay':
|
||||
# Update whether the tipper is using Gratipay for free
|
||||
self.update_is_free_rider(None if amount == 0 else False, cursor)
|
||||
|
||||
return t._asdict()
|
||||
|
||||
@ -1173,12 +1139,6 @@ class Participant(Model, MixinTeam):
|
||||
|
||||
|
||||
def get_giving_for_profile(self):
|
||||
"""Given a participant id and a date, return a list and a Decimal.
|
||||
|
||||
This function is used to populate a participant's page for their own
|
||||
viewing pleasure.
|
||||
|
||||
"""
|
||||
|
||||
TIPS = """\
|
||||
|
||||
@ -1232,13 +1192,7 @@ class Participant(Model, MixinTeam):
|
||||
unclaimed_tips = self.db.all(UNCLAIMED_TIPS, (self.username,))
|
||||
|
||||
|
||||
# Compute the total.
|
||||
# ==================
|
||||
# For payday we only want to process payments to tippees who have
|
||||
# themselves opted into Gratipay. For the tipper's profile page we want
|
||||
# to show the total amount they've pledged (so they're not surprised
|
||||
# when someone *does* start accepting tips and all of a sudden they're
|
||||
# hit with bigger charges.
|
||||
# Compute the total
|
||||
|
||||
total = sum([t.amount for t in tips])
|
||||
if not total:
|
||||
@ -1282,19 +1236,6 @@ class Participant(Model, MixinTeam):
|
||||
return self.db.all(TIPS, (self.username,), back_as=dict)
|
||||
|
||||
|
||||
def get_og_title(self):
|
||||
out = self.username
|
||||
receiving = self.receiving
|
||||
giving = self.giving
|
||||
if (giving > receiving) and not self.anonymous_giving:
|
||||
out += " gives $%.2f/wk" % giving
|
||||
elif receiving > 0 and not self.anonymous_receiving:
|
||||
out += " receives $%.2f/wk" % receiving
|
||||
else:
|
||||
out += " is"
|
||||
return out + " on Gratipay"
|
||||
|
||||
|
||||
def get_age_in_seconds(self):
|
||||
out = -1
|
||||
if self.claimed_time is not None:
|
||||
@ -1406,7 +1347,7 @@ class Participant(Model, MixinTeam):
|
||||
""", ( username
|
||||
, username.lower()
|
||||
, self.username
|
||||
), default=NotSane)
|
||||
), default=Exception)
|
||||
return check
|
||||
|
||||
archived_as = safely_reserve_a_username(cursor, reserve=reserve)
|
||||
@ -1426,16 +1367,16 @@ class Participant(Model, MixinTeam):
|
||||
Returns None or raises NeedConfirmation.
|
||||
|
||||
This method associates an account on another platform (GitHub, Twitter,
|
||||
etc.) with the given Gratipay participant. Every account elsewhere has an
|
||||
associated Gratipay participant account, even if its only a stub
|
||||
etc.) with the given Liberapay participant. Every account elsewhere has an
|
||||
associated Liberapay participant account, even if its only a stub
|
||||
participant (it allows us to track pledges to that account should they
|
||||
ever decide to join Gratipay).
|
||||
ever decide to join Liberapay).
|
||||
|
||||
In certain circumstances, we want to present the user with a
|
||||
confirmation before proceeding to transfer the account elsewhere to
|
||||
the new Gratipay account; NeedConfirmation is the signal to request
|
||||
the new Liberapay account; NeedConfirmation is the signal to request
|
||||
confirmation. If it was the last account elsewhere connected to the old
|
||||
Gratipay account, then we absorb the old Gratipay account into the new one,
|
||||
Liberapay account, then we absorb the old Liberapay account into the new one,
|
||||
effectively archiving the old account.
|
||||
|
||||
Here's what absorbing means:
|
||||
@ -1596,7 +1537,7 @@ class Participant(Model, MixinTeam):
|
||||
# Load the existing connection.
|
||||
# =============================
|
||||
# Every account elsewhere has at least a stub participant account
|
||||
# on Gratipay.
|
||||
# on Liberapay.
|
||||
|
||||
elsewhere = cursor.one("""
|
||||
|
||||
@ -1605,7 +1546,7 @@ class Participant(Model, MixinTeam):
|
||||
JOIN participants ON participant=participants.username
|
||||
WHERE elsewhere.platform=%s AND elsewhere.user_id=%s
|
||||
|
||||
""", (platform, user_id), default=NotSane)
|
||||
""", (platform, user_id), default=Exception)
|
||||
other = elsewhere.participant
|
||||
|
||||
|
||||
@ -1622,13 +1563,12 @@ class Participant(Model, MixinTeam):
|
||||
# three cases:
|
||||
#
|
||||
# - the other participant is not a stub; we are taking the
|
||||
# account elsewhere away from another viable Gratipay
|
||||
# participant
|
||||
# account elsewhere away from another viable participant
|
||||
#
|
||||
# - the other participant has no other accounts elsewhere; taking
|
||||
# away the account elsewhere will leave the other Gratipay
|
||||
# participant without any means of logging in, and it will be
|
||||
# archived and its tips absorbed by us
|
||||
# away the account elsewhere will leave the other participant
|
||||
# without any means of logging in, and it will be archived
|
||||
# and its tips absorbed by us
|
||||
#
|
||||
# - we already have an account elsewhere connected from the given
|
||||
# platform, and it will be handed off to a new stub
|
||||
@ -1785,7 +1725,6 @@ class Participant(Model, MixinTeam):
|
||||
, 'username': self.username
|
||||
, 'avatar': self.avatar_url
|
||||
, 'number': self.number
|
||||
, 'on': 'gratipay'
|
||||
}
|
||||
|
||||
if not details:
|
||||
|
@ -4,10 +4,10 @@ import binascii
|
||||
from datetime import date
|
||||
|
||||
from aspen import Response
|
||||
from gratipay.models.participant import Participant
|
||||
from gratipay.security import csrf
|
||||
from gratipay.security.crypto import constant_time_compare
|
||||
from gratipay.security.user import User, SESSION
|
||||
from liberapay.models.participant import Participant
|
||||
from liberapay.security import csrf
|
||||
from liberapay.security.crypto import constant_time_compare
|
||||
from liberapay.security.user import User, SESSION
|
||||
|
||||
|
||||
ANON = User()
|
||||
|
@ -3,8 +3,8 @@ from datetime import timedelta
|
||||
import uuid
|
||||
|
||||
from aspen.utils import utcnow
|
||||
from gratipay.models.participant import Participant
|
||||
from gratipay.utils import erase_cookie, set_cookie
|
||||
from liberapay.models.participant import Participant
|
||||
from liberapay.utils import erase_cookie, set_cookie
|
||||
|
||||
|
||||
SESSION = b'session'
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Helpers for testing Gratipay.
|
||||
"""Helpers for testing.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
@ -9,13 +9,13 @@ from os.path import dirname, join, realpath
|
||||
from aspen import resources
|
||||
from aspen.utils import utcnow
|
||||
from aspen.testing.client import Client
|
||||
from gratipay.billing.exchanges import record_exchange, record_exchange_result
|
||||
from gratipay.elsewhere import UserInfo
|
||||
from gratipay.main import website
|
||||
from gratipay.models.account_elsewhere import AccountElsewhere
|
||||
from gratipay.models.exchange_route import ExchangeRoute
|
||||
from gratipay.security.user import User
|
||||
from gratipay.testing.vcr import use_cassette
|
||||
from liberapay.billing.exchanges import record_exchange, record_exchange_result
|
||||
from liberapay.elsewhere import UserInfo
|
||||
from liberapay.main import website
|
||||
from liberapay.models.account_elsewhere import AccountElsewhere
|
||||
from liberapay.models.exchange_route import ExchangeRoute
|
||||
from liberapay.security.user import User
|
||||
from liberapay.testing.vcr import use_cassette
|
||||
from psycopg2 import IntegrityError, InternalError
|
||||
|
||||
|
||||
|
@ -4,9 +4,9 @@ import itertools
|
||||
|
||||
import balanced
|
||||
|
||||
from gratipay.models.exchange_route import ExchangeRoute
|
||||
from gratipay.testing import Harness
|
||||
from gratipay.testing.vcr import use_cassette
|
||||
from liberapay.models.exchange_route import ExchangeRoute
|
||||
from liberapay.testing import Harness
|
||||
from liberapay.testing.vcr import use_cassette
|
||||
|
||||
|
||||
class BalancedHarness(Harness):
|
||||
@ -55,13 +55,6 @@ with use_cassette('BalancedHarness'):
|
||||
'state': 'Confusion',
|
||||
'postal_code': '90210',
|
||||
},
|
||||
# gratipay stores some of the address data in the meta fields,
|
||||
# continue using them to support backwards compatibility
|
||||
meta={
|
||||
'address_2': 'Box 2',
|
||||
'city_town': '',
|
||||
'region': 'Confusion',
|
||||
}
|
||||
).save()
|
||||
cls.card.associate_to_customer(cls.janet_href)
|
||||
cls.card_href = unicode(cls.card.href)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import mock
|
||||
|
||||
from gratipay.models.participant import Participant
|
||||
from gratipay.testing import Harness
|
||||
from liberapay.models.participant import Participant
|
||||
from liberapay.testing import Harness
|
||||
|
||||
|
||||
class EmailHarness(Harness):
|
||||
@ -11,7 +11,7 @@ class EmailHarness(Harness):
|
||||
self.mailer_patcher = mock.patch.object(Participant._mailer.messages, 'send')
|
||||
self.mailer = self.mailer_patcher.start()
|
||||
self.addCleanup(self.mailer_patcher.stop)
|
||||
sleep_patcher = mock.patch('gratipay.models.participant.sleep')
|
||||
sleep_patcher = mock.patch('liberapay.models.participant.sleep')
|
||||
sleep_patcher.start()
|
||||
self.addCleanup(sleep_patcher.stop)
|
||||
|
||||
|
@ -9,7 +9,7 @@ from aspen.utils import to_rfc822, utcnow
|
||||
from dependency_injection import resolve_dependencies
|
||||
from postgres.cursors import SimpleCursorBase
|
||||
|
||||
import gratipay
|
||||
import liberapay
|
||||
|
||||
|
||||
BEGINNING_OF_EPOCH = to_rfc822(datetime(1970, 1, 1)).encode('ascii')
|
||||
@ -59,7 +59,7 @@ def get_participant(state, restrict=True, resolve_unclaimed=True):
|
||||
if user.ANON:
|
||||
raise Response(403, _("You need to log in to access this page."))
|
||||
|
||||
from gratipay.models.participant import Participant # avoid circular import
|
||||
from liberapay.models.participant import Participant # avoid circular import
|
||||
participant = Participant.from_username(slug)
|
||||
|
||||
if participant is None:
|
||||
@ -76,7 +76,7 @@ def get_participant(state, restrict=True, resolve_unclaimed=True):
|
||||
to = participant.resolve_unclaimed()
|
||||
if to:
|
||||
# This is a stub account (someone on another platform who hasn't
|
||||
# actually registered with Gratipay yet)
|
||||
# actually registered with Liberapay yet)
|
||||
request.redirect(to)
|
||||
else:
|
||||
# This is an archived account (result of take_over)
|
||||
@ -100,22 +100,6 @@ def update_global_stats(website):
|
||||
website.gnactive = stats[0]
|
||||
website.gtransfer_volume = stats[1]
|
||||
|
||||
nbackers = website.db.one("""
|
||||
SELECT npatrons
|
||||
FROM participants
|
||||
WHERE username = 'Gratipay'
|
||||
""", default=0)
|
||||
website.support_current = cur = int(round(nbackers / stats[0] * 100)) if stats[0] else 0
|
||||
if cur < 10: goal = 20
|
||||
elif cur < 15: goal = 30
|
||||
elif cur < 25: goal = 40
|
||||
elif cur < 35: goal = 50
|
||||
elif cur < 45: goal = 60
|
||||
elif cur < 55: goal = 70
|
||||
elif cur < 65: goal = 80
|
||||
elif cur > 70: goal = None
|
||||
website.support_goal = goal
|
||||
|
||||
|
||||
def _execute(this, sql, params=[]):
|
||||
print(sql.strip(), params)
|
||||
@ -166,7 +150,7 @@ def set_cookie(cookies, key, value, expires=None, httponly=True, path=b'/'):
|
||||
cookie[b'httponly'] = True
|
||||
if path:
|
||||
cookie[b'path'] = path
|
||||
if gratipay.canonical_scheme == 'https':
|
||||
if liberapay.canonical_scheme == 'https':
|
||||
cookie[b'secure'] = True
|
||||
|
||||
|
||||
|
@ -7,11 +7,11 @@ import sys
|
||||
from faker import Factory
|
||||
from psycopg2 import IntegrityError
|
||||
|
||||
from gratipay import wireup, MAX_TIP_SINGULAR, MIN_TIP
|
||||
from gratipay.elsewhere import PLATFORMS
|
||||
from gratipay.models.participant import Participant
|
||||
from gratipay.models import community
|
||||
from gratipay.models import check_db
|
||||
from liberapay import wireup, MAX_TIP, MIN_TIP
|
||||
from liberapay.elsewhere import PLATFORMS
|
||||
from liberapay.models.participant import Participant
|
||||
from liberapay.models import community
|
||||
from liberapay.models import check_db
|
||||
|
||||
faker = Factory.create()
|
||||
|
||||
@ -88,8 +88,7 @@ def fake_community(db, creator):
|
||||
|
||||
|
||||
def fake_tip_amount():
|
||||
amount = ((D(random.random()) * (MAX_TIP_SINGULAR - MIN_TIP))
|
||||
+ MIN_TIP)
|
||||
amount = (D(random.random()) * (MAX_TIP - MIN_TIP) + MIN_TIP)
|
||||
|
||||
decimal_amount = D(amount).quantize(D('.01'))
|
||||
while decimal_amount == D('0.00'):
|
||||
|
@ -72,9 +72,9 @@ def add_caching_to_response(response, request=None, etag=None):
|
||||
# https://developers.google.com/speed/docs/best-practices/caching
|
||||
response.headers['Etag'] = etag
|
||||
|
||||
# Set CORS header for https://assets.gratipay.com (see issue #2970)
|
||||
# Set CORS header for https://static.liberapay.com
|
||||
if 'Access-Control-Allow-Origin' not in response.headers:
|
||||
response.headers['Access-Control-Allow-Origin'] = 'https://gratipay.com'
|
||||
response.headers['Access-Control-Allow-Origin'] = 'https://liberapay.com'
|
||||
|
||||
if request.line.uri.querystring.get('etag'):
|
||||
# We can cache "indefinitely" when the querystring contains the etag.
|
||||
|
@ -1,6 +1,7 @@
|
||||
# encoding: utf8
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
from datetime import date, datetime
|
||||
from io import BytesIO
|
||||
import re
|
||||
from unicodedata import combining, normalize
|
||||
@ -147,7 +148,9 @@ def n_get_text(tell_sentry, state, loc, s, p, n, *a, **kw):
|
||||
|
||||
|
||||
def to_age(dt, loc, **kw):
|
||||
if isinstance(dt, datetime):
|
||||
return format_timedelta(dt - utcnow(), locale=loc, **kw)
|
||||
return format_timedelta(dt - date.today(), locale=loc, granularity='day', **kw)
|
||||
|
||||
|
||||
def regularize_locale(loc):
|
||||
|
@ -1,52 +0,0 @@
|
||||
from decimal import Decimal as D, ROUND_HALF_EVEN
|
||||
|
||||
|
||||
def suggested_payment(usage):
|
||||
if usage >= 500:
|
||||
percentage = D('0.02')
|
||||
elif usage >= 20:
|
||||
percentage = D('0.05')
|
||||
else:
|
||||
percentage = D('0.10')
|
||||
|
||||
suggestion = usage * percentage
|
||||
if suggestion == 0:
|
||||
rounded = suggestion
|
||||
elif suggestion < 0.25:
|
||||
rounded = D('0.25')
|
||||
elif suggestion < 0.50:
|
||||
rounded = D('0.50')
|
||||
elif suggestion < 1:
|
||||
rounded = D('1.00')
|
||||
else:
|
||||
rounded = suggestion.quantize(D('0'), ROUND_HALF_EVEN)
|
||||
|
||||
return rounded
|
||||
|
||||
|
||||
def suggested_payment_low_high(usage):
|
||||
# Above $500/wk we suggest 2%.
|
||||
if usage >= 5000:
|
||||
low = D('100.00')
|
||||
high = D('1000.00')
|
||||
elif usage >= 500:
|
||||
low = D('10.00')
|
||||
high = D('100.00')
|
||||
|
||||
# From $20 to $499 we suggest 5%.
|
||||
elif usage >= 100:
|
||||
low = D('5.00')
|
||||
high = D('25.00')
|
||||
elif usage >= 20:
|
||||
low = D('1.00')
|
||||
high = D('5.00')
|
||||
|
||||
# Below $20 we suggest 10%.
|
||||
elif usage >= 5:
|
||||
low = D('0.50')
|
||||
high = D('2.00')
|
||||
else:
|
||||
low = D('0.10')
|
||||
high = D('1.00')
|
||||
|
||||
return low, high
|
@ -1,10 +0,0 @@
|
||||
from os.path import abspath, dirname, join
|
||||
|
||||
def get_version():
|
||||
root = dirname(dirname(abspath(__file__)))
|
||||
with open(join(root, 'www/version.txt')) as f:
|
||||
version = f.read().strip()
|
||||
return version
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(get_version())
|
@ -13,45 +13,45 @@ from babel.core import Locale
|
||||
from babel.messages.pofile import read_po
|
||||
from babel.numbers import parse_pattern
|
||||
import balanced
|
||||
import gratipay
|
||||
import gratipay.billing.payday
|
||||
import liberapay
|
||||
import liberapay.billing.payday
|
||||
import raven
|
||||
import mandrill
|
||||
from environment import Environment, is_yesish
|
||||
from gratipay.elsewhere import PlatformRegistry
|
||||
from gratipay.elsewhere.bitbucket import Bitbucket
|
||||
from gratipay.elsewhere.bountysource import Bountysource
|
||||
from gratipay.elsewhere.github import GitHub
|
||||
from gratipay.elsewhere.facebook import Facebook
|
||||
from gratipay.elsewhere.google import Google
|
||||
from gratipay.elsewhere.openstreetmap import OpenStreetMap
|
||||
from gratipay.elsewhere.twitter import Twitter
|
||||
from gratipay.elsewhere.venmo import Venmo
|
||||
from gratipay.models.account_elsewhere import AccountElsewhere
|
||||
from gratipay.models.community import Community
|
||||
from gratipay.models.exchange_route import ExchangeRoute
|
||||
from gratipay.models.participant import Participant
|
||||
from gratipay.models import GratipayDB
|
||||
from gratipay.utils.emails import compile_email_spt
|
||||
from gratipay.utils.http_caching import asset_etag
|
||||
from gratipay.utils.i18n import (
|
||||
from liberapay.elsewhere import PlatformRegistry
|
||||
from liberapay.elsewhere.bitbucket import Bitbucket
|
||||
from liberapay.elsewhere.bountysource import Bountysource
|
||||
from liberapay.elsewhere.github import GitHub
|
||||
from liberapay.elsewhere.facebook import Facebook
|
||||
from liberapay.elsewhere.google import Google
|
||||
from liberapay.elsewhere.openstreetmap import OpenStreetMap
|
||||
from liberapay.elsewhere.twitter import Twitter
|
||||
from liberapay.elsewhere.venmo import Venmo
|
||||
from liberapay.models.account_elsewhere import AccountElsewhere
|
||||
from liberapay.models.community import Community
|
||||
from liberapay.models.exchange_route import ExchangeRoute
|
||||
from liberapay.models.participant import Participant
|
||||
from liberapay.models import DB
|
||||
from liberapay.utils.emails import compile_email_spt
|
||||
from liberapay.utils.http_caching import asset_etag
|
||||
from liberapay.utils.i18n import (
|
||||
ALIASES, ALIASES_R, COUNTRIES, LANGUAGES_2, LOCALES,
|
||||
get_function_from_rule, make_sorted_dict
|
||||
)
|
||||
|
||||
def canonical(env):
|
||||
gratipay.canonical_scheme = env.canonical_scheme
|
||||
gratipay.canonical_host = env.canonical_host
|
||||
liberapay.canonical_scheme = env.canonical_scheme
|
||||
liberapay.canonical_host = env.canonical_host
|
||||
|
||||
|
||||
def db(env):
|
||||
dburl = env.database_url
|
||||
maxconn = env.database_maxconn
|
||||
db = GratipayDB(dburl, maxconn=maxconn)
|
||||
db = DB(dburl, maxconn=maxconn)
|
||||
|
||||
for model in (AccountElsewhere, Community, ExchangeRoute, Participant):
|
||||
db.register_model(model)
|
||||
gratipay.billing.payday.Payday.db = db
|
||||
liberapay.billing.payday.Payday.db = db
|
||||
|
||||
return db
|
||||
|
||||
@ -70,7 +70,7 @@ def billing(env):
|
||||
|
||||
|
||||
def username_restrictions(website):
|
||||
gratipay.RESTRICTED_USERNAMES = os.listdir(website.www_root)
|
||||
liberapay.RESTRICTED_USERNAMES = os.listdir(website.www_root)
|
||||
|
||||
|
||||
def make_sentry_teller(env):
|
||||
@ -95,7 +95,7 @@ def make_sentry_teller(env):
|
||||
# Only log server errors to Sentry. For responses < 500 we use
|
||||
# stream-/line-based access logging. See discussion on:
|
||||
|
||||
# https://github.com/gratipay/gratipay.com/pull/1560.
|
||||
# https://github.com/liberapay/liberapay.com/pull/1560.
|
||||
|
||||
return
|
||||
|
||||
@ -130,7 +130,7 @@ def make_sentry_teller(env):
|
||||
, 'is_admin': user.participant.is_admin
|
||||
, 'is_suspicious': user.participant.is_suspicious
|
||||
, 'claimed_time': user.participant.claimed_time.isoformat()
|
||||
, 'url': 'https://gratipay.com/{}/'.format(username)
|
||||
, 'url': 'https://liberapay.com/{}/'.format(username)
|
||||
}
|
||||
|
||||
|
||||
@ -310,10 +310,7 @@ def load_i18n(project_root, tell_sentry):
|
||||
|
||||
|
||||
def other_stuff(website, env):
|
||||
website.cache_static = env.gratipay_cache_static
|
||||
website.compress_assets = env.gratipay_compress_assets
|
||||
|
||||
if website.cache_static:
|
||||
if env.cache_static:
|
||||
def asset(path):
|
||||
fspath = website.www_root+'/assets/'+path
|
||||
etag = ''
|
||||
@ -321,16 +318,13 @@ def other_stuff(website, env):
|
||||
etag = asset_etag(fspath)
|
||||
except Exception as e:
|
||||
website.tell_sentry(e, {})
|
||||
return env.gratipay_asset_url+path+(etag and '?etag='+etag)
|
||||
return env.asset_url+path+(etag and '?etag='+etag)
|
||||
website.asset = asset
|
||||
compile_assets(website)
|
||||
else:
|
||||
website.asset = lambda path: env.gratipay_asset_url+path
|
||||
website.asset = lambda path: env.asset_url+path
|
||||
clean_assets(website.www_root)
|
||||
|
||||
website.optimizely_id = env.optimizely_id
|
||||
website.include_piwik = env.include_piwik
|
||||
|
||||
website.log_metrics = env.log_metrics
|
||||
|
||||
|
||||
@ -340,9 +334,9 @@ def env():
|
||||
CANONICAL_HOST = unicode,
|
||||
CANONICAL_SCHEME = unicode,
|
||||
DATABASE_MAXCONN = int,
|
||||
GRATIPAY_ASSET_URL = unicode,
|
||||
GRATIPAY_CACHE_STATIC = is_yesish,
|
||||
GRATIPAY_COMPRESS_ASSETS = is_yesish,
|
||||
ASSET_URL = unicode,
|
||||
CACHE_STATIC = is_yesish,
|
||||
COMPRESS_ASSETS = is_yesish,
|
||||
BALANCED_API_SECRET = unicode,
|
||||
GITHUB_CLIENT_ID = unicode,
|
||||
GITHUB_CLIENT_SECRET = unicode,
|
||||
@ -374,10 +368,8 @@ def env():
|
||||
UPDATE_GLOBAL_STATS_EVERY = int,
|
||||
CHECK_DB_EVERY = int,
|
||||
DEQUEUE_EMAILS_EVERY = int,
|
||||
OPTIMIZELY_ID = unicode,
|
||||
SENTRY_DSN = unicode,
|
||||
LOG_METRICS = is_yesish,
|
||||
INCLUDE_PIWIK = is_yesish,
|
||||
MANDRILL_KEY = unicode,
|
||||
RAISE_SIGNIN_NOTIFICATIONS = is_yesish,
|
||||
|
||||
@ -391,43 +383,24 @@ def env():
|
||||
# ==============
|
||||
|
||||
if env.malformed:
|
||||
these = len(env.malformed) != 1 and 'these' or 'this'
|
||||
plural = len(env.malformed) != 1 and 's' or ''
|
||||
aspen.log_dammit("=" * 42)
|
||||
aspen.log_dammit( "Oh no! Gratipay.com couldn't understand %s " % these
|
||||
, "environment variable%s:" % plural
|
||||
)
|
||||
aspen.log_dammit("Malformed environment variable%s:" % plural)
|
||||
aspen.log_dammit(" ")
|
||||
for key, err in env.malformed:
|
||||
aspen.log_dammit(" {} ({})".format(key, err))
|
||||
aspen.log_dammit(" ")
|
||||
aspen.log_dammit("See ./default_local.env for hints.")
|
||||
|
||||
aspen.log_dammit("=" * 42)
|
||||
keys = ', '.join([key for key in env.malformed])
|
||||
raise BadEnvironment("Malformed envvar{}: {}.".format(plural, keys))
|
||||
|
||||
if env.missing:
|
||||
these = len(env.missing) != 1 and 'these' or 'this'
|
||||
plural = len(env.missing) != 1 and 's' or ''
|
||||
aspen.log_dammit("=" * 42)
|
||||
aspen.log_dammit( "Oh no! Gratipay.com needs %s missing " % these
|
||||
, "environment variable%s:" % plural
|
||||
)
|
||||
aspen.log_dammit("Missing environment variable%s:" % plural)
|
||||
aspen.log_dammit(" ")
|
||||
for key in env.missing:
|
||||
aspen.log_dammit(" " + key)
|
||||
aspen.log_dammit(" ")
|
||||
aspen.log_dammit( "(Sorry, we must've started looking for "
|
||||
, "%s since you last updated Gratipay!)" % these
|
||||
)
|
||||
aspen.log_dammit(" ")
|
||||
aspen.log_dammit("Running Gratipay locally? Edit ./local.env.")
|
||||
aspen.log_dammit("Running the test suite? Edit ./tests/env.")
|
||||
aspen.log_dammit(" ")
|
||||
aspen.log_dammit("See ./default_local.env for hints.")
|
||||
|
||||
aspen.log_dammit("=" * 42)
|
||||
keys = ', '.join([key for key in env.missing])
|
||||
raise BadEnvironment("Missing envvar{}: {}.".format(plural, keys))
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
heroku config -s | honcho run -e /dev/stdin ./env/bin/python ./bin/masspay.py -i
|
||||
./bin/masspay.py -o
|
1741
npm-shrinkwrap.json
generated
1741
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "gratipay",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"grunt": "~0.4.2",
|
||||
"grunt-cli": "~0.1.9",
|
||||
"grunt-contrib-jshint": "~0.9.2",
|
||||
"grunt-contrib-watch": "~0.6.0",
|
||||
"grunt-webdriver": "^0.4.4",
|
||||
"ini": "~1.1.0",
|
||||
"js-yaml": "~3.0.2",
|
||||
"phantomjs": "^1.9.12",
|
||||
"sync-exec": "^0.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
}
|
||||
}
|
103
payday.sh
103
payday.sh
@ -1,103 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Fail on error.
|
||||
# ==============
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
# Be somewhere predictable.
|
||||
# =========================
|
||||
|
||||
cd "`dirname $0`"
|
||||
|
||||
|
||||
# --help
|
||||
# ======
|
||||
|
||||
if [ $# = 0 -o "$1" = "" ]; then
|
||||
echo
|
||||
echo "Usage: $0 <number> [\"for_real_please\"]"
|
||||
echo
|
||||
echo " This is a payday wrapper script for Gratipay. It runs payday, logging to a file."
|
||||
echo " You must pass at least one argument, a small integer indicating which week of "
|
||||
echo " Gratipay you are running (it's only used to decide where to log). If you pass a"
|
||||
echo " second arg then it must be the string \"for_real_please\", and in that case we"
|
||||
echo " try to run against the production database. Without that string we run using "
|
||||
echo " your local.env configuration."
|
||||
echo
|
||||
echo " Payday is designed such that you can rerun it if it fails. In particular, it "
|
||||
echo " will append to the log (not overwrite it)."
|
||||
echo
|
||||
exit
|
||||
fi
|
||||
|
||||
|
||||
# Helpers
|
||||
# =======
|
||||
|
||||
confirm () {
|
||||
proceed=""
|
||||
while [ "$proceed" != "y" ]; do
|
||||
read -p"$1 (y/N) " proceed
|
||||
if [ "$proceed" == "n" -o "$proceed" == "N" -o "$proceed" == "" ]
|
||||
then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
require () {
|
||||
if [ ! `which $1` ]; then
|
||||
echo "The '$1' command was not found."
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
start () {
|
||||
echo "Logging to $LOG."
|
||||
echo >> $LOG
|
||||
date -u >> $LOG
|
||||
}
|
||||
|
||||
|
||||
# Work
|
||||
# ====
|
||||
|
||||
if [ "$2" == "for_real_please" ]; then
|
||||
LOG="../paydays/gratipay-$1.log"
|
||||
else
|
||||
LOG="../paydays/test-$1.log"
|
||||
fi
|
||||
|
||||
if [ -f $LOG ]; then
|
||||
RUN="Rerun"
|
||||
else
|
||||
# If the path is bad the next line will fail and we'll exit.
|
||||
touch $LOG
|
||||
RUN="Run"
|
||||
fi
|
||||
|
||||
export PATH="./env/bin:$PATH"
|
||||
require honcho
|
||||
confirm "$RUN payday #$1?" || exit 0
|
||||
case "$2" in
|
||||
"")
|
||||
start
|
||||
honcho run -e defaults.env,local.env ./env/bin/payday >>$LOG 2>&1 &
|
||||
;;
|
||||
"for_real_please")
|
||||
confirm "$RUN payday #$1 FOR REAL?!?!?!??!?!?" || exit 0
|
||||
start
|
||||
heroku config -s | honcho run -e /dev/stdin ./env/bin/payday >>$LOG 2>&1 &
|
||||
;;
|
||||
*)
|
||||
echo "Your second arg was $2. Wazzat mean?"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
disown -a
|
||||
tail -f $LOG
|
1
pytest.ini
Normal file
1
pytest.ini
Normal file
@ -0,0 +1 @@
|
||||
# the existence of this file sets pytest's `rootdir`
|
@ -1,12 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
# usage: DATABASE_URL=postgres://foo:bar@baz:5234/buz recreate-schema.sh
|
||||
|
||||
# Exit if any subcommands or pipeline returns a non-zero status.
|
||||
set -e
|
||||
|
||||
# Make a database for Gratipay.
|
||||
#
|
||||
# usage: DATABASE_URL=postgres://foo:bar@baz:5234/buz recreate-schema.sh
|
||||
|
||||
echo "=============================================================================="
|
||||
|
||||
# I got the idea for dropping the schema as a way to clear out the db from
|
||||
@ -23,6 +21,15 @@ echo "==========================================================================
|
||||
echo "Applying sql/schema.sql ..."
|
||||
echo
|
||||
|
||||
if [ "$1" = "test" ]; then
|
||||
psql $DATABASE_URL <<EOF
|
||||
DO \$$
|
||||
BEGIN
|
||||
EXECUTE 'ALTER DATABASE '||current_database()||' SET synchronous_commit TO off';
|
||||
END
|
||||
\$$
|
||||
EOF
|
||||
fi
|
||||
psql $DATABASE_URL < sql/schema.sql
|
||||
|
||||
|
||||
|
18
release.sh
18
release.sh
@ -42,19 +42,19 @@ git pull
|
||||
|
||||
|
||||
# Compute the next version number
|
||||
prev="$(git describe --tags --match '[0-9]*' | cut -d- -f1 | sed 's/\.//g')"
|
||||
prev="$(git describe --tags --match '[0-9]*' | cut -d- -f1)"
|
||||
version="$((prev + 1))"
|
||||
|
||||
|
||||
# Check that the environment contains all required variables
|
||||
heroku config -sa gratipay | ./env/bin/honcho run -e /dev/stdin \
|
||||
./env/bin/python gratipay/wireup.py
|
||||
heroku config -sa liberapay | ./env/bin/honcho run -e /dev/stdin \
|
||||
./env/bin/python liberapay/wireup.py
|
||||
|
||||
|
||||
# Sync the translations
|
||||
echo "Syncing translations..."
|
||||
if [ ! -e .transifexrc -a ! -e ~/.transifexrc ]; then
|
||||
heroku config -sa gratipay | ./env/bin/honcho run -e /dev/stdin make transifexrc
|
||||
heroku config -sa liberapay | ./env/bin/honcho run -e /dev/stdin make transifexrc
|
||||
fi
|
||||
make i18n_upload
|
||||
make i18n_download
|
||||
@ -86,17 +86,15 @@ fi
|
||||
|
||||
# Ask confirmation and bump the version
|
||||
yesno "Tag and deploy version $version?" || exit
|
||||
echo $version >www/version.txt
|
||||
git commit www/version.txt -m "Bump version to $version"
|
||||
git tag $version
|
||||
|
||||
|
||||
# Deploy to Heroku
|
||||
[ "$maintenance" = "yes" ] && heroku maintenance:on -a gratipay
|
||||
[ "$run_sql" = "before" ] && heroku pg:psql -a gratipay <sql/branch.sql
|
||||
[ "$maintenance" = "yes" ] && heroku maintenance:on -a liberapay
|
||||
[ "$run_sql" = "before" ] && heroku pg:psql -a liberapay <sql/branch.sql
|
||||
git push --force heroku master
|
||||
[ "$maintenance" = "yes" ] && heroku maintenance:off -a gratipay
|
||||
[ "$run_sql" = "after" ] && heroku pg:psql -a gratipay <sql/branch.sql
|
||||
[ "$maintenance" = "yes" ] && heroku maintenance:off -a liberapay
|
||||
[ "$run_sql" = "after" ] && heroku pg:psql -a liberapay <sql/branch.sql
|
||||
rm -f sql/branch.sql
|
||||
|
||||
|
||||
|
@ -35,7 +35,7 @@ fake-factory==0.3.2
|
||||
|
||||
six==1.8.0
|
||||
libsass==0.3.0
|
||||
honcho==cfcc7ebb
|
||||
honcho==0.5.0
|
||||
|
||||
environment==1.0.0
|
||||
|
||||
|
@ -1,7 +0,0 @@
|
||||
DROP DATABASE IF EXISTS "gratipay-test";
|
||||
DROP DATABASE IF EXISTS gratipay;
|
||||
DROP ROLE IF EXISTS gratipay;
|
||||
|
||||
CREATE ROLE gratipay LOGIN PASSWORD 'gratipay' SUPERUSER;
|
||||
CREATE DATABASE gratipay WITH OWNER = gratipay;
|
||||
CREATE DATABASE "gratipay-test" WITH OWNER = gratipay;
|
@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# installs Python-independent project dependencies
|
||||
|
||||
apt-get -y install postgresql
|
||||
apt-get -y install libpq-dev
|
||||
apt-get -y install python-dev # for building psycopg2
|
||||
apt-get -y install g++ # for libsass
|
||||
apt-get -y install git # release.sh and commit process
|
||||
apt-get -y install npm # for jstests
|
@ -1,3 +0,0 @@
|
||||
DROP ROLE IF EXISTS vagrant;
|
||||
|
||||
CREATE ROLE vagrant LOGIN SUPERUSER;
|
@ -1,22 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# Set login directory to root vagrant share
|
||||
sudo sh -c "echo 'cd /vagrant' > /etc/profile.d/login-directory.sh"
|
||||
|
||||
# Configure Postgres (using system user 'postgres' run command
|
||||
# 'psql' with PostreSQL user 'postgres` to quietly execute scripts)
|
||||
sudo -u postgres psql -U postgres -qf /vagrant/scripts/reset-db.sql
|
||||
sudo -u postgres psql -U postgres -qf /vagrant/scripts/vagrant-postgre.sql
|
||||
|
||||
# Set up the environment, the database, and run Gratipay
|
||||
cd /vagrant
|
||||
sudo -u vagrant make clean env schema data
|
||||
|
||||
# Output helper text
|
||||
cat <<EOF
|
||||
|
||||
Gratipay installed! To run,
|
||||
$ vagrant ssh --command "make run"
|
||||
EOF
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
This is the top of the Gratipay CSS tree.
|
||||
This is the top of the CSS tree.
|
||||
|
||||
Our code organization here was inspired by Brad Frost's Atomic Web Design:
|
||||
|
||||
@ -38,10 +38,8 @@
|
||||
@import "components/payments-by";
|
||||
@import "components/profile-statement";
|
||||
@import "components/sign_in";
|
||||
@import "components/support_gratipay";
|
||||
@import "components/table";
|
||||
@import "components/tip-distribution";
|
||||
@import "components/tipr";
|
||||
@import "components/upgrade";
|
||||
|
||||
@import "fragments/accounts";
|
@ -1,60 +0,0 @@
|
||||
.support-gratipay {
|
||||
a {
|
||||
color: $brown ! important;
|
||||
&:hover { color: $green ! important; }
|
||||
text-decoration: underline;
|
||||
}
|
||||
padding: 3px 20px;
|
||||
border-top: 1px solid $brown;
|
||||
background: transparentize($gold, 0.05);
|
||||
text-align: left;
|
||||
clear: both;
|
||||
|
||||
.cta {
|
||||
padding-top: 4px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.knobs {
|
||||
width: 133px;
|
||||
button {
|
||||
box-sizing: border-box;
|
||||
width: 64px;
|
||||
border: 1px solid $green;
|
||||
background: $green;
|
||||
color: $white;
|
||||
margin: 0;
|
||||
&:hover {
|
||||
border: 1px solid $brown;
|
||||
background: $brown;
|
||||
color: $white;
|
||||
}
|
||||
&.low {
|
||||
margin: 0 5px 0 0;
|
||||
}
|
||||
}
|
||||
a {
|
||||
box-sizing: border-box;
|
||||
font-weight: normal;
|
||||
font-size: 11px;
|
||||
display: inline-block;
|
||||
width: 64px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
|
||||
&.other {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.help {
|
||||
margin-top: 2px;
|
||||
font-size: 13px;
|
||||
font-style: italic;
|
||||
a {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user