Skip to content

Commit 21779b5

Browse files
committed
docker setup
1 parent 56bcd63 commit 21779b5

12 files changed

+1416
-104
lines changed

.gitignore

+130
Original file line numberDiff line numberDiff line change
@@ -1 +1,131 @@
1+
2+
# Created by https://www.toptal.com/developers/gitignore/api/python
3+
# Edit at https://www.toptal.com/developers/gitignore?templates=python
4+
5+
### Python ###
6+
# Byte-compiled / optimized / DLL files
7+
__pycache__/
8+
*.py[cod]
9+
*$py.class
10+
11+
# C extensions
12+
*.so
13+
14+
# Distribution / packaging
15+
.Python
16+
build/
17+
develop-eggs/
18+
dist/
19+
downloads/
20+
eggs/
21+
.eggs/
22+
lib/
23+
lib64/
24+
parts/
25+
sdist/
26+
var/
27+
wheels/
28+
pip-wheel-metadata/
29+
share/python-wheels/
30+
*.egg-info/
31+
.installed.cfg
32+
*.egg
33+
MANIFEST
34+
35+
# PyInstaller
36+
# Usually these files are written by a python script from a template
37+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
38+
*.manifest
39+
*.spec
40+
41+
# Installer logs
42+
pip-log.txt
43+
pip-delete-this-directory.txt
44+
45+
# Unit test / coverage reports
46+
htmlcov/
47+
.tox/
48+
.nox/
49+
.coverage
50+
.coverage.*
51+
.cache
52+
nosetests.xml
53+
coverage.xml
54+
*.cover
55+
.hypothesis/
56+
.pytest_cache/
57+
58+
# Translations
59+
*.mo
60+
*.pot
61+
62+
# Django stuff:
63+
*.log
64+
local_settings.py
65+
db.sqlite3
66+
67+
# Flask stuff:
68+
instance/
69+
.webassets-cache
70+
71+
# Scrapy stuff:
72+
.scrapy
73+
74+
# Sphinx documentation
75+
docs/_build/
76+
77+
# PyBuilder
78+
target/
79+
80+
# Jupyter Notebook
81+
.ipynb_checkpoints
82+
83+
# IPython
84+
profile_default/
85+
ipython_config.py
86+
87+
# pyenv
88+
.python-version
89+
90+
# pipenv
91+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
93+
# having no cross-platform support, pipenv may install dependencies that don’t work, or not
94+
# install all needed dependencies.
95+
#Pipfile.lock
96+
97+
# celery beat schedule file
98+
celerybeat-schedule
99+
100+
# SageMath parsed files
101+
*.sage.py
102+
103+
# Environments
104+
.env
105+
.venv
106+
env/
107+
venv/
108+
ENV/
109+
env.bak/
110+
venv.bak/
111+
112+
# Spyder project settings
113+
.spyderproject
114+
.spyproject
115+
116+
# Rope project settings
117+
.ropeproject
118+
119+
# mkdocs documentation
120+
/site
121+
122+
# mypy
123+
.mypy_cache/
124+
.dmypy.json
125+
dmypy.json
126+
127+
# Pyre type checker
128+
.pyre/
129+
130+
# End of https://www.toptal.com/developers/gitignore/api/python
1131
.secret/

Dockerfile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from python:3.6
2+
add requirements.txt .
3+
run python -m pip install -r requirements.txt

announce/const.py

+19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
1+
import os
12
from collections import namedtuple
23

4+
5+
def env(key, default=None):
6+
val = os.environ.get(key, default)
7+
return val
8+
9+
10+
database_url = env("DATABASE_URL")
11+
secret = os.environ.get("BOTTLE_SECRET", "pyjdoorman")
12+
cookie_name = "pyj"
13+
cookie_kwargs = {"path": "/", "domain": base_domain}
14+
base_domain = env("BASE_DOMAIN")
15+
protocol = "https"
16+
is_dev = base_domain is None
17+
if is_dev:
18+
cookie_kwargs = {}
19+
base_domain = "localhost:8000"
20+
protocol = "http"
21+
322
423
tw = "https://api.twitter.com/1.1"
524
tw_upload = "https://upload.twitter.com/1.1"

announce/models.py

+69-1
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,84 @@
55
String,
66
DateTime,
77
ForeignKey,
8+
relationship,
9+
UniqueConstraint,
810
)
911
from sqlalchemy.dialects.postgresql import JSON
1012
from sqlalchemy.ext.declarative import declarative_base
1113
from sqlalchemy.orm import sessionmaker
1214
from sqlalchemy.ext.hybrid import hybrid_property
1315
import bottle_tools as bt
16+
from secrets import token_urlsafe
17+
from announce import const
1418

15-
engine = create_engine(database_url)
19+
engine = create_engine(const.database_url)
1620
Base = declarative_base()
1721

1822

23+
class AnonUser:
24+
id = tg_handle = None
25+
26+
27+
class User(Base):
28+
__tablename__ = "user"
29+
id = Column(Integer, primary_key=True)
30+
tg_handle = Column(String)
31+
32+
def get_groups(self, session):
33+
return (
34+
session.query(Group)
35+
.join(Member)
36+
.filter(Member.user_id == self.id)
37+
.distinct()
38+
)
39+
40+
41+
class Group(Base):
42+
__tablename__ = "group"
43+
name = Column(String)
44+
45+
46+
class Member(Base):
47+
__tablename__ = "member"
48+
user_id = Column(Integer, ForeignKey("user.id"))
49+
group_id = Column(Integer, ForeignKey("group.id"))
50+
__table_args__ = (UniqueConstraint("user_id", "group_id"),)
51+
52+
53+
class Otp(Base):
54+
__tablename__ = "otp"
55+
id = Column(Integer, primary_key=True)
56+
tg_handle = Column(String)
57+
otp = Column(String)
58+
59+
@staticmethod
60+
def loop_create(session, **kwargs):
61+
"Try to create a token and retry if uniqueness fails"
62+
while True:
63+
tok = Otp(otp=token_urlsafe(), **kwargs)
64+
session.add(tok)
65+
session.commit()
66+
return tok
67+
68+
69+
class LoginToken(Base):
70+
__tablename__ = "logintoken"
71+
user_id = Column(Integer, ForeignKey("user.id"))
72+
token = Column(String, nullable=False, unique=True)
73+
has_logged_out = Column(Boolean, default=False)
74+
user = relationship("User")
75+
76+
@staticmethod
77+
def loop_create(session, **kwargs):
78+
"Try to create a token and retry if uniqueness fails"
79+
while True:
80+
tok = LoginToken(token=token_urlsafe(), **kwargs)
81+
session.add(tok)
82+
session.commit()
83+
return tok
84+
85+
1986
class Image(Base):
2087
__tablename__ = "image"
2188
id = Column(Integer, primary_key=True)
@@ -27,6 +94,7 @@ class Cred(Base):
2794
id = Column(Integer, primary_key=True)
2895
name = Column(String)
2996
var = Column(String)
97+
group_id = Column(Integer, ForeignKey("image.id"))
3098

3199

32100
class Event(Base):

announce/plugins.py

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import bottle
2+
from announce import models
3+
4+
5+
def render(template_name, **kwargs):
6+
kwargs.update(
7+
{"request": bottle.request,}
8+
)
9+
return bottle.jinja2_template(template_name, **kwargs)
10+
11+
12+
class Plugin:
13+
name = None
14+
api = 2
15+
16+
def setup(self, app):
17+
self.app = app
18+
19+
20+
class AutoSession(Plugin):
21+
name = "auto_session"
22+
23+
def apply(self, callback, route):
24+
@wraps(callback)
25+
def wrapper(*a, **kw):
26+
bottle.request.session = models.Session()
27+
try:
28+
return callback(*a, **kw)
29+
finally:
30+
bottle.request.session.close()
31+
32+
return wrapper
33+
34+
35+
class CurrentUser(Plugin):
36+
name = "current_user"
37+
38+
def apply(self, callback, route):
39+
@wraps(callback)
40+
def wrapper(*a, **kw):
41+
# Check cookies
42+
user = models.AnonUser()
43+
cook = bottle.request.get_cookie(const.cookie_name, secret=const.secret)
44+
if cook is not None:
45+
token = (
46+
bottle.request.session.query(models.LoginToken)
47+
.filter_by(token=cook, has_logged_out=False)
48+
.first()
49+
)
50+
bottle.request.token = token
51+
if token is not None:
52+
user = token.user
53+
bottle.request.user = user
54+
return callback(*a, **kw)
55+
56+
return wrapper
57+
58+
59+
class LoginRequired(Plugin):
60+
name = "login_required"
61+
62+
def apply(self, callback, route):
63+
@wraps(callback)
64+
def wrapper(*a, **kw):
65+
if isinstance(bottle.request.user, models.AnonUser):
66+
return bottle.redirect(
67+
self.app.get_url("get_login", next_url=bottle.request.url)
68+
)
69+
return callback(*a, **kw)
70+
71+
return wrapper

0 commit comments

Comments
 (0)