mirror of
https://github.com/Cernobor/oko-server.git
synced 2025-02-24 08:27:17 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e713ae8da | |||
| 8440e3b7d7 | |||
| 63e79c657c | |||
| f168a5966b | |||
| d25e58905f | |||
| 90f8fef40a | |||
| f9850c3264 | |||
| 7c8a430f49 | |||
| c9377b04fc | |||
| c1cdd4f904 | |||
| a5d98c2eb7 | |||
| 7b1c3cfc28 | |||
| 19fc8e0322 | |||
| 2379a33594 | |||
| e6fef137e7 | |||
| 4234c29ae2 | |||
| 17256ad351 | |||
| 7506421848 | |||
| 1cf44e3bfc | |||
| 7deb7e3f39 | |||
| f8b42f35f6 |
+2
-1
@@ -38,4 +38,5 @@ go.work
|
||||
oko-server
|
||||
*.sqlite*
|
||||
.vscode
|
||||
.git
|
||||
data
|
||||
local-testing
|
||||
+2
-1
@@ -37,4 +37,5 @@ go.work
|
||||
# Project-specific
|
||||
oko-server
|
||||
*.sqlite*
|
||||
.vscode
|
||||
.vscode
|
||||
__debug_bin
|
||||
+7
-7
@@ -1,19 +1,19 @@
|
||||
FROM alpine:3.15.0 AS build
|
||||
FROM alpine:3.16 AS build
|
||||
|
||||
ARG CAPROVER_GIT_COMMIT_SHA=""
|
||||
VOLUME ["/data"]
|
||||
COPY . /oko-server/git
|
||||
|
||||
RUN apk add --no-cache go && \
|
||||
RUN apk update && \
|
||||
apk add go && \
|
||||
cd /oko-server/git && \
|
||||
go build
|
||||
go build -ldflags "-X \"main.sha1ver=${CAPROVER_GIT_COMMIT_SHA:-$(cat .git/$(cat .git/HEAD | sed 's|ref: ||g'))}\" -X \"main.buildTime=$(date -Iseconds)\""
|
||||
|
||||
FROM alpine:3.15.0
|
||||
FROM alpine:3.16
|
||||
WORKDIR /oko-server
|
||||
VOLUME [ "/data" ]
|
||||
|
||||
RUN echo -e '#!/bin/sh\n/oko-server/oko-server "$@"' > /oko-server/entrypoint.sh && \
|
||||
chmod +x /oko-server/entrypoint.sh
|
||||
COPY --from=build /oko-server/git/oko-server/ /oko-server/
|
||||
|
||||
ENTRYPOINT ["/oko-server/entrypoint.sh"]
|
||||
ENTRYPOINT ["/oko-server/oko-server"]
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<p align="center">
|
||||
<a href="" rel="noopener">
|
||||
<img src="https://raw.githubusercontent.com/Cernobor/oko-app/master/assets/splash.png" alt="Project logo" style="background: #153d24;"></a>
|
||||
</p>
|
||||
|
||||
<h3 align="center">Organizátorské Kartografické OKO - server</h3>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[]()
|
||||
[](https://github.com/Cernobor/oko-server/issues)
|
||||
[](https://github.com/Cernobor/oko-server/pulls)
|
||||
<!-- [](/LICENSE) -->
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 📝 Table of Contents
|
||||
|
||||
- [About](#about)
|
||||
- [Building](#building)
|
||||
- [Usage](#usage)
|
||||
- [Built Using](#built_using)
|
||||
- [Authors](#authors)
|
||||
- [Acknowledgments](#acknowledgement)
|
||||
|
||||
## 🧐 About <a name = "about"></a>
|
||||
|
||||
Central server for the [OKO](https://github.com/Cernobor/oko-app) app.
|
||||
Manges users, provides map tiles and the whole map tile pack, receives geo features submitted by users and serves them back.
|
||||
|
||||
## 🏗 Building <a name = "building"></a>
|
||||
|
||||
The only requirement to build the server is [go](https://go.dev).
|
||||
|
||||
In the root of the project, run
|
||||
|
||||
```
|
||||
go build
|
||||
```
|
||||
|
||||
which will produce the executable ``oko-server`` which you can just run (see [Usage](#usage)).
|
||||
|
||||
## 🎈 Usage <a name="usage"></a>
|
||||
|
||||
### Using docker
|
||||
|
||||
Dockerfile is included, therefore to build the docker image, all that is needed to do is to run
|
||||
|
||||
```
|
||||
docker build -t name:tag /path/to/the/root/of/the/repository
|
||||
```
|
||||
or, if you have no local changes, you can build the image even without cloning the repository at all
|
||||
```
|
||||
docker build -t name:tag https://github.com/Cernobor/oko-server.git
|
||||
```
|
||||
|
||||
The image can then be run using the ``name:tag`` that you have given in the previous step.
|
||||
Volume ``/data`` is available to share data between the host and the container.
|
||||
|
||||
```
|
||||
docker run -v /path/to/data:/data name:tag [options...]
|
||||
```
|
||||
|
||||
### Without docker
|
||||
|
||||
Just run the previously built (see [Building](#building)) executable
|
||||
```
|
||||
/path/to/oko-server [options...]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
The server is configurable via a set of options, some of which are required.
|
||||
The table of options follows:
|
||||
|
||||
| option | description |
|
||||
| ------ | ----------- |
|
||||
| ``-tilepack <path>`` | **Required.** File that will be sent to clients when they request a tile pack, also used to serve tiles in online mode. |
|
||||
| ``-port <port>`` | Port where the server will listen. Default is ``8080``. |
|
||||
| ``-dbfile <path>`` | Path to the sqlite3 database file used for data storage. Will be created, if it does not exist upons startup. Default is ``./data.sqlite3``. |
|
||||
| ``-min-zoom <int>`` | Minimum supported zoom. Clients will receive this value during handshake. Default is ``1``. |
|
||||
| ``-default-center-lat <float>`` | Latitude of the default map center, formatted as a floating point number of degrees. Clients will receive this value during handhake. Default is ``0``. |
|
||||
| ``-default-center-lng <float>`` | Longitude of the default map center, formatted as a floating point number of degrees. Clients will receive this value during handhake. Default is ``0``. |
|
||||
| ``-max-photo-width <int>`` | Maximum width of stored pohotos. Uploaded photos exceeding this width will be rescaled to fit (keeping aspect ratio). If ``0``, no scaling will be done. Default is ``0``. |
|
||||
| ``-max-photo-height <int>`` | Maximum height of stored pohotos. Uploaded photos exceeding this height will be rescaled to fit (keeping aspect ratio). If ``0``, no scaling will be done. Default is ``0``. |
|
||||
| ``-photo-quality <int>`` | JPEG photo quality. Photos are reencoded to JPEG with this quality setting when uploaded. Default is ``90``. |
|
||||
| ``-debug`` | If specified, logging level will be set do debug instead of info. w
|
||||
|
||||
## ⛏️ Built Using <a name = "built_using"></a>
|
||||
|
||||
- [Go](https://go.dev/) - Programming language
|
||||
- [Gin](https://gin-gonic.com/) - HTTP handler
|
||||
- [SQLite](https://www.sqlite.org/) - Database
|
||||
- [mbtileserver](https://github.com/consbio/mbtileserver) - MBTiles tile server
|
||||
|
||||
## ✍️ Authors <a name = "authors"></a>
|
||||
|
||||
- [@zegkljan](https://github.com/zegkljan) - Idea & Initial work
|
||||
- [@0x416E64](https://github.com/0x416E64) - Docker and deployment tweaks
|
||||
|
||||
See also the list of [contributors](https://github.com/Cernobor/oko-server/contributors) who participated in this project.
|
||||
|
||||
## 🎉 Acknowledgements <a name = "acknowledgement"></a>
|
||||
|
||||
- Hat tip to anyone whose code was used
|
||||
+878
@@ -0,0 +1,878 @@
|
||||
{
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "OKO server API",
|
||||
"version": "1.0"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://localhost:8080/",
|
||||
"description": "Local testing."
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
"securitySchemes": {},
|
||||
"schemas": {
|
||||
"Error": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"message"
|
||||
]
|
||||
},
|
||||
"AppVersionInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {
|
||||
"type": "string",
|
||||
"description": "URL from which the app of this version can be downloaded."
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "Version of the app in the format 'major.minor.patch[-prerelease][+metadata]' (without quotes)."
|
||||
}
|
||||
},
|
||||
"required": ["address", "version"],
|
||||
"description": "Info about an available app."
|
||||
},
|
||||
"HandshakeChallenge": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the user."
|
||||
},
|
||||
"exists": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates to the server whether this user is supposed to already exist and therefore should only bind to this existing user."
|
||||
}
|
||||
},
|
||||
"required": ["name"],
|
||||
"description": "Data for handshake initiation."
|
||||
},
|
||||
"HandshakeResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"description": "User ID assigned by the server."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the user as requested by the client."
|
||||
},
|
||||
"map_info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"map_pack_path": {
|
||||
"type": "string",
|
||||
"description": "URL path to the map pack."
|
||||
},
|
||||
"map_pack_size": {
|
||||
"type": "integer",
|
||||
"description": "Size of the map pack in bytes."
|
||||
},
|
||||
"tile_path_template": {
|
||||
"type": "string",
|
||||
"description": "Template of the URL path to the map tiles."
|
||||
},
|
||||
"min_zoom": {
|
||||
"type": "integer",
|
||||
"description": "Minimum zoom supported by the server/map pack."
|
||||
},
|
||||
"default_center": {
|
||||
"$ref": "#/components/schemas/Coords"
|
||||
}
|
||||
},
|
||||
"required": ["map_pack_path", "map_pack_size", "tile_path_template", "min_zoom", "default_center"],
|
||||
"description": "Info about map settings."
|
||||
}
|
||||
},
|
||||
"required": ["id", "name", "map_info"],
|
||||
"description": "Response to successful handshake. Tells the client how to set itself up."
|
||||
},
|
||||
"Coords": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lat": {
|
||||
"type": "number",
|
||||
"description": "Latitude (north-south coordinate). Positive number indicates north, negative south."
|
||||
},
|
||||
"lng": {
|
||||
"type": "number",
|
||||
"description": "Longitude (east-west coordinate). Positive number indicates east, negative west."
|
||||
}
|
||||
},
|
||||
"required": ["lat", "lng"],
|
||||
"description": "WGS84 coordinates - latitude and longitude."
|
||||
},
|
||||
"User": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"description": "User ID."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "User name."
|
||||
}
|
||||
},
|
||||
"required": ["id", "name"],
|
||||
"description": "A user in the system."
|
||||
},
|
||||
"UserInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"description": "User ID."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "User name."
|
||||
},
|
||||
"app_version": {
|
||||
"type": "string",
|
||||
"description": "Version of the app last used by the user."
|
||||
},
|
||||
"last_seen_time": {
|
||||
"anyOf": [{"$ref": "#/components/schemas/LocalDateTime"}],
|
||||
"description": "Time of last contact (any) with the server."
|
||||
},
|
||||
"last_upload_time": {
|
||||
"anyOf": [{"$ref": "#/components/schemas/LocalDateTime"}],
|
||||
"description": "Time of last data upload."
|
||||
},
|
||||
"last_download_time": {
|
||||
"anyOf": [{"$ref": "#/components/schemas/LocalDateTime"}],
|
||||
"description": "Time of last data download."
|
||||
}
|
||||
},
|
||||
"required": ["id", "name", "app_version", "last_seen_time", "last_upload_time", "last_download_time"],
|
||||
"description": "Extended info about a user."
|
||||
},
|
||||
"Feature": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"description": "ID of the feature."
|
||||
},
|
||||
"owner_id": {
|
||||
"type": "integer",
|
||||
"description": "ID of the user owning this feature."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the feature."
|
||||
},
|
||||
"deadline": {
|
||||
"allOf": [{"$ref": "#/components/schemas/LocalDateTime"}],
|
||||
"description": "Time at which the feature should be deleted."
|
||||
},
|
||||
"properties": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"description": "Any extra data associated with the feature."
|
||||
},
|
||||
"geometry": {
|
||||
"allOf": [{"$ref": "https://geojson.org/schema/Geometry.json"}],
|
||||
"description": "Geometry describing this feature."
|
||||
},
|
||||
"photo_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": "IDs of photos associated with this feature."
|
||||
}
|
||||
},
|
||||
"required": ["id", "owner_id", "name", "geometry"]
|
||||
},
|
||||
"PhotoMetadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content_type": {
|
||||
"type": "string",
|
||||
"description": "MIME type of the file containing the photo."
|
||||
},
|
||||
"thumbnail_content_type": {
|
||||
"type": "string",
|
||||
"description": "MIME type of the file containing the thumbnail of the photo."
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"description": "Size of the file containing the photo in bytes."
|
||||
},
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"description": "ID of the photo."
|
||||
},
|
||||
"thumbnail_filename": {
|
||||
"type": "string",
|
||||
"description": "Name of the file containing the thumbnail of the photo."
|
||||
}
|
||||
},
|
||||
"required": ["content_type", "thumbnail_content_type", "size", "id", "thumbnail_filename"],
|
||||
"description": "Metadata about a photo."
|
||||
},
|
||||
"Proposal": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner_id": {
|
||||
"type": "integer",
|
||||
"description": "ID of the owner (author) of the proposal."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Description of what should the system do better/different/what it does not do now."
|
||||
},
|
||||
"how": {
|
||||
"type": "string",
|
||||
"description": "How should the system behave to meet this demand."
|
||||
}
|
||||
},
|
||||
"required": ["owner_id", "description", "how"],
|
||||
"description": "A proposal for improvement of the system."
|
||||
},
|
||||
"Data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
},
|
||||
"description": "Users present in the system."
|
||||
},
|
||||
"features": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Feature"
|
||||
},
|
||||
"description": "Features present in the system."
|
||||
},
|
||||
"photo_metadata": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/components/schemas/PhotoMetadata"
|
||||
},
|
||||
"description": "Mapping from photo ID to metadata describing that photo."
|
||||
}
|
||||
},
|
||||
"required": ["users", "features"],
|
||||
"description": "Data about users and features."
|
||||
},
|
||||
"Update": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"create": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Feature"
|
||||
},
|
||||
"description": "Newly created features. The IDs of these features are considered 'local' to this set of features and must be unique among them."
|
||||
},
|
||||
"created_photos": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Mapping from ID of a feature defined in 'created' to a multipart/form-data filed name of a photo associated with that feature."
|
||||
},
|
||||
"add_photos": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Mapping from ID of an already existing feature to a multipart/form-data filed name of a photo that is to be added to that feature."
|
||||
},
|
||||
"update": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Feature"
|
||||
},
|
||||
"description": "Features to be updated. They are matched by their ID."
|
||||
},
|
||||
"delete": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": "IDs of features that are to be deleted."
|
||||
},
|
||||
"delete_photos": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": "IDs of photos that are to be deleted."
|
||||
},
|
||||
"proposals": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Proposal"
|
||||
},
|
||||
"description": "New proposals for improvement of the system."
|
||||
}
|
||||
},
|
||||
"description": "Data update to server."
|
||||
},
|
||||
"LocalDateTime": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"example": "2020-05-13T17:26:57.719+02:00"
|
||||
},
|
||||
"BuildInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version_hash": {
|
||||
"type": "string",
|
||||
"description": "Commit ID (hash)."
|
||||
},
|
||||
"build_time": {
|
||||
"anyOf": [{"$ref": "#/components/schemas/LocalDateTime"}],
|
||||
"description": "Build time."
|
||||
}
|
||||
},
|
||||
"required": ["version_hash", "build_time"],
|
||||
"description": "Server build info."
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"UserID": {
|
||||
"in": "header",
|
||||
"name": "X-User-ID",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [],
|
||||
"paths": {
|
||||
"/ping" : {
|
||||
"parameters": [{"$ref": "#/components/parameters/UserID"}],
|
||||
"get": {
|
||||
"operationId": "ping",
|
||||
"tags": ["app"],
|
||||
"summary": "Checks the server liveness and provides info about (newer) app versions.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Server is alive, and there is a newer version of the app available. The response contains the app info.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AppVersionInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"204": {
|
||||
"description": "Server is alive, and there is no newer version of the app."
|
||||
},
|
||||
"400": {
|
||||
"description": "App version info in User-Agent header is malformed."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/handshake": {
|
||||
"parameters": [{"$ref": "#/components/parameters/UserID"}],
|
||||
"post": {
|
||||
"operationId": "handshake",
|
||||
"tags": ["app"],
|
||||
"summary": "Performs pairing of the client with the server.",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"description": "Handshake challenge.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HandshakeChallenge"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Handshake successful, response body contains the handshake response.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HandshakeResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Malformed handshake challenge."
|
||||
},
|
||||
"403": {
|
||||
"description": "Attempted to associate ('exists' was set to true) with a reserved (system) user."
|
||||
},
|
||||
"404": {
|
||||
"description": "Attempted to associate ('exists' was set to true) with a non-existing user."
|
||||
},
|
||||
"409": {
|
||||
"description": "Attempted to create ('exists' was not set or set to false) an already existing user."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/data": {
|
||||
"parameters": [{"$ref": "#/components/parameters/UserID"}],
|
||||
"get": {
|
||||
"operationId": "getData",
|
||||
"tags": ["app"],
|
||||
"summary": "Retrieves the data about users and features from the server.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The data, either by themselves (application/json), or with the photos associated with the features (application/zip). Determined by the Accept header.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Data"
|
||||
}
|
||||
},
|
||||
"application/zip": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"description": "A zip-file containing all the data. In the zip file, there is a file data.json, which contains the feature data in the same format as for the applicaion/json response. The photos are stored in sibling files with name patterns img{id} and thumb_img{id} for the photo and its thumbnail respectively, where {id} is the ID of the photo corresponding to the photo IDs described in the feature data."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"406": {
|
||||
"description": "Unsupported Accept header value."
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"operationId": "postData",
|
||||
"tags": ["app"],
|
||||
"summary": "Uploads new data to the server.",
|
||||
"description": "The update process goes through these steps:\n\n 1. all created features (if any) are added to the system\n 2. new photos (if any) for both the new and existing features are saved\n 3. the features that should be updated (if any) are updated \n 4. the features to be deleted (if any) are deleted\n 5. the photos to be deleted (if any) are deleted\n 6. the proposals (if any) are saved\n\nThe whole upload process is atomic, i.e. it either succeedes or fails, and does not interfere with other users doing the same (this, or their requests may fail e.g. due to a deleted feature that is referenced in a request that is serialized after).",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"description": "Data to be uploaded.\n\nIf Content-Type is application/json, the data is expected directly in the request body.\n\nIf Content-Type is multipart/form-data, a zip-file is expcected containing both the data, and the other binary files (photos) associated with the data.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Update"
|
||||
}
|
||||
},
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Update data stored in the string, interpreted in the same way as the request body would be, if the Content-Type was application/json."
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"description": "A file containing the update data which will be interpreted in the same way as the request body would be, if the Content-Type was application/json."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"type": "string",
|
||||
"format": "binary",
|
||||
"description": "A photo that is to be added to a feature (new or existing). Is referenced from the 'data' via 'created_photos' and 'add_photos'."
|
||||
},
|
||||
"required": ["data"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Update has been performed successfully."
|
||||
},
|
||||
"400": {
|
||||
"description": "There has been some problem with the request. Possible reasons are:\n\n * unsupported Content-Type of the request (i.e. other than application/json or multipart/form-data),\n * malformed JSON data, either directly in request body (when Content-Type is application/json), or in the 'data' field (when Content-Type is multipart/form-data),\n * malformed multipart/form-data request body,\n * IDs of created features are not unique,\n * 'created_photos' and/or 'add_photos' have been specified but the Content-Type is application/json,\n * the 'data' field is missing in the request body (when Content-Type is multipart/form-data),\n * the 'data' field contains more than 1 item (when Content-Type is multipart/form-data),\n * other fields (that are supposed to contain 1 photo each) contain more than 1 item (when Content-Type is multipart/form-data),\n * unsupported Content-Type of a photo (when request Content-Type is multipart/form-data).\n\nThe response body contains the error description in plain text.",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": {
|
||||
"malformed request body (application/json)": {
|
||||
"value": "malfomred data: <description of error>"
|
||||
},
|
||||
"malformed request body (multipart/form-data)": {
|
||||
"value": "malfomred multipart/form-data content"
|
||||
},
|
||||
"photos specified (application/json)": {
|
||||
"value": "created_photos and/or add_photos present, but Content-Type is application/json"
|
||||
},
|
||||
"no 'data' field (multipart/form-data)": {
|
||||
"value": "value 'data' is missing from the content"
|
||||
},
|
||||
"multiple items for 'data' field (multipart/form-data)": {
|
||||
"value": "value 'data' does not contain exactly 1 item"
|
||||
},
|
||||
"malformed 'data' (multipart/form-data)": {
|
||||
"value": "malformed 'data' value: <description of error>"
|
||||
},
|
||||
"non-unique created feature IDs (both)": {
|
||||
"value": "created features do not have unique IDs"
|
||||
},
|
||||
"multiple items for another field than 'data' (multipart/form-data)": {
|
||||
"value": "file item <name of field> does not contain exactly 1 file"
|
||||
},
|
||||
"unsupported photo Content-Type (multipart/form-data)": {
|
||||
"value": "photo <name of field> has an unsupported Content-Type"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/data/people": {
|
||||
"parameters": [{"$ref": "#/components/parameters/UserID"}],
|
||||
"get": {
|
||||
"operationId": "getPeople",
|
||||
"tags": ["app"],
|
||||
"summary": "Lists the users present in the system.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of users present in the system.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
},
|
||||
"description": "List of users."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/data/features": {
|
||||
"parameters": [{"$ref": "#/components/parameters/UserID"}],
|
||||
"get": {
|
||||
"operationId": "getFeatures",
|
||||
"tags": ["app"],
|
||||
"summary": "Lists the features present in the system.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of features present in the system.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Feature"
|
||||
},
|
||||
"description": "List of features."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/data/features/{featureID}/photos/{photoID}": {
|
||||
"parameters": [
|
||||
{"$ref": "#/components/parameters/UserID"},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "featureID",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
},
|
||||
"required": true,
|
||||
"description": "ID of a feature."
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "photoID",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
},
|
||||
"required": true,
|
||||
"description": "ID of a photo"
|
||||
}
|
||||
],
|
||||
"get": {
|
||||
"operationId": "getPhoto",
|
||||
"tags": ["app"],
|
||||
"summary": "Retrieves the specified photo of the specified feature.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The requested photo. The Content-Type is the same as of the photo file stored on the server."
|
||||
},
|
||||
"400": {
|
||||
"description": "Either the feature ID or the photo ID path parameter was malformed.",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"examples": {
|
||||
"malformed feature ID": {
|
||||
"value": "malformed feature ID"
|
||||
},
|
||||
"malfomred photo ID": {
|
||||
"value": "malfomred photo ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Photo does not exist (for the given feature)."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/data/proposals": {
|
||||
"parameters": [{"$ref": "#/components/parameters/UserID"}],
|
||||
"get": {
|
||||
"operationId": "getProposals",
|
||||
"tags": ["app"],
|
||||
"summary": "Lists the proposals submitted to the system.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of proposals submitted to the system.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Proposal"
|
||||
},
|
||||
"description": "List of proposals."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/mappack": {
|
||||
"parameters": [{"$ref": "#/components/parameters/UserID"}],
|
||||
"get": {
|
||||
"operationId": "getTilePack",
|
||||
"tags": ["app"],
|
||||
"summary": "Returns a file containing a pack of map tiles.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Tile pack file."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/build-info": {
|
||||
"parameters": [{"$ref": "#/components/parameters/UserID"}],
|
||||
"get": {
|
||||
"operationId": "getBuildInfo",
|
||||
"tags": ["debug", "utils"],
|
||||
"summary": "Returns the info about the server build.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Server build info.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/BuildInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/hard-fail": {
|
||||
"parameters": [{"$ref": "#/components/parameters/UserID"}],
|
||||
"get": {
|
||||
"operationId": "hardFail",
|
||||
"tags": ["debug"],
|
||||
"summary": "Debug endpoint for safe simulation of server-side error.",
|
||||
"responses": {
|
||||
"501": {
|
||||
"description": "An error."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/soft-fail": {
|
||||
"parameters": [{"$ref": "#/components/parameters/UserID"}],
|
||||
"get": {
|
||||
"operationId": "softFail",
|
||||
"tags": ["debug"],
|
||||
"summary": "Debug endpoint for safe simulation of an error reported through OK and json response body.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "An error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/app-versions": {
|
||||
"parameters": [{"$ref": "#/components/parameters/UserID"}],
|
||||
"get": {
|
||||
"operationId": "getAppVersions",
|
||||
"tags": ["utils", "debug"],
|
||||
"summary": "Retrieves the known/registered app versions.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of known/registered app versions.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/AppVersionInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"operationId": "postAppVersion",
|
||||
"tags": ["utils"],
|
||||
"summary": "Registers an app version.",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AppVersionInfo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "App version info."
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Version has been registered."
|
||||
},
|
||||
"400": {
|
||||
"description": "Malformed version info."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/app-versions/{version}": {
|
||||
"parameters": [
|
||||
{"$ref": "#/components/parameters/UserID"},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "version",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"get": {
|
||||
"operationId": "getAppVersion",
|
||||
"tags": ["app"],
|
||||
"summary": "Retrieves an app version info.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "App version info.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AppVersionInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Version not specified."
|
||||
},
|
||||
"404": {
|
||||
"description": "Requested version not found."
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"operationId": "deleteAppVersion",
|
||||
"tags": ["utils"],
|
||||
"summary": "Deletes an app version.",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "The specified version has been deleted."
|
||||
},
|
||||
"400": {
|
||||
"description": "Version not specified."
|
||||
},
|
||||
"404": {
|
||||
"description": "The specified version not found."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/reinit": {
|
||||
"parameters": [{"$ref": "#/components/parameters/UserID"}],
|
||||
"post": {
|
||||
"operationId": "reinitDb",
|
||||
"tags": ["debug", "utils"],
|
||||
"summary": "Reinitializes DB (deletes all data).",
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "DB has been reinitialized."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/usage-info": {
|
||||
"get": {
|
||||
"operationId": "getUsageInfo",
|
||||
"tags": ["utils", "debug"],
|
||||
"summary": "Similar to /data/people, but used app version and times of last contact, upload, and download are retreived as well.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "User usage info.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/UserInfo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,22 @@ func (e *ErrPhotoNotProvided) Error() string {
|
||||
return fmt.Sprintf("referenced photo %s which was not provided", e.Reference)
|
||||
}
|
||||
|
||||
type EErrPhotoThumbnailNotProvided *ErrPhotoThumbnailNotProvided
|
||||
|
||||
type ErrPhotoThumbnailNotProvided struct {
|
||||
Reference string
|
||||
}
|
||||
|
||||
func NewErrPhotoThumbnailNotProvided(reference string) *ErrPhotoThumbnailNotProvided {
|
||||
return &ErrPhotoThumbnailNotProvided{
|
||||
Reference: reference,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ErrPhotoThumbnailNotProvided) Error() string {
|
||||
return fmt.Sprintf("referenced photo %s the thumbnail of which was not provided", e.Reference)
|
||||
}
|
||||
|
||||
type ErrFeatureForPhotoNotExists struct {
|
||||
PhotoFeatureReference int64
|
||||
}
|
||||
|
||||
@@ -1,31 +1,53 @@
|
||||
module cernobor.cz/oko-server
|
||||
|
||||
go 1.17
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
crawshaw.io/sqlite v0.3.2
|
||||
github.com/consbio/mbtileserver v0.8.1
|
||||
github.com/gin-gonic/gin v1.7.7
|
||||
github.com/consbio/mbtileserver v0.9.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/paulmach/go.geojson v1.4.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
zombiezen.com/go/sqlite v0.13.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/brendan-ward/mbtiles-go v0.0.0-20211210015813-553bc514bbdf // indirect
|
||||
crawshaw.io/sqlite v0.3.3-0.20211227050848-2cdb5c1a86a1 // indirect
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.24.1 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.6.0 // indirect
|
||||
modernc.org/sqlite v1.23.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/brendan-ward/mbtiles-go v0.1.1-0.20220830154734-a6cb9b848bab // indirect
|
||||
github.com/coreos/go-semver v0.3.1
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.10.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/ugorji/go/codec v1.2.6 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
github.com/mssola/user_agent v0.6.0
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/image v0.7.0
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,847 +1,149 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
|
||||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
|
||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
||||
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
|
||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797 h1:yDf7ARQc637HoxDho7xjqdvO5ZA2Yb+xzv/fOnnvZzw=
|
||||
crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk=
|
||||
crawshaw.io/sqlite v0.3.2 h1:N6IzTjkiw9FItHAa0jp+ZKC6tuLzXqAYIv+ccIWos1I=
|
||||
crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/brendan-ward/mbtiles-go v0.0.0-20211210015813-553bc514bbdf h1:RT/qOIiM0yuaELiwbyli7g6bHFARWlU8B27Ff1r8K6g=
|
||||
github.com/brendan-ward/mbtiles-go v0.0.0-20211210015813-553bc514bbdf/go.mod h1:rlKkU0/sjOysMB+dvVI+b90UyzmD5alQK7KlhJplVrg=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/consbio/mbtileserver v0.8.1 h1:Q1x3Vf4Wbb92uNQ9ZAIn2bkS+nzMbcnH2qidq81lxOs=
|
||||
github.com/consbio/mbtileserver v0.8.1/go.mod h1:efp5wvQOhIhElvcXrFhXUw+k43dwn9EfdACvXBwHy7o=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
crawshaw.io/sqlite v0.3.3-0.20211227050848-2cdb5c1a86a1 h1:Ee5qKxzDpjFjX2tdxl8NlPCTr3PDpagnyf0jL5ow+PY=
|
||||
crawshaw.io/sqlite v0.3.3-0.20211227050848-2cdb5c1a86a1/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
||||
github.com/brendan-ward/mbtiles-go v0.1.1-0.20220830154734-a6cb9b848bab h1:/+iZRJ5VeHYqgQQtfiAaP6niUH+2zSfox6QetbqB6eI=
|
||||
github.com/brendan-ward/mbtiles-go v0.1.1-0.20220830154734-a6cb9b848bab/go.mod h1:PjU6MOcQtVNQbHT42VnBVLQh4pze1Hy+6AkwRoW4BBY=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/consbio/mbtileserver v0.9.0 h1:HV7mNSeOcr25ew8qkKVyt0jcw3859zTVnIhPbcv6Kao=
|
||||
github.com/consbio/mbtileserver v0.9.0/go.mod h1:S0NvNKLJEeUOdVa8vklmpyiiB4Jsxh6wVjaGSmlXamg=
|
||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
|
||||
github.com/evalphobia/logrus_sentry v0.8.2/go.mod h1:pKcp+vriitUqu9KiWj/VRFbRfFNUwz95/UkgG8a6MNc=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
|
||||
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
|
||||
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/echo/v4 v4.6.1/go.mod h1:RnjgMWNDB9g/HucVWhQYNQP9PvbYf6adqftqryo7s9k=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/mssola/user_agent v0.6.0 h1:uwPR4rtWlCHRFyyP9u2KOV0u8iQXmS7Z7feTrstQwk4=
|
||||
github.com/mssola/user_agent v0.6.0/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
|
||||
github.com/paulmach/go.geojson v1.4.0 h1:5x5moCkCtDo5x8af62P9IOAYGQcYHtxz2QJ3x1DoCgY=
|
||||
github.com/paulmach/go.geojson v1.4.0/go.mod h1:YaKx1hKpWF+T2oj2lFJPsW/t1Q5e1jQI61eoQSTwpIs=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
|
||||
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
|
||||
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI=
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
|
||||
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211214170744-3b038e5940ed/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
|
||||
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
|
||||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
|
||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
|
||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
||||
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
||||
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
|
||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
||||
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
||||
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
|
||||
modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o=
|
||||
modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/sqlite v1.23.0 h1:MWTFBI5H1WLnXpNBh/BTruBVqzzoh28DA0iOnlkkRaM=
|
||||
modernc.org/sqlite v1.23.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
zombiezen.com/go/sqlite v0.13.0 h1:iEeyVqcm3fk5PCA8OQBhBxPnqrP4yYuVJBF+XZpSnOE=
|
||||
zombiezen.com/go/sqlite v0.13.0/go.mod h1:Ht/5Rg3Ae2hoyh1I7gbWtWAl89CNocfqeb/aAMTkJr4=
|
||||
|
||||
@@ -7,21 +7,29 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"cernobor.cz/oko-server/models"
|
||||
"cernobor.cz/oko-server/server"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
sha1ver string
|
||||
buildTime string
|
||||
)
|
||||
|
||||
func main() {
|
||||
tilepackFileArg := flag.String("tilepack", "", "File that will be sent to clients when they request a tile pack, also used to serve tiles in online mode. Required.")
|
||||
portArg := flag.Int("port", 8080, "Port where the server will listen to. Default is 8080.")
|
||||
dbFileArg := flag.String("dbfile", "./data.sqlite3", "File that holds the server's sqlite3 database. Will be created if it does not exist. Default is \"./data.sqlite3\".")
|
||||
apkFileArg := flag.String("apk", "", "APK file with the client app. If not specified, no APK will be available (404).")
|
||||
reinitDBArg := flag.Bool("reinit-db", false, "Reinitializes the DB, which means all the tables will be recreated, deleting all data.")
|
||||
minZoomArg := flag.Int("min-zoom", 1, "Minimum zoom that will be sent to clients.")
|
||||
defaultCenterLatArg := flag.Float64("default-center-lat", 0, "Latitude of the default map center.")
|
||||
defaultCenterLngArg := flag.Float64("default-center-lng", 0, "Longitude of the default map center.")
|
||||
maxPhotoXArg := flag.Int("max-photo-width", 0, "Maximum width of photos. 0 means no limit.")
|
||||
maxPhotoYArg := flag.Int("max-photo-height", 0, "Maximum height of photos. 0 means no limit.")
|
||||
photoQualityArg := flag.Int("photo-quality", 90, "Photo JPEG quality.")
|
||||
debug := flag.Bool("debug", false, "If specified, logging level will be set to debug instead of info.")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
@@ -31,17 +39,31 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
t, err := time.Parse(time.RFC3339, buildTime)
|
||||
if err != nil {
|
||||
t = time.Now()
|
||||
}
|
||||
|
||||
if *maxPhotoXArg < 0 || *maxPhotoYArg < 0 {
|
||||
fmt.Fprintln(os.Stderr, "Max photo width and height cannot be less than 0.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
s := server.New(server.ServerConfig{
|
||||
VersionHash: sha1ver,
|
||||
BuildTime: &t,
|
||||
Port: *portArg,
|
||||
DbPath: *dbFileArg,
|
||||
TilepackPath: *tilepackFileArg,
|
||||
ApkPath: *apkFileArg,
|
||||
ReinitDB: *reinitDBArg,
|
||||
MinZoom: *minZoomArg,
|
||||
DefaultCenter: models.Coords{
|
||||
Lat: *defaultCenterLatArg,
|
||||
Lng: *defaultCenterLngArg,
|
||||
},
|
||||
MaxPhotoX: *maxPhotoXArg,
|
||||
MaxPhotoY: *maxPhotoYArg,
|
||||
PhotoQuality: *photoQualityArg,
|
||||
Debug: *debug,
|
||||
})
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
|
||||
+38
-2
@@ -4,6 +4,7 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
geojson "github.com/paulmach/go.geojson"
|
||||
)
|
||||
|
||||
@@ -18,6 +19,14 @@ type User struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
User
|
||||
AppVersion *semver.Version `json:"app_version"`
|
||||
LastSeenTime *time.Time `json:"last_seen_time"`
|
||||
LastUploadTime *time.Time `json:"last_upload_time"`
|
||||
LastDownloadTime *time.Time `json:"last_download_time"`
|
||||
}
|
||||
|
||||
type Feature struct {
|
||||
// ID is an ID of the feature.
|
||||
// When the feature is submitted by a client for creation (i.e. in Update.Create) it is considered a 'local' ID which must be unique across all submitted features.
|
||||
@@ -33,8 +42,18 @@ type Feature struct {
|
||||
PhotoIDs []FeaturePhotoID `json:"photo_ids"`
|
||||
}
|
||||
|
||||
type BuildInfo struct {
|
||||
VersionHash string `json:"version_hash"`
|
||||
BuildTime *time.Time `json:"build_time"`
|
||||
}
|
||||
|
||||
// transport objects
|
||||
|
||||
type AppVersionInfo struct {
|
||||
Version semver.Version `json:"version"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type Coords struct {
|
||||
Lat float64 `json:"lat"`
|
||||
Lng float64 `json:"lng"`
|
||||
@@ -55,6 +74,7 @@ type Update struct {
|
||||
Update []Feature `json:"update"`
|
||||
Delete []FeatureID `json:"delete"`
|
||||
DeletePhotos []FeaturePhotoID `json:"delete_photos"`
|
||||
Proposals []Proposal `json:"proposals"`
|
||||
}
|
||||
|
||||
type HandshakeChallenge struct {
|
||||
@@ -69,8 +89,10 @@ type HandshakeResponse struct {
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
Users []User `json:"users"`
|
||||
Features []Feature `json:"features"`
|
||||
Users []User `json:"users"`
|
||||
Features []Feature `json:"features"`
|
||||
Proposals []Proposal `json:"proposals"`
|
||||
PhotoMetadata map[string]PhotoMetadata `json:"photo_metadata,omitempty"`
|
||||
}
|
||||
|
||||
type Photo struct {
|
||||
@@ -78,3 +100,17 @@ type Photo struct {
|
||||
File io.ReadCloser
|
||||
Size int64
|
||||
}
|
||||
|
||||
type PhotoMetadata struct {
|
||||
ContentType string `json:"content_type"`
|
||||
ThumbnailContentType string `json:"thumbnail_content_type"`
|
||||
Size int64 `json:"size"`
|
||||
ID FeaturePhotoID `json:"id"`
|
||||
ThumbnailFilename string `json:"thumbnail_filename"`
|
||||
}
|
||||
|
||||
type Proposal struct {
|
||||
OwnerID UserID `json:"owner_id"`
|
||||
Description string `json:"description"`
|
||||
How string `json:"how"`
|
||||
}
|
||||
|
||||
@@ -2,16 +2,24 @@ package server
|
||||
|
||||
const (
|
||||
URIPing = "/ping"
|
||||
URIBuildInfo = "/build-info"
|
||||
URIHardFail = "/hard-fail"
|
||||
URISoftFail = "/soft-fail"
|
||||
URIAppVersions = "/app-versions"
|
||||
URIAppVersion = "/app-versions/:version"
|
||||
URIReinit = "/reinit"
|
||||
URIUsageInfo = "/usage-info"
|
||||
URIMapPack = "/mappack"
|
||||
URIHandshake = "/handshake"
|
||||
URIData = "/data"
|
||||
URIDataPeople = "/data/people"
|
||||
URIDataFeatures = "/data/features"
|
||||
URIDataFeaturesPhoto = "/data/features/:feature/photos/:photo"
|
||||
URIDataProposals = "/data/proposals"
|
||||
URITileserverRoot = "/tileserver"
|
||||
URITileserver = URITileserverRoot + "/*x"
|
||||
URITileTemplate = URITileserverRoot + "/map/tiles/{z}/{x}/{y}.pbf"
|
||||
|
||||
AppName = "OKO"
|
||||
UserIDHeader = "X-User-ID"
|
||||
)
|
||||
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"zombiezen.com/go/sqlite"
|
||||
"zombiezen.com/go/sqlite/sqlitemigration"
|
||||
"zombiezen.com/go/sqlite/sqlitex"
|
||||
)
|
||||
|
||||
func (s *Server) cleanupDb() {
|
||||
close(s.checkpointNotice)
|
||||
s.log.Info("Closing db connection pool...")
|
||||
s.dbpool.Close()
|
||||
|
||||
// manually force truncate checkpoint
|
||||
conn, err := sqlite.OpenConn(fmt.Sprintf("file:%s", s.config.DbPath), 0)
|
||||
if err != nil {
|
||||
s.log.WithError(err).Error("Failed to open connection for final checkpoint.")
|
||||
return
|
||||
}
|
||||
err = sqlitex.Execute(conn, "vacuum", nil)
|
||||
if err != nil {
|
||||
s.log.WithError(err).Error("Failed to vacuum db.")
|
||||
}
|
||||
s.checkpointDb(conn, true)
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func (s *Server) getDbConn() *sqlite.Conn {
|
||||
conn, err := s.dbpool.Get(s.ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func (s *Server) returnDbConn(conn *sqlite.Conn) {
|
||||
s.dbpool.Put(conn)
|
||||
}
|
||||
|
||||
func withDbConn[T any](s *Server, f func(conn *sqlite.Conn) T) T {
|
||||
conn := s.getDbConn()
|
||||
defer s.returnDbConn(conn)
|
||||
return f(conn)
|
||||
}
|
||||
|
||||
func (s *Server) checkpointDb(conn *sqlite.Conn, truncate bool) {
|
||||
var query string
|
||||
if truncate {
|
||||
query = "PRAGMA wal_checkpoint(TRUNCATE)"
|
||||
} else {
|
||||
query = "PRAGMA wal_checkpoint(RESTART)"
|
||||
}
|
||||
stmt, _, err := conn.PrepareTransient(query)
|
||||
if err != nil {
|
||||
s.log.WithError(err).Error("Failed to prepare checkpoint query.")
|
||||
return
|
||||
}
|
||||
defer stmt.Finalize()
|
||||
|
||||
has, err := stmt.Step()
|
||||
if err != nil {
|
||||
s.log.WithError(err).Error("Failed to step through checkpoint query.")
|
||||
return
|
||||
}
|
||||
if !has {
|
||||
s.log.Error("Checkpoint query returned no rows.")
|
||||
return
|
||||
}
|
||||
|
||||
blocked := stmt.ColumnInt(0)
|
||||
noWalPages := stmt.ColumnInt(1)
|
||||
noReclaimedPages := stmt.ColumnInt(2)
|
||||
if blocked == 1 {
|
||||
s.log.Warn("Checkpoint query was blocked.")
|
||||
}
|
||||
s.log.Debugf("Checkpoint complete. %d pages written to WAL, %d pages written back to DB.", noWalPages, noReclaimedPages)
|
||||
}
|
||||
|
||||
func (s *Server) setupDB() error {
|
||||
s.dbAvailable.Store(false)
|
||||
s.log.Debugf("Using db %s", s.config.DbPath)
|
||||
|
||||
ready := make(chan struct{})
|
||||
migErr := make(chan error)
|
||||
s.dbpool = sqlitemigration.NewPool(fmt.Sprintf("file:%s", s.config.DbPath), sqlSchema, sqlitemigration.Options{
|
||||
PoolSize: 10,
|
||||
PrepareConn: func(conn *sqlite.Conn) error {
|
||||
return sqlitex.ExecuteTransient(conn, "PRAGMA foreign_keys = ON;", nil)
|
||||
},
|
||||
OnReady: func() {
|
||||
close(ready)
|
||||
},
|
||||
OnError: func(err error) {
|
||||
migErr <- err
|
||||
},
|
||||
})
|
||||
select {
|
||||
case <-ready:
|
||||
case err := <-migErr:
|
||||
return fmt.Errorf("error during db migration: %w", err)
|
||||
}
|
||||
s.checkpointNotice = make(chan struct{})
|
||||
|
||||
// aggressively checkpoint the database on idle times
|
||||
go func() {
|
||||
s.log.Debug("Starting manual restart checkpointing.")
|
||||
defer s.log.Debug("Manual restart checkpointing stopped.")
|
||||
delay := time.Minute * 15
|
||||
var (
|
||||
timer <-chan time.Time
|
||||
ok bool
|
||||
)
|
||||
for {
|
||||
select {
|
||||
case _, ok = <-s.checkpointNotice:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
timer = time.After(delay)
|
||||
case <-timer:
|
||||
withDbConn(s, func(conn *sqlite.Conn) any {
|
||||
s.checkpointDb(conn, false)
|
||||
return nil
|
||||
})
|
||||
timer = nil
|
||||
}
|
||||
}
|
||||
}()
|
||||
s.dbAvailable.Store(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) requestCheckpoint() {
|
||||
go func() {
|
||||
s.checkpointNotice <- struct{}{}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *Server) reinitDb() error {
|
||||
s.log.Debug("Reinitializing db.")
|
||||
s.dbAvailable.Store(false)
|
||||
defer s.dbAvailable.Store(true)
|
||||
close(s.checkpointNotice)
|
||||
err := s.dbpool.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to close db: %w", err)
|
||||
}
|
||||
s.log.Debug("Removing main db file.")
|
||||
err = os.Remove(s.config.DbPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to remove db file %s: %w", s.config.DbPath, err)
|
||||
}
|
||||
s.log.Debug("Removing WAL file.")
|
||||
err = os.Remove(s.config.DbPath + "-wal")
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to remove db-wal file %s-wal: %w", s.config.DbPath, err)
|
||||
}
|
||||
s.log.Debug("Initializing db.")
|
||||
err = s.setupDB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to setup db during reinit")
|
||||
}
|
||||
s.log.Debug("DB reinitialized.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// SQL schema
|
||||
|
||||
//go:embed sql_schema/V*.sql
|
||||
var sqlSchemaFiles embed.FS
|
||||
var sqlSchema sqlitemigration.Schema = func() sqlitemigration.Schema {
|
||||
type migration struct {
|
||||
content string
|
||||
version int
|
||||
name string
|
||||
}
|
||||
|
||||
entries, err := sqlSchemaFiles.ReadDir("sql_schema")
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to read sql_schema migrations: %w", err))
|
||||
}
|
||||
|
||||
pattern := regexp.MustCompile("^V([0-9]+)_(.*)[.][sS][qQ][lL]$")
|
||||
migrations := []*migration{}
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if entry.IsDir() {
|
||||
panic(fmt.Errorf("embedded sql migration '%s' is a directory", name))
|
||||
}
|
||||
matches := pattern.FindStringSubmatch(name)
|
||||
if matches == nil {
|
||||
panic(fmt.Errorf("embedded sql migration '%s' does not match the filename pattern", name))
|
||||
}
|
||||
if len(matches) != 3 {
|
||||
panic(fmt.Errorf("embedded sql migration '%s' does not have the correct number of submatches", name))
|
||||
}
|
||||
version, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to parse version number of migration '%s': %w", name, err))
|
||||
}
|
||||
migName := matches[2]
|
||||
file := path.Join("sql_schema", name)
|
||||
content, err := sqlSchemaFiles.ReadFile(file)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to read embedded migration %s", entry.Name()))
|
||||
}
|
||||
migrations = append(migrations, &migration{
|
||||
content: string(content),
|
||||
version: version,
|
||||
name: migName,
|
||||
})
|
||||
}
|
||||
sort.Slice(migrations, func(i, j int) bool {
|
||||
return migrations[i].version < migrations[j].version
|
||||
})
|
||||
|
||||
return sqlitemigration.Schema{
|
||||
Migrations: Map(func(m *migration) string { return m.content }, migrations),
|
||||
}
|
||||
}()
|
||||
+335
-84
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -13,73 +13,155 @@ import (
|
||||
|
||||
"cernobor.cz/oko-server/errs"
|
||||
"cernobor.cz/oko-server/models"
|
||||
"github.com/coreos/go-semver/semver"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mssola/user_agent"
|
||||
"github.com/sirupsen/logrus"
|
||||
"zombiezen.com/go/sqlite"
|
||||
"zombiezen.com/go/sqlite/sqlitex"
|
||||
)
|
||||
|
||||
func internalError(gc *gin.Context, err error) {
|
||||
gc.Error(err)
|
||||
gc.String(http.StatusInternalServerError, "%v", err)
|
||||
}
|
||||
|
||||
func badRequest(gc *gin.Context, err error) {
|
||||
gc.Error(err)
|
||||
gc.String(http.StatusBadRequest, "%v", err)
|
||||
}
|
||||
|
||||
func (s *Server) setupRouter() *gin.Engine {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
router := gin.New()
|
||||
router.Use(gin.Recovery())
|
||||
|
||||
// logging
|
||||
ginLogger := logrus.New()
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
hostname = "unknown"
|
||||
}
|
||||
router.Use(func(gc *gin.Context) {
|
||||
path := gc.Request.URL.Path
|
||||
start := time.Now()
|
||||
gc.Next()
|
||||
stop := time.Since(start)
|
||||
latency := int(math.Ceil(float64(stop.Nanoseconds()) / 1_000_000.0))
|
||||
statusCode := gc.Writer.Status()
|
||||
clientIP := gc.ClientIP()
|
||||
clientUserAgent := gc.Request.UserAgent()
|
||||
referer := gc.Request.Referer()
|
||||
dataLength := gc.Writer.Size()
|
||||
if dataLength < 0 {
|
||||
dataLength = 0
|
||||
}
|
||||
entry := ginLogger.WithFields(logrus.Fields{
|
||||
"hostname": hostname,
|
||||
"statusCode": statusCode,
|
||||
"latency": latency,
|
||||
"clientIP": clientIP,
|
||||
"method": gc.Request.Method,
|
||||
"path": path,
|
||||
"referer": referer,
|
||||
"dataLength": dataLength,
|
||||
"userAgent": clientUserAgent,
|
||||
})
|
||||
|
||||
if len(gc.Errors) > 0 {
|
||||
entry.Error(gc.Errors.ByType(gin.ErrorTypePrivate).String())
|
||||
} else {
|
||||
msg := fmt.Sprintf(
|
||||
"%s - %s [%s] \"%s %s\" %d %d \"%s\" \"%s\" (%dms)",
|
||||
clientIP, hostname, time.Now().Format(time.RFC3339), gc.Request.Method, path, statusCode, dataLength, referer, clientUserAgent, latency,
|
||||
)
|
||||
if statusCode >= 500 {
|
||||
entry.Error(msg)
|
||||
} else if statusCode >= 400 {
|
||||
entry.Warn(msg)
|
||||
} else if path == URIPing {
|
||||
entry.Debug(msg)
|
||||
} else {
|
||||
entry.Info(msg)
|
||||
router.Use(
|
||||
func(gc *gin.Context) {
|
||||
path := gc.Request.URL.Path
|
||||
start := time.Now()
|
||||
gc.Next()
|
||||
stop := time.Since(start)
|
||||
latency := int(math.Ceil(float64(stop.Nanoseconds()) / 1_000_000.0))
|
||||
statusCode := gc.Writer.Status()
|
||||
clientIP := gc.ClientIP()
|
||||
clientUserAgent := gc.Request.UserAgent()
|
||||
userId := gc.GetHeader(UserIDHeader)
|
||||
referer := gc.Request.Referer()
|
||||
dataLength := gc.Writer.Size()
|
||||
if dataLength < 0 {
|
||||
dataLength = 0
|
||||
}
|
||||
}
|
||||
})
|
||||
entry := s.log.WithFields(logrus.Fields{
|
||||
"hostname": hostname,
|
||||
"statusCode": statusCode,
|
||||
"latency": latency,
|
||||
"clientIP": clientIP,
|
||||
"method": gc.Request.Method,
|
||||
"path": path,
|
||||
"referer": referer,
|
||||
"dataLength": dataLength,
|
||||
"userAgent": clientUserAgent,
|
||||
"userID": userId,
|
||||
})
|
||||
|
||||
if len(gc.Errors) > 0 {
|
||||
entry.Error(gc.Errors.ByType(gin.ErrorTypePrivate).String())
|
||||
} else {
|
||||
msg := fmt.Sprintf(
|
||||
"%s - %s [%s] \"%s %s\" %d %d \"%s\" \"%s\" (%dms)",
|
||||
clientIP, hostname, time.Now().Format(time.RFC3339), gc.Request.Method, path, statusCode, dataLength, referer, clientUserAgent, latency,
|
||||
)
|
||||
if statusCode >= 500 {
|
||||
entry.Error(msg)
|
||||
} else if statusCode >= 400 {
|
||||
entry.Warn(msg)
|
||||
} else if path == URIPing {
|
||||
entry.Debug(msg)
|
||||
} else {
|
||||
entry.Info(msg)
|
||||
}
|
||||
}
|
||||
},
|
||||
func(gc *gin.Context) {
|
||||
if !s.dbAvailable.Load() {
|
||||
gc.AbortWithError(http.StatusServiceUnavailable, fmt.Errorf("server database is not ready/available"))
|
||||
gc.Header("Retry-After", "60")
|
||||
return
|
||||
}
|
||||
gc.Next()
|
||||
},
|
||||
func(gc *gin.Context) {
|
||||
userIdStr := gc.GetHeader(UserIDHeader)
|
||||
userIdPresent := userIdStr != ""
|
||||
var userId int64
|
||||
var userIdErr error
|
||||
if userIdPresent {
|
||||
userId, userIdErr = strconv.ParseInt(userIdStr, 10, 0)
|
||||
}
|
||||
appVersion, appVersionErr := extractAppVersion(gc)
|
||||
if userIdErr != nil {
|
||||
gc.Error(fmt.Errorf("malformed %s: %w", UserIDHeader, userIdErr))
|
||||
} else if userIdPresent {
|
||||
gc.Set("uid", userId)
|
||||
var err error
|
||||
if appVersion == nil {
|
||||
err = withDbConn(s, func(conn *sqlite.Conn) error {
|
||||
err := sqlitex.Execute(conn, "update users set last_seen_time = ? where id = ?", &sqlitex.ExecOptions{
|
||||
Args: []interface{}{time.Now().Unix(), userId},
|
||||
})
|
||||
return err
|
||||
})
|
||||
} else {
|
||||
err = withDbConn(s, func(conn *sqlite.Conn) error {
|
||||
err := sqlitex.Execute(conn, "update users set app_version = ?, last_seen_time = ? where id = ?", &sqlitex.ExecOptions{
|
||||
Args: []interface{}{appVersion, time.Now().Unix(), userId},
|
||||
})
|
||||
return err
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
gc.AbortWithError(http.StatusInternalServerError, fmt.Errorf("failed to store user data into db: %w", err))
|
||||
}
|
||||
}
|
||||
if appVersion != nil {
|
||||
gc.Set("app-version", appVersion)
|
||||
}
|
||||
if appVersionErr != nil {
|
||||
gc.Error(appVersionErr)
|
||||
gc.Set("app-version-error", appVersionErr.Error())
|
||||
}
|
||||
gc.Next()
|
||||
},
|
||||
)
|
||||
|
||||
// tileserver
|
||||
router.GET(URITileserver, gin.WrapH(s.tileserverSvSet.Handler()))
|
||||
|
||||
/*** API ***/
|
||||
router.GET(URIPing, s.handleGETPing)
|
||||
router.POST(URIHandshake, s.handlePOSTHandshake)
|
||||
router.GET(URIData, s.handleGETData)
|
||||
router.POST(URIData, s.handlePOSTData)
|
||||
router.GET(URIDataPeople, s.handleGETDataPeople)
|
||||
router.GET(URIDataFeatures, s.handleGETDataFeatures)
|
||||
router.GET(URIDataFeaturesPhoto, s.handleGETDataFeaturesPhoto)
|
||||
router.GET(URIDataProposals, s.handleGETDataProposals)
|
||||
|
||||
// resources
|
||||
router.GET(URIMapPack, s.handleGETTilepack)
|
||||
|
||||
// utility/debug paths
|
||||
router.GET(URIPing, func(gc *gin.Context) {
|
||||
gc.Status(http.StatusNoContent)
|
||||
router.GET(URIBuildInfo, func(gc *gin.Context) {
|
||||
gc.JSON(http.StatusOK, models.BuildInfo{
|
||||
VersionHash: s.config.VersionHash,
|
||||
BuildTime: s.config.BuildTime,
|
||||
})
|
||||
})
|
||||
router.GET(URIHardFail, func(gc *gin.Context) {
|
||||
gc.Status(http.StatusNotImplemented)
|
||||
@@ -87,32 +169,130 @@ func (s *Server) setupRouter() *gin.Engine {
|
||||
router.GET(URISoftFail, func(gc *gin.Context) {
|
||||
gc.JSON(http.StatusOK, map[string]string{"error": "artificial fail"})
|
||||
})
|
||||
router.GET(URIAppVersions, s.handleGETAppVersions)
|
||||
router.POST(URIAppVersions, s.handlePOSTAppVersions)
|
||||
router.GET(URIAppVersion, s.handleGETAppVersion)
|
||||
router.DELETE(URIAppVersion, s.handleDELETEAppVersion)
|
||||
router.POST(URIReinit, s.handlePOSTReset)
|
||||
|
||||
// resources
|
||||
router.GET(URIMapPack, s.handleGETTilepack)
|
||||
|
||||
// API
|
||||
router.POST(URIHandshake, s.handlePOSTHandshake)
|
||||
router.GET(URIData, s.handleGETData)
|
||||
router.POST(URIData, s.handlePOSTData)
|
||||
router.GET(URIDataPeople, s.handleGETDataPeople)
|
||||
router.GET(URIDataFeatures, s.handleGETDataFeatures)
|
||||
router.GET(URIDataFeaturesPhoto, s.handleGETDataFeaturesPhoto)
|
||||
|
||||
// tileserver
|
||||
router.GET(URITileserver, gin.WrapH(s.tileserverSvSet.Handler()))
|
||||
router.GET(URIUsageInfo, s.handleGETUsageInfo)
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
func (s *Server) handlePOSTReset(gc *gin.Context) {
|
||||
err := s.reinitDB()
|
||||
func extractAppVersion(gc *gin.Context) (*semver.Version, error) {
|
||||
ua := user_agent.New(gc.Request.UserAgent())
|
||||
n, v := ua.Browser()
|
||||
if n != AppName {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
version, err := semver.NewVersion(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed version in User-Agent header: %w", err)
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func (s *Server) handleGETPing(gc *gin.Context) {
|
||||
versionRaw, exists := gc.Get("app-version")
|
||||
if !exists {
|
||||
versionErr := gc.GetString("app-version-error")
|
||||
if versionErr != "" {
|
||||
badRequest(gc, fmt.Errorf("malformed app version: %v", &versionErr))
|
||||
} else {
|
||||
badRequest(gc, fmt.Errorf("app version not specified"))
|
||||
}
|
||||
return
|
||||
}
|
||||
version, ok := versionRaw.(*semver.Version)
|
||||
if !ok {
|
||||
internalError(gc, fmt.Errorf("malformed app version extracted"))
|
||||
return
|
||||
}
|
||||
|
||||
res, err := s.getLatestVersion(version)
|
||||
if err != nil {
|
||||
internalError(gc, err)
|
||||
return
|
||||
}
|
||||
gc.Status(http.StatusOK)
|
||||
|
||||
if res == nil {
|
||||
gc.Status(http.StatusNoContent)
|
||||
} else {
|
||||
gc.JSON(http.StatusOK, res)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleGETAppVersions(gc *gin.Context) {
|
||||
versions, err := s.getAppVersions()
|
||||
if err != nil {
|
||||
internalError(gc, err)
|
||||
return
|
||||
}
|
||||
gc.JSON(http.StatusOK, versions)
|
||||
}
|
||||
|
||||
func (s *Server) handlePOSTAppVersions(gc *gin.Context) {
|
||||
var versionInfo models.AppVersionInfo
|
||||
err := gc.ShouldBindJSON(&versionInfo)
|
||||
if err != nil {
|
||||
badRequest(gc, fmt.Errorf("malformed version info: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
err = s.putAppVersion(&versionInfo)
|
||||
if err != nil {
|
||||
internalError(gc, err)
|
||||
return
|
||||
}
|
||||
gc.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (s *Server) handleGETAppVersion(gc *gin.Context) {
|
||||
version := gc.Param("version")
|
||||
if version == "" {
|
||||
badRequest(gc, fmt.Errorf("version not provided"))
|
||||
return
|
||||
}
|
||||
|
||||
ver, err := s.getAppVersion(version)
|
||||
if err != nil {
|
||||
internalError(gc, err)
|
||||
return
|
||||
}
|
||||
if ver == nil {
|
||||
gc.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
gc.JSON(http.StatusOK, ver)
|
||||
}
|
||||
|
||||
func (s *Server) handleDELETEAppVersion(gc *gin.Context) {
|
||||
version := gc.Param("version")
|
||||
if version == "" {
|
||||
badRequest(gc, fmt.Errorf("version not provided"))
|
||||
return
|
||||
}
|
||||
|
||||
deleted, err := s.deleteAppVersion(version)
|
||||
if err != nil {
|
||||
internalError(gc, err)
|
||||
return
|
||||
}
|
||||
if !deleted {
|
||||
gc.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
gc.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (s *Server) handlePOSTReset(gc *gin.Context) {
|
||||
err := s.reinitDb()
|
||||
if err != nil {
|
||||
internalError(gc, err)
|
||||
return
|
||||
}
|
||||
gc.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (s *Server) handleGETTilepack(gc *gin.Context) {
|
||||
@@ -123,7 +303,7 @@ func (s *Server) handlePOSTHandshake(gc *gin.Context) {
|
||||
var hs models.HandshakeChallenge
|
||||
err := gc.ShouldBindJSON(&hs)
|
||||
if err != nil {
|
||||
gc.String(http.StatusBadRequest, fmt.Sprintf("malformed handshake challenge: %v", err))
|
||||
badRequest(gc, fmt.Errorf("malformed handshake challenge: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -155,22 +335,75 @@ func (s *Server) handlePOSTHandshake(gc *gin.Context) {
|
||||
}
|
||||
|
||||
func (s *Server) handleGETData(gc *gin.Context) {
|
||||
data, err := s.getData()
|
||||
if err != nil {
|
||||
internalError(gc, err)
|
||||
uidRaw, uidExists := gc.Get("uid")
|
||||
uid, _ := uidRaw.(int64)
|
||||
accept := gc.GetHeader("Accept")
|
||||
if accept == "application/json" {
|
||||
data, err := s.getDataOnly()
|
||||
if err != nil {
|
||||
internalError(gc, err)
|
||||
return
|
||||
}
|
||||
gc.JSON(http.StatusOK, data)
|
||||
} else if accept == "application/zip" {
|
||||
file, err := s.getDataWithPhotos()
|
||||
defer func() {
|
||||
file.Close()
|
||||
os.Remove(file.Name())
|
||||
}()
|
||||
if err != nil {
|
||||
internalError(gc, err)
|
||||
return
|
||||
}
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
internalError(gc, err)
|
||||
return
|
||||
}
|
||||
size := fi.Size()
|
||||
_, err = file.Seek(0, 0)
|
||||
if err != nil {
|
||||
internalError(gc, err)
|
||||
return
|
||||
}
|
||||
gc.DataFromReader(http.StatusOK, size, "application/zip", file, nil)
|
||||
} else {
|
||||
gc.String(http.StatusNotAcceptable, "%s is not acceptable", accept)
|
||||
return
|
||||
}
|
||||
gc.JSON(http.StatusOK, data)
|
||||
if uidExists {
|
||||
err := withDbConn(s, func(conn *sqlite.Conn) error {
|
||||
return sqlitex.Execute(conn, "update users set last_download_time = ? where id = ?", &sqlitex.ExecOptions{
|
||||
Args: []interface{}{time.Now().Unix(), uid},
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
gc.Error(fmt.Errorf("failed to store last download time: %w", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handlePOSTData(gc *gin.Context) {
|
||||
uidRaw, uidExists := gc.Get("uid")
|
||||
uid, _ := uidRaw.(int64)
|
||||
switch gc.ContentType() {
|
||||
case "application/json":
|
||||
s.handlePOSTDataJSON(gc)
|
||||
case "multipart/form-data":
|
||||
s.handlePOSTDataMultipart(gc)
|
||||
default:
|
||||
gc.String(http.StatusBadRequest, "unsupported Content-Type")
|
||||
badRequest(gc, fmt.Errorf("unsupported Content-Type"))
|
||||
return
|
||||
}
|
||||
if uidExists {
|
||||
err := withDbConn(s, func(conn *sqlite.Conn) error {
|
||||
return sqlitex.Execute(conn, "update users set last_upload_time = ? where id = ?", &sqlitex.ExecOptions{
|
||||
Args: []interface{}{time.Now().Unix(), uid},
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
gc.Error(fmt.Errorf("failed to store last upload time: %w", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,17 +411,17 @@ func (s *Server) handlePOSTDataJSON(gc *gin.Context) {
|
||||
var data models.Update
|
||||
err := gc.ShouldBindJSON(&data)
|
||||
if err != nil {
|
||||
gc.String(http.StatusBadRequest, fmt.Sprintf("malformed data: %v", err))
|
||||
badRequest(gc, fmt.Errorf("malformed data: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if !isUniqueFeatureID(data.Create) {
|
||||
gc.String(http.StatusBadRequest, "created features do not have unique IDs")
|
||||
badRequest(gc, fmt.Errorf("created features do not have unique IDs"))
|
||||
return
|
||||
}
|
||||
|
||||
if data.CreatedPhotos != nil || data.AddPhotos != nil {
|
||||
gc.String(http.StatusBadRequest, "created_photos and/or add_photos present, but Content-Type is application/json")
|
||||
badRequest(gc, fmt.Errorf("created_photos and/or add_photos present, but Content-Type is application/json"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -203,7 +436,7 @@ func (s *Server) handlePOSTDataJSON(gc *gin.Context) {
|
||||
func (s *Server) handlePOSTDataMultipart(gc *gin.Context) {
|
||||
form, err := gc.MultipartForm()
|
||||
if err != nil {
|
||||
gc.String(http.StatusBadRequest, "malformed multipart/form-data content")
|
||||
badRequest(gc, fmt.Errorf("malformed multipart/form-data content"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -211,11 +444,11 @@ func (s *Server) handlePOSTDataMultipart(gc *gin.Context) {
|
||||
if !ok {
|
||||
dataFile, ok := form.File["data"]
|
||||
if !ok {
|
||||
gc.String(http.StatusBadRequest, "value 'data' is missing from the content")
|
||||
badRequest(gc, fmt.Errorf("value 'data' is missing from the content"))
|
||||
return
|
||||
}
|
||||
if len(dataFile) != 1 {
|
||||
gc.String(http.StatusBadRequest, "value 'data' does not contain exactly 1 item")
|
||||
badRequest(gc, fmt.Errorf("value 'data' does not contain exactly 1 item"))
|
||||
return
|
||||
}
|
||||
df, err := dataFile[0].Open()
|
||||
@@ -223,7 +456,7 @@ func (s *Server) handlePOSTDataMultipart(gc *gin.Context) {
|
||||
internalError(gc, fmt.Errorf("failed to open 'data' 'file': %w", err))
|
||||
return
|
||||
}
|
||||
dataBytes, err := ioutil.ReadAll(df)
|
||||
dataBytes, err := io.ReadAll(df)
|
||||
if err != nil {
|
||||
internalError(gc, fmt.Errorf("failed to open 'data' 'file': %w", err))
|
||||
return
|
||||
@@ -231,26 +464,26 @@ func (s *Server) handlePOSTDataMultipart(gc *gin.Context) {
|
||||
dataStr = []string{string(dataBytes)}
|
||||
}
|
||||
if len(dataStr) != 1 {
|
||||
gc.String(http.StatusBadRequest, "value 'data' does not contain exactly 1 item")
|
||||
badRequest(gc, fmt.Errorf("value 'data' does not contain exactly 1 item"))
|
||||
return
|
||||
}
|
||||
|
||||
var data models.Update
|
||||
err = json.Unmarshal([]byte(dataStr[0]), &data)
|
||||
if err != nil {
|
||||
gc.String(http.StatusBadRequest, "malformed 'data' value: %v", err)
|
||||
badRequest(gc, fmt.Errorf("malformed 'data' value: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if !isUniqueFeatureID(data.Create) {
|
||||
gc.String(http.StatusBadRequest, "created features do not have unique IDs")
|
||||
badRequest(gc, fmt.Errorf("created features do not have unique IDs"))
|
||||
return
|
||||
}
|
||||
|
||||
photos := make(map[string]models.Photo, len(form.File))
|
||||
for name, fh := range form.File {
|
||||
if len(fh) != 1 {
|
||||
gc.String(http.StatusBadRequest, "file item %s does not contain exactly 1 file", name)
|
||||
badRequest(gc, fmt.Errorf("file item %s does not contain exactly 1 file", name))
|
||||
return
|
||||
}
|
||||
var photo models.Photo
|
||||
@@ -268,7 +501,7 @@ func (s *Server) handlePOSTDataMultipart(gc *gin.Context) {
|
||||
if err != nil {
|
||||
var e *errs.ErrUnsupportedContentType
|
||||
if errors.As(err, &e) {
|
||||
gc.String(http.StatusBadRequest, e.Error())
|
||||
badRequest(gc, e)
|
||||
return
|
||||
}
|
||||
internalError(gc, fmt.Errorf("failed to update data: %w", err))
|
||||
@@ -299,12 +532,12 @@ func (s *Server) handleGETDataFeatures(gc *gin.Context) {
|
||||
func (s *Server) handleGETDataFeaturesPhoto(gc *gin.Context) {
|
||||
reqFeatureID, err := strconv.Atoi(gc.Param("feature"))
|
||||
if err != nil {
|
||||
gc.String(http.StatusBadRequest, "malformed feature ID")
|
||||
badRequest(gc, fmt.Errorf("malformed feature ID"))
|
||||
return
|
||||
}
|
||||
reqPhotoID, err := strconv.Atoi(gc.Param("photo"))
|
||||
if err != nil {
|
||||
gc.String(http.StatusBadRequest, "malformed photo ID")
|
||||
badRequest(gc, fmt.Errorf("malformed photo ID"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -320,3 +553,21 @@ func (s *Server) handleGETDataFeaturesPhoto(gc *gin.Context) {
|
||||
|
||||
gc.Data(http.StatusOK, contentType, photoBytes)
|
||||
}
|
||||
|
||||
func (s *Server) handleGETDataProposals(gc *gin.Context) {
|
||||
proposals, err := s.getProposals(nil)
|
||||
if err != nil {
|
||||
internalError(gc, err)
|
||||
return
|
||||
}
|
||||
gc.JSON(http.StatusOK, proposals)
|
||||
}
|
||||
|
||||
func (s *Server) handleGETUsageInfo(gc *gin.Context) {
|
||||
usage, err := s.getUsageInfo(nil)
|
||||
if err != nil {
|
||||
internalError(gc, err)
|
||||
return
|
||||
}
|
||||
gc.JSON(http.StatusOK, usage)
|
||||
}
|
||||
|
||||
+511
-150
@@ -1,6 +1,8 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -8,39 +10,43 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"cernobor.cz/oko-server/errs"
|
||||
"cernobor.cz/oko-server/models"
|
||||
"crawshaw.io/sqlite"
|
||||
"crawshaw.io/sqlite/sqlitex"
|
||||
mbsh "github.com/consbio/mbtileserver/handlers"
|
||||
"github.com/coreos/go-semver/semver"
|
||||
geojson "github.com/paulmach/go.geojson"
|
||||
"github.com/sirupsen/logrus"
|
||||
"zombiezen.com/go/sqlite"
|
||||
"zombiezen.com/go/sqlite/sqlitemigration"
|
||||
"zombiezen.com/go/sqlite/sqlitex"
|
||||
)
|
||||
|
||||
//go:embed initdb.sql
|
||||
var initDB string
|
||||
|
||||
type Server struct {
|
||||
config ServerConfig
|
||||
dbpool *sqlitex.Pool
|
||||
log *logrus.Logger
|
||||
ctx context.Context
|
||||
tileserverSvSet *mbsh.ServiceSet
|
||||
mapPackSize int64
|
||||
config ServerConfig
|
||||
dbpool *sqlitemigration.Pool
|
||||
dbAvailable atomic.Bool
|
||||
checkpointNotice chan struct{}
|
||||
log *logrus.Logger
|
||||
ctx context.Context
|
||||
tileserverSvSet *mbsh.ServiceSet
|
||||
mapPackSize int64
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
VersionHash string
|
||||
BuildTime *time.Time
|
||||
Port int
|
||||
DbPath string
|
||||
TilepackPath string
|
||||
ApkPath string
|
||||
ReinitDB bool
|
||||
MinZoom int
|
||||
DefaultCenter models.Coords
|
||||
MaxPhotoX int
|
||||
MaxPhotoY int
|
||||
PhotoQuality int
|
||||
Debug bool
|
||||
}
|
||||
|
||||
func New(config ServerConfig) *Server {
|
||||
@@ -51,10 +57,19 @@ func New(config ServerConfig) *Server {
|
||||
|
||||
func (s *Server) Run(ctx context.Context) {
|
||||
s.log = logrus.New()
|
||||
s.log.SetLevel(logrus.DebugLevel)
|
||||
if s.config.Debug {
|
||||
s.log.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
|
||||
s.dbAvailable.Store(false)
|
||||
|
||||
s.ctx = ctx
|
||||
s.setupDB()
|
||||
err := s.setupDB()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer s.cleanupDb()
|
||||
|
||||
s.setupTiles()
|
||||
|
||||
router := s.setupRouter()
|
||||
@@ -72,39 +87,13 @@ func (s *Server) Run(ctx context.Context) {
|
||||
|
||||
<-s.ctx.Done()
|
||||
s.log.Info("Shutting down server...")
|
||||
defer s.log.Info("Server exitting.")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
s.log.WithError(err).Fatal("Server forced to shutdown.")
|
||||
}
|
||||
s.dbpool.Close()
|
||||
|
||||
s.log.Info("Server exitting.")
|
||||
}
|
||||
|
||||
func (s *Server) getDbConn() *sqlite.Conn {
|
||||
conn := s.dbpool.Get(s.ctx)
|
||||
_, err := conn.Prep("PRAGMA foreign_keys = ON").Step()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
func (s *Server) setupDB() {
|
||||
dbpool, err := sqlitex.Open(fmt.Sprintf("file:%s", s.config.DbPath), 0, 10)
|
||||
if err != nil {
|
||||
s.log.WithError(err).Fatal("Failed to open/create DB.")
|
||||
}
|
||||
s.dbpool = dbpool
|
||||
|
||||
if s.config.ReinitDB {
|
||||
err = s.reinitDB()
|
||||
if err != nil {
|
||||
s.log.WithError(err).Fatal("init DB transaction failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) setupTiles() {
|
||||
@@ -135,12 +124,109 @@ func (s *Server) setupTiles() {
|
||||
s.mapPackSize = info.Size()
|
||||
}
|
||||
|
||||
func (s *Server) reinitDB() error {
|
||||
s.log.Info("Reinitializing DB.")
|
||||
func (s *Server) getLatestVersion(v *semver.Version) (*models.AppVersionInfo, error) {
|
||||
conn := s.getDbConn()
|
||||
defer s.dbpool.Put(conn)
|
||||
|
||||
return sqlitex.ExecScript(conn, initDB)
|
||||
var latest *models.AppVersionInfo
|
||||
versions, err := s.getAppVersions()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve app versions from db: %w", err)
|
||||
}
|
||||
|
||||
for _, ver := range versions {
|
||||
if (v == nil || v.LessThan(ver.Version)) && (latest == nil || latest.Version.LessThan(ver.Version)) {
|
||||
latest = ver
|
||||
}
|
||||
}
|
||||
|
||||
return latest, nil
|
||||
}
|
||||
|
||||
func (s *Server) getAppVersions() ([]*models.AppVersionInfo, error) {
|
||||
conn := s.getDbConn()
|
||||
defer s.dbpool.Put(conn)
|
||||
|
||||
versions := []*models.AppVersionInfo{}
|
||||
err := sqlitex.Execute(conn, "select version, address from app_versions", &sqlitex.ExecOptions{
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
verStr := stmt.ColumnText(0)
|
||||
addr := stmt.ColumnText(1)
|
||||
|
||||
ver, err := semver.NewVersion(verStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse version: %w", err)
|
||||
}
|
||||
|
||||
versions = append(versions, &models.AppVersionInfo{
|
||||
Version: *ver,
|
||||
Address: addr,
|
||||
})
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to insert/retrieve user from db: %w", err)
|
||||
}
|
||||
|
||||
return versions, nil
|
||||
}
|
||||
|
||||
func (s *Server) putAppVersion(versionInfo *models.AppVersionInfo) error {
|
||||
conn := s.getDbConn()
|
||||
defer s.dbpool.Put(conn)
|
||||
|
||||
err := sqlitex.Execute(conn, "insert into app_versions(version, address) values(?, ?) on conflict(version) do update set address = excluded.address", &sqlitex.ExecOptions{
|
||||
Args: []interface{}{versionInfo.Version.String(), versionInfo.Address},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert app version into db: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) getAppVersion(version string) (*models.AppVersionInfo, error) {
|
||||
conn := s.getDbConn()
|
||||
defer s.dbpool.Put(conn)
|
||||
|
||||
var v *models.AppVersionInfo
|
||||
err := sqlitex.Execute(conn, "select version, address from app_versions where version = ?", &sqlitex.ExecOptions{
|
||||
Args: []interface{}{version},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
verStr := stmt.ColumnText(0)
|
||||
ver, err := semver.NewVersion(verStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse version string %s from db: %w", verStr, err)
|
||||
}
|
||||
addr := stmt.ColumnText(1)
|
||||
v = &models.AppVersionInfo{
|
||||
Version: *ver,
|
||||
Address: addr,
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve app version %s from db: %w", version, err)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (s *Server) deleteAppVersion(version string) (bool, error) {
|
||||
conn := s.getDbConn()
|
||||
defer s.dbpool.Put(conn)
|
||||
|
||||
err := sqlitex.Execute(conn, "delete from app_versions where version = ?", &sqlitex.ExecOptions{
|
||||
Args: []interface{}{version},
|
||||
})
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to delete app version %s from db: %w", version, err)
|
||||
}
|
||||
n := conn.Changes()
|
||||
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
func (s *Server) handshake(hc models.HandshakeChallenge) (models.UserID, error) {
|
||||
@@ -152,11 +238,14 @@ func (s *Server) handshake(hc models.HandshakeChallenge) (models.UserID, error)
|
||||
|
||||
var id *int64
|
||||
if hc.Exists {
|
||||
err = sqlitex.Exec(conn, "select id from users where name = ?", func(stmt *sqlite.Stmt) error {
|
||||
id = ptrInt64(stmt.ColumnInt64(0))
|
||||
return nil
|
||||
}, hc.Name)
|
||||
if sqlite.ErrCode(err) != sqlite.SQLITE_OK {
|
||||
err = sqlitex.Execute(conn, "select id from users where name = ?", &sqlitex.ExecOptions{
|
||||
Args: []interface{}{hc.Name},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
id = ptr(stmt.ColumnInt64(0))
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if sqlite.ErrCode(err) != sqlite.ResultOK {
|
||||
return 0, err
|
||||
}
|
||||
if id == nil {
|
||||
@@ -166,17 +255,21 @@ func (s *Server) handshake(hc models.HandshakeChallenge) (models.UserID, error)
|
||||
return 0, errs.ErrAttemptedSystemUser
|
||||
}
|
||||
} else {
|
||||
err = sqlitex.Exec(conn, "insert into users(name) values(?)", func(stmt *sqlite.Stmt) error {
|
||||
id = ptrInt64(stmt.ColumnInt64(0))
|
||||
return nil
|
||||
}, hc.Name)
|
||||
if sqlite.ErrCode(err) == sqlite.SQLITE_CONSTRAINT_UNIQUE {
|
||||
err = sqlitex.Execute(conn, "insert into users(name) values(?)", &sqlitex.ExecOptions{
|
||||
Args: []interface{}{hc.Name},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
id = ptr(stmt.ColumnInt64(0))
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if sqlite.ErrCode(err) == sqlite.ResultConstraintUnique {
|
||||
return 0, errs.ErrUserAlreadyExists
|
||||
}
|
||||
if sqlite.ErrCode(err) != sqlite.SQLITE_OK {
|
||||
if sqlite.ErrCode(err) != sqlite.ResultOK {
|
||||
return 0, err
|
||||
}
|
||||
id = ptrInt64(conn.LastInsertRowID())
|
||||
id = ptr(conn.LastInsertRowID())
|
||||
s.requestCheckpoint()
|
||||
}
|
||||
return *id, nil
|
||||
}()
|
||||
@@ -187,40 +280,162 @@ func (s *Server) handshake(hc models.HandshakeChallenge) (models.UserID, error)
|
||||
return models.UserID(userID), nil
|
||||
}
|
||||
|
||||
func (s *Server) getData() (models.Data, error) {
|
||||
func (s *Server) getData(conn *sqlite.Conn) (data models.Data, err error) {
|
||||
err = s.deleteExpiredFeatures(conn)
|
||||
if err != nil {
|
||||
return models.Data{}, fmt.Errorf("failed to delete expired featured: %w", err)
|
||||
}
|
||||
people, err := s.getPeople(conn)
|
||||
if err != nil {
|
||||
return models.Data{}, fmt.Errorf("failed to retreive people: %w", err)
|
||||
}
|
||||
features, err := s.getFeatures(conn)
|
||||
if err != nil {
|
||||
return models.Data{}, fmt.Errorf("failed to retreive features: %w", err)
|
||||
}
|
||||
proposals, err := s.getProposals(conn)
|
||||
if err != nil {
|
||||
return models.Data{}, fmt.Errorf("failed to retrieve proposals: %w", err)
|
||||
}
|
||||
|
||||
return models.Data{
|
||||
Users: people,
|
||||
Features: features,
|
||||
Proposals: proposals,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) getDataOnly() (data models.Data, err error) {
|
||||
conn := s.getDbConn()
|
||||
defer s.dbpool.Put(conn)
|
||||
defer sqlitex.Save(conn)(&err)
|
||||
|
||||
data, err = s.getData(conn)
|
||||
if err != nil {
|
||||
return models.Data{}, fmt.Errorf("failed to get json data: %w", err)
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (s *Server) getDataWithPhotos() (file *os.File, err error) {
|
||||
conn := s.getDbConn()
|
||||
defer s.dbpool.Put(conn)
|
||||
|
||||
return func() (data models.Data, err error) {
|
||||
defer sqlitex.Save(conn)(&err)
|
||||
defer sqlitex.Save(conn)(&err)
|
||||
|
||||
err = s.deleteExpiredFeatures(conn)
|
||||
if err != nil {
|
||||
return models.Data{}, fmt.Errorf("failed to delete expired featured: %w", err)
|
||||
}
|
||||
people, err := s.getPeople(conn)
|
||||
if err != nil {
|
||||
return models.Data{}, fmt.Errorf("failed to retreive people: %w", err)
|
||||
}
|
||||
features, err := s.getFeatures(conn)
|
||||
if err != nil {
|
||||
return models.Data{}, fmt.Errorf("failed to retreive features: %w", err)
|
||||
}
|
||||
makePhotoFilename := func(id models.FeaturePhotoID) string {
|
||||
return fmt.Sprintf("img%d", id)
|
||||
}
|
||||
makeThumbnailFilename := func(id models.FeaturePhotoID) string {
|
||||
return fmt.Sprintf("thumb_%s", makePhotoFilename(id))
|
||||
}
|
||||
|
||||
return models.Data{
|
||||
Users: people,
|
||||
Features: features,
|
||||
}, nil
|
||||
}()
|
||||
data, err := s.getData(conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get json data: %w", err)
|
||||
}
|
||||
|
||||
data.PhotoMetadata = make(map[string]models.PhotoMetadata, 100)
|
||||
err = sqlitex.Execute(conn, "select id, content_type, length(contents) from feature_photos", &sqlitex.ExecOptions{
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
id := models.FeaturePhotoID(stmt.ColumnInt64(0))
|
||||
contentType := stmt.ColumnText(1)
|
||||
fileSize := stmt.ColumnInt64(2)
|
||||
data.PhotoMetadata[makePhotoFilename(id)] = models.PhotoMetadata{
|
||||
ContentType: contentType,
|
||||
Size: fileSize,
|
||||
ID: id,
|
||||
ThumbnailFilename: makeThumbnailFilename(id),
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to collect photo metadata: %w", err)
|
||||
}
|
||||
|
||||
dataBytes, err := json.MarshalIndent(&data, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal json data: %w", err)
|
||||
}
|
||||
|
||||
f, err := os.CreateTemp("", "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create temporary file: %w", err)
|
||||
}
|
||||
|
||||
zw := zip.NewWriter(f)
|
||||
defer zw.Close()
|
||||
w, err := zw.Create("data.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create data zip entry: %w", err)
|
||||
}
|
||||
_, err = w.Write(dataBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to write data zip entry: %w", err)
|
||||
}
|
||||
|
||||
err = sqlitex.Execute(conn, "select id from feature_photos fp where exists (select 1 from features f where f.id = fp.feature_id)", &sqlitex.ExecOptions{
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
id := stmt.ColumnInt64(0)
|
||||
|
||||
blob, err := conn.OpenBlob("", "feature_photos", "thumbnail_contents", id, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open photo ID %d thumbnail content blob: %w", id, err)
|
||||
}
|
||||
err = func() error {
|
||||
defer blob.Close()
|
||||
w, err := zw.Create(makeThumbnailFilename(models.FeaturePhotoID(id)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create zip entry: %w", err)
|
||||
}
|
||||
_, err = io.Copy(w, blob)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write zip entry: %w", err)
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write photo ID %d thumbnail: %w", id, err)
|
||||
}
|
||||
|
||||
blob, err = conn.OpenBlob("", "feature_photos", "contents", id, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open photo ID %d photo content blob: %w", id, err)
|
||||
}
|
||||
err = func() error {
|
||||
defer blob.Close()
|
||||
w, err := zw.Create(makePhotoFilename(models.FeaturePhotoID(id)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create zip entry: %w", err)
|
||||
}
|
||||
_, err = io.Copy(w, blob)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write zip entry: %w", err)
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write photo ID %d photo: %w", id, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to collect photo files: %w", err)
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (s *Server) update(data models.Update, photos map[string]models.Photo) error {
|
||||
s.log.Debugf("Updating data: %d created, %d created photos, %d updated, %d deleted, %d deleted photos, %d photo files", len(data.Create), len(data.CreatedPhotos), len(data.Update), len(data.Delete), len(data.DeletePhotos), len(photos))
|
||||
s.log.Debugf("Updating data: %d created, %d created photos, %d updated, %d deleted, %d deleted photos, %d photo files, %d proposals", len(data.Create), len(data.CreatedPhotos), len(data.Update), len(data.Delete), len(data.DeletePhotos), len(photos), len(data.Proposals))
|
||||
conn := s.getDbConn()
|
||||
defer s.dbpool.Put(conn)
|
||||
|
||||
return func() (err error) {
|
||||
defer sqlitex.Save(conn)(&err)
|
||||
defer sqlitex.Transaction(conn)(&err)
|
||||
|
||||
var createdIDMapping map[models.FeatureID]models.FeatureID
|
||||
if data.Create != nil {
|
||||
@@ -262,39 +477,45 @@ func (s *Server) update(data models.Update, photos map[string]models.Photo) erro
|
||||
return
|
||||
}
|
||||
}
|
||||
if data.Proposals != nil {
|
||||
err = s.addProposals(conn, data.Proposals)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to add proposals: %w", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *Server) deleteExpiredFeatures(conn *sqlite.Conn) error {
|
||||
stmt, err := conn.Prepare("delete from features where deadline < ?")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare statement: %w", err)
|
||||
}
|
||||
defer stmt.Finalize()
|
||||
|
||||
now := time.Now().Unix()
|
||||
err = sqlitex.Exec(conn, "delete from features where deadline < ?", func(stmt *sqlite.Stmt) error { return nil }, now)
|
||||
err := sqlitex.Execute(conn, "delete from features where deadline < ?", &sqlitex.ExecOptions{
|
||||
Args: []interface{}{now},
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete expired features: %w", err)
|
||||
}
|
||||
s.requestCheckpoint()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) getPeople(conn *sqlite.Conn) ([]models.User, error) {
|
||||
if conn == nil {
|
||||
conn = s.getDbConn()
|
||||
defer s.dbpool.Put(conn)
|
||||
defer s.returnDbConn(conn)
|
||||
}
|
||||
|
||||
users := make([]models.User, 0, 50)
|
||||
err := sqlitex.Exec(conn, "select id, name from users", func(stmt *sqlite.Stmt) error {
|
||||
users = append(users, models.User{
|
||||
ID: models.UserID(stmt.ColumnInt(0)),
|
||||
Name: stmt.ColumnText(1),
|
||||
})
|
||||
return nil
|
||||
err := sqlitex.Execute(conn, "select id, name from users", &sqlitex.ExecOptions{
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
users = append(users, models.User{
|
||||
ID: models.UserID(stmt.ColumnInt(0)),
|
||||
Name: stmt.ColumnText(1),
|
||||
})
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get users from db: %w", err)
|
||||
@@ -309,56 +530,58 @@ func (s *Server) getFeatures(conn *sqlite.Conn) ([]models.Feature, error) {
|
||||
}
|
||||
|
||||
features := make([]models.Feature, 0, 100)
|
||||
err := sqlitex.Exec(conn, `select f.id, f.owner_id, f.name, f.deadline, f.properties, f.geom, '[' || coalesce(group_concat(p.id, ', '), '') || ']'
|
||||
err := sqlitex.Execute(conn, `select f.id, f.owner_id, f.name, f.deadline, f.properties, f.geom, '[' || coalesce(group_concat(p.id, ', '), '') || ']'
|
||||
from features f
|
||||
left join feature_photos p on f.id = p.feature_id
|
||||
group by f.id, f.owner_id, f.name, f.properties, f.geom`, func(stmt *sqlite.Stmt) error {
|
||||
group by f.id, f.owner_id, f.name, f.properties, f.geom`, &sqlitex.ExecOptions{
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
|
||||
id := stmt.ColumnInt64(0)
|
||||
id := stmt.ColumnInt64(0)
|
||||
|
||||
ownerID := stmt.ColumnInt64(1)
|
||||
ownerID := stmt.ColumnInt64(1)
|
||||
|
||||
name := stmt.ColumnText(2)
|
||||
name := stmt.ColumnText(2)
|
||||
|
||||
var deadline *time.Time
|
||||
if stmt.ColumnType(3) != sqlite.SQLITE_NULL {
|
||||
dl := time.Unix(stmt.ColumnInt64(3), 0)
|
||||
deadline = &dl
|
||||
}
|
||||
var deadline *time.Time
|
||||
if stmt.ColumnType(3) != sqlite.TypeNull {
|
||||
dl := time.Unix(stmt.ColumnInt64(3), 0)
|
||||
deadline = &dl
|
||||
}
|
||||
|
||||
propertiesRaw := stmt.ColumnText(4)
|
||||
var properties map[string]interface{}
|
||||
err := json.Unmarshal([]byte(propertiesRaw), &properties)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse properties for feature id=%d: %w", id, err)
|
||||
}
|
||||
propertiesRaw := stmt.ColumnText(4)
|
||||
var properties map[string]interface{}
|
||||
err := json.Unmarshal([]byte(propertiesRaw), &properties)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse properties for feature id=%d: %w", id, err)
|
||||
}
|
||||
|
||||
geomRaw := stmt.ColumnText(5)
|
||||
var geom geojson.Geometry
|
||||
err = json.Unmarshal([]byte(geomRaw), &geom)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse geometry for feature id=%d: %w", id, err)
|
||||
}
|
||||
geomRaw := stmt.ColumnText(5)
|
||||
var geom geojson.Geometry
|
||||
err = json.Unmarshal([]byte(geomRaw), &geom)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse geometry for feature id=%d: %w", id, err)
|
||||
}
|
||||
|
||||
photosRaw := stmt.ColumnText(6)
|
||||
var photos []models.FeaturePhotoID
|
||||
err = json.Unmarshal([]byte(photosRaw), &photos)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse list of photo IDs: %w", err)
|
||||
}
|
||||
photosRaw := stmt.ColumnText(6)
|
||||
var photos []models.FeaturePhotoID
|
||||
err = json.Unmarshal([]byte(photosRaw), &photos)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse list of photo IDs: %w", err)
|
||||
}
|
||||
|
||||
feature := models.Feature{
|
||||
ID: models.FeatureID(id),
|
||||
OwnerID: models.UserID(ownerID),
|
||||
Name: name,
|
||||
Deadline: deadline,
|
||||
Properties: properties,
|
||||
Geometry: geom,
|
||||
PhotoIDs: photos,
|
||||
}
|
||||
feature := models.Feature{
|
||||
ID: models.FeatureID(id),
|
||||
OwnerID: models.UserID(ownerID),
|
||||
Name: name,
|
||||
Deadline: deadline,
|
||||
Properties: properties,
|
||||
Geometry: geom,
|
||||
PhotoIDs: photos,
|
||||
}
|
||||
|
||||
features = append(features, feature)
|
||||
return nil
|
||||
features = append(features, feature)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get users from db: %w", err)
|
||||
@@ -420,11 +643,12 @@ func (s *Server) addFeatures(conn *sqlite.Conn, features []models.Feature) (map[
|
||||
|
||||
localIDMapping[feature.ID] = models.FeatureID(conn.LastInsertRowID())
|
||||
}
|
||||
s.requestCheckpoint()
|
||||
return localIDMapping, nil
|
||||
}
|
||||
|
||||
func (s *Server) addPhotos(conn *sqlite.Conn, createdFeatureMapping, addedFeatureMapping map[models.FeatureID][]string, createdIDMapping map[models.FeatureID]models.FeatureID, photos map[string]models.Photo) error {
|
||||
stmt, err := conn.Prepare("insert into feature_photos(feature_id, content_type, file_contents) values(?, ?, ?)")
|
||||
stmt, err := conn.Prepare("insert into feature_photos(feature_id, thumbnail_content_type, content_type, thumbnail_contents, contents) values(?, ?, ?, ?, ?)")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare statement: %w", err)
|
||||
}
|
||||
@@ -436,6 +660,10 @@ func (s *Server) addPhotos(conn *sqlite.Conn, createdFeatureMapping, addedFeatur
|
||||
if !ok {
|
||||
return errs.NewErrPhotoNotProvided(photoName)
|
||||
}
|
||||
thumbnail, ok := photos[fmt.Sprintf("thumb_%s", photoName)]
|
||||
if !ok {
|
||||
return errs.NewErrPhotoThumbnailNotProvided(photoName)
|
||||
}
|
||||
if !checkImageContentType(photo.ContentType) {
|
||||
return errs.NewErrUnsupportedContentType(photoName)
|
||||
}
|
||||
@@ -449,24 +677,51 @@ func (s *Server) addPhotos(conn *sqlite.Conn, createdFeatureMapping, addedFeatur
|
||||
return fmt.Errorf("failed to clear bindings of prepared statement: %w", err)
|
||||
}
|
||||
|
||||
resizedPhoto, err := func() ([]byte, error) {
|
||||
defer photo.File.Close()
|
||||
|
||||
resized, err := resizePhoto(s.config.MaxPhotoX, s.config.MaxPhotoY, s.config.PhotoQuality, photo.File)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resized, nil
|
||||
}()
|
||||
|
||||
stmt.BindInt64(1, int64(featureID))
|
||||
stmt.BindText(2, photo.ContentType)
|
||||
stmt.BindZeroBlob(3, photo.Size)
|
||||
stmt.BindText(2, thumbnail.ContentType)
|
||||
stmt.BindText(3, "image/jpeg")
|
||||
stmt.BindZeroBlob(4, thumbnail.Size)
|
||||
stmt.BindZeroBlob(5, int64(len(resizedPhoto)))
|
||||
|
||||
_, err = stmt.Step()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to evaluate prepared statement: %w", err)
|
||||
}
|
||||
|
||||
blob, err := conn.OpenBlob("", "feature_photos", "file_contents", conn.LastInsertRowID(), true)
|
||||
blob, err := conn.OpenBlob("", "feature_photos", "thumbnail_contents", conn.LastInsertRowID(), true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open thumbnail content blob: %w", err)
|
||||
}
|
||||
err = func() error {
|
||||
defer blob.Close()
|
||||
defer thumbnail.File.Close()
|
||||
|
||||
_, err := io.Copy(blob, thumbnail.File)
|
||||
return err
|
||||
}()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to thumbnail content blob: %w", err)
|
||||
}
|
||||
|
||||
blob, err = conn.OpenBlob("", "feature_photos", "contents", conn.LastInsertRowID(), true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open photo content blob: %w", err)
|
||||
}
|
||||
err = func() error {
|
||||
defer blob.Close()
|
||||
defer photo.File.Close()
|
||||
|
||||
_, err := io.Copy(blob, photo.File)
|
||||
_, err = io.Copy(blob, bytes.NewBuffer(resizedPhoto))
|
||||
return err
|
||||
}()
|
||||
if err != nil {
|
||||
@@ -476,18 +731,26 @@ func (s *Server) addPhotos(conn *sqlite.Conn, createdFeatureMapping, addedFeatur
|
||||
return nil
|
||||
}
|
||||
|
||||
changed := false
|
||||
defer func() {
|
||||
if changed {
|
||||
s.requestCheckpoint()
|
||||
}
|
||||
}()
|
||||
for localFeatureID, photoNames := range createdFeatureMapping {
|
||||
featureID, ok := createdIDMapping[localFeatureID]
|
||||
if !ok {
|
||||
return errs.NewErrFeatureForPhotoNotExists(int64(localFeatureID))
|
||||
}
|
||||
err = uploadPhotos(featureID, photoNames)
|
||||
changed = true
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to upload photos for created features: %w", err)
|
||||
}
|
||||
}
|
||||
for featureID, photoNames := range addedFeatureMapping {
|
||||
err = uploadPhotos(featureID, photoNames)
|
||||
changed = true
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to upload photos for existing features: %w", err)
|
||||
}
|
||||
@@ -545,6 +808,7 @@ func (s *Server) updateFeatures(conn *sqlite.Conn, features []models.Feature) er
|
||||
return fmt.Errorf("failed to evaluate prepared statement: %w", err)
|
||||
}
|
||||
}
|
||||
s.requestCheckpoint()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -572,6 +836,7 @@ func (s *Server) deleteFeatures(conn *sqlite.Conn, featureIDs []models.FeatureID
|
||||
return fmt.Errorf("failed to evaluate prepared statement: %w", err)
|
||||
}
|
||||
}
|
||||
s.requestCheckpoint()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -599,6 +864,7 @@ func (s *Server) deletePhotos(conn *sqlite.Conn, photoIDs []models.FeaturePhotoI
|
||||
return fmt.Errorf("failed to evaluate prepared statement: %w", err)
|
||||
}
|
||||
}
|
||||
s.requestCheckpoint()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -609,16 +875,19 @@ func (s *Server) getPhoto(featureID models.FeatureID, photoID models.FeaturePhot
|
||||
var contentType *string = nil
|
||||
var data []byte = nil
|
||||
found := false
|
||||
err := sqlitex.Exec(conn, "select content_type, file_contents from feature_photos where id = ? and feature_id = ?", func(stmt *sqlite.Stmt) error {
|
||||
if found {
|
||||
return fmt.Errorf("multiple photos returned for feature id %d, photo id %d", featureID, photoID)
|
||||
}
|
||||
contentType = ptrString(stmt.ColumnText(0))
|
||||
data = make([]byte, stmt.ColumnLen(1))
|
||||
stmt.ColumnBytes(1, data)
|
||||
found = true
|
||||
return nil
|
||||
}, photoID, featureID)
|
||||
err := sqlitex.Execute(conn, "select content_type, contents from feature_photos where id = ? and feature_id = ?", &sqlitex.ExecOptions{
|
||||
Args: []interface{}{photoID, featureID},
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
if found {
|
||||
return fmt.Errorf("multiple photos returned for feature id %d, photo id %d", featureID, photoID)
|
||||
}
|
||||
contentType = ptr(stmt.ColumnText(0))
|
||||
data = make([]byte, stmt.ColumnLen(1))
|
||||
stmt.ColumnBytes(1, data)
|
||||
found = true
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("photo db query failed: %w", err)
|
||||
}
|
||||
@@ -629,3 +898,95 @@ func (s *Server) getPhoto(featureID models.FeatureID, photoID models.FeaturePhot
|
||||
|
||||
return data, *contentType, nil
|
||||
}
|
||||
|
||||
func (s *Server) addProposals(conn *sqlite.Conn, proposals []models.Proposal) error {
|
||||
stmt, err := conn.Prepare("insert into proposals(owner_id, description, how) values(?, ?, ?)")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare statement: %w", err)
|
||||
}
|
||||
defer stmt.Finalize()
|
||||
|
||||
for _, proposal := range proposals {
|
||||
err := stmt.Reset()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to reset prepared statement: %w", err)
|
||||
}
|
||||
err = stmt.ClearBindings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to clear bindings of prepared statement: %w", err)
|
||||
}
|
||||
|
||||
stmt.BindInt64(1, int64(proposal.OwnerID))
|
||||
stmt.BindText(2, proposal.Description)
|
||||
stmt.BindText(3, proposal.How)
|
||||
|
||||
_, err = stmt.Step()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to evaluate prepared statement: %w", err)
|
||||
}
|
||||
}
|
||||
s.requestCheckpoint()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) getProposals(conn *sqlite.Conn) ([]models.Proposal, error) {
|
||||
if conn == nil {
|
||||
conn = s.getDbConn()
|
||||
defer s.dbpool.Put(conn)
|
||||
}
|
||||
|
||||
proposals := make([]models.Proposal, 0, 100)
|
||||
err := sqlitex.Execute(conn, "select owner_id, description, how from proposals", &sqlitex.ExecOptions{
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
proposals = append(proposals, models.Proposal{
|
||||
OwnerID: models.UserID(stmt.ColumnInt(0)),
|
||||
Description: stmt.ColumnText(1),
|
||||
How: stmt.ColumnText(2),
|
||||
})
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get proposals from db: %w", err)
|
||||
}
|
||||
return proposals, nil
|
||||
}
|
||||
|
||||
func (s *Server) getUsageInfo(conn *sqlite.Conn) ([]models.UserInfo, error) {
|
||||
if conn == nil {
|
||||
conn = s.getDbConn()
|
||||
defer s.returnDbConn(conn)
|
||||
}
|
||||
|
||||
users := make([]models.UserInfo, 0, 50)
|
||||
err := sqlitex.Execute(conn, "select id, name, app_version, last_seen_time, last_upload_time, last_download_time from users", &sqlitex.ExecOptions{
|
||||
ResultFunc: func(stmt *sqlite.Stmt) error {
|
||||
ver, _ := semver.NewVersion(stmt.ColumnText(2))
|
||||
var lstp, lutp, ldtp *time.Time
|
||||
if stmt.ColumnType(3) != sqlite.TypeNull {
|
||||
lstp = ptr(time.Unix(stmt.ColumnInt64(3), 0))
|
||||
}
|
||||
if stmt.ColumnType(4) != sqlite.TypeNull {
|
||||
lutp = ptr(time.Unix(stmt.ColumnInt64(3), 0))
|
||||
}
|
||||
if stmt.ColumnType(5) != sqlite.TypeNull {
|
||||
ldtp = ptr(time.Unix(stmt.ColumnInt64(3), 0))
|
||||
}
|
||||
users = append(users, models.UserInfo{
|
||||
User: models.User{
|
||||
ID: models.UserID(stmt.ColumnInt(0)),
|
||||
Name: stmt.ColumnText(1),
|
||||
},
|
||||
AppVersion: ver,
|
||||
LastSeenTime: lstp,
|
||||
LastUploadTime: lutp,
|
||||
LastDownloadTime: ldtp,
|
||||
})
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get users from db: %w", err)
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
DROP TABLE IF EXISTS users;
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
CREATE TABLE users (
|
||||
id integer PRIMARY KEY AUTOINCREMENT,
|
||||
name text NOT NULL UNIQUE
|
||||
);
|
||||
INSERT INTO users(id, name) VALUES(0, 'system');
|
||||
|
||||
DROP TABLE IF EXISTS features;
|
||||
CREATE TABLE IF NOT EXISTS features (
|
||||
CREATE TABLE features (
|
||||
id integer PRIMARY KEY AUTOINCREMENT,
|
||||
owner_id integer NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name text NOT NULL,
|
||||
deadline text,
|
||||
deadline integer,
|
||||
properties text NOT NULL,
|
||||
geom text NOT NULL
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS feature_photos;
|
||||
CREATE TABLE IF NOT EXISTS feature_photos (
|
||||
CREATE TABLE feature_photos (
|
||||
id integer PRIMARY KEY AUTOINCREMENT,
|
||||
feature_id integer NOT NULL REFERENCES features(id) ON DELETE CASCADE,
|
||||
thumbnail_content_type text NOT NULL,
|
||||
content_type text NOT NULL,
|
||||
file_contents blob NOT NULL
|
||||
);
|
||||
thumbnail_contents blob NOT NULL,
|
||||
contents blob NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE proposals (
|
||||
owner_id integer NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||
description text NOT NULL,
|
||||
how text NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,4 @@
|
||||
CREATE TABLE app_versions (
|
||||
version text NOT NULL PRIMARY KEY,
|
||||
address text NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE users ADD COLUMN app_version text;
|
||||
ALTER TABLE users ADD COLUMN last_seen_time integer;
|
||||
ALTER TABLE users ADD COLUMN last_upload_time integer;
|
||||
ALTER TABLE users ADD COLUMN last_download_time integer;
|
||||
+70
-7
@@ -1,19 +1,28 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"golang.org/x/image/draw"
|
||||
|
||||
"cernobor.cz/oko-server/models"
|
||||
)
|
||||
|
||||
func ptrInt(x int) *int {
|
||||
func ptr[T any](x T) *T {
|
||||
return &x
|
||||
}
|
||||
|
||||
func ptrInt64(x int64) *int64 {
|
||||
return &x
|
||||
}
|
||||
|
||||
func ptrString(x string) *string {
|
||||
return &x
|
||||
func Map[T any, U any](f func(T) U, x []T) []U {
|
||||
res := make([]U, len(x))
|
||||
for i, e := range x {
|
||||
res[i] = f(e)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
var contentTypes map[string]struct{} = map[string]struct{}{"image/jpeg": {}, "image/png": {}}
|
||||
@@ -32,3 +41,57 @@ func isUniqueFeatureID(features []models.Feature) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func resizePhoto(maxX, maxY, quality int, data io.Reader) ([]byte, error) {
|
||||
src, _, err := image.Decode(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode image: %w", err)
|
||||
}
|
||||
|
||||
if maxX == 0 && maxY == 0 {
|
||||
output := &bytes.Buffer{}
|
||||
jpeg.Encode(output, src, &jpeg.Options{
|
||||
Quality: 90,
|
||||
})
|
||||
|
||||
return output.Bytes(), nil
|
||||
}
|
||||
|
||||
var dst draw.Image
|
||||
srcX := src.Bounds().Max.X
|
||||
srcY := src.Bounds().Max.Y
|
||||
srcRatio := float64(srcX) / float64(srcY)
|
||||
if maxX == 0 && srcY > maxY {
|
||||
newX := int(math.Round(float64(maxY) * srcRatio))
|
||||
newY := maxY
|
||||
dst = image.NewRGBA(image.Rect(0, 0, newX, newY))
|
||||
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
|
||||
} else if maxY == 0 && srcX > maxX {
|
||||
newX := maxX
|
||||
newY := int(math.Round(float64(maxX) / srcRatio))
|
||||
dst = image.NewRGBA(image.Rect(0, 0, newX, newY))
|
||||
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
|
||||
} else if srcX > maxX || srcY > maxY {
|
||||
tgtRatio := float64(maxX) / float64(maxY)
|
||||
var newX, newY int
|
||||
if srcRatio > tgtRatio {
|
||||
newX = maxX
|
||||
newY = int(math.Round(float64(maxX) / srcRatio))
|
||||
} else {
|
||||
newX = int(math.Round(float64(maxY) * srcRatio))
|
||||
newY = maxY
|
||||
}
|
||||
dst = image.NewRGBA(image.Rect(0, 0, newX, newY))
|
||||
draw.CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Over, nil)
|
||||
} else {
|
||||
dst = image.NewRGBA(image.Rect(0, 0, srcX, srcY))
|
||||
draw.Copy(dst, image.Point{X: 0, Y: 0}, src, src.Bounds(), draw.Over, nil)
|
||||
}
|
||||
|
||||
output := &bytes.Buffer{}
|
||||
jpeg.Encode(output, dst, &jpeg.Options{
|
||||
Quality: quality,
|
||||
})
|
||||
|
||||
return output.Bytes(), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user