├── .eslintrc.json
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .pylintrc
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.md
├── activities
├── __init__.py
├── auth
│ ├── __init__.py
│ ├── flask_app.py
│ └── templates
│ │ ├── auth_complete.html
│ │ ├── main.html
│ │ └── sync_complete.html
├── generator
│ ├── __init__.py
│ ├── db.py
│ └── valuerange.py
└── run.py
├── config-example.json
├── mypy.ini
├── requirements-dev.txt
├── requirements.txt
├── screenshot.png
├── setup.py
├── tox.ini
└── web
├── app.js
├── index.html
└── style.css
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2020": true,
5 | "jquery": true
6 | },
7 | "extends": "eslint:recommended",
8 | "parserOptions": {
9 | "ecmaVersion": 11
10 | },
11 | "rules": {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Continuous Integration
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | tox:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | max-parallel: 4
10 | matrix:
11 | python-version: [3.9]
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Set up Python ${{ matrix.python-version }}
15 | uses: actions/setup-python@v2
16 | with:
17 | python-version: ${{ matrix.python-version }}
18 | - name: Install dependencies
19 | run: |
20 | python -m pip install --upgrade pip
21 | pip install tox
22 | - name: Test with tox
23 | run: tox -e format
24 |
25 | eslint:
26 | runs-on: ubuntu-latest
27 | steps:
28 | - uses: actions/checkout@v2
29 | - name: Set up Node.js
30 | uses: actions/setup-node@v1
31 | with:
32 | node-version: '12.x'
33 | - name: Install dependencies
34 | run: npm install --no-save eslint
35 | - name: Run ESLint
36 | run: node_modules/.bin/eslint web/app.js
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env/
2 | .venv/
3 | .tox/
4 |
5 | node_modules
6 | __pycache__
7 | account.json
8 | config.json
9 | activities.js
10 | data.db
11 | *.egg-info/
12 | *.ipynb*
13 | build
14 |
--------------------------------------------------------------------------------
/.pylintrc:
--------------------------------------------------------------------------------
1 | [MASTER]
2 |
3 | # Specify a configuration file.
4 | #rcfile=
5 |
6 | # Python code to execute, usually for sys.path manipulation such as
7 | # pygtk.require().
8 | #init-hook=
9 |
10 | # Add files or directories to the blacklist. They should be base names, not
11 | # paths.
12 | ignore=.tox,.env,.venv,.eggs,build,migrations,south_migrations
13 |
14 | # Pickle collected data for later comparisons.
15 | persistent=yes
16 |
17 | # List of plugins (as comma separated values of python modules names) to load,
18 | # usually to register additional checkers.
19 | load-plugins = pylint.extensions.check_docs
20 |
21 | # Use multiple processes to speed up Pylint.
22 | jobs=0
23 |
24 | # Allow loading of arbitrary C extensions. Extensions are imported into the
25 | # active Python interpreter and may run arbitrary code.
26 | unsafe-load-any-extension=no
27 |
28 | # A comma-separated list of package or module names from where C extensions may
29 | # be loaded. Extensions are loading into the active Python interpreter and may
30 | # run arbitrary code
31 | extension-pkg-whitelist=
32 |
33 |
34 | [MESSAGES CONTROL]
35 |
36 | # Only show warnings with the listed confidence levels. Leave empty to show
37 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
38 | confidence=
39 |
40 | # Enable the message, report, category or checker with the given id(s). You can
41 | # either give multiple identifier separated by comma (,) or put this option
42 | # multiple time. See also the "--disable" option for examples.
43 | #enable=
44 |
45 | # Disable the message, report, category or checker with the given id(s). You
46 | # can either give multiple identifiers separated by comma (,) or put this
47 | # option multiple times (only on the command line, not in the configuration
48 | # file where it should appear only once).You can also use "--disable=all" to
49 | # disable everything first and then reenable specific checks. For example, if
50 | # you want to run only the similarities checker, you can use "--disable=all
51 | # --enable=similarities". If you want to run only the classes checker, but have
52 | # no Warning level messages displayed, use"--disable=all --enable=classes
53 | # --disable=W"
54 | disable=
55 | bad-continuation,
56 | missing-docstring,
57 | no-init,
58 | no-member,
59 | no-value-for-parameter,
60 | too-few-public-methods,
61 | too-many-arguments,
62 | too-many-locals
63 |
64 |
65 | [REPORTS]
66 |
67 | # Set the output format. Available formats are text, parseable, colorized, msvs
68 | # (visual studio) and html. You can also give a reporter class, eg
69 | # mypackage.mymodule.MyReporterClass.
70 | output-format=colorized
71 |
72 | # Put messages in a separate file for each module / package specified on the
73 | # command line instead of printing them on stdout. Reports (if any) will be
74 | # written in a file name "pylint_global.[txt|html]".
75 | files-output=no
76 |
77 | # Tells whether to display a full report or only the messages
78 | reports=no
79 |
80 | # Python expression which should return a note less than 10 (10 is the highest
81 | # note). You have access to the variables errors warning, statement which
82 | # respectively contain the number of errors / warnings messages and the total
83 | # number of statements analyzed. This is used by the global evaluation report
84 | # (RP0004).
85 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
86 |
87 | # Template used to display messages. This is a python new-style format string
88 | # used to format the message information. See doc for all details
89 | #msg-template=
90 |
91 |
92 | [LOGGING]
93 |
94 | # Logging modules to check that the string format arguments are in logging
95 | # function parameter format
96 | logging-modules=logging
97 |
98 |
99 | [MISCELLANEOUS]
100 |
101 | # List of note tags to take in consideration, separated by a comma.
102 | notes=FIXME,XXX,TODO
103 |
104 |
105 | [SIMILARITIES]
106 |
107 | # Minimum lines number of a similarity.
108 | min-similarity-lines=5
109 |
110 | # Ignore comments when computing similarities.
111 | ignore-comments=no
112 |
113 | # Ignore docstrings when computing similarities.
114 | ignore-docstrings=no
115 |
116 | # Ignore imports when computing similarities.
117 | ignore-imports=no
118 |
119 |
120 | [VARIABLES]
121 |
122 | # Tells whether we should check for unused import in __init__ files.
123 | init-import=no
124 |
125 | # A regular expression matching the name of dummy variables (i.e. expectedly
126 | # not used).
127 | dummy-variables-rgx=_$|dummy|tmp$
128 |
129 | # List of additional names supposed to be defined in builtins. Remember that
130 | # you should avoid to define new builtins when possible.
131 | additional-builtins=
132 |
133 | # List of strings which can identify a callback function by name. A callback
134 | # name must start or end with one of those strings.
135 | callbacks=cb_,_cb
136 |
137 |
138 | [FORMAT]
139 |
140 | # Maximum number of characters on a single line.
141 | max-line-length=120
142 |
143 | # Regexp for a line that is allowed to be longer than the limit.
144 | ignore-long-lines=^\s*(# )??$
145 |
146 | # Allow the body of an if to be on the same line as the test if there is no
147 | # else.
148 | single-line-if-stmt=no
149 |
150 | # List of optional constructs for which whitespace checking is disabled. `dict-
151 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
152 | # `trailing-comma` allows a space between comma and closing bracket: (a, ).
153 | # `empty-line` allows space-only lines.
154 | no-space-check=trailing-comma
155 |
156 | # Maximum number of lines in a module
157 | max-module-lines=500
158 |
159 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
160 | # tab).
161 | indent-string=' '
162 |
163 | # Number of spaces of indent required inside a hanging or continued line.
164 | indent-after-paren=4
165 |
166 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
167 | expected-line-ending-format=LF
168 |
169 |
170 | [BASIC]
171 |
172 | # List of builtins function names that should not be used, separated by a comma
173 | bad-functions=map,filter,input
174 |
175 | # Good variable names which should always be accepted, separated by a comma
176 | good-names=i,_
177 |
178 | # Bad variable names which should always be refused, separated by a comma
179 | bad-names=foo,bar,baz,toto,tutu,tata,wtf
180 |
181 | # Colon-delimited sets of names that determine each other's naming style when
182 | # the name regexes allow several styles.
183 | name-group=
184 |
185 | # Include a hint for the correct naming format with invalid-name
186 | include-naming-hint=yes
187 |
188 | # Regular expression matching correct function names
189 | function-rgx=([a-z_][a-z0-9_]{1,40}|test_[A-Za-z0-9_]{3,70})$
190 |
191 | # Naming hint for function names
192 | function-name-hint=[a-z_][a-z0-9_]{1,40}$
193 |
194 | # Regular expression matching correct variable names
195 | variable-rgx=[a-z_][a-z0-9_]{0,40}$
196 |
197 | # Naming hint for variable names
198 | variable-name-hint=[a-z_][a-z0-9_]{0,40}$
199 |
200 | # Regular expression matching correct constant names
201 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__)|(urls|urlpatterns|register))$
202 |
203 | # Naming hint for constant names
204 | const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
205 |
206 | # Regular expression matching correct attribute names
207 | attr-rgx=[a-z_][a-z0-9_]{0,30}$
208 |
209 | # Naming hint for attribute names
210 | attr-name-hint=[a-z_][a-z0-9_]{0,30}$
211 |
212 | # Regular expression matching correct argument names
213 | argument-rgx=[a-z_][a-z0-9_]{0,30}$
214 |
215 | # Naming hint for argument names
216 | argument-name-hint=[a-z_][a-z0-9_]{0,30}$
217 |
218 | # Regular expression matching correct class attribute names
219 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{1,40}|(__.*__))$
220 |
221 | # Naming hint for class attribute names
222 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{1,40}|(__.*__))$
223 |
224 | # Regular expression matching correct inline iteration names
225 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
226 |
227 | # Naming hint for inline iteration names
228 | inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
229 |
230 | # Regular expression matching correct class names
231 | class-rgx=[A-Z_][a-zA-Z0-9]+$
232 |
233 | # Naming hint for class names
234 | class-name-hint=[A-Z_][a-zA-Z0-9]+$
235 |
236 | # Regular expression matching correct module names
237 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
238 |
239 | # Naming hint for module names
240 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
241 |
242 | # Regular expression matching correct method names
243 | method-rgx=[a-z_][a-z0-9_]{1,30}$
244 |
245 | # Naming hint for method names
246 | method-name-hint=[a-z_][a-z0-9_]{1,30}$
247 |
248 | # Regular expression which should only match function or class names that do
249 | # not require a docstring.
250 | no-docstring-rgx=^_
251 |
252 | # Minimum line length for functions/classes that require docstrings, shorter
253 | # ones are exempt.
254 | docstring-min-length=-1
255 |
256 |
257 | [ELIF]
258 |
259 | # Maximum number of nested blocks for function / method body
260 | max-nested-blocks=5
261 |
262 |
263 | [TYPECHECK]
264 |
265 | # Tells whether missing members accessed in mixin class should be ignored. A
266 | # mixin class is detected if its name ends with "mixin" (case insensitive).
267 | ignore-mixin-members=yes
268 |
269 | # List of module names for which member attributes should not be checked
270 | # (useful for modules/projects where namespaces are manipulated during runtime
271 | # and thus existing member attributes cannot be deduced by static analysis.
272 | ignored-modules =
273 |
274 | # List of classes names for which member attributes should not be checked
275 | # (useful for classes with attributes dynamically set).
276 | ignored-classes=
277 |
278 | # List of members which are set dynamically and missed by pylint inference
279 | # system, and so shouldn't trigger E1101 when accessed. Python regular
280 | # expressions are accepted.
281 | generated-members=
282 |
283 |
284 | [SPELLING]
285 |
286 | # Spelling dictionary name. Available dictionaries: none. To make it working
287 | # install python-enchant package.
288 | spelling-dict=
289 |
290 | # List of comma separated words that should not be checked.
291 | spelling-ignore-words=
292 |
293 | # A path to a file that contains private dictionary; one word per line.
294 | spelling-private-dict-file=
295 |
296 | # Tells whether to store unknown words to indicated private dictionary in
297 | # --spelling-private-dict-file option instead of raising a message.
298 | spelling-store-unknown-words=no
299 |
300 |
301 | [DESIGN]
302 |
303 | # Maximum number of arguments for function / method
304 | max-args=5
305 |
306 | # Argument names that match this expression will be ignored. Default to name
307 | # with leading underscore
308 | ignored-argument-names=_.*
309 |
310 | # Maximum number of locals for function / method body
311 | max-locals=15
312 |
313 | # Maximum number of return / yield for function / method body
314 | max-returns=6
315 |
316 | # Maximum number of branch for function / method body
317 | max-branches=12
318 |
319 | # Maximum number of statements in function / method body
320 | max-statements=50
321 |
322 | # Maximum number of parents for a class (see R0901).
323 | max-parents=8
324 |
325 | # Maximum number of attributes for a class (see R0902).
326 | max-attributes=7
327 |
328 | # Minimum number of public methods for a class (see R0903).
329 | min-public-methods=1
330 |
331 | # Maximum number of public methods for a class (see R0904).
332 | max-public-methods=20
333 |
334 | # Maximum number of boolean expressions in a if statement
335 | max-bool-expr=5
336 |
337 |
338 | [CLASSES]
339 |
340 | # List of method names used to declare (i.e. assign) instance attributes.
341 | defining-attr-methods=__init__,__new__,setUp
342 |
343 | # List of valid names for the first argument in a class method.
344 | valid-classmethod-first-arg=cls
345 |
346 | # List of valid names for the first argument in a metaclass class method.
347 | valid-metaclass-classmethod-first-arg=mcs
348 |
349 | # List of member names, which should be excluded from the protected access
350 | # warning.
351 | exclude-protected=_meta
352 |
353 |
354 | [IMPORTS]
355 |
356 | # Deprecated modules which should not be used, separated by a comma
357 | deprecated-modules=regsub,TERMIOS,Bastion,rexec
358 |
359 | # Create a graph of every (i.e. internal and external) dependencies in the
360 | # given file (report RP0402 must not be disabled)
361 | import-graph=
362 |
363 | # Create a graph of external dependencies in the given file (report RP0402 must
364 | # not be disabled)
365 | ext-import-graph=
366 |
367 | # Create a graph of internal dependencies in the given file (report RP0402 must
368 | # not be disabled)
369 | int-import-graph=
370 |
371 |
372 | [EXCEPTIONS]
373 |
374 | # Exceptions that will emit a warning when being caught. Defaults to
375 | # "Exception"
376 | overgeneral-exceptions=Exception
377 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Florian Pigorsch
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include requirements.txt
2 | include requirements-dev.txt
3 |
4 | recursive-include activities/auth/templates *
5 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: setup
2 | setup:
3 | python3.9 -m venv .env
4 | .env/bin/pip install --use-pep517 --upgrade pip
5 | .env/bin/pip install --use-pep517 --upgrade -r requirements.txt
6 |
7 | .PHONY: setup-dev
8 | setup-dev: setup
9 | .env/bin/pip install --use-pep517 --upgrade -r requirements-dev.txt
10 |
11 | .PHONY: format
12 | format:
13 | .env/bin/black activities -l 120
14 |
15 | .PHONY: lint
16 | lint: mypy pylint
17 |
18 | .PHONY: pylint
19 | pylint:
20 | .env/bin/pylint activities
21 |
22 | .PHONY: mypy
23 | mypy:
24 | PYTHONPATH=. .env/bin/mypy activities
25 |
26 | .PHONY: auth
27 | auth:
28 | PYTHONPATH=. .env/bin/python activities/run.py --auth
29 |
30 | .PHONY: run
31 | run:
32 | PYTHONPATH=. .env/bin/python activities/run.py
33 |
34 | .PHONY: run+sync
35 | run+sync:
36 | PYTHONPATH=. .env/bin/python activities/run.py --sync
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Activities
2 |
3 | [](https://github.com/ambv/black)
4 | 
5 |
6 |
7 | Your self-hosted activities overview (running, cycling, ...). Synced with [Strava](https://www.strava.com).
8 |
9 | https://activities.flopp.net
10 |
11 | 
12 |
13 |
14 | ## Features
15 |
16 | - Built-in http server to authenticate with Strava.
17 | - Fetching of Strava activities.
18 | - Visited POI (predefined point-of-interest) matching.
19 | - Filtering by activity name, activity type, min/max distance, visited POI.
20 | - Running streak computation.
21 | - Heatmaps.
22 |
23 | ## Usage
24 |
25 | ### Installation
26 |
27 | ```
28 | git clone https://github.com/flopp/activities.git
29 | cd activities
30 | python3 -m venv .env
31 | .env/bin/pip install --upgrade pip
32 | .env/bin/pip install .
33 | ```
34 |
35 | ### Fetch API Config from Strava (once!)
36 |
37 | 1. Create an "Application" on https://www.strava.com/settings/api; for "Authorization Callback Domain" use `localhost`, for all other properties you can basically use whatever you want ;)
38 | 2. Copy `config-example.json` to `config.json` and fill in the "Client ID" and the "Client Secret" from the "My API Application" section on https://www.strava.com/settings/api.
39 |
40 | ### Authenticate with Strava (once!)
41 |
42 | ```
43 | .env/bin/activities \
44 | --auth
45 | ```
46 |
47 | Now a web browser window should open with an "Authenticate with Strava" button. If not, manually open `localhost:5000` in a web browser of your choice. Click "Authenticate with Strava". Allow access for the app.
48 | The authentication data is now saved in `data.db` for later use.
49 |
50 | ### Sync
51 |
52 | ```
53 | .env/bin/activities \
54 | --sync \
55 | --browser
56 | ```
57 |
58 | This fetches your Strava data, creates a static website, and opens a browser to view the website.
59 | You can also manually point a web browser of your choice to `file:///INSTALLATION_PATH/web/index.html`...
60 |
61 | ### Visited POI Computation
62 |
63 | If you want to know which points-of-interest (POI), e.g. peaks of mountains, you have visited on each activity, create a JSON file containing the names and lat/lon pairs of your POI, e.g.
64 |
65 | ```
66 | {
67 | "Belchen": {"lat": 47.822496, "lon": 7.833198},
68 | "Feldberg": {"lat": 47.873986, "lon": 8.004683},
69 | "Hinterwaldkopf": {"lat": 47.918979, "lon": 8.016681},
70 | "Kandel": {"lat": 48.062517, "lon": 8.011391},
71 | "Kybfelsen": {"lat": 47.960851, "lon": 7.885071},
72 | "Rosskopf": {"lat": 48.010010, "lon": 7.901702},
73 | "Schauinsland": {"lat": 47.911940, "lon": 7.898506},
74 | "Schönberg": {"lat": 47.954722, "lon": 7.805504}
75 | }
76 | ```
77 |
78 | Then just add the option `--poi mypoi.json` to your `.env/bin/activities` command.
79 |
80 |
81 | ## Made with
82 |
83 | - [Bulma](https://bulma.io/)
84 | - [Click](https://click.palletsprojects.com/)
85 | - [Flask](https://flask.palletsprojects.com/)
86 | - [heatmap.js](https://www.patrick-wied.at/static/heatmapjs/)
87 | - [geopy](https://github.com/geopy/geopy)
88 | - [jQuery](https://jquery.com/)
89 | - [Leaflet](https://leafletjs.com/)
90 | - [Leaflet.BeautifyMarker](https://github.com/masajid390/BeautifyMarker)
91 | - [Leaflet.distance-markers](https://github.com/adoroszlai/leaflet-distance-markers)
92 | - [Leaflet.encoded](https://github.com/jieter/Leaflet.encoded)
93 | - [noUiSlider](https://refreshless.com/nouislider/)
94 | - [polyline](https://github.com/hicsail/polyline)
95 | - [SQLAlchemy](https://www.sqlalchemy.org)
96 | - [Stravalib](https://github.com/hozn/stravalib)
97 |
98 | ## License
99 |
100 | ```
101 | MIT License
102 |
103 | Copyright (c) 2020 Florian Pigorsch
104 | ```
105 |
--------------------------------------------------------------------------------
/activities/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flopp/activities/f0bcf06e071123b437e7cb667116c4f120f8c913/activities/__init__.py
--------------------------------------------------------------------------------
/activities/auth/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flopp/activities/f0bcf06e071123b437e7cb667116c4f120f8c913/activities/auth/__init__.py
--------------------------------------------------------------------------------
/activities/auth/flask_app.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import json
3 | from typing import Union
4 |
5 | import flask
6 | from werkzeug.wrappers import Response
7 | import stravalib # type: ignore
8 |
9 | from activities.generator import Generator
10 | from activities.generator.db import init_db, Auth
11 |
12 |
13 | app = flask.Flask(__name__)
14 |
15 |
16 | def configure(config: str, data: str, pois: str) -> None:
17 | with open(config, encoding="utf-8") as f:
18 | config_content = json.load(f)
19 |
20 | app.config["client_id"] = config_content["client_id"]
21 | app.config["client_secret"] = config_content["client_secret"]
22 | app.config["data"] = data
23 | app.config["config"] = config
24 | app.config["pois"] = pois
25 |
26 |
27 | @app.route("/")
28 | def homepage() -> Union[str, Response]:
29 | session = init_db(app.config["data"])
30 | auth_info = session.query(Auth).first()
31 | if auth_info:
32 | return flask.redirect(flask.url_for("auth_complete"))
33 |
34 | client = stravalib.client.Client()
35 | auth_url = client.authorization_url(
36 | client_id=app.config["client_id"],
37 | scope=None,
38 | redirect_uri="http://localhost:5000/auth",
39 | )
40 | return flask.render_template("main.html", auth_url=auth_url, data_file=app.config["data"])
41 |
42 |
43 | @app.route("/auth_complete")
44 | def auth_complete() -> str:
45 | return flask.render_template("auth_complete.html")
46 |
47 |
48 | @app.route("/auth")
49 | def auth() -> Response:
50 | code = flask.request.args.get("code", "")
51 | client = stravalib.client.Client()
52 | token = client.exchange_code_for_token(
53 | client_id=app.config["client_id"],
54 | client_secret=app.config["client_secret"],
55 | code=code,
56 | )
57 |
58 | session = init_db(app.config["data"])
59 | auth_data = Auth(
60 | access_token=token["access_token"],
61 | refresh_token=token["refresh_token"],
62 | expires_at=datetime.datetime.fromtimestamp(token["expires_at"]),
63 | )
64 |
65 | session.add(auth_data)
66 | session.commit()
67 |
68 | return flask.redirect(flask.url_for("auth_complete"))
69 |
70 |
71 | @app.route("/sync")
72 | def sync() -> str:
73 | generator = Generator(app.config["config"], app.config["data"], app.config["pois"])
74 | generator.sync()
75 |
76 | return flask.render_template("sync_complete.html")
77 |
--------------------------------------------------------------------------------
/activities/auth/templates/auth_complete.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |