astrospace 1.2.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- astrospace-1.2.8/.gitignore +191 -0
- astrospace-1.2.8/AstroSpace/__init__.py +112 -0
- astrospace-1.2.8/AstroSpace/__main__.py +44 -0
- astrospace-1.2.8/AstroSpace/auth.py +105 -0
- astrospace-1.2.8/AstroSpace/blog.py +679 -0
- astrospace-1.2.8/AstroSpace/config.py +28 -0
- astrospace-1.2.8/AstroSpace/constants.py +79 -0
- astrospace-1.2.8/AstroSpace/db.py +161 -0
- astrospace-1.2.8/AstroSpace/logging_utils.py +94 -0
- astrospace-1.2.8/AstroSpace/profile/__init__.py +0 -0
- astrospace-1.2.8/AstroSpace/profile/private.py +761 -0
- astrospace-1.2.8/AstroSpace/profile/public.py +38 -0
- astrospace-1.2.8/AstroSpace/repositories/__init__.py +1 -0
- astrospace-1.2.8/AstroSpace/repositories/images.py +444 -0
- astrospace-1.2.8/AstroSpace/schema.sql +344 -0
- astrospace-1.2.8/AstroSpace/services/__init__.py +1 -0
- astrospace-1.2.8/AstroSpace/services/authorization.py +18 -0
- astrospace-1.2.8/AstroSpace/services/collection_filters.py +244 -0
- astrospace-1.2.8/AstroSpace/services/content.py +38 -0
- astrospace-1.2.8/AstroSpace/services/uploads.py +34 -0
- astrospace-1.2.8/AstroSpace/static/assets/images/default_profile.png +0 -0
- astrospace-1.2.8/AstroSpace/static/assets/images/moon.png +0 -0
- astrospace-1.2.8/AstroSpace/static/assets/images/pixinsight-140x40-black.en.png +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-Black.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-BlackItalic.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-Bold.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-BoldItalic.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-Hairline.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-HairlineItalic.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-Heavy.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-HeavyItalic.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-Italic.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-Light.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-LightItalic.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-Medium.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-MediumItalic.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-Regular.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-Semibold.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-SemiboldItalic.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-Thin.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Lato-ThinItalic.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Patung.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/fonts/Scribble.woff2 +0 -0
- astrospace-1.2.8/AstroSpace/static/input.css +169 -0
- astrospace-1.2.8/AstroSpace/static/js/binds.js +205 -0
- astrospace-1.2.8/AstroSpace/static/js/fitsUtils.js +62 -0
- astrospace-1.2.8/AstroSpace/static/js/phd2Plotly.js +454 -0
- astrospace-1.2.8/AstroSpace/static/js/plotHR.js +71 -0
- astrospace-1.2.8/AstroSpace/static/js/subFramePlot.js +145 -0
- astrospace-1.2.8/AstroSpace/static/js/utils.js +18 -0
- astrospace-1.2.8/AstroSpace/static/styles.css +2578 -0
- astrospace-1.2.8/AstroSpace/templates/auth/auth.html +26 -0
- astrospace-1.2.8/AstroSpace/templates/base.html +217 -0
- astrospace-1.2.8/AstroSpace/templates/collection.html +474 -0
- astrospace-1.2.8/AstroSpace/templates/create.html +611 -0
- astrospace-1.2.8/AstroSpace/templates/home.html +98 -0
- astrospace-1.2.8/AstroSpace/templates/image_detail.html +1175 -0
- astrospace-1.2.8/AstroSpace/templates/profile.html +539 -0
- astrospace-1.2.8/AstroSpace/templates/public_profile.html +61 -0
- astrospace-1.2.8/AstroSpace/utils/__init__.py +0 -0
- astrospace-1.2.8/AstroSpace/utils/moon_phase.py +38 -0
- astrospace-1.2.8/AstroSpace/utils/phd2logparser.py +324 -0
- astrospace-1.2.8/AstroSpace/utils/platesolve.py +530 -0
- astrospace-1.2.8/AstroSpace/utils/queries.py +15 -0
- astrospace-1.2.8/AstroSpace/utils/simbad_object_description.json +187 -0
- astrospace-1.2.8/AstroSpace/utils/utils.py +72 -0
- astrospace-1.2.8/AstroSpace/utils/xisf_reader.py +184 -0
- astrospace-1.2.8/LICENSE +674 -0
- astrospace-1.2.8/PKG-INFO +217 -0
- astrospace-1.2.8/README.md +191 -0
- astrospace-1.2.8/pyproject.toml +52 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
hatch.bat
|
|
6
|
+
nginx/.env
|
|
7
|
+
# C extensions
|
|
8
|
+
*.so
|
|
9
|
+
dist/
|
|
10
|
+
instance/
|
|
11
|
+
blogs/
|
|
12
|
+
postgres/
|
|
13
|
+
**/node_modules/
|
|
14
|
+
**/uploads/
|
|
15
|
+
**/watch.bat
|
|
16
|
+
serve.bat
|
|
17
|
+
cmd.txt
|
|
18
|
+
.vscode/
|
|
19
|
+
# Distribution / packaging
|
|
20
|
+
.Python
|
|
21
|
+
build/
|
|
22
|
+
develop-eggs/
|
|
23
|
+
dist/
|
|
24
|
+
downloads/
|
|
25
|
+
eggs/
|
|
26
|
+
.eggs/
|
|
27
|
+
lib/
|
|
28
|
+
lib64/
|
|
29
|
+
parts/
|
|
30
|
+
sdist/
|
|
31
|
+
var/
|
|
32
|
+
wheels/
|
|
33
|
+
share/python-wheels/
|
|
34
|
+
*.egg-info/
|
|
35
|
+
.installed.cfg
|
|
36
|
+
*.egg
|
|
37
|
+
MANIFEST
|
|
38
|
+
publish/
|
|
39
|
+
# PyInstaller
|
|
40
|
+
# Usually these files are written by a python script from a template
|
|
41
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
42
|
+
*.manifest
|
|
43
|
+
*.spec
|
|
44
|
+
|
|
45
|
+
# Installer logs
|
|
46
|
+
pip-log.txt
|
|
47
|
+
pip-delete-this-directory.txt
|
|
48
|
+
|
|
49
|
+
# Unit test / coverage reports
|
|
50
|
+
htmlcov/
|
|
51
|
+
.tox/
|
|
52
|
+
.nox/
|
|
53
|
+
.coverage
|
|
54
|
+
.coverage.*
|
|
55
|
+
.cache
|
|
56
|
+
nosetests.xml
|
|
57
|
+
coverage.xml
|
|
58
|
+
*.cover
|
|
59
|
+
*.py,cover
|
|
60
|
+
.hypothesis/
|
|
61
|
+
.pytest_cache/
|
|
62
|
+
cover/
|
|
63
|
+
|
|
64
|
+
# Translations
|
|
65
|
+
*.mo
|
|
66
|
+
*.pot
|
|
67
|
+
|
|
68
|
+
# Django stuff:
|
|
69
|
+
*.log
|
|
70
|
+
local_settings.py
|
|
71
|
+
db.sqlite3
|
|
72
|
+
db.sqlite3-journal
|
|
73
|
+
|
|
74
|
+
# Flask stuff:
|
|
75
|
+
instance/
|
|
76
|
+
.webassets-cache
|
|
77
|
+
|
|
78
|
+
# Scrapy stuff:
|
|
79
|
+
.scrapy
|
|
80
|
+
|
|
81
|
+
# Sphinx documentation
|
|
82
|
+
docs/_build/
|
|
83
|
+
|
|
84
|
+
# PyBuilder
|
|
85
|
+
.pybuilder/
|
|
86
|
+
target/
|
|
87
|
+
|
|
88
|
+
# Jupyter Notebook
|
|
89
|
+
.ipynb_checkpoints
|
|
90
|
+
|
|
91
|
+
# IPython
|
|
92
|
+
profile_default/
|
|
93
|
+
ipython_config.py
|
|
94
|
+
|
|
95
|
+
# pyenv
|
|
96
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
97
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
98
|
+
# .python-version
|
|
99
|
+
|
|
100
|
+
# pipenv
|
|
101
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
102
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
103
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
104
|
+
# install all needed dependencies.
|
|
105
|
+
#Pipfile.lock
|
|
106
|
+
|
|
107
|
+
# UV
|
|
108
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
109
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
110
|
+
# commonly ignored for libraries.
|
|
111
|
+
#uv.lock
|
|
112
|
+
|
|
113
|
+
# poetry
|
|
114
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
115
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
116
|
+
# commonly ignored for libraries.
|
|
117
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
118
|
+
#poetry.lock
|
|
119
|
+
|
|
120
|
+
# pdm
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
122
|
+
#pdm.lock
|
|
123
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
124
|
+
# in version control.
|
|
125
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
126
|
+
.pdm.toml
|
|
127
|
+
.pdm-python
|
|
128
|
+
.pdm-build/
|
|
129
|
+
|
|
130
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
131
|
+
__pypackages__/
|
|
132
|
+
|
|
133
|
+
# Celery stuff
|
|
134
|
+
celerybeat-schedule
|
|
135
|
+
celerybeat.pid
|
|
136
|
+
|
|
137
|
+
# SageMath parsed files
|
|
138
|
+
*.sage.py
|
|
139
|
+
|
|
140
|
+
# Environments
|
|
141
|
+
.env
|
|
142
|
+
.venv
|
|
143
|
+
env/
|
|
144
|
+
venv/
|
|
145
|
+
ENV/
|
|
146
|
+
env.bak/
|
|
147
|
+
venv.bak/
|
|
148
|
+
|
|
149
|
+
# Spyder project settings
|
|
150
|
+
.spyderproject
|
|
151
|
+
.spyproject
|
|
152
|
+
|
|
153
|
+
# Rope project settings
|
|
154
|
+
.ropeproject
|
|
155
|
+
|
|
156
|
+
# mkdocs documentation
|
|
157
|
+
/site
|
|
158
|
+
|
|
159
|
+
# mypy
|
|
160
|
+
.mypy_cache/
|
|
161
|
+
.dmypy.json
|
|
162
|
+
dmypy.json
|
|
163
|
+
|
|
164
|
+
# Pyre type checker
|
|
165
|
+
.pyre/
|
|
166
|
+
|
|
167
|
+
# pytype static type analyzer
|
|
168
|
+
.pytype/
|
|
169
|
+
|
|
170
|
+
# Cython debug symbols
|
|
171
|
+
cython_debug/
|
|
172
|
+
|
|
173
|
+
# PyCharm
|
|
174
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
175
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
176
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
177
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
178
|
+
#.idea/
|
|
179
|
+
|
|
180
|
+
# Ruff stuff:
|
|
181
|
+
.ruff_cache/
|
|
182
|
+
|
|
183
|
+
# PyPI configuration file
|
|
184
|
+
.pypirc
|
|
185
|
+
|
|
186
|
+
# Cursor
|
|
187
|
+
# Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to
|
|
188
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
189
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
190
|
+
.cursorignore
|
|
191
|
+
.cursorindexingignore
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
from flask import Flask, jsonify, g
|
|
4
|
+
from flask_wtf.csrf import CSRFProtect
|
|
5
|
+
from werkzeug.exceptions import RequestEntityTooLarge
|
|
6
|
+
from AstroSpace.logging_utils import configure_app_logging, debug_log
|
|
7
|
+
|
|
8
|
+
csrf = CSRFProtect()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_app(test_config=None):
|
|
12
|
+
# create and configure the app
|
|
13
|
+
app = Flask(__name__, instance_relative_config=True)
|
|
14
|
+
|
|
15
|
+
# first priority
|
|
16
|
+
app.config.from_object('AstroSpace.config.Config')
|
|
17
|
+
|
|
18
|
+
# second priority
|
|
19
|
+
# load the environment variables from the path set in environment variable
|
|
20
|
+
app.config.from_envvar('ASTROSPACE_SETTINGS', silent=True)
|
|
21
|
+
|
|
22
|
+
if test_config is None:
|
|
23
|
+
# load the instance config, if it exists, when not testing
|
|
24
|
+
app.config.from_pyfile('config.py', silent=True)
|
|
25
|
+
else:
|
|
26
|
+
# load the test config if passed in
|
|
27
|
+
app.config.from_mapping(test_config)
|
|
28
|
+
|
|
29
|
+
app.config['root_path'] = os.path.dirname(__file__)
|
|
30
|
+
app.config['MAX_CONTENT_LENGTH'] = 2 * (1024 ** 3) #about 2GB
|
|
31
|
+
configure_app_logging(app)
|
|
32
|
+
|
|
33
|
+
@app.errorhandler(RequestEntityTooLarge)
|
|
34
|
+
def handle_file_too_large(e):
|
|
35
|
+
return jsonify(error=f"Uploaded File is too large. Max size is {app.config['MAX_CONTENT_LENGTH']/(1024**3)} GB."), 413
|
|
36
|
+
|
|
37
|
+
#app.config['UPLOAD_PATH'] = os.path.join(app.root_path, 'uploads')
|
|
38
|
+
os.makedirs(app.config['UPLOAD_PATH'],exist_ok=True)
|
|
39
|
+
skip_db_init = app.config.get("SKIP_DB_INIT", False)
|
|
40
|
+
debug_log(
|
|
41
|
+
"Application configured (instance_path=%s, skip_db_init=%s, max_upload_bytes=%s)",
|
|
42
|
+
app.instance_path,
|
|
43
|
+
skip_db_init,
|
|
44
|
+
app.config["MAX_CONTENT_LENGTH"],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# ensure the instance folder exists
|
|
48
|
+
try:
|
|
49
|
+
os.makedirs(app.instance_path)
|
|
50
|
+
except OSError:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
for key in ['SECRET_KEY', 'DB_NAME', 'DB_USER', 'DB_PASSWORD', 'DB_HOST', 'DB_PORT']:
|
|
54
|
+
if key not in app.config:
|
|
55
|
+
raise ValueError(f"{key} must be set in the configuration", app.config)
|
|
56
|
+
if not app.config.get("SECRET_KEY"):
|
|
57
|
+
raise ValueError("SECRET_KEY must be set in the configuration")
|
|
58
|
+
|
|
59
|
+
csrf.init_app(app)
|
|
60
|
+
|
|
61
|
+
from . import db
|
|
62
|
+
if not skip_db_init:
|
|
63
|
+
db.init_app(app)
|
|
64
|
+
|
|
65
|
+
with app.app_context():
|
|
66
|
+
exists = db.check_images_table_exists()
|
|
67
|
+
debug_log("Database bootstrap check completed (images_table_exists=%s)", exists)
|
|
68
|
+
if not exists:
|
|
69
|
+
debug_log(
|
|
70
|
+
"images table is missing; initializing database schema.",
|
|
71
|
+
level=logging.INFO,
|
|
72
|
+
)
|
|
73
|
+
db.init_db()
|
|
74
|
+
db.ensure_runtime_schema()
|
|
75
|
+
|
|
76
|
+
@app.before_request
|
|
77
|
+
def load_web_info():
|
|
78
|
+
if skip_db_init:
|
|
79
|
+
g.web_info = {}
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
conn = db.get_conn()
|
|
83
|
+
with conn.cursor() as cur:
|
|
84
|
+
cur.execute("SELECT * FROM web_info LIMIT 1")
|
|
85
|
+
info = cur.fetchone()
|
|
86
|
+
|
|
87
|
+
g.web_info = info or {} # fallback to empty dict
|
|
88
|
+
|
|
89
|
+
@app.context_processor
|
|
90
|
+
def inject_web_info():
|
|
91
|
+
return {
|
|
92
|
+
"web_info": getattr(g, "web_info", {})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
from . import auth
|
|
96
|
+
app.register_blueprint(auth.bp)
|
|
97
|
+
|
|
98
|
+
from . import blog
|
|
99
|
+
app.register_blueprint(blog.bp)
|
|
100
|
+
|
|
101
|
+
from .profile import private, public
|
|
102
|
+
app.register_blueprint(private.bp)
|
|
103
|
+
app.register_blueprint(public.bp)
|
|
104
|
+
|
|
105
|
+
app.add_url_rule('/', endpoint='index')
|
|
106
|
+
|
|
107
|
+
return app
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
app = create_app()
|
|
112
|
+
app.run(debug=True, host="0.0.0.0", port=9000)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from AstroSpace.logging_utils import debug_log, runtime_debug_enabled, strip_debug_flag
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
DEFAULT_GUNICORN_OPTIONS = [
|
|
9
|
+
"-w",
|
|
10
|
+
"3",
|
|
11
|
+
"-b",
|
|
12
|
+
"0.0.0.0:9000",
|
|
13
|
+
"--timeout",
|
|
14
|
+
"1000",
|
|
15
|
+
"--worker-class",
|
|
16
|
+
"gevent",
|
|
17
|
+
]
|
|
18
|
+
APP_TARGET = "AstroSpace:create_app()"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_gunicorn_command(argv=None):
|
|
22
|
+
args = list(sys.argv[1:] if argv is None else argv)
|
|
23
|
+
filtered_args, flag_requested = strip_debug_flag(args)
|
|
24
|
+
debug_enabled = flag_requested or runtime_debug_enabled(filtered_args)
|
|
25
|
+
|
|
26
|
+
command = ["gunicorn", *DEFAULT_GUNICORN_OPTIONS]
|
|
27
|
+
if debug_enabled:
|
|
28
|
+
command.extend(["--log-level", "debug"])
|
|
29
|
+
command.extend(filtered_args)
|
|
30
|
+
command.append(APP_TARGET)
|
|
31
|
+
return command, debug_enabled
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main(argv=None):
|
|
35
|
+
command, debug_enabled = build_gunicorn_command(argv)
|
|
36
|
+
if debug_enabled:
|
|
37
|
+
os.environ["ASTROSPACE_DEBUG"] = "1"
|
|
38
|
+
debug_log("Starting Gunicorn with AstroSpace debug logging enabled.", level=logging.INFO)
|
|
39
|
+
|
|
40
|
+
os.execvp(command[0], command)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
main()
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
|
|
3
|
+
from psycopg2 import IntegrityError
|
|
4
|
+
from flask import (
|
|
5
|
+
Blueprint, flash, g, redirect, render_template, request, session, url_for, current_app
|
|
6
|
+
)
|
|
7
|
+
from werkzeug.security import check_password_hash, generate_password_hash
|
|
8
|
+
|
|
9
|
+
from AstroSpace.db import get_conn
|
|
10
|
+
|
|
11
|
+
bp = Blueprint('auth', __name__, url_prefix='/auth')
|
|
12
|
+
|
|
13
|
+
@bp.route('/register', methods=('GET', 'POST'))
|
|
14
|
+
def register():
|
|
15
|
+
if request.method == 'POST':
|
|
16
|
+
username = request.form['username'].lower()
|
|
17
|
+
password = request.form['password']
|
|
18
|
+
db = get_conn()
|
|
19
|
+
error = None
|
|
20
|
+
|
|
21
|
+
if not username:
|
|
22
|
+
error = 'Username is required.'
|
|
23
|
+
elif not password:
|
|
24
|
+
error = 'Password is required.'
|
|
25
|
+
|
|
26
|
+
if error is None:
|
|
27
|
+
try:
|
|
28
|
+
with db.cursor() as cur:
|
|
29
|
+
cur.execute("LOCK TABLE users IN EXCLUSIVE MODE")
|
|
30
|
+
cur.execute("SELECT COUNT(*) AS user_count FROM users")
|
|
31
|
+
user_count = cur.fetchone()["user_count"]
|
|
32
|
+
|
|
33
|
+
if user_count >= current_app.config['MAX_USERS']:
|
|
34
|
+
error = 'Sorry maximum number of users reached :('
|
|
35
|
+
else:
|
|
36
|
+
is_first_user = user_count == 0
|
|
37
|
+
cur.execute(
|
|
38
|
+
"INSERT INTO users (username, password, admin) VALUES (%s, %s, %s)",
|
|
39
|
+
(username, generate_password_hash(password), is_first_user),
|
|
40
|
+
)
|
|
41
|
+
if error is None:
|
|
42
|
+
db.commit()
|
|
43
|
+
return redirect(url_for("auth.login"))
|
|
44
|
+
db.rollback()
|
|
45
|
+
except IntegrityError:
|
|
46
|
+
db.rollback()
|
|
47
|
+
error = f"User {username} is already registered."
|
|
48
|
+
|
|
49
|
+
flash(error)
|
|
50
|
+
|
|
51
|
+
return render_template('auth/auth.html', title='Register', WebName = current_app.config["TITLE"])
|
|
52
|
+
|
|
53
|
+
@bp.route('/login', methods=('GET', 'POST'))
|
|
54
|
+
def login():
|
|
55
|
+
if request.method == 'POST':
|
|
56
|
+
username = request.form['username'].lower()
|
|
57
|
+
password = request.form['password']
|
|
58
|
+
db = get_conn()
|
|
59
|
+
error = None
|
|
60
|
+
with db.cursor() as cur:
|
|
61
|
+
cur.execute(
|
|
62
|
+
"SELECT * FROM users WHERE username = %s", (username,) )
|
|
63
|
+
user = cur.fetchone()
|
|
64
|
+
|
|
65
|
+
if user is None:
|
|
66
|
+
error = 'Incorrect username.'
|
|
67
|
+
elif not check_password_hash(user['password'], password):
|
|
68
|
+
error = 'Incorrect password.'
|
|
69
|
+
|
|
70
|
+
if error is None:
|
|
71
|
+
session.clear()
|
|
72
|
+
session['user_id'] = user['id']
|
|
73
|
+
return redirect(url_for('index'))
|
|
74
|
+
|
|
75
|
+
flash(error)
|
|
76
|
+
|
|
77
|
+
return render_template('auth/auth.html', title='Log In', WebName = current_app.config["TITLE"])
|
|
78
|
+
|
|
79
|
+
@bp.before_app_request
|
|
80
|
+
def load_logged_in_user():
|
|
81
|
+
user_id = session.get('user_id')
|
|
82
|
+
|
|
83
|
+
if user_id is None:
|
|
84
|
+
g.user = None
|
|
85
|
+
else:
|
|
86
|
+
cur = get_conn().cursor()
|
|
87
|
+
cur.execute(
|
|
88
|
+
"SELECT * FROM users WHERE id = %s", (user_id,)
|
|
89
|
+
)
|
|
90
|
+
g.user = cur.fetchone()
|
|
91
|
+
|
|
92
|
+
@bp.route('/logout', methods=('POST',))
|
|
93
|
+
def logout():
|
|
94
|
+
session.clear()
|
|
95
|
+
return redirect(url_for('index'))
|
|
96
|
+
|
|
97
|
+
def login_required(view):
|
|
98
|
+
@functools.wraps(view)
|
|
99
|
+
def wrapped_view(**kwargs):
|
|
100
|
+
if g.user is None:
|
|
101
|
+
return redirect(url_for('auth.login'))
|
|
102
|
+
|
|
103
|
+
return view(**kwargs)
|
|
104
|
+
|
|
105
|
+
return wrapped_view
|