init
This commit is contained in:
commit
2e0c9b34d7
|
@ -0,0 +1,24 @@
|
||||||
|
# Nuxt dev/build outputs
|
||||||
|
.output
|
||||||
|
.data
|
||||||
|
.nuxt
|
||||||
|
.nitro
|
||||||
|
.cache
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Node dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.fleet
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Local env files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
// 使用 IntelliSense 了解相关属性。
|
||||||
|
// 悬停以查看现有属性的描述。
|
||||||
|
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Package in Debug Mode",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "debug",
|
||||||
|
"program": "./main.go",
|
||||||
|
"args": ["start"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Launch Package in Prod Mode",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "debug",
|
||||||
|
"preLaunchTask": "prepare",
|
||||||
|
"program": "./main.go",
|
||||||
|
"args": ["start", "--mode", "prod"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"sqltools.connections": [
|
||||||
|
{
|
||||||
|
"previewLimit": 50,
|
||||||
|
"server": "localhost",
|
||||||
|
"port": 5432,
|
||||||
|
"driver": "PostgreSQL",
|
||||||
|
"name": "local-maptile",
|
||||||
|
"database": "maptile",
|
||||||
|
"username": "maptile",
|
||||||
|
"password": "maptile_passwd"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "prepare-swaggo",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "go install github.com/swaggo/swag/cmd/swag@latest"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "prepare-go",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "go mod tidy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "swaggo-init",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "swag init --ot json"
|
||||||
|
}, {
|
||||||
|
"label": "prepare",
|
||||||
|
"dependsOn":[
|
||||||
|
"prepare-go",
|
||||||
|
"swaggo-init",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
echoSwagger "github.com/swaggo/echo-swagger"
|
||||||
|
|
||||||
|
_ "git.zhouxhere.com/zhouxhere/maptile/docs"
|
||||||
|
"git.zhouxhere.com/zhouxhere/maptile/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type API struct {
|
||||||
|
*echo.Echo
|
||||||
|
store *store.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPI(e *echo.Echo, s *store.Store) {
|
||||||
|
api := &API{e, s}
|
||||||
|
|
||||||
|
api.Validator = NewCustomValidator()
|
||||||
|
|
||||||
|
api.Use(middleware.Recover())
|
||||||
|
|
||||||
|
api.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{LogStatus: true,
|
||||||
|
LogURI: true,
|
||||||
|
LogError: true,
|
||||||
|
HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code
|
||||||
|
LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
|
||||||
|
if v.Error != nil {
|
||||||
|
slog.LogAttrs(context.Background(), slog.LevelError, "REQUEST", slog.String("method", v.Method), slog.String("uri", v.URI), slog.Int("status", v.Status), slog.String("latency", v.Latency.String()), slog.String("error", v.Error.Error()))
|
||||||
|
} else {
|
||||||
|
slog.LogAttrs(context.Background(), slog.LevelInfo, "REQUEST", slog.String("method", v.Method), slog.String("uri", v.URI), slog.Int("status", v.Status), slog.String("latency", v.Latency.String()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
api.HTTPErrorHandler = func(err error, c echo.Context) {
|
||||||
|
req := c.Request()
|
||||||
|
slog.LogAttrs(context.Background(), slog.LevelError, "REQUEST", slog.String("method", req.Method), slog.String("uri", req.RequestURI), slog.String("error", err.Error()))
|
||||||
|
c.JSON(http.StatusOK, Response[interface{}]{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
Message: err.Error(),
|
||||||
|
Data: nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// swagger
|
||||||
|
api.GET("/swagger/*", echoSwagger.WrapHandler)
|
||||||
|
api.GET("/swagger", func(c echo.Context) error {
|
||||||
|
return c.Redirect(http.StatusMovedPermanently, "/swagger/index.html")
|
||||||
|
})
|
||||||
|
api.Register()
|
||||||
|
|
||||||
|
// for _, route := range api.Routes() {
|
||||||
|
// fmt.Println(route.Method, route.Path)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) Register() {
|
||||||
|
v := reflect.ValueOf(a)
|
||||||
|
t := reflect.TypeOf(a)
|
||||||
|
|
||||||
|
group := a.Group("/api/v1")
|
||||||
|
|
||||||
|
for i := 0; i < v.NumMethod(); i++ {
|
||||||
|
method := v.Method(i)
|
||||||
|
methodType := t.Method(i)
|
||||||
|
methodName := methodType.Name
|
||||||
|
|
||||||
|
// 使用正则表达式匹配方法名
|
||||||
|
re := regexp.MustCompile(`^(Post|Get|Put|Delete)([A-Za-z]+)(By[A-Za-z]+)?$`)
|
||||||
|
matches := re.FindStringSubmatch(methodName)
|
||||||
|
if len(matches) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
action := matches[1]
|
||||||
|
route := strings.ToLower(matches[2])
|
||||||
|
if matches[3] != "" {
|
||||||
|
param := strings.ToLower(matches[3][2:])
|
||||||
|
route = "/" + route + "/:" + param
|
||||||
|
} else {
|
||||||
|
route = "/" + route
|
||||||
|
}
|
||||||
|
|
||||||
|
switch action {
|
||||||
|
case "Post":
|
||||||
|
group.POST(route, method.Interface().(func(echo.Context) error))
|
||||||
|
case "Get":
|
||||||
|
group.GET(route, method.Interface().(func(echo.Context) error))
|
||||||
|
case "Put":
|
||||||
|
group.PUT(route, method.Interface().(func(echo.Context) error))
|
||||||
|
case "Delete":
|
||||||
|
group.DELETE(route, method.Interface().(func(echo.Context) error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Response[T any] struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data T `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Pagination[T any] struct {
|
||||||
|
Page int `json:"page"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
Data []T `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomValidator struct {
|
||||||
|
validator *validator.Validate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *CustomValidator) Validate(i interface{}) error {
|
||||||
|
return cv.validator.Struct(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCustomValidator() *CustomValidator {
|
||||||
|
return &CustomValidator{validator: validator.New()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BindAndValidate(c echo.Context, i interface{}) error {
|
||||||
|
if err := c.Bind(i); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Validate(i); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Success[T any](c echo.Context, data T, message string) error {
|
||||||
|
return c.JSON(http.StatusOK, Response[T]{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
Message: message,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Failed(c echo.Context, message string) error {
|
||||||
|
return c.JSON(http.StatusOK, Response[interface{}]{
|
||||||
|
Code: http.StatusInternalServerError,
|
||||||
|
Message: message,
|
||||||
|
Data: nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Error(c echo.Context, code int, message string) error {
|
||||||
|
return c.JSON(code, Response[interface{}]{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
Data: nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Paginate[T any](c echo.Context, data []T, page, size, total int, message string) error {
|
||||||
|
return Success(c, Pagination[T]{
|
||||||
|
Page: page,
|
||||||
|
Size: size,
|
||||||
|
Total: total,
|
||||||
|
Data: data,
|
||||||
|
}, message,
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.zhouxhere.com/zhouxhere/maptile/model"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListFeature 获取要素列表
|
||||||
|
// @Summary 获取要素列表
|
||||||
|
// @Description 获取要素列表
|
||||||
|
// @Tags feature
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param page query int false "页码"
|
||||||
|
// @Param size query int false "每页数量"
|
||||||
|
// @Success 200 {object} Pagination[FeaturePublic]
|
||||||
|
// @Router /features [get]
|
||||||
|
func (a *API) GetFeatures(c echo.Context) error {
|
||||||
|
var featureList model.FeatureList
|
||||||
|
if err := BindAndValidate(c, &featureList); err != nil {
|
||||||
|
return Error(c, http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
features, total, err := a.store.ListFeature(&featureList)
|
||||||
|
if err != nil {
|
||||||
|
return Error(c, http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
// 将 features 转换为 FeaturePublic
|
||||||
|
featurePublics := make([]model.FeaturePublic, len(features))
|
||||||
|
for i, feature := range features {
|
||||||
|
featurePublics[i] = feature.ToPublic()
|
||||||
|
}
|
||||||
|
return Paginate(c, featurePublics, featureList.Page, featureList.Size, total, "获取要素列表成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFeature 创建要素
|
||||||
|
// @Summary 创建要素
|
||||||
|
// @Description 创建要素
|
||||||
|
// @Tags feature
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param feature body store.FeatureCreate true "要素"
|
||||||
|
// @Success 200 {object} Response[FeaturePublic]
|
||||||
|
// @Router /feature [post]
|
||||||
|
func (a *API) PostFeature(c echo.Context) error {
|
||||||
|
var featureCreate model.FeatureCreate
|
||||||
|
if err := BindAndValidate(c, &featureCreate); err != nil {
|
||||||
|
return Error(c, http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
feature, err := a.store.CreateFeature(&featureCreate)
|
||||||
|
if err != nil {
|
||||||
|
return Error(c, http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return Success(c, feature.ToPublic(), "创建要素成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFeatureByID 获取要素
|
||||||
|
// @Summary 获取要素
|
||||||
|
// @Description 获取要素
|
||||||
|
// @Tags feature
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "要素 ID"
|
||||||
|
// @Success 200 {object} Response[FeaturePublic]
|
||||||
|
// @Router /feature/{id} [get]
|
||||||
|
func (a *API) GetFeatureByID(c echo.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFeature 更新要素
|
||||||
|
// @Summary 更新要素
|
||||||
|
// @Description 更新要素
|
||||||
|
// @Tags feature
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param feature body store.FeatureUpdate true "要素"
|
||||||
|
// @Success 200 {object} Response[FeaturePublic]
|
||||||
|
// @Router /feature [put]
|
||||||
|
func (a *API) PutFeature(c echo.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFeature 删除要素
|
||||||
|
// @Summary 删除要素
|
||||||
|
// @Description 删除要素
|
||||||
|
// @Tags feature
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param feature body store.FeatureDeleteOrBanned true "要素"
|
||||||
|
// @Success 200 {object} Response[FeaturePublic]
|
||||||
|
// @Router /feature/{id} [delete]
|
||||||
|
func (a *API) DeleteFeature(c echo.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BannedFeature 禁用要素
|
||||||
|
// @Summary 禁用要素
|
||||||
|
// @Description 禁用要素
|
||||||
|
// @Tags feature
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param feature body store.FeatureDeleteOrBanned true "要素"
|
||||||
|
// @Success 200 {object} Response[FeaturePublic]
|
||||||
|
// @Router /feature/banned/{id} [post]
|
||||||
|
func (a *API) BannedFeature(c echo.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
// // CreateTag 创建标签
|
||||||
|
// // @Summary 创建标签
|
||||||
|
// // @Description 创建标签
|
||||||
|
// // @Tags tag
|
||||||
|
// // @Accept json
|
||||||
|
// // @Produce json
|
||||||
|
// // @Param tag body TagCreate true "标签"
|
||||||
|
// // @Success 200 {object} TagCreate
|
||||||
|
// // @Router /tag [post]
|
||||||
|
// func PostTag(c echo.Context) error {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // GetTag 获取标签
|
||||||
|
// // @Summary 获取标签
|
||||||
|
// // @Description 获取标签
|
||||||
|
// // @Tags tag
|
||||||
|
// // @Accept json
|
||||||
|
// // @Produce json
|
||||||
|
// // @Param id path string true "标签 ID"
|
||||||
|
// // @Success 200 {object} TagGet
|
||||||
|
// // @Router /tag/{id} [get]
|
||||||
|
// func GetTagByID(c echo.Context) error {
|
||||||
|
// return nil
|
||||||
|
// }
|
|
@ -0,0 +1,95 @@
|
||||||
|
package bin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"git.zhouxhere.com/zhouxhere/maptile/config"
|
||||||
|
_ "git.zhouxhere.com/zhouxhere/maptile/log"
|
||||||
|
"git.zhouxhere.com/zhouxhere/maptile/server"
|
||||||
|
"git.zhouxhere.com/zhouxhere/maptile/store"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "maptile",
|
||||||
|
Short: "一个简单的地图vector tile 管理",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
config := &config.Config{
|
||||||
|
Mode: viper.GetString("mode"),
|
||||||
|
Addr: viper.GetString("addr"),
|
||||||
|
Port: viper.GetInt("port"),
|
||||||
|
DSN: viper.GetString("dsn"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := config.Validate(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
store, err := store.NewStore(config.DSN)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
cancel()
|
||||||
|
slog.Error("store init failed", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := store.Migrate(ctx); err != nil {
|
||||||
|
cancel()
|
||||||
|
slog.Error("store migrate failed", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s := server.NewServer(ctx, config, store)
|
||||||
|
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
|
||||||
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
|
if err := s.Start(ctx); err != nil {
|
||||||
|
if err != http.ErrServerClosed {
|
||||||
|
slog.Error("server start failed", "err", err)
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-c
|
||||||
|
s.Stop(ctx)
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-ctx.Done()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
viper.SetDefault("mode", "dev")
|
||||||
|
viper.SetDefault("addr", "0.0.0.0")
|
||||||
|
viper.SetDefault("port", 8080)
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().String("mode", "dev", `mode of server, can be "prod" or "dev"`)
|
||||||
|
rootCmd.PersistentFlags().String("addr", "0.0.0.0", "address of server")
|
||||||
|
rootCmd.PersistentFlags().Int("port", 8080, "port of server")
|
||||||
|
rootCmd.PersistentFlags().String("dsn", "postgresql://maptile:maptile_passwd@localhost:5432/maptile?sslmode=disable", "database source name")
|
||||||
|
|
||||||
|
if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
viper.SetEnvPrefix("maptile")
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunCMD() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Mode string
|
||||||
|
Addr string
|
||||||
|
Port int
|
||||||
|
DSN string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) Validate() error {
|
||||||
|
if c.Mode != "dev" && c.Mode != "prod" {
|
||||||
|
return fmt.Errorf("invalid mode: %s", c.Mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Addr == "" {
|
||||||
|
return fmt.Errorf("invalid address: %s", c.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Port == 0 {
|
||||||
|
return fmt.Errorf("invalid port: %d", c.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.DSN == "" {
|
||||||
|
return fmt.Errorf("invalid DSN: %s", c.DSN)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package docs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
|
||||||
|
"github.com/swaggo/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed swagger.json
|
||||||
|
var swaggerJSON embed.FS
|
||||||
|
|
||||||
|
var SwaggerInfo = &swag.Spec{
|
||||||
|
Version: "1.0",
|
||||||
|
Host: "localhost:8080",
|
||||||
|
BasePath: "/api/v1",
|
||||||
|
Schemes: []string{"http"},
|
||||||
|
Title: "地图图层 API",
|
||||||
|
Description: "这是一个使用 Gin 和 GORM 实现的地图图层数据管理 API。",
|
||||||
|
InfoInstanceName: "swagger",
|
||||||
|
LeftDelim: "{{",
|
||||||
|
RightDelim: "}}",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
swaggerData, err := swaggerJSON.ReadFile("swagger.json")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
SwaggerInfo.SwaggerTemplate = string(swaggerData)
|
||||||
|
|
||||||
|
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||||
|
}
|
|
@ -0,0 +1,313 @@
|
||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"contact": {}
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/feature": {
|
||||||
|
"put": {
|
||||||
|
"description": "更新要素",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"feature"
|
||||||
|
],
|
||||||
|
"summary": "更新要素",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "要素",
|
||||||
|
"name": "feature",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/store.FeatureUpdate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Response-model_Feature"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"description": "创建要素",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"feature"
|
||||||
|
],
|
||||||
|
"summary": "创建要素",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "要素",
|
||||||
|
"name": "feature",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/store.FeatureCreate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Response-model_Feature"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/feature/banned/{id}": {
|
||||||
|
"post": {
|
||||||
|
"description": "禁用要素",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"feature"
|
||||||
|
],
|
||||||
|
"summary": "禁用要素",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "要素",
|
||||||
|
"name": "feature",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/store.FeatureDeleteOrBanned"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Response-model_Feature"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/feature/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "获取要素",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"feature"
|
||||||
|
],
|
||||||
|
"summary": "获取要素",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "要素 ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Response-model_Feature"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"description": "删除要素",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"feature"
|
||||||
|
],
|
||||||
|
"summary": "删除要素",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "要素",
|
||||||
|
"name": "feature",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/store.FeatureDeleteOrBanned"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Response-model_Feature"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/features": {
|
||||||
|
"get": {
|
||||||
|
"description": "获取要素列表",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"feature"
|
||||||
|
],
|
||||||
|
"summary": "获取要素列表",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "页码",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "每页数量",
|
||||||
|
"name": "size",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Pagination-model_Feature"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"api.Pagination-model_Feature": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/model.Feature"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api.Response-model_Feature": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/model.Feature"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"model.Feature": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"created_at": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"geometry": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"store.FeatureCreate": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"geometry",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"geometry": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"store.FeatureDeleteOrBanned": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"store.FeatureUpdate": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"geometry",
|
||||||
|
"id",
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"geometry": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
module git.zhouxhere.com/zhouxhere/maptile
|
||||||
|
|
||||||
|
go 1.22.5
|
||||||
|
|
||||||
|
require github.com/twpayne/go-geom v1.6.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
github.com/swaggo/files/v2 v2.0.0 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
|
golang.org/x/time v0.8.0 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||||
|
github.com/go-openapi/spec v0.21.0 // indirect
|
||||||
|
github.com/go-openapi/swag v0.23.0 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.25.0
|
||||||
|
github.com/golang-migrate/migrate/v4 v4.18.2
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/labstack/echo/v4 v4.13.3
|
||||||
|
github.com/lib/pq v1.10.9
|
||||||
|
github.com/mailru/easyjson v0.9.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/spf13/cobra v1.9.1
|
||||||
|
github.com/spf13/viper v1.19.0
|
||||||
|
github.com/swaggo/echo-swagger v1.4.1
|
||||||
|
github.com/swaggo/swag v1.16.4
|
||||||
|
golang.org/x/crypto v0.33.0 // indirect
|
||||||
|
golang.org/x/net v0.35.0 // indirect
|
||||||
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
|
golang.org/x/text v0.22.0 // indirect
|
||||||
|
golang.org/x/tools v0.30.0 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,206 @@
|
||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||||
|
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||||
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY=
|
||||||
|
github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||||
|
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||||
|
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dhui/dktest v0.4.4 h1:+I4s6JRE1yGuqflzwqG+aIaMdgXIorCf5P98JnaAWa8=
|
||||||
|
github.com/dhui/dktest v0.4.4/go.mod h1:4+22R4lgsdAXrDyaH4Nqx2JEz2hLp49MqQmm9HLCQhM=
|
||||||
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
|
github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4=
|
||||||
|
github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
|
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
|
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||||
|
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||||
|
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||||
|
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||||
|
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||||
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
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.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
|
||||||
|
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8=
|
||||||
|
github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
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.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
|
||||||
|
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
|
||||||
|
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||||
|
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
|
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||||
|
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
|
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
|
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||||
|
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||||
|
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||||
|
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
|
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||||
|
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||||
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||||
|
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk=
|
||||||
|
github.com/swaggo/echo-swagger v1.4.1/go.mod h1:C8bSi+9yH2FLZsnhqMZLIZddpUxZdBYuNHbtaS1Hljc=
|
||||||
|
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw=
|
||||||
|
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM=
|
||||||
|
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
||||||
|
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
||||||
|
github.com/twpayne/go-geom v1.6.0 h1:WPOJLCdd8OdcnHvKQepLKwOZrn5BzVlNxtQB59IDHRE=
|
||||||
|
github.com/twpayne/go-geom v1.6.0/go.mod h1:Kr+Nly6BswFsKM5sd31YaoWS5PeDDH2NftJTK7Gd028=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
||||||
|
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
||||||
|
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
||||||
|
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
|
||||||
|
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
||||||
|
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
||||||
|
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
||||||
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
|
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
|
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
||||||
|
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
|
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||||
|
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
|
||||||
|
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
|
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -0,0 +1,20 @@
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logFile := &lumberjack.Logger{
|
||||||
|
Filename: "logs/app.log", // 日志文件路径
|
||||||
|
MaxSize: 10, // 单个日志文件的最大大小(MB)
|
||||||
|
MaxBackups: 5, // 保留的旧日志文件最大数量
|
||||||
|
MaxAge: 30, // 保留旧日志文件的最大天数
|
||||||
|
Compress: true, // 是否压缩旧日志文件
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := slog.New(slog.NewJSONHandler(logFile, nil))
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "git.zhouxhere.com/zhouxhere/maptile/bin"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
bin.RunCMD()
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
type ListQuery struct {
|
||||||
|
Page int `json:"page" query:"page"`
|
||||||
|
Size int `json:"size" query:"size"`
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusNormal = "normal"
|
||||||
|
StatusDeleted = "deleted"
|
||||||
|
StatusBanned = "banned"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Feature struct {
|
||||||
|
ID uuid.UUID `db:"id" json:"id"`
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Geometry []byte `db:"geometry" json:"geometry" swaggertype:"object"`
|
||||||
|
Status string `db:"status" json:"status"`
|
||||||
|
CreatedAt int64 `db:"created_at" json:"created_at"`
|
||||||
|
UpdatedAt int64 `db:"updated_at" json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FeatureList struct {
|
||||||
|
ListQuery
|
||||||
|
Name string `json:"name" query:"name"`
|
||||||
|
Status string `json:"status" query:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FeatureCreate struct {
|
||||||
|
Name string `json:"name" binding:"required"`
|
||||||
|
Geometry map[string]interface{} `json:"geometry" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FeatureUpdate struct {
|
||||||
|
ID uuid.UUID `json:"id" binding:"required"`
|
||||||
|
Name string `json:"name" binding:"required"`
|
||||||
|
Geometry map[string]interface{} `json:"geometry" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FeatureDeleteOrBanned struct {
|
||||||
|
ID uuid.UUID `json:"id" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FeaturePublic struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Geometry map[string]interface{} `json:"geometry"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Feature) ToPublic() FeaturePublic {
|
||||||
|
return FeaturePublic{
|
||||||
|
ID: f.ID.String(),
|
||||||
|
Name: f.Name,
|
||||||
|
Geometry: func() map[string]interface{} {
|
||||||
|
var geom map[string]interface{}
|
||||||
|
_ = json.Unmarshal(f.Geometry, &geom)
|
||||||
|
return geom
|
||||||
|
}(),
|
||||||
|
Status: f.Status,
|
||||||
|
CreatedAt: time.Unix(f.CreatedAt, 0),
|
||||||
|
UpdatedAt: time.Unix(f.UpdatedAt, 0),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.zhouxhere.com/zhouxhere/maptile/api"
|
||||||
|
"git.zhouxhere.com/zhouxhere/maptile/config"
|
||||||
|
"git.zhouxhere.com/zhouxhere/maptile/store"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
// "github.com/soheilhy/cmux"
|
||||||
|
// "google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
config *config.Config
|
||||||
|
Store *store.Store
|
||||||
|
httpServer *http.Server
|
||||||
|
// grpcServer *grpc.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(ctx context.Context, config *config.Config, store *store.Store) *Server {
|
||||||
|
s := &Server{
|
||||||
|
Store: store,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.config = config
|
||||||
|
|
||||||
|
echoServer := echo.New()
|
||||||
|
|
||||||
|
api.NewAPI(echoServer, store)
|
||||||
|
|
||||||
|
addr := fmt.Sprintf("%s:%d", config.Addr, config.Port)
|
||||||
|
s.httpServer = &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: echoServer,
|
||||||
|
}
|
||||||
|
|
||||||
|
// grpcServer := grpc.NewServer()
|
||||||
|
// s.grpcServer = grpcServer
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start(ctx context.Context) error {
|
||||||
|
addr := fmt.Sprintf("%s:%d", s.config.Addr, s.config.Port)
|
||||||
|
listener, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to listen")
|
||||||
|
}
|
||||||
|
|
||||||
|
// muxServer := cmux.New(listener)
|
||||||
|
|
||||||
|
// go func() {
|
||||||
|
// grpcListener := muxServer.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
|
||||||
|
// if err := s.grpcServer.Serve(grpcListener); err != nil {
|
||||||
|
// slog.Error("failed to start grpc server", "error", err)
|
||||||
|
// }
|
||||||
|
// }()
|
||||||
|
|
||||||
|
// go func() {
|
||||||
|
// httpListener := muxServer.Match(cmux.HTTP1Fast(http.MethodPatch))
|
||||||
|
// if err := s.httpServer.Serve(httpListener); err != nil {
|
||||||
|
// slog.Error("failed to start http server", "error", err)
|
||||||
|
// }
|
||||||
|
// }()
|
||||||
|
|
||||||
|
// go func() {
|
||||||
|
// if err := muxServer.Serve(); err != nil {
|
||||||
|
// slog.Error("failed to start server", "error", err)
|
||||||
|
// }
|
||||||
|
// }()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := s.httpServer.Serve(listener); err != nil {
|
||||||
|
slog.Error("failed to start http server", "error", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Stop(ctx context.Context) {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := s.httpServer.Shutdown(ctx); err != nil {
|
||||||
|
slog.Error("failed to stop http server", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// s.grpcServer.GracefulStop()
|
||||||
|
|
||||||
|
if err := s.Store.Close(); err != nil {
|
||||||
|
slog.Error("failed to close store", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("server stopped")
|
||||||
|
}
|
|
@ -0,0 +1,167 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.zhouxhere.com/zhouxhere/maptile/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/twpayne/go-geom"
|
||||||
|
"github.com/twpayne/go-geom/encoding/geojson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListFeature 获取要素列表
|
||||||
|
func (s *Store) ListFeature(query *model.FeatureList) ([]model.Feature, int, error) {
|
||||||
|
queryStr := `
|
||||||
|
SELECT id, name, ST_AsGeoJSON(geometry) AS geometry, status, created_at, updated_at
|
||||||
|
FROM feature WHERE 1 = 1
|
||||||
|
`
|
||||||
|
if query.Status != "" {
|
||||||
|
queryStr += " AND status = :status"
|
||||||
|
}
|
||||||
|
if query.Name != "" {
|
||||||
|
queryStr += " AND name LIKE :name"
|
||||||
|
}
|
||||||
|
queryStr += " ORDER BY status, created_at DESC"
|
||||||
|
queryStr += " LIMIT :limit OFFSET :offset"
|
||||||
|
|
||||||
|
namedParams := map[string]interface{}{
|
||||||
|
"status": query.Status,
|
||||||
|
"name": "'%" + query.Name + "%'",
|
||||||
|
"limit": query.Size,
|
||||||
|
"offset": (query.Page - 1) * query.Size,
|
||||||
|
}
|
||||||
|
|
||||||
|
queryStr, args, err := s.DB.BindNamed(queryStr, namedParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行查询
|
||||||
|
features := []model.Feature{}
|
||||||
|
|
||||||
|
if err := s.DB.Select(&features, queryStr, args...); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
countQuery := `
|
||||||
|
SELECT COUNT(*) FROM feature WHERE 1 = 1
|
||||||
|
`
|
||||||
|
if query.Status != "" {
|
||||||
|
countQuery += " AND status = :status"
|
||||||
|
}
|
||||||
|
if query.Name != "" {
|
||||||
|
countQuery += " AND name LIKE :name"
|
||||||
|
}
|
||||||
|
|
||||||
|
countQuery, countArgs, err := s.DB.BindNamed(countQuery, namedParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int
|
||||||
|
if err := s.DB.Get(&count, countQuery, countArgs...); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return features, count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFeatureByID 获取要素
|
||||||
|
func (s *Store) GetFeatureByID(id uuid.UUID) (*model.Feature, error) {
|
||||||
|
query := `
|
||||||
|
SELECT id, name, ST_AsGeoJSON(geometry) AS geometry, status, created_at, updated_at
|
||||||
|
FROM feature
|
||||||
|
WHERE id = :id
|
||||||
|
`
|
||||||
|
f := &model.Feature{}
|
||||||
|
if err := s.DB.Get(f, query, id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFeature 创建要素
|
||||||
|
func (s *Store) CreateFeature(feature *model.FeatureCreate) (*model.Feature, error) {
|
||||||
|
// 将传入的 JSON 格式的 geometry 转换为 geom.T 类型
|
||||||
|
geometryBytes, err := json.Marshal(feature.Geometry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var geomT geom.T
|
||||||
|
if err := geojson.Unmarshal(geometryBytes, &geomT); err != nil {
|
||||||
|
return nil, errors.New("invalid GeoJSON format")
|
||||||
|
}
|
||||||
|
query := `
|
||||||
|
INSERT INTO feature (id, name, geometry, status, created_at, updated_at)
|
||||||
|
VALUES (:id, :name, ST_GeomFromGeoJSON(:geometry), :status, :created_at, :updated_at)
|
||||||
|
`
|
||||||
|
|
||||||
|
f := &model.Feature{
|
||||||
|
ID: uuid.New(),
|
||||||
|
Name: feature.Name,
|
||||||
|
Geometry: geometryBytes,
|
||||||
|
Status: model.StatusNormal,
|
||||||
|
CreatedAt: time.Now().Unix(),
|
||||||
|
UpdatedAt: time.Now().Unix(),
|
||||||
|
}
|
||||||
|
_, err = s.DB.NamedExec(query, f)
|
||||||
|
return f, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFeature 更新要素
|
||||||
|
func (s *Store) UpdateFeature(feature *model.FeatureUpdate) (*model.Feature, error) {
|
||||||
|
// 将传入的 JSON 格式的 geometry 转换为 geom.T 类型
|
||||||
|
geometryBytes, err := json.Marshal(feature.Geometry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var geomT geom.T
|
||||||
|
if err := geojson.Unmarshal(geometryBytes, &geomT); err != nil {
|
||||||
|
return nil, errors.New("invalid GeoJSON format")
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `
|
||||||
|
UPDATE feature
|
||||||
|
SET name = :name, geometry = ST_GeomFromGeoJSON(:geometry), updated_at = :updated_at
|
||||||
|
WHERE id = :id
|
||||||
|
`
|
||||||
|
f := &model.Feature{
|
||||||
|
ID: feature.ID,
|
||||||
|
Name: feature.Name,
|
||||||
|
Geometry: geometryBytes,
|
||||||
|
UpdatedAt: time.Now().Unix(),
|
||||||
|
}
|
||||||
|
_, err = s.DB.NamedExec(query, f)
|
||||||
|
return f, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFeature 删除要素
|
||||||
|
func (s *Store) DeleteFeature(feature *model.FeatureDeleteOrBanned) error {
|
||||||
|
query := `
|
||||||
|
UPDATE feature SET status = :status, updated_at = :updated_at WHERE id = :id
|
||||||
|
`
|
||||||
|
f := &model.Feature{
|
||||||
|
ID: feature.ID,
|
||||||
|
Status: model.StatusDeleted,
|
||||||
|
UpdatedAt: time.Now().Unix(),
|
||||||
|
}
|
||||||
|
_, err := s.DB.NamedExec(query, f)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BannedFeature 禁用要素
|
||||||
|
func (s *Store) BannedFeature(feature *model.FeatureDeleteOrBanned) error {
|
||||||
|
query := `
|
||||||
|
UPDATE feature SET status = :status, updated_at = :updated_at WHERE id = :id
|
||||||
|
`
|
||||||
|
f := &model.Feature{
|
||||||
|
ID: feature.ID,
|
||||||
|
Status: model.StatusBanned,
|
||||||
|
UpdatedAt: time.Now().Unix(),
|
||||||
|
}
|
||||||
|
_, err := s.DB.NamedExec(query, f)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
-- 删除地图标签关联表
|
||||||
|
DROP TABLE IF EXISTS feature_tag;
|
||||||
|
|
||||||
|
-- 删除标签数据表
|
||||||
|
DROP TABLE IF EXISTS tag;
|
||||||
|
|
||||||
|
-- 删除地图相关数据表
|
||||||
|
DROP TABLE IF EXISTS feature;
|
||||||
|
|
||||||
|
-- 删除地图数据状态枚举类型
|
||||||
|
DROP TYPE IF EXISTS feature_status;
|
||||||
|
|
||||||
|
-- 删除 PostGIS 扩展
|
||||||
|
DROP EXTENSION IF EXISTS postgis;
|
||||||
|
|
||||||
|
-- 删除 UUID 扩展
|
||||||
|
DROP EXTENSION IF EXISTS "uuid-ossp";
|
|
@ -0,0 +1,53 @@
|
||||||
|
-- Description: 数据库初始化脚本
|
||||||
|
-- CREATE ROLE maptile WITH LOGIN PASSWORD 'maptile_passwd';
|
||||||
|
-- CREATE DATABASE maptile WITH OWNER maptile;
|
||||||
|
|
||||||
|
-- Postgis extension
|
||||||
|
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||||
|
|
||||||
|
-- UUID extension
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||||
|
|
||||||
|
-- 地图数据状态
|
||||||
|
CREATE TYPE feature_status AS ENUM ('normal', 'banned', 'deleted');
|
||||||
|
COMMENT ON TYPE feature_status IS '地图数据状态枚举类型,包括正常、未激活、封禁和删除';
|
||||||
|
|
||||||
|
-- 地图相关数据
|
||||||
|
CREATE TABLE feature (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
geometry Geometry(Geometry, 4326),
|
||||||
|
status feature_status NOT NULL DEFAULT 'normal',
|
||||||
|
created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()),
|
||||||
|
updated_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())
|
||||||
|
);
|
||||||
|
COMMENT ON COLUMN feature.id IS '地图ID';
|
||||||
|
COMMENT ON COLUMN feature.name IS '地图名称';
|
||||||
|
COMMENT ON COLUMN feature.geometry IS '地图几何数据';
|
||||||
|
COMMENT ON COLUMN feature.created_at IS '创建时间';
|
||||||
|
COMMENT ON COLUMN feature.updated_at IS '更新时间';
|
||||||
|
|
||||||
|
-- 标签数据
|
||||||
|
CREATE TABLE tag (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()),
|
||||||
|
updated_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())
|
||||||
|
);
|
||||||
|
COMMENT ON COLUMN tag.id IS '标签ID';
|
||||||
|
COMMENT ON COLUMN tag.name IS '标签名称';
|
||||||
|
COMMENT ON COLUMN tag.created_at IS '创建时间';
|
||||||
|
COMMENT ON COLUMN tag.updated_at IS '更新时间';
|
||||||
|
|
||||||
|
-- 地图标签关联表
|
||||||
|
CREATE TABLE feature_tag (
|
||||||
|
feature_id UUID NOT NULL,
|
||||||
|
tag_id INTEGER NOT NULL,
|
||||||
|
created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()),
|
||||||
|
updated_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()),
|
||||||
|
UNIQUE (feature_id, tag_id)
|
||||||
|
);
|
||||||
|
COMMENT ON COLUMN feature_tag.feature_id IS '地图ID';
|
||||||
|
COMMENT ON COLUMN feature_tag.tag_id IS '标签ID';
|
||||||
|
COMMENT ON COLUMN feature_tag.created_at IS '创建时间';
|
||||||
|
COMMENT ON COLUMN feature_tag.updated_at IS '更新时间';
|
|
@ -0,0 +1,71 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"embed"
|
||||||
|
|
||||||
|
"github.com/golang-migrate/migrate/v4"
|
||||||
|
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
||||||
|
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed migrations/*.sql
|
||||||
|
var sqlMigrations embed.FS
|
||||||
|
|
||||||
|
type Store struct {
|
||||||
|
dsn string
|
||||||
|
DB *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStore(dsn string) (*Store, error) {
|
||||||
|
db, err := sqlx.Connect("postgres", dsn)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Store{
|
||||||
|
dsn: dsn,
|
||||||
|
DB: db,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Close() error {
|
||||||
|
return s.DB.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Migrate(ctx context.Context) error {
|
||||||
|
source, err := iofs.New(sqlMigrations, "migrations")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := migrate.NewWithSourceInstance("iofs", source, s.dsn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceVersion, err := source.First()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
version, dirty, err := m.Version()
|
||||||
|
|
||||||
|
if err != nil && err != migrate.ErrNilVersion {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if version == sourceVersion && !dirty {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = m.Up(); err != nil && err != migrate.ErrNoChange {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Nuxt Minimal Starter
|
||||||
|
|
||||||
|
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Make sure to install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Server
|
||||||
|
|
||||||
|
Start the development server on `http://localhost:3000`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn dev
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
Build the application for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn build
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Locally preview production build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run preview
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm preview
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn preview
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
|
@ -0,0 +1,6 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NuxtRouteAnnouncer />
|
||||||
|
<NuxtWelcome />
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,3 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
|
@ -0,0 +1,12 @@
|
||||||
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
compatibilityDate: "2024-11-01",
|
||||||
|
devtools: { enabled: true },
|
||||||
|
css: ["@/assets/css/main.css"],
|
||||||
|
postcss: {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"name": "nuxt-app",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"nuxt": "^3.15.4",
|
||||||
|
"vue": "latest",
|
||||||
|
"vue-router": "latest"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"daisyui": "^4.12.23",
|
||||||
|
"postcss": "^8.5.3",
|
||||||
|
"tailwindcss": "^3.4.17"
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "../.nuxt/tsconfig.server.json"
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import daisyui from 'daisyui'
|
||||||
|
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
"./components/**/*.{js,vue,ts}",
|
||||||
|
"./layouts/**/*.vue",
|
||||||
|
"./pages/**/*.vue",
|
||||||
|
"./plugins/**/*.{js,ts}",
|
||||||
|
"./app.vue",
|
||||||
|
"./error.vue",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [daisyui],
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
|
"extends": "./.nuxt/tsconfig.json"
|
||||||
|
}
|
Loading…
Reference in New Issue