geovisio 2.8.1__py3-none-any.whl → 2.10.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 +6 -1
- geovisio/config_app.py +16 -5
- geovisio/translations/ar/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/ar/LC_MESSAGES/messages.po +818 -0
- geovisio/translations/br/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/da/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/da/LC_MESSAGES/messages.po +4 -3
- geovisio/translations/de/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/de/LC_MESSAGES/messages.po +55 -2
- geovisio/translations/el/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/en/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/en/LC_MESSAGES/messages.po +193 -139
- geovisio/translations/eo/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/eo/LC_MESSAGES/messages.po +53 -4
- geovisio/translations/es/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/fi/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/fr/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/fr/LC_MESSAGES/messages.po +101 -6
- geovisio/translations/hu/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/it/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/it/LC_MESSAGES/messages.po +63 -3
- geovisio/translations/ja/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/ko/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/messages.pot +185 -129
- geovisio/translations/nl/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/nl/LC_MESSAGES/messages.po +421 -86
- geovisio/translations/oc/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/oc/LC_MESSAGES/messages.po +818 -0
- geovisio/translations/pl/LC_MESSAGES/messages.po +1 -1
- geovisio/translations/sv/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/sv/LC_MESSAGES/messages.po +823 -0
- geovisio/translations/ti/LC_MESSAGES/messages.mo +0 -0
- geovisio/translations/ti/LC_MESSAGES/messages.po +762 -0
- geovisio/translations/zh_Hant/LC_MESSAGES/messages.po +1 -1
- geovisio/utils/annotations.py +183 -0
- geovisio/utils/auth.py +14 -13
- geovisio/utils/cql2.py +134 -0
- geovisio/utils/db.py +7 -0
- geovisio/utils/fields.py +38 -9
- geovisio/utils/items.py +44 -0
- geovisio/utils/model_query.py +4 -4
- geovisio/utils/pic_shape.py +63 -0
- geovisio/utils/pictures.py +164 -29
- geovisio/utils/reports.py +10 -17
- geovisio/utils/semantics.py +196 -57
- geovisio/utils/sentry.py +1 -2
- geovisio/utils/sequences.py +191 -93
- geovisio/utils/tags.py +31 -0
- geovisio/utils/upload_set.py +287 -209
- geovisio/utils/website.py +1 -1
- geovisio/web/annotations.py +346 -9
- geovisio/web/auth.py +1 -1
- geovisio/web/collections.py +73 -54
- geovisio/web/configuration.py +26 -5
- geovisio/web/docs.py +143 -11
- geovisio/web/items.py +232 -155
- geovisio/web/map.py +25 -13
- geovisio/web/params.py +55 -52
- geovisio/web/pictures.py +34 -0
- geovisio/web/stac.py +19 -12
- geovisio/web/tokens.py +49 -1
- geovisio/web/upload_set.py +148 -37
- geovisio/web/users.py +4 -4
- geovisio/web/utils.py +2 -2
- geovisio/workers/runner_pictures.py +190 -24
- {geovisio-2.8.1.dist-info → geovisio-2.10.0.dist-info}/METADATA +27 -26
- geovisio-2.10.0.dist-info/RECORD +105 -0
- {geovisio-2.8.1.dist-info → geovisio-2.10.0.dist-info}/WHEEL +1 -1
- geovisio-2.8.1.dist-info/RECORD +0 -92
- {geovisio-2.8.1.dist-info → geovisio-2.10.0.dist-info}/licenses/LICENSE +0 -0
geovisio/web/map.py
CHANGED
|
@@ -352,23 +352,35 @@ def _get_query(z: int, x: int, y: int, onlyForUser: Optional[UUID], additional_f
|
|
|
352
352
|
sql.SQL("nb_360_pictures"),
|
|
353
353
|
sql.SQL("nb_pictures - nb_360_pictures AS nb_flat_pictures"),
|
|
354
354
|
sql.SQL(
|
|
355
|
-
"""((CASE WHEN nb_pictures = 0
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
"""((CASE WHEN nb_pictures = 0
|
|
356
|
+
THEN 0
|
|
357
|
+
WHEN nb_pictures <= (SELECT PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY nb_pictures) FILTER (WHERE nb_pictures > 0) FROM pictures_grid)
|
|
358
|
+
THEN
|
|
359
|
+
nb_pictures::float / (SELECT PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY nb_pictures) FILTER (WHERE nb_pictures > 0) FROM pictures_grid) * 0.5
|
|
360
|
+
ELSE
|
|
361
|
+
0.5 + nb_pictures::float / (SELECT MAX(nb_pictures) FROM pictures_grid) * 0.5
|
|
358
362
|
END) * 10)::int / 10::float AS coef"""
|
|
359
363
|
),
|
|
360
364
|
sql.SQL(
|
|
361
|
-
"""((CASE WHEN nb_360_pictures = 0
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
+
"""((CASE WHEN nb_360_pictures = 0
|
|
366
|
+
THEN 0
|
|
367
|
+
WHEN (SELECT PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY nb_360_pictures) FILTER (WHERE nb_360_pictures > 0) FROM pictures_grid) = 0
|
|
368
|
+
THEN 0
|
|
369
|
+
WHEN nb_360_pictures <= (SELECT PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY nb_360_pictures) FILTER (WHERE nb_360_pictures > 0) FROM pictures_grid)
|
|
370
|
+
THEN nb_360_pictures::float / (SELECT PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY nb_360_pictures) FILTER (WHERE nb_360_pictures > 0) FROM pictures_grid) * 0.5
|
|
371
|
+
ELSE
|
|
372
|
+
0.5 + nb_360_pictures::float / (SELECT MAX(nb_360_pictures) FROM pictures_grid) * 0.5
|
|
365
373
|
END) * 10)::int / 10::float AS coef_360_pictures"""
|
|
366
374
|
),
|
|
367
375
|
sql.SQL(
|
|
368
|
-
"""((CASE WHEN (nb_pictures - nb_360_pictures) = 0
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
376
|
+
"""((CASE WHEN (nb_pictures - nb_360_pictures) = 0
|
|
377
|
+
THEN 0
|
|
378
|
+
WHEN (SELECT PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY (nb_pictures - nb_360_pictures)) FILTER (WHERE (nb_pictures - nb_360_pictures) > 0) FROM pictures_grid) = 0
|
|
379
|
+
THEN 0
|
|
380
|
+
WHEN (nb_pictures - nb_360_pictures) <= (SELECT PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY (nb_pictures - nb_360_pictures)) FILTER (WHERE (nb_pictures - nb_360_pictures) > 0) FROM pictures_grid)
|
|
381
|
+
THEN (nb_pictures - nb_360_pictures)::float / (SELECT PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY (nb_pictures - nb_360_pictures)) FILTER (WHERE (nb_pictures - nb_360_pictures) > 0) FROM pictures_grid) * 0.5
|
|
382
|
+
ELSE
|
|
383
|
+
0.5 + (nb_pictures - nb_360_pictures)::float / (SELECT MAX((nb_pictures - nb_360_pictures)) FROM pictures_grid) * 0.5
|
|
372
384
|
END) * 10)::int / 10::float AS coef_flat_pictures"""
|
|
373
385
|
),
|
|
374
386
|
]
|
|
@@ -616,7 +628,7 @@ def getUserTile(userId: UUID, z: int, x: int, y: int, format: str):
|
|
|
616
628
|
format: binary
|
|
617
629
|
"""
|
|
618
630
|
|
|
619
|
-
filter = params.
|
|
631
|
+
filter = params.parse_collection_filter(request.args.get("filter"))
|
|
620
632
|
return _getTile(z, x, y, format, onlyForUser=userId, filter=filter)
|
|
621
633
|
|
|
622
634
|
|
|
@@ -688,5 +700,5 @@ def getMyTile(account: Account, z: int, x: int, y: int, format: str):
|
|
|
688
700
|
type: string
|
|
689
701
|
format: binary
|
|
690
702
|
"""
|
|
691
|
-
filter = params.
|
|
703
|
+
filter = params.parse_collection_filter(request.args.get("filter"))
|
|
692
704
|
return _getTile(z, x, y, format, onlyForUser=UUID(account.id), filter=filter)
|
geovisio/web/params.py
CHANGED
|
@@ -8,14 +8,15 @@ import datetime
|
|
|
8
8
|
import re
|
|
9
9
|
from werkzeug.datastructures import MultiDict
|
|
10
10
|
from typing import Optional, Tuple, Any, List
|
|
11
|
-
from pygeofilter.backends.sql import to_sql_where
|
|
12
|
-
from pygeofilter.parsers.ecql import parse as ecql_parser
|
|
13
11
|
from pygeofilter import ast
|
|
14
12
|
from pygeofilter.backends.evaluator import Evaluator, handle
|
|
15
13
|
from psycopg import sql
|
|
16
14
|
from geovisio.utils.sequences import STAC_FIELD_MAPPINGS, STAC_FIELD_TO_SQL_FILTER
|
|
17
15
|
from geovisio.utils.fields import SortBy, SQLDirection, SortByField
|
|
18
16
|
from flask_babel import gettext as _
|
|
17
|
+
from geovisio.utils import items as utils_items
|
|
18
|
+
|
|
19
|
+
from geovisio.utils.cql2 import parse_cql2_filter
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
RGX_SORTBY = re.compile("[+-]?[A-Za-z_].*(,[+-]?[A-Za-z_].*)*")
|
|
@@ -43,11 +44,11 @@ def parse_datetime(value, error, fallback_as_UTC=False):
|
|
|
43
44
|
|
|
44
45
|
"""
|
|
45
46
|
# Hack to parse a date
|
|
46
|
-
# dateutils know how to parse lots of date, but fail to correctly parse date
|
|
47
|
+
# dateutils know how to parse lots of date, but fail to correctly parse date formatted by `datetime.isoformat()`
|
|
47
48
|
# (like all the dates returned by the API).
|
|
48
49
|
# datetime.isoformat is like: `2023-06-17T21:22:18.406856+02:00`
|
|
49
|
-
# dateutils silently fails the parse, and create an
|
|
50
|
-
# so we first try to parse it like an
|
|
50
|
+
# dateutils silently fails the parse, and create an incorrect date
|
|
51
|
+
# so we first try to parse it like an isoformatted date, and if this fails we try the flexible dateutils
|
|
51
52
|
d = None
|
|
52
53
|
try:
|
|
53
54
|
d = datetime.datetime.fromisoformat(value)
|
|
@@ -325,37 +326,27 @@ def parse_list(value: Optional[Any], tryFallbacks: bool = True, paramName: Optio
|
|
|
325
326
|
return None
|
|
326
327
|
|
|
327
328
|
|
|
328
|
-
def
|
|
329
|
+
def parse_collection_filter(value: Optional[str]) -> Optional[sql.SQL]:
|
|
329
330
|
"""Reads STAC filter parameter and sends SQL condition back.
|
|
330
331
|
|
|
331
|
-
>>>
|
|
332
|
+
>>> parse_collection_filter('')
|
|
332
333
|
|
|
333
|
-
>>>
|
|
334
|
+
>>> parse_collection_filter("updated >= '2023-12-31'")
|
|
334
335
|
SQL("(s.updated_at >= '2023-12-31')")
|
|
335
|
-
>>>
|
|
336
|
+
>>> parse_collection_filter("updated >= '2023-12-31' AND created < '2023-10-31'")
|
|
336
337
|
SQL("((s.updated_at >= '2023-12-31') AND (s.inserted_at < '2023-10-31'))")
|
|
337
|
-
>>>
|
|
338
|
+
>>> parse_collection_filter("status IN ('deleted','ready')") # when we ask for deleted, we should also have hidden collections
|
|
338
339
|
SQL("s.status IN ('deleted', 'ready', 'hidden')")
|
|
339
|
-
>>>
|
|
340
|
+
>>> parse_collection_filter("status = 'deleted' OR status = 'ready'")
|
|
340
341
|
SQL("(((s.status = 'deleted') OR (s.status = 'hidden')) OR (s.status = 'ready'))")
|
|
341
|
-
>>>
|
|
342
|
+
>>> parse_collection_filter('invalid = 10') # doctest: +IGNORE_EXCEPTION_DETAIL
|
|
342
343
|
Traceback (most recent call last):
|
|
343
344
|
geovisio.errors.InvalidAPIUsage: Unsupported filter parameter
|
|
344
|
-
>>>
|
|
345
|
+
>>> parse_collection_filter('updated == 10') # doctest: +IGNORE_EXCEPTION_DETAIL
|
|
345
346
|
Traceback (most recent call last):
|
|
346
347
|
geovisio.errors.InvalidAPIUsage: Unsupported filter parameter
|
|
347
348
|
"""
|
|
348
|
-
|
|
349
|
-
try:
|
|
350
|
-
filterAst = ecql_parser(value)
|
|
351
|
-
altered_ast = _alterFilterAst(filterAst) # type: ignore
|
|
352
|
-
|
|
353
|
-
f = to_sql_where(altered_ast, STAC_FIELD_TO_SQL_FILTER).replace('"', "")
|
|
354
|
-
return sql.SQL(f) # type: ignore
|
|
355
|
-
except:
|
|
356
|
-
raise errors.InvalidAPIUsage(_("Unsupported filter parameter"), status_code=400)
|
|
357
|
-
else:
|
|
358
|
-
return None
|
|
349
|
+
return parse_cql2_filter(value, STAC_FIELD_TO_SQL_FILTER, ast_updater=_alterFilterAst)
|
|
359
350
|
|
|
360
351
|
|
|
361
352
|
def parse_picture_heading(heading: Optional[str]) -> Optional[int]:
|
|
@@ -410,7 +401,25 @@ def _alterFilterAst(ast: ast.Node):
|
|
|
410
401
|
return filtered
|
|
411
402
|
|
|
412
403
|
|
|
413
|
-
def
|
|
404
|
+
def _parse_sorty_by(value: Optional[str], field_mapping_func, SortByCls):
|
|
405
|
+
if not value:
|
|
406
|
+
return None
|
|
407
|
+
# Check value pattern
|
|
408
|
+
if not RGX_SORTBY.match(value):
|
|
409
|
+
raise errors.InvalidAPIUsage(_("Unsupported sortby parameter: syntax isn't correct"), status_code=400)
|
|
410
|
+
values = value.split(",")
|
|
411
|
+
orders = []
|
|
412
|
+
for v in values:
|
|
413
|
+
direction = SQLDirection.DESC if v.startswith("-") else SQLDirection.ASC
|
|
414
|
+
raw_field = v.lstrip("+-")
|
|
415
|
+
f = field_mapping_func(raw_field, direction)
|
|
416
|
+
|
|
417
|
+
orders.append(f)
|
|
418
|
+
|
|
419
|
+
return SortByCls(fields=orders)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def parse_collection_sortby(value: Optional[str]) -> Optional[SortBy]:
|
|
414
423
|
"""Reads STAC/OGC sortby parameter, and sends a SQL ORDER BY string.
|
|
415
424
|
|
|
416
425
|
Parameters
|
|
@@ -426,46 +435,40 @@ def parse_sortby(value: Optional[str]) -> Optional[SortBy]:
|
|
|
426
435
|
|
|
427
436
|
None if no sort by is found
|
|
428
437
|
|
|
429
|
-
>>>
|
|
430
|
-
>>>
|
|
431
|
-
>>>
|
|
438
|
+
>>> parse_collection_sortby(None)
|
|
439
|
+
>>> parse_collection_sortby("")
|
|
440
|
+
>>> parse_collection_sortby('updated')
|
|
432
441
|
SortBy(fields=[SortByField(field=FieldMapping(sql_column=SQL('updated_at'), stac='updated'), direction=<SQLDirection.ASC: SQL('ASC')>)])
|
|
433
|
-
>>>
|
|
442
|
+
>>> parse_collection_sortby('+created')
|
|
434
443
|
SortBy(fields=[SortByField(field=FieldMapping(sql_column=SQL('inserted_at'), stac='created'), direction=<SQLDirection.ASC: SQL('ASC')>)])
|
|
435
|
-
>>>
|
|
444
|
+
>>> parse_collection_sortby('-created')
|
|
436
445
|
SortBy(fields=[SortByField(field=FieldMapping(sql_column=SQL('inserted_at'), stac='created'), direction=<SQLDirection.DESC: SQL('DESC')>)])
|
|
437
|
-
>>>
|
|
446
|
+
>>> parse_collection_sortby('+updated,-created')
|
|
438
447
|
SortBy(fields=[SortByField(field=FieldMapping(sql_column=SQL('updated_at'), stac='updated'), direction=<SQLDirection.ASC: SQL('ASC')>), SortByField(field=FieldMapping(sql_column=SQL('inserted_at'), stac='created'), direction=<SQLDirection.DESC: SQL('DESC')>)])
|
|
439
|
-
>>>
|
|
448
|
+
>>> parse_collection_sortby('invalid') # doctest: +IGNORE_EXCEPTION_DETAIL
|
|
440
449
|
Traceback (most recent call last):
|
|
441
450
|
geovisio.errors.InvalidAPIUsage: Unsupported sortby parameter
|
|
442
|
-
>>>
|
|
451
|
+
>>> parse_collection_sortby('~nb') # doctest: +IGNORE_EXCEPTION_DETAIL
|
|
443
452
|
Traceback (most recent call last):
|
|
444
453
|
geovisio.errors.InvalidAPIUsage: Unsupported sortby parameter
|
|
445
454
|
"""
|
|
446
455
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
orders = []
|
|
452
|
-
for v in values:
|
|
453
|
-
direction = SQLDirection.DESC if v.startswith("-") else SQLDirection.ASC
|
|
454
|
-
vOnly = v.replace("+", "").replace("-", "")
|
|
456
|
+
def mapping(raw_field: str, direction: SQLDirection):
|
|
457
|
+
if raw_field not in STAC_FIELD_MAPPINGS:
|
|
458
|
+
raise errors.InvalidAPIUsage(_("Unsupported sortby parameter: invalid column name"), status_code=400)
|
|
459
|
+
return SortByField(field=STAC_FIELD_MAPPINGS[raw_field], direction=direction)
|
|
455
460
|
|
|
456
|
-
|
|
457
|
-
if vOnly not in STAC_FIELD_MAPPINGS:
|
|
458
|
-
raise errors.InvalidAPIUsage(_("Unsupported sortby parameter: invalid column name"), status_code=400)
|
|
459
|
-
field_mapping = STAC_FIELD_MAPPINGS[vOnly]
|
|
461
|
+
return _parse_sorty_by(value, mapping, SortByCls=SortBy)
|
|
460
462
|
|
|
461
|
-
orders.append(SortByField(field=field_mapping, direction=direction))
|
|
462
463
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
return
|
|
464
|
+
def parse_item_sortby(value: Optional[str]) -> Optional[utils_items.SortBy]:
|
|
465
|
+
def mapping(raw_field: str, direction: SQLDirection):
|
|
466
|
+
if raw_field == "distance_to" or raw_field not in utils_items.SortableItemField.__dict__:
|
|
467
|
+
# distance to is for the moment only an implicit sort when search a point or in a bbox
|
|
468
|
+
raise errors.InvalidAPIUsage(_("Unsupported sortby parameter: invalid field"), status_code=400)
|
|
469
|
+
return utils_items.ItemSortByField(field=utils_items.SortableItemField[raw_field], direction=direction)
|
|
470
|
+
|
|
471
|
+
return _parse_sorty_by(value, mapping, SortByCls=utils_items.SortBy)
|
|
469
472
|
|
|
470
473
|
|
|
471
474
|
def parse_collections_limit(limit: Optional[str]) -> int:
|
geovisio/web/pictures.py
CHANGED
|
@@ -208,3 +208,37 @@ def getPictureTile(pictureId, col, row, format):
|
|
|
208
208
|
raise errors.InvalidAPIUsage(_("Unable to read picture on filesystem"), status_code=500)
|
|
209
209
|
|
|
210
210
|
return utils.pictures.sendInFormat(picture, "jpeg", format)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@bp.route("/<uuid:pictureId>")
|
|
214
|
+
def getPictureById(pictureId):
|
|
215
|
+
"""Get picture's STAC definition.
|
|
216
|
+
|
|
217
|
+
It's the non-stac alias to the `/api/collections/<collectionId>/items/<itemId>` endpoint (but you don't need to know the collection ID here).
|
|
218
|
+
---
|
|
219
|
+
tags:
|
|
220
|
+
- Pictures
|
|
221
|
+
parameters:
|
|
222
|
+
- name: pictureId
|
|
223
|
+
in: path
|
|
224
|
+
description: ID of the picture (called item in STAC) to retrieve
|
|
225
|
+
required: true
|
|
226
|
+
schema:
|
|
227
|
+
type: string
|
|
228
|
+
responses:
|
|
229
|
+
102:
|
|
230
|
+
description: the picture (which is still under process)
|
|
231
|
+
content:
|
|
232
|
+
application/geo+json:
|
|
233
|
+
schema:
|
|
234
|
+
$ref: '#/components/schemas/GeoVisioItem'
|
|
235
|
+
200:
|
|
236
|
+
description: the wanted picture
|
|
237
|
+
content:
|
|
238
|
+
application/geo+json:
|
|
239
|
+
schema:
|
|
240
|
+
$ref: '#/components/schemas/GeoVisioItem'
|
|
241
|
+
"""
|
|
242
|
+
from geovisio.web.items import getCollectionItem
|
|
243
|
+
|
|
244
|
+
return getCollectionItem(collectionId=None, itemId=pictureId)
|
geovisio/web/stac.py
CHANGED
|
@@ -20,10 +20,11 @@ from geovisio.utils.sequences import (
|
|
|
20
20
|
get_collections,
|
|
21
21
|
CollectionsRequest,
|
|
22
22
|
STAC_FIELD_MAPPINGS,
|
|
23
|
+
get_dataset_bounds,
|
|
23
24
|
get_pagination_links,
|
|
24
25
|
)
|
|
25
26
|
from geovisio.web.params import (
|
|
26
|
-
|
|
27
|
+
parse_collection_filter,
|
|
27
28
|
parse_collections_limit,
|
|
28
29
|
)
|
|
29
30
|
|
|
@@ -334,12 +335,17 @@ def getUserCatalog(userId, userIdMatchesAccount=False):
|
|
|
334
335
|
"""
|
|
335
336
|
|
|
336
337
|
collection_request = CollectionsRequest(
|
|
337
|
-
sort_by=SortBy(
|
|
338
|
+
sort_by=SortBy(
|
|
339
|
+
fields=[
|
|
340
|
+
SortByField(field=STAC_FIELD_MAPPINGS["created"], direction=SQLDirection.ASC),
|
|
341
|
+
SortByField(field=STAC_FIELD_MAPPINGS["id"], direction=SQLDirection.ASC),
|
|
342
|
+
]
|
|
343
|
+
),
|
|
338
344
|
user_id=userId,
|
|
339
345
|
userOwnsAllCollections=userIdMatchesAccount,
|
|
340
346
|
)
|
|
341
347
|
collection_request.limit = parse_collections_limit(request.args.get("limit"))
|
|
342
|
-
collection_request.pagination_filter =
|
|
348
|
+
collection_request.pagination_filter = parse_collection_filter(request.args.get("page"))
|
|
343
349
|
|
|
344
350
|
userName = None
|
|
345
351
|
meta_collection = None
|
|
@@ -350,12 +356,14 @@ def getUserCatalog(userId, userIdMatchesAccount=False):
|
|
|
350
356
|
raise errors.InvalidAPIUsage(_("Impossible to find user %(u)s", u=userId))
|
|
351
357
|
userName = userName["name"]
|
|
352
358
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
359
|
+
datasetBounds = get_dataset_bounds(
|
|
360
|
+
cursor.connection,
|
|
361
|
+
collection_request.sort_by,
|
|
362
|
+
additional_filters=SQL("s.account_id = %(account)s"),
|
|
363
|
+
additional_filters_params={"account": userId},
|
|
364
|
+
)
|
|
357
365
|
|
|
358
|
-
if
|
|
366
|
+
if datasetBounds is None:
|
|
359
367
|
# No data found, trying to give the most meaningfull error message
|
|
360
368
|
raise errors.InvalidAPIUsage(_("No data loaded for user %(u)s", u=userId), 404)
|
|
361
369
|
|
|
@@ -388,10 +396,9 @@ def getUserCatalog(userId, userIdMatchesAccount=False):
|
|
|
388
396
|
pagination_links = get_pagination_links(
|
|
389
397
|
route="stac.getUserCatalog",
|
|
390
398
|
routeArgs={"userId": str(userId), "limit": collection_request.limit},
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
dataBounds=db_collections.query_first_order_bounds,
|
|
399
|
+
sortBy=collection_request.sort_by,
|
|
400
|
+
datasetBounds=datasetBounds,
|
|
401
|
+
dataBounds=db_collections.query_bounds,
|
|
395
402
|
additional_filters=None,
|
|
396
403
|
)
|
|
397
404
|
|
geovisio/web/tokens.py
CHANGED
|
@@ -22,7 +22,7 @@ def list_tokens(account):
|
|
|
22
22
|
|
|
23
23
|
The list of tokens will not contain their JWT counterpart (the JWT is the real token used in authentication).
|
|
24
24
|
|
|
25
|
-
The JWT counterpart can be
|
|
25
|
+
The JWT counterpart can be retrieved by providing the token's id to the endpoint [/users/me/tokens/{token_id}](#/Auth/get_api_users_me_tokens__token_id_).
|
|
26
26
|
---
|
|
27
27
|
tags:
|
|
28
28
|
- Auth
|
|
@@ -254,6 +254,54 @@ def claim_non_associated_token(token_id, account):
|
|
|
254
254
|
return "You are now logged in the CLI, you can upload your pictures", 200
|
|
255
255
|
|
|
256
256
|
|
|
257
|
+
@bp.route("/users/me/tokens", methods=["POST"])
|
|
258
|
+
@auth.login_required_with_redirect()
|
|
259
|
+
def generate_associated_token(account: auth.Account):
|
|
260
|
+
"""
|
|
261
|
+
Generate a new token associated to the current user
|
|
262
|
+
|
|
263
|
+
The response contains the JWT token and is directly usable (unlike tokens created by `/auth/tokens/generate` that are not associated to a user by default). This token does not need to be claimed.
|
|
264
|
+
---
|
|
265
|
+
tags:
|
|
266
|
+
- Auth
|
|
267
|
+
requestBody:
|
|
268
|
+
content:
|
|
269
|
+
application/json:
|
|
270
|
+
schema:
|
|
271
|
+
$ref: '#/components/schemas/GeovisioPostToken'
|
|
272
|
+
responses:
|
|
273
|
+
200:
|
|
274
|
+
description: The newly generated token
|
|
275
|
+
content:
|
|
276
|
+
application/json:
|
|
277
|
+
schema:
|
|
278
|
+
$ref: '#/components/schemas/GeoVisioEncodedToken'
|
|
279
|
+
"""
|
|
280
|
+
if request.is_json:
|
|
281
|
+
description = request.json.get("description", "")
|
|
282
|
+
else:
|
|
283
|
+
description = None
|
|
284
|
+
|
|
285
|
+
token = db.fetchone(
|
|
286
|
+
current_app,
|
|
287
|
+
"INSERT INTO tokens (description, account_id) VALUES (%(description)s, %(account_id)s) RETURNING *",
|
|
288
|
+
{"account_id": account.id, "description": description},
|
|
289
|
+
row_factory=dict_row,
|
|
290
|
+
)
|
|
291
|
+
if not token:
|
|
292
|
+
raise errors.InternalError(_("Impossible to generate a new token"))
|
|
293
|
+
|
|
294
|
+
jwt_token = _generate_jwt_token(token["id"])
|
|
295
|
+
return flask.jsonify(
|
|
296
|
+
{
|
|
297
|
+
"jwt_token": jwt_token,
|
|
298
|
+
"id": token["id"],
|
|
299
|
+
"description": token["description"],
|
|
300
|
+
"generated_at": token["generated_at"].astimezone(tz.gettz("UTC")).isoformat(),
|
|
301
|
+
}
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
257
305
|
def _generate_jwt_token(token_id: uuid.UUID) -> str:
|
|
258
306
|
"""
|
|
259
307
|
Generate a JWT token from a token's id.
|