geovisio 2.6.0__py3-none-any.whl → 2.7.0__py3-none-any.whl
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.
- geovisio/__init__.py +36 -7
- geovisio/admin_cli/db.py +1 -4
- geovisio/config_app.py +40 -1
- geovisio/db_migrations.py +24 -3
- geovisio/templates/main.html +13 -13
- geovisio/templates/viewer.html +3 -3
- geovisio/translations/de/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/de/LC_MESSAGES/messages.po +667 -0
- geovisio/translations/en/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/en/LC_MESSAGES/messages.po +730 -0
- geovisio/translations/es/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/es/LC_MESSAGES/messages.po +778 -0
- geovisio/translations/fi/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/fi/LC_MESSAGES/messages.po +589 -0
- geovisio/translations/fr/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/fr/LC_MESSAGES/messages.po +814 -0
- geovisio/translations/ko/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/ko/LC_MESSAGES/messages.po +685 -0
- geovisio/translations/messages.pot +686 -0
- geovisio/translations/nl/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/nl/LC_MESSAGES/messages.po +594 -0
- geovisio/utils/__init__.py +1 -1
- geovisio/utils/auth.py +50 -11
- geovisio/utils/db.py +65 -0
- geovisio/utils/excluded_areas.py +83 -0
- geovisio/utils/extent.py +30 -0
- geovisio/utils/fields.py +1 -1
- geovisio/utils/filesystems.py +0 -1
- geovisio/utils/link.py +14 -0
- geovisio/utils/params.py +20 -0
- geovisio/utils/pictures.py +92 -68
- geovisio/utils/reports.py +171 -0
- geovisio/utils/sequences.py +264 -126
- geovisio/utils/tokens.py +37 -42
- geovisio/utils/upload_set.py +654 -0
- geovisio/web/auth.py +37 -37
- geovisio/web/collections.py +286 -302
- geovisio/web/configuration.py +14 -0
- geovisio/web/docs.py +241 -14
- geovisio/web/excluded_areas.py +377 -0
- geovisio/web/items.py +156 -108
- geovisio/web/map.py +20 -20
- geovisio/web/params.py +69 -26
- geovisio/web/pictures.py +14 -31
- geovisio/web/reports.py +399 -0
- geovisio/web/rss.py +13 -7
- geovisio/web/stac.py +129 -134
- geovisio/web/tokens.py +98 -109
- geovisio/web/upload_set.py +768 -0
- geovisio/web/users.py +100 -73
- geovisio/web/utils.py +28 -9
- geovisio/workers/runner_pictures.py +252 -204
- {geovisio-2.6.0.dist-info → geovisio-2.7.0.dist-info}/METADATA +16 -13
- geovisio-2.7.0.dist-info/RECORD +66 -0
- geovisio-2.6.0.dist-info/RECORD +0 -41
- {geovisio-2.6.0.dist-info → geovisio-2.7.0.dist-info}/LICENSE +0 -0
- {geovisio-2.6.0.dist-info → geovisio-2.7.0.dist-info}/WHEEL +0 -0
geovisio/web/stac.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import psycopg
|
|
2
1
|
from psycopg.sql import SQL
|
|
3
2
|
from flask import Blueprint, current_app, request, url_for
|
|
3
|
+
from flask_babel import gettext as _
|
|
4
4
|
from geovisio import errors
|
|
5
|
-
from geovisio.utils import auth
|
|
5
|
+
from geovisio.utils import auth, db
|
|
6
6
|
from psycopg.rows import dict_row
|
|
7
7
|
from geovisio.utils.fields import SortBy, SQLDirection, Bounds, SortByField
|
|
8
8
|
from geovisio.web.utils import (
|
|
@@ -14,6 +14,7 @@ from geovisio.web.utils import (
|
|
|
14
14
|
get_root_link,
|
|
15
15
|
removeNoneInDict,
|
|
16
16
|
user_dependant_response,
|
|
17
|
+
get_api_version,
|
|
17
18
|
)
|
|
18
19
|
from geovisio.utils.sequences import (
|
|
19
20
|
get_collections,
|
|
@@ -57,140 +58,134 @@ def getLanding():
|
|
|
57
58
|
$ref: '#/components/schemas/GeoVisioLanding'
|
|
58
59
|
"""
|
|
59
60
|
|
|
60
|
-
with
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
"temporal": (
|
|
81
|
-
{"interval": [[dbTsToStac(temporal_min), dbTsToStac(temporal_max)]]} if temporal_min is not None else None
|
|
82
|
-
),
|
|
83
|
-
}
|
|
84
|
-
)
|
|
85
|
-
if spatial_xmin is not None or temporal_min is not None
|
|
86
|
-
else None
|
|
61
|
+
with db.cursor(current_app) as cursor:
|
|
62
|
+
spatial_xmin, spatial_ymin, spatial_xmax, spatial_ymax, temporal_min, temporal_max = cursor.execute(
|
|
63
|
+
"""SELECT
|
|
64
|
+
GREATEST(-180, ST_XMin(ST_EstimatedExtent('pictures', 'geom'))),
|
|
65
|
+
GREATEST(-90, ST_YMin(ST_EstimatedExtent('pictures', 'geom'))),
|
|
66
|
+
LEAST(180, ST_XMax(ST_EstimatedExtent('pictures', 'geom'))),
|
|
67
|
+
LEAST(90, ST_YMax(ST_EstimatedExtent('pictures', 'geom'))),
|
|
68
|
+
MIN(ts), MAX(ts)
|
|
69
|
+
FROM pictures
|
|
70
|
+
"""
|
|
71
|
+
).fetchone()
|
|
72
|
+
|
|
73
|
+
extent = (
|
|
74
|
+
cleanNoneInDict(
|
|
75
|
+
{
|
|
76
|
+
"spatial": ({"bbox": [[spatial_xmin, spatial_ymin, spatial_xmax, spatial_ymax]]} if spatial_xmin is not None else None),
|
|
77
|
+
"temporal": (
|
|
78
|
+
{"interval": [[dbTsToStac(temporal_min), dbTsToStac(temporal_max)]]} if temporal_min is not None else None
|
|
79
|
+
),
|
|
80
|
+
}
|
|
87
81
|
)
|
|
82
|
+
if spatial_xmin is not None or temporal_min is not None
|
|
83
|
+
else None
|
|
84
|
+
)
|
|
88
85
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
).fetchall()
|
|
98
|
-
]
|
|
86
|
+
catalog = dbSequencesToStacCatalog(
|
|
87
|
+
id="geovisio",
|
|
88
|
+
title=current_app.config["API_SUMMARY"].name.get("en"),
|
|
89
|
+
description=current_app.config["API_SUMMARY"].description.get("en"),
|
|
90
|
+
sequences=[],
|
|
91
|
+
request=request,
|
|
92
|
+
extent=extent,
|
|
93
|
+
)
|
|
99
94
|
|
|
100
|
-
|
|
101
|
-
"geovisio",
|
|
102
|
-
"GeoVisio STAC API",
|
|
103
|
-
"This catalog list all geolocated pictures available in this GeoVisio instance",
|
|
104
|
-
sequences,
|
|
105
|
-
request,
|
|
106
|
-
extent,
|
|
107
|
-
)
|
|
95
|
+
catalog["geovisio_version"] = get_api_version()
|
|
108
96
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
97
|
+
mapUrl = (
|
|
98
|
+
url_for("map.getTile", x="11111", y="22222", z="33333", format="mvt", _external=True)
|
|
99
|
+
.replace("11111", "{x}")
|
|
100
|
+
.replace("22222", "{y}")
|
|
101
|
+
.replace("33333", "{z}")
|
|
102
|
+
)
|
|
103
|
+
userMapUrl = (
|
|
104
|
+
url_for("map.getUserTile", userId="bob", x="11111", y="22222", z="33333", format="mvt", _external=True)
|
|
105
|
+
.replace("11111", "{x}")
|
|
106
|
+
.replace("22222", "{y}")
|
|
107
|
+
.replace("33333", "{z}")
|
|
108
|
+
.replace("bob", "{userId}")
|
|
109
|
+
)
|
|
110
|
+
userStyleUrl = url_for("map.getUserStyle", userId="bob", _external=True).replace("bob", "{userId}")
|
|
111
|
+
|
|
112
|
+
if "stac_extensions" not in catalog:
|
|
113
|
+
catalog["stac_extensions"] = []
|
|
114
|
+
|
|
115
|
+
catalog["stac_extensions"] += ["https://stac-extensions.github.io/web-map-links/v1.0.0/schema.json"]
|
|
116
|
+
|
|
117
|
+
catalog["links"] += cleanNoneInList(
|
|
118
|
+
[
|
|
119
|
+
{"rel": "service-desc", "type": "application/json", "href": url_for("flasgger.swagger", _external=True)},
|
|
120
|
+
{"rel": "service-doc", "type": "text/html", "href": url_for("flasgger.apidocs", _external=True)},
|
|
121
|
+
{"rel": "conformance", "type": "application/json", "href": url_for("stac.getConformance", _external=True)},
|
|
122
|
+
{"rel": "data", "type": "application/json", "href": url_for("stac_collections.getAllCollections", _external=True)},
|
|
123
|
+
{"rel": "child", "type": "application/json", "href": url_for("user.listUsers", _external=True)},
|
|
124
|
+
{
|
|
125
|
+
"rel": "data",
|
|
126
|
+
"type": "application/rss+xml",
|
|
127
|
+
"href": url_for("stac_collections.getAllCollections", _external=True, format="rss"),
|
|
128
|
+
},
|
|
129
|
+
{"rel": "search", "type": "application/geo+json", "href": url_for("stac_items.searchItems", _external=True)},
|
|
130
|
+
{
|
|
131
|
+
"rel": "xyz",
|
|
132
|
+
"type": "application/vnd.mapbox-vector-tile",
|
|
133
|
+
"href": mapUrl,
|
|
134
|
+
"title": "Pictures and sequences vector tiles",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"rel": "xyz-style",
|
|
138
|
+
"type": "application/json",
|
|
139
|
+
"href": url_for("map.getStyle", _external=True),
|
|
140
|
+
"title": "MapLibre Style JSON",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"rel": "user-xyz",
|
|
144
|
+
"type": "application/vnd.mapbox-vector-tile",
|
|
145
|
+
"href": userMapUrl,
|
|
146
|
+
"title": "Pictures and sequences vector tiles for a given user",
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"rel": "user-xyz-style",
|
|
150
|
+
"type": "application/json",
|
|
151
|
+
"href": userStyleUrl,
|
|
152
|
+
"title": "MapLibre Style JSON",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"rel": "collection-preview",
|
|
156
|
+
"type": "image/jpeg",
|
|
157
|
+
"href": url_for("stac_collections.getCollectionThumbnail", collectionId="{id}", _external=True),
|
|
158
|
+
"title": "Thumbnail URL for a given sequence",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"rel": "item-preview",
|
|
162
|
+
"type": "image/jpeg",
|
|
163
|
+
"href": url_for("pictures.getPictureThumb", pictureId="{id}", format="jpg", _external=True),
|
|
164
|
+
"title": "Thumbnail URL for a given picture",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"rel": "users",
|
|
168
|
+
"type": "application/json",
|
|
169
|
+
"href": url_for("user.listUsers", _external=True),
|
|
170
|
+
"title": "List of users",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"rel": "user-search",
|
|
174
|
+
"type": "application/json",
|
|
175
|
+
"href": url_for("user.searchUser", _external=True),
|
|
176
|
+
"title": "Search users",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"rel": "report",
|
|
180
|
+
"type": "application/json",
|
|
181
|
+
"href": url_for("reports.postReport", _external=True),
|
|
182
|
+
"title": "Post feedback/report about picture or sequence",
|
|
183
|
+
},
|
|
184
|
+
get_license_link(),
|
|
185
|
+
]
|
|
186
|
+
)
|
|
192
187
|
|
|
193
|
-
|
|
188
|
+
return catalog, 200, {"Content-Type": "application/json"}
|
|
194
189
|
|
|
195
190
|
|
|
196
191
|
@bp.route("/conformance")
|
|
@@ -335,11 +330,11 @@ def getUserCatalog(userId, userIdMatchesAccount=False):
|
|
|
335
330
|
|
|
336
331
|
userName = None
|
|
337
332
|
meta_collection = None
|
|
338
|
-
with
|
|
333
|
+
with db.cursor(current_app, row_factory=dict_row) as cursor:
|
|
339
334
|
userName = cursor.execute("SELECT name FROM accounts WHERE id = %s", [userId]).fetchone()
|
|
340
335
|
|
|
341
336
|
if not userName:
|
|
342
|
-
raise errors.InvalidAPIUsage(
|
|
337
|
+
raise errors.InvalidAPIUsage(_("Impossible to find user %(u)s", u=userId))
|
|
343
338
|
userName = userName["name"]
|
|
344
339
|
|
|
345
340
|
meta_collection = cursor.execute(
|
|
@@ -349,7 +344,7 @@ def getUserCatalog(userId, userIdMatchesAccount=False):
|
|
|
349
344
|
|
|
350
345
|
if not meta_collection or meta_collection["min_order"] is None:
|
|
351
346
|
# No data found, trying to give the most meaningfull error message
|
|
352
|
-
raise errors.InvalidAPIUsage(
|
|
347
|
+
raise errors.InvalidAPIUsage(_("No data loaded for user %(u)s", u=userId), 404)
|
|
353
348
|
|
|
354
349
|
db_collections = get_collections(collection_request)
|
|
355
350
|
|
geovisio/web/tokens.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import flask
|
|
2
2
|
from flask import current_app, url_for, request
|
|
3
|
+
from flask_babel import gettext as _
|
|
3
4
|
from dateutil import tz
|
|
4
|
-
import psycopg
|
|
5
5
|
from psycopg.rows import dict_row
|
|
6
6
|
from authlib.jose import jwt
|
|
7
7
|
from authlib.jose.errors import DecodeError
|
|
8
8
|
import logging
|
|
9
9
|
import uuid
|
|
10
|
-
from geovisio.utils import auth
|
|
10
|
+
from geovisio.utils import auth, db
|
|
11
11
|
from geovisio import errors, utils
|
|
12
12
|
|
|
13
13
|
|
|
@@ -36,31 +36,29 @@ def list_tokens(account):
|
|
|
36
36
|
$ref: '#/components/schemas/GeoVisioTokens'
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
records = db.fetchall(
|
|
40
|
+
current_app,
|
|
41
|
+
"SELECT id, description, generated_at FROM tokens WHERE account_id = %(account)s",
|
|
42
|
+
{"account": account.id},
|
|
43
|
+
row_factory=dict_row,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
tokens = [
|
|
47
|
+
{
|
|
48
|
+
"id": r["id"],
|
|
49
|
+
"description": r["description"],
|
|
50
|
+
"generated_at": r["generated_at"].astimezone(tz.gettz("UTC")).isoformat(),
|
|
51
|
+
"links": [
|
|
49
52
|
{
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"links": [
|
|
54
|
-
{
|
|
55
|
-
"rel": "self",
|
|
56
|
-
"type": "application/json",
|
|
57
|
-
"href": url_for("tokens.get_jwt_token", token_id=r["id"], _external=True),
|
|
58
|
-
}
|
|
59
|
-
],
|
|
53
|
+
"rel": "self",
|
|
54
|
+
"type": "application/json",
|
|
55
|
+
"href": url_for("tokens.get_jwt_token", token_id=r["id"], _external=True),
|
|
60
56
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
],
|
|
58
|
+
}
|
|
59
|
+
for r in records
|
|
60
|
+
]
|
|
61
|
+
return flask.jsonify(tokens)
|
|
64
62
|
|
|
65
63
|
|
|
66
64
|
@bp.route("/users/me/tokens/<uuid:token_id>", methods=["GET"])
|
|
@@ -90,28 +88,26 @@ def get_jwt_token(token_id: uuid.UUID, account: auth.Account):
|
|
|
90
88
|
$ref: '#/components/schemas/GeoVisioEncodedToken'
|
|
91
89
|
"""
|
|
92
90
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
)
|
|
91
|
+
token = db.fetchone(
|
|
92
|
+
current_app,
|
|
93
|
+
"SELECT id, description, generated_at FROM tokens WHERE account_id = %(account)s AND id = %(token)s",
|
|
94
|
+
{"account": account.id, "token": token_id},
|
|
95
|
+
row_factory=dict_row,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# check token existence
|
|
99
|
+
if not token:
|
|
100
|
+
raise errors.InvalidAPIUsage(_("Impossible to find token"), status_code=404)
|
|
101
|
+
|
|
102
|
+
jwt_token = _generate_jwt_token(token["id"])
|
|
103
|
+
return flask.jsonify(
|
|
104
|
+
{
|
|
105
|
+
"jwt_token": jwt_token,
|
|
106
|
+
"id": token["id"],
|
|
107
|
+
"description": token["description"],
|
|
108
|
+
"generated_at": token["generated_at"].astimezone(tz.gettz("UTC")).isoformat(),
|
|
109
|
+
}
|
|
110
|
+
)
|
|
115
111
|
|
|
116
112
|
|
|
117
113
|
@bp.route("/users/me/tokens/<uuid:token_id>", methods=["DELETE"])
|
|
@@ -136,22 +132,16 @@ def revoke_token(token_id: uuid.UUID, account: auth.Account):
|
|
|
136
132
|
description: The token has been correctly deleted
|
|
137
133
|
"""
|
|
138
134
|
|
|
139
|
-
with
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
""",
|
|
146
|
-
{"account": account.id, "token": token_id},
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
token_deleted = res.rowcount
|
|
135
|
+
with db.execute(
|
|
136
|
+
current_app,
|
|
137
|
+
"DELETE FROM tokens WHERE account_id = %(account)s AND id = %(token)s",
|
|
138
|
+
{"account": account.id, "token": token_id},
|
|
139
|
+
) as res:
|
|
140
|
+
token_deleted = res.rowcount
|
|
150
141
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return flask.jsonify({"message": "token revoked"}), 200
|
|
142
|
+
if not token_deleted:
|
|
143
|
+
raise errors.InvalidAPIUsage(_("Impossible to find token"), status_code=404)
|
|
144
|
+
return flask.jsonify({"message": "token revoked"}), 200
|
|
155
145
|
|
|
156
146
|
|
|
157
147
|
@bp.route("/auth/tokens/generate", methods=["POST"])
|
|
@@ -180,30 +170,31 @@ def generate_non_associated_token():
|
|
|
180
170
|
$ref: '#/components/schemas/JWTokenClaimable'
|
|
181
171
|
"""
|
|
182
172
|
description = request.args.get("description", "")
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
],
|
|
173
|
+
|
|
174
|
+
token = db.fetchone(
|
|
175
|
+
current_app,
|
|
176
|
+
"INSERT INTO tokens (description) VALUES (%(description)s) RETURNING *",
|
|
177
|
+
{"description": description},
|
|
178
|
+
row_factory=dict_row,
|
|
179
|
+
)
|
|
180
|
+
if not token:
|
|
181
|
+
raise errors.InternalError(_("Impossible to generate a new token"))
|
|
182
|
+
|
|
183
|
+
jwt_token = _generate_jwt_token(token["id"])
|
|
184
|
+
token = {
|
|
185
|
+
"id": token["id"],
|
|
186
|
+
"jwt_token": jwt_token,
|
|
187
|
+
"description": token["description"],
|
|
188
|
+
"generated_at": token["generated_at"].astimezone(tz.gettz("UTC")).isoformat(),
|
|
189
|
+
"links": [
|
|
190
|
+
{
|
|
191
|
+
"rel": "claim",
|
|
192
|
+
"type": "application/json",
|
|
193
|
+
"href": url_for("tokens.claim_non_associated_token", token_id=token["id"], _external=True),
|
|
205
194
|
}
|
|
206
|
-
|
|
195
|
+
],
|
|
196
|
+
}
|
|
197
|
+
return flask.jsonify(token)
|
|
207
198
|
|
|
208
199
|
|
|
209
200
|
@bp.route("/auth/tokens/<uuid:token_id>/claim", methods=["GET"])
|
|
@@ -229,30 +220,28 @@ def claim_non_associated_token(token_id, account):
|
|
|
229
220
|
200:
|
|
230
221
|
description: The token has been correctly associated to the account
|
|
231
222
|
"""
|
|
232
|
-
with
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if associated_account:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
conn.commit()
|
|
255
|
-
return "You are now logged in the CLI, you can upload your pictures", 200
|
|
223
|
+
with db.cursor(current_app, row_factory=dict_row) as cursor:
|
|
224
|
+
token = cursor.execute(
|
|
225
|
+
"""
|
|
226
|
+
SELECT account_id FROM tokens WHERE id = %(token)s
|
|
227
|
+
""",
|
|
228
|
+
{"token": token_id},
|
|
229
|
+
).fetchone()
|
|
230
|
+
if not token:
|
|
231
|
+
raise errors.InvalidAPIUsage(_("Impossible to find token"), status_code=404)
|
|
232
|
+
|
|
233
|
+
associated_account = token["account_id"]
|
|
234
|
+
if associated_account:
|
|
235
|
+
if associated_account != account.id:
|
|
236
|
+
raise errors.InvalidAPIUsage(_("Token already claimed by another account"), status_code=403)
|
|
237
|
+
else:
|
|
238
|
+
return flask.jsonify({"message": "token already associated to account"}), 200
|
|
239
|
+
|
|
240
|
+
cursor.execute(
|
|
241
|
+
"UPDATE tokens SET account_id = %(account)s WHERE id = %(token)s",
|
|
242
|
+
{"account": account.id, "token": token_id},
|
|
243
|
+
)
|
|
244
|
+
return "You are now logged in the CLI, you can upload your pictures", 200
|
|
256
245
|
|
|
257
246
|
|
|
258
247
|
def _generate_jwt_token(token_id: uuid.UUID) -> str:
|