First checkpoint
This commit is contained in:
38
server/categories/create.go
Normal file
38
server/categories/create.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package categories
|
||||
|
||||
import (
|
||||
"gis/app"
|
||||
"gis/server/httputil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type CreateCategoryRequest struct {
|
||||
Name string `json:"name" validate:"required,max=255"`
|
||||
Description string `json:"description" validate:"required"`
|
||||
}
|
||||
|
||||
func createCategoryRoute(application *app.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := httputil.DecodeJSON[CreateCategoryRequest](w, r)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := application.Validator.Struct(req); err != nil {
|
||||
httputil.WriteValidationErrors(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = application.Db.Exec(application.Ctx,
|
||||
"INSERT INTO categories (name, description) VALUES ($1, $2)",
|
||||
req.Name, req.Description,
|
||||
)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
}
|
||||
27
server/categories/delete.go
Normal file
27
server/categories/delete.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package categories
|
||||
|
||||
import (
|
||||
"gis/app"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func deleteCategoryRoute(application *app.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
|
||||
tag, err := application.Db.Exec(application.Ctx,
|
||||
"DELETE FROM categories WHERE id=$1",
|
||||
id,
|
||||
)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
39
server/categories/index.go
Normal file
39
server/categories/index.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package categories
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gis/app"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func listCategoriesRoute(application *app.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
rows, err := application.Db.Query(application.Ctx, "SELECT id, name FROM categories")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
cats := make([]Category, 0)
|
||||
for rows.Next() {
|
||||
var c Category
|
||||
if err := rows.Scan(&c.ID, &c.Name); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
cats = append(cats, c)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(map[string][]Category{"data": cats}); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
13
server/categories/routes.go
Normal file
13
server/categories/routes.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package categories
|
||||
|
||||
import (
|
||||
"gis/app"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func AddCategoriesRoutes(application *app.App, mux *http.ServeMux) {
|
||||
mux.HandleFunc("GET /categories", listCategoriesRoute(application))
|
||||
mux.HandleFunc("POST /categories", createCategoryRoute(application))
|
||||
mux.HandleFunc("PUT /categories/{id}", updateCategoryRoute(application))
|
||||
mux.HandleFunc("DELETE /categories/{id}", deleteCategoryRoute(application))
|
||||
}
|
||||
6
server/categories/types.go
Normal file
6
server/categories/types.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package categories
|
||||
|
||||
type Category struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
44
server/categories/update.go
Normal file
44
server/categories/update.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package categories
|
||||
|
||||
import (
|
||||
"gis/app"
|
||||
"gis/server/httputil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type UpdateCategoryRequest struct {
|
||||
Name string `json:"name" validate:"required,max=255"`
|
||||
Description string `json:"description" validate:"required"`
|
||||
}
|
||||
|
||||
func updateCategoryRoute(application *app.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
|
||||
req, err := httputil.DecodeJSON[UpdateCategoryRequest](w, r)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := application.Validator.Struct(req); err != nil {
|
||||
httputil.WriteValidationErrors(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
tag, err := application.Db.Exec(application.Ctx,
|
||||
"UPDATE categories SET name=$1, description=$2, updated_at=now() WHERE id=$3",
|
||||
req.Name, req.Description, id,
|
||||
)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
30
server/datasets/create.go
Normal file
30
server/datasets/create.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package datasets
|
||||
|
||||
import (
|
||||
"gis/app"
|
||||
"gis/server/httputil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type CreateDatasetRequest struct {
|
||||
Name string `json:"name" validate:"required,max=255"`
|
||||
Description string `json:"description" validate:"required"`
|
||||
}
|
||||
|
||||
func createDatasetRoute(application *app.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := httputil.DecodeJSON[CreateDatasetRequest](w, r)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := application.Validator.Struct(req); err != nil {
|
||||
httputil.WriteValidationErrors(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
}
|
||||
27
server/datasets/delete.go
Normal file
27
server/datasets/delete.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package datasets
|
||||
|
||||
import (
|
||||
"gis/app"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func deleteDatasetRoute(application *app.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
|
||||
tag, err := application.Db.Exec(application.Ctx,
|
||||
"DELETE FROM datasets WHERE id=$1",
|
||||
id,
|
||||
)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
45
server/datasets/index.go
Normal file
45
server/datasets/index.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package datasets
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gis/app"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func listDatasetsRoute(application *app.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
rows, err := application.Db.Query(application.Ctx, "select id, name from datasets")
|
||||
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
datasets := make([]Dataset, 0)
|
||||
|
||||
for rows.Next() {
|
||||
var dataset Dataset
|
||||
if err := rows.Scan(&dataset.ID, &dataset.Name); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
datasets = append(datasets, dataset)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(map[string][]Dataset{"data": datasets})
|
||||
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
13
server/datasets/routes.go
Normal file
13
server/datasets/routes.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package datasets
|
||||
|
||||
import (
|
||||
"gis/app"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func AddDatasetsRoutes(application *app.App, mux *http.ServeMux) {
|
||||
mux.HandleFunc("GET /datasets", listDatasetsRoute(application))
|
||||
mux.HandleFunc("POST /datasets", createDatasetRoute(application))
|
||||
mux.HandleFunc("PUT /datasets/{id}", updateDatasetRoute(application))
|
||||
mux.HandleFunc("DELETE /datasets/{id}", deleteDatasetRoute(application))
|
||||
}
|
||||
6
server/datasets/types.go
Normal file
6
server/datasets/types.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package datasets
|
||||
|
||||
type Dataset struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
44
server/datasets/update.go
Normal file
44
server/datasets/update.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package datasets
|
||||
|
||||
import (
|
||||
"gis/app"
|
||||
"gis/server/httputil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type UpdateDatasetRequest struct {
|
||||
Name string `json:"name" validate:"required,max=255"`
|
||||
Description string `json:"description" validate:"required"`
|
||||
}
|
||||
|
||||
func updateDatasetRoute(application *app.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
|
||||
req, err := httputil.DecodeJSON[UpdateDatasetRequest](w, r)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := application.Validator.Struct(req); err != nil {
|
||||
httputil.WriteValidationErrors(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
tag, err := application.Db.Exec(application.Ctx,
|
||||
"UPDATE datasets SET name=$1, description=$2, updated_at=now() WHERE id=$3",
|
||||
req.Name, req.Description, id,
|
||||
)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
49
server/files/delete.go
Normal file
49
server/files/delete.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gis/app"
|
||||
"gis/server/httputil"
|
||||
"net/http"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/minio/minio-go/v7"
|
||||
)
|
||||
|
||||
func deleteFileRoute(application *app.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("file_id")
|
||||
|
||||
var storageKey string
|
||||
err := application.Db.QueryRow(r.Context(),
|
||||
"SELECT storage_key FROM files WHERE id=$1",
|
||||
id,
|
||||
).Scan(&storageKey)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
httputil.WriteJSON(w, http.StatusNotFound, map[string]string{"error": "not found"})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := application.S3.RemoveObject(
|
||||
r.Context(),
|
||||
application.Cfg.S3Bucket,
|
||||
storageKey,
|
||||
minio.RemoveObjectOptions{},
|
||||
); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = application.Db.Exec(r.Context(), "DELETE FROM files WHERE id=$1", id)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
39
server/files/get.go
Normal file
39
server/files/get.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gis/app"
|
||||
"gis/server/httputil"
|
||||
"net/http"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
func getFileRoute(application *app.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("file_id")
|
||||
|
||||
var gf GeoFile
|
||||
err := application.Db.QueryRow(r.Context(),
|
||||
`SELECT id, filename, file_type, validation_status,
|
||||
validation_error, kato_column, crs, feature_count,
|
||||
uploaded_at, updated_at
|
||||
FROM files WHERE id=$1`,
|
||||
id,
|
||||
).Scan(
|
||||
&gf.ID, &gf.Filename, &gf.FileType, &gf.ValidationStatus,
|
||||
&gf.ValidationError, &gf.KatoColumn, &gf.CRS, &gf.FeatureCount,
|
||||
&gf.UploadedAt, &gf.UpdatedAt,
|
||||
)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
httputil.WriteJSON(w, http.StatusNotFound, map[string]string{"error": "not found"})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.WriteJSON(w, http.StatusOK, gf)
|
||||
}
|
||||
}
|
||||
12
server/files/routes.go
Normal file
12
server/files/routes.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"gis/app"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func AddFilesRoutes(application *app.App, mux *http.ServeMux) {
|
||||
mux.HandleFunc("POST /files", uploadFileRoute(application))
|
||||
mux.HandleFunc("GET /files/{file_id}", getFileRoute(application))
|
||||
mux.HandleFunc("DELETE /files/{file_id}", deleteFileRoute(application))
|
||||
}
|
||||
40
server/files/types.go
Normal file
40
server/files/types.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package files
|
||||
|
||||
import "time"
|
||||
|
||||
type FileType string
|
||||
|
||||
const (
|
||||
FileTypeVectorWithTable FileType = "vector_with_table"
|
||||
FileTypeVector FileType = "vector"
|
||||
FileTypeRaster FileType = "raster"
|
||||
)
|
||||
|
||||
type ValidationStatus string
|
||||
|
||||
const (
|
||||
ValidationStatusPending ValidationStatus = "pending"
|
||||
ValidationStatusValid ValidationStatus = "valid"
|
||||
ValidationStatusFailed ValidationStatus = "failed"
|
||||
)
|
||||
|
||||
type GeoFile struct {
|
||||
ID string `json:"id"`
|
||||
Filename string `json:"filename"`
|
||||
FileType FileType `json:"file_type"`
|
||||
ValidationStatus ValidationStatus `json:"validation_status"`
|
||||
ValidationError *string `json:"validation_error"`
|
||||
KatoColumn *string `json:"kato_column"`
|
||||
CRS *string `json:"crs"`
|
||||
FeatureCount *int `json:"feature_count"`
|
||||
UploadedAt time.Time `json:"uploaded_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
var allowedExtensions = map[string]FileType{
|
||||
".zip": FileTypeVectorWithTable,
|
||||
".geojson": FileTypeVectorWithTable,
|
||||
".gpkg": FileTypeVectorWithTable,
|
||||
".tif": FileTypeRaster,
|
||||
".tiff": FileTypeRaster,
|
||||
}
|
||||
85
server/files/upload.go
Normal file
85
server/files/upload.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gis/app"
|
||||
"gis/server/httputil"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
)
|
||||
|
||||
func uploadFileRoute(application *app.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseMultipartForm(64 << 20); err != nil {
|
||||
httputil.WriteJSON(w, http.StatusBadRequest, map[string]string{"error": "request too large or not multipart"})
|
||||
return
|
||||
}
|
||||
|
||||
rawFileType := r.FormValue("file_type")
|
||||
if rawFileType == "" {
|
||||
httputil.WriteJSON(w, http.StatusUnprocessableEntity, map[string]string{"error": "file_type is required"})
|
||||
return
|
||||
}
|
||||
ft := FileType(rawFileType)
|
||||
if ft != FileTypeVectorWithTable && ft != FileTypeVector && ft != FileTypeRaster {
|
||||
httputil.WriteJSON(w, http.StatusUnprocessableEntity, map[string]string{"error": "invalid file_type"})
|
||||
return
|
||||
}
|
||||
|
||||
f, header, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
httputil.WriteJSON(w, http.StatusBadRequest, map[string]string{"error": "file is required"})
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(header.Filename))
|
||||
if ext == "" {
|
||||
httputil.WriteJSON(w, http.StatusBadRequest, map[string]string{"error": "unsupported file format"})
|
||||
return
|
||||
}
|
||||
if _, ok := allowedExtensions[ext]; !ok {
|
||||
httputil.WriteJSON(w, http.StatusBadRequest, map[string]string{"error": "unsupported file format"})
|
||||
return
|
||||
}
|
||||
|
||||
storageKey := fmt.Sprintf("%d_%s", time.Now().UnixNano(), header.Filename)
|
||||
|
||||
_, err = application.S3.PutObject(
|
||||
r.Context(),
|
||||
application.Cfg.S3Bucket,
|
||||
storageKey,
|
||||
f,
|
||||
header.Size,
|
||||
minio.PutObjectOptions{ContentType: header.Header.Get("Content-Type")},
|
||||
)
|
||||
if err != nil {
|
||||
httputil.WriteJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to store file"})
|
||||
return
|
||||
}
|
||||
|
||||
var gf GeoFile
|
||||
err = application.Db.QueryRow(r.Context(),
|
||||
`INSERT INTO files (filename, storage_key, file_type)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, filename, file_type, validation_status,
|
||||
validation_error, kato_column, crs, feature_count,
|
||||
uploaded_at, updated_at`,
|
||||
header.Filename, storageKey, ft,
|
||||
).Scan(
|
||||
&gf.ID, &gf.Filename, &gf.FileType, &gf.ValidationStatus,
|
||||
&gf.ValidationError, &gf.KatoColumn, &gf.CRS, &gf.FeatureCount,
|
||||
&gf.UploadedAt, &gf.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
httputil.WriteJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to save file record"})
|
||||
return
|
||||
}
|
||||
|
||||
httputil.WriteJSON(w, http.StatusAccepted, gf)
|
||||
}
|
||||
}
|
||||
18
server/helpers.go
Normal file
18
server/helpers.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"gis/server/httputil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func writeJSON(w http.ResponseWriter, status int, data any) {
|
||||
httputil.WriteJSON(w, status, data)
|
||||
}
|
||||
|
||||
func decodeJSON[T any](w http.ResponseWriter, r *http.Request) (T, error) {
|
||||
return httputil.DecodeJSON[T](w, r)
|
||||
}
|
||||
|
||||
func writeValidationErrors(w http.ResponseWriter, err error) {
|
||||
httputil.WriteValidationErrors(w, err)
|
||||
}
|
||||
62
server/httputil/httputil.go
Normal file
62
server/httputil/httputil.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
func WriteJSON(w http.ResponseWriter, status int, data any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
_ = json.NewEncoder(w).Encode(data)
|
||||
}
|
||||
|
||||
func DecodeJSON[T any](w http.ResponseWriter, r *http.Request) (T, error) {
|
||||
var v T
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
|
||||
|
||||
dec := json.NewDecoder(r.Body)
|
||||
dec.DisallowUnknownFields()
|
||||
|
||||
if err := dec.Decode(&v); err != nil {
|
||||
return v, err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func WriteValidationErrors(w http.ResponseWriter, err error) {
|
||||
var ve validator.ValidationErrors
|
||||
if !errors.As(err, &ve) {
|
||||
WriteJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||
return
|
||||
}
|
||||
|
||||
problems := make(map[string]string, len(ve))
|
||||
for _, fe := range ve {
|
||||
problems[fe.Field()] = messageForTag(fe)
|
||||
}
|
||||
WriteJSON(w, http.StatusBadRequest, map[string]any{"errors": problems})
|
||||
}
|
||||
|
||||
func messageForTag(fe validator.FieldError) string {
|
||||
switch fe.Tag() {
|
||||
case "required":
|
||||
return "is required"
|
||||
case "email":
|
||||
return "must be a valid email address"
|
||||
case "min":
|
||||
return fmt.Sprintf("must be at least %s characters", fe.Param())
|
||||
case "max":
|
||||
return fmt.Sprintf("must be at most %s characters", fe.Param())
|
||||
case "gte":
|
||||
return fmt.Sprintf("must be %s or greater", fe.Param())
|
||||
case "lte":
|
||||
return fmt.Sprintf("must be %s or less", fe.Param())
|
||||
default:
|
||||
return "is invalid"
|
||||
}
|
||||
}
|
||||
20
server/router.go
Normal file
20
server/router.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"gis/app"
|
||||
"gis/server/categories"
|
||||
"gis/server/datasets"
|
||||
"gis/server/files"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func AppRouter(application *app.App) http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.Handle("GET /up", upRoute(application))
|
||||
datasets.AddDatasetsRoutes(application, mux)
|
||||
categories.AddCategoriesRoutes(application, mux)
|
||||
files.AddFilesRoutes(application, mux)
|
||||
|
||||
return mux
|
||||
}
|
||||
14
server/up.go
Normal file
14
server/up.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gis/app"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func upRoute(application *app.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user