init
This commit is contained in:
207
backend/app/http/api/controller.go
Normal file
207
backend/app/http/api/controller.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"quyun/v2/app/errorx"
|
||||
"quyun/v2/app/tenancy"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// @provider
|
||||
type ApiController struct{}
|
||||
|
||||
type PostsQuery struct {
|
||||
Page int `query:"page"`
|
||||
Limit int `query:"limit"`
|
||||
Keyword string `query:"keyword"`
|
||||
}
|
||||
|
||||
type UpdateUsernameReq struct {
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
// WeChatOAuthStart
|
||||
//
|
||||
// @Router /v1/auth/wechat [get]
|
||||
func (a *ApiController) WeChatOAuthStart(ctx fiber.Ctx) error {
|
||||
return ctx.Status(fiber.StatusNotImplemented).JSON(fiber.Map{"ok": false, "todo": "wechat oauth: /auth/wechat"})
|
||||
}
|
||||
|
||||
// WeChatOAuthCallback
|
||||
//
|
||||
// @Router /v1/auth/login [get]
|
||||
func (a *ApiController) WeChatOAuthCallback(ctx fiber.Ctx) error {
|
||||
return ctx.Status(fiber.StatusNotImplemented).JSON(fiber.Map{"ok": false, "todo": "wechat oauth: /auth/login"})
|
||||
}
|
||||
|
||||
// ListPosts
|
||||
//
|
||||
// @Router /v1/posts [get]
|
||||
// @Bind query query
|
||||
func (a *ApiController) ListPosts(ctx fiber.Ctx, query *PostsQuery) error {
|
||||
page := 1
|
||||
limit := 10
|
||||
if query != nil {
|
||||
if query.Page > 0 {
|
||||
page = query.Page
|
||||
}
|
||||
if query.Limit > 0 {
|
||||
limit = query.Limit
|
||||
}
|
||||
}
|
||||
return ctx.JSON(fiber.Map{
|
||||
"items": []any{},
|
||||
"total": 0,
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
})
|
||||
}
|
||||
|
||||
// ShowPost
|
||||
//
|
||||
// @Router /v1/posts/:id/show [get]
|
||||
// @Bind id path
|
||||
func (a *ApiController) ShowPost(ctx fiber.Ctx, id int) error {
|
||||
if id <= 0 {
|
||||
return errorx.ErrInvalidParameter.WithMsg("invalid id")
|
||||
}
|
||||
return fiber.ErrNotFound
|
||||
}
|
||||
|
||||
// PlayPost
|
||||
//
|
||||
// @Router /v1/posts/:id/play [get]
|
||||
// @Bind id path
|
||||
func (a *ApiController) PlayPost(ctx fiber.Ctx, id int) error {
|
||||
if id <= 0 {
|
||||
return errorx.ErrInvalidParameter.WithMsg("invalid id")
|
||||
}
|
||||
return ctx.Status(fiber.StatusNotImplemented).JSON(fiber.Map{"ok": false, "todo": "post play"})
|
||||
}
|
||||
|
||||
// MinePosts
|
||||
//
|
||||
// @Router /v1/posts/mine [get]
|
||||
// @Bind query query
|
||||
func (a *ApiController) MinePosts(ctx fiber.Ctx, query *PostsQuery) error {
|
||||
page := 1
|
||||
limit := 10
|
||||
if query != nil {
|
||||
if query.Page > 0 {
|
||||
page = query.Page
|
||||
}
|
||||
if query.Limit > 0 {
|
||||
limit = query.Limit
|
||||
}
|
||||
}
|
||||
return ctx.JSON(fiber.Map{
|
||||
"items": []any{},
|
||||
"total": 0,
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
})
|
||||
}
|
||||
|
||||
// BuyPost
|
||||
//
|
||||
// @Router /v1/posts/:id/buy [post]
|
||||
// @Bind id path
|
||||
func (a *ApiController) BuyPost(ctx fiber.Ctx, id int) error {
|
||||
if id <= 0 {
|
||||
return errorx.ErrInvalidParameter.WithMsg("invalid id")
|
||||
}
|
||||
return ctx.Status(fiber.StatusNotImplemented).JSON(fiber.Map{"ok": false, "todo": "post buy"})
|
||||
}
|
||||
|
||||
// UserProfile
|
||||
//
|
||||
// @Router /v1/users/profile [get]
|
||||
func (a *ApiController) UserProfile(ctx fiber.Ctx) error {
|
||||
tenantCode := ctx.Locals(tenancy.LocalTenantCode)
|
||||
return ctx.JSON(fiber.Map{
|
||||
"id": 0,
|
||||
"created_at": "1970-01-01T00:00:00Z",
|
||||
"username": "",
|
||||
"avatar": "",
|
||||
"balance": 0,
|
||||
"tenant": tenantCode,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUsername
|
||||
//
|
||||
// @Router /v1/users/username [put]
|
||||
// @Bind req body
|
||||
func (a *ApiController) UpdateUsername(ctx fiber.Ctx, req *UpdateUsernameReq) error {
|
||||
if req == nil {
|
||||
return errorx.ErrInvalidJSON
|
||||
}
|
||||
name := strings.TrimSpace(req.Username)
|
||||
if name == "" {
|
||||
return errorx.ErrDataValidationFail.WithMsg("username required")
|
||||
}
|
||||
if len([]rune(name)) > 12 {
|
||||
return errorx.ErrDataValidationFail.WithMsg("username too long")
|
||||
}
|
||||
return ctx.JSON(fiber.Map{"ok": true})
|
||||
}
|
||||
|
||||
// WechatJSSDK
|
||||
//
|
||||
// @Router /v1/wechats/js-sdk [get]
|
||||
func (a *ApiController) WechatJSSDK(ctx fiber.Ctx) error {
|
||||
return ctx.Status(fiber.StatusNotImplemented).JSON(fiber.Map{"ok": false, "todo": "wechat js-sdk"})
|
||||
}
|
||||
|
||||
// AdminAuth (stub)
|
||||
//
|
||||
// @Router /v1/admin/auth [post]
|
||||
func (a *ApiController) AdminAuth(ctx fiber.Ctx) error {
|
||||
return ctx.Status(fiber.StatusNotImplemented).JSON(fiber.Map{"ok": false, "todo": "admin auth"})
|
||||
}
|
||||
|
||||
// AdminStatistics (stub)
|
||||
//
|
||||
// @Router /v1/admin/statistics [get]
|
||||
func (a *ApiController) AdminStatistics(ctx fiber.Ctx) error {
|
||||
return ctx.Status(fiber.StatusNotImplemented).JSON(fiber.Map{"ok": false, "todo": "admin statistics"})
|
||||
}
|
||||
|
||||
// AdminOrders (stub)
|
||||
//
|
||||
// @Router /v1/admin/orders [get]
|
||||
func (a *ApiController) AdminOrders(ctx fiber.Ctx) error {
|
||||
return ctx.Status(fiber.StatusNotImplemented).JSON(fiber.Map{"ok": false, "todo": "admin orders list"})
|
||||
}
|
||||
|
||||
// AdminOrderRefund (stub)
|
||||
//
|
||||
// @Router /v1/admin/orders/:id/refund [post]
|
||||
// @Bind id path
|
||||
func (a *ApiController) AdminOrderRefund(ctx fiber.Ctx, id int) error {
|
||||
if id <= 0 {
|
||||
return errorx.ErrInvalidParameter.WithMsg("invalid id")
|
||||
}
|
||||
return ctx.Status(fiber.StatusNotImplemented).JSON(fiber.Map{"ok": false, "todo": "admin orders refund"})
|
||||
}
|
||||
|
||||
// AdminMedias (stub)
|
||||
//
|
||||
// @Router /v1/admin/medias [get]
|
||||
func (a *ApiController) AdminMedias(ctx fiber.Ctx) error {
|
||||
return ctx.Status(fiber.StatusNotImplemented).JSON(fiber.Map{"ok": false, "todo": "admin medias list"})
|
||||
}
|
||||
|
||||
// AdminMediaShow (stub)
|
||||
//
|
||||
// @Router /v1/admin/medias/:id [get]
|
||||
func (a *ApiController) AdminMediaShow(ctx fiber.Ctx) error {
|
||||
_, err := strconv.ParseInt(ctx.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errorx.ErrInvalidParameter.WithMsg("invalid id")
|
||||
}
|
||||
return ctx.Status(fiber.StatusNotImplemented).JSON(fiber.Map{"ok": false, "todo": "admin medias show"})
|
||||
}
|
||||
33
backend/app/http/api/provider.gen.go
Executable file
33
backend/app/http/api/provider.gen.go
Executable file
@@ -0,0 +1,33 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"go.ipao.vip/atom"
|
||||
"go.ipao.vip/atom/container"
|
||||
"go.ipao.vip/atom/contracts"
|
||||
"go.ipao.vip/atom/opt"
|
||||
)
|
||||
|
||||
func Provide(opts ...opt.Option) error {
|
||||
if err := container.Container.Provide(func() (*ApiController, error) {
|
||||
obj := &ApiController{}
|
||||
|
||||
return obj, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Container.Provide(func(
|
||||
apiController *ApiController,
|
||||
) (contracts.HttpRoute, error) {
|
||||
obj := &Routes{
|
||||
apiController: apiController,
|
||||
}
|
||||
if err := obj.Prepare(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}, atom.GroupRoutes); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
114
backend/app/http/api/routes.gen.go
Normal file
114
backend/app/http/api/routes.gen.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Code generated by atomctl. DO NOT EDIT.
|
||||
|
||||
// Package api provides HTTP route definitions and registration
|
||||
// for the quyun/v2 application.
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
_ "go.ipao.vip/atom"
|
||||
_ "go.ipao.vip/atom/contracts"
|
||||
. "go.ipao.vip/atom/fen"
|
||||
)
|
||||
|
||||
// Routes implements the HttpRoute contract and provides route registration
|
||||
// for all controllers in the api module.
|
||||
//
|
||||
// @provider contracts.HttpRoute atom.GroupRoutes
|
||||
type Routes struct {
|
||||
log *log.Entry `inject:"false"`
|
||||
// Controller instances
|
||||
apiController *ApiController
|
||||
}
|
||||
|
||||
// Prepare initializes the routes provider with logging configuration.
|
||||
func (r *Routes) Prepare() error {
|
||||
r.log = log.WithField("module", "routes.api")
|
||||
r.log.Info("Initializing routes module")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the unique identifier for this routes provider.
|
||||
func (r *Routes) Name() string {
|
||||
return "api"
|
||||
}
|
||||
|
||||
// Register registers all HTTP routes with the provided fiber router.
|
||||
// Each route is registered with its corresponding controller action and parameter bindings.
|
||||
func (r *Routes) Register(router fiber.Router) {
|
||||
// Register routes for controller: ApiController
|
||||
r.log.Debugf("Registering route: Get /v1/admin/medias -> apiController.AdminMedias")
|
||||
router.Get("/v1/admin/medias", Func0(
|
||||
r.apiController.AdminMedias,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/admin/medias/:id -> apiController.AdminMediaShow")
|
||||
router.Get("/v1/admin/medias/:id", Func0(
|
||||
r.apiController.AdminMediaShow,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/admin/orders -> apiController.AdminOrders")
|
||||
router.Get("/v1/admin/orders", Func0(
|
||||
r.apiController.AdminOrders,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/admin/statistics -> apiController.AdminStatistics")
|
||||
router.Get("/v1/admin/statistics", Func0(
|
||||
r.apiController.AdminStatistics,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/auth/login -> apiController.WeChatOAuthCallback")
|
||||
router.Get("/v1/auth/login", Func0(
|
||||
r.apiController.WeChatOAuthCallback,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/auth/wechat -> apiController.WeChatOAuthStart")
|
||||
router.Get("/v1/auth/wechat", Func0(
|
||||
r.apiController.WeChatOAuthStart,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/posts -> apiController.ListPosts")
|
||||
router.Get("/v1/posts", Func1(
|
||||
r.apiController.ListPosts,
|
||||
Query[PostsQuery]("query"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/posts/:id/play -> apiController.PlayPost")
|
||||
router.Get("/v1/posts/:id/play", Func1(
|
||||
r.apiController.PlayPost,
|
||||
PathParam[int]("id"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/posts/:id/show -> apiController.ShowPost")
|
||||
router.Get("/v1/posts/:id/show", Func1(
|
||||
r.apiController.ShowPost,
|
||||
PathParam[int]("id"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/posts/mine -> apiController.MinePosts")
|
||||
router.Get("/v1/posts/mine", Func1(
|
||||
r.apiController.MinePosts,
|
||||
Query[PostsQuery]("query"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/users/profile -> apiController.UserProfile")
|
||||
router.Get("/v1/users/profile", Func0(
|
||||
r.apiController.UserProfile,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/wechats/js-sdk -> apiController.WechatJSSDK")
|
||||
router.Get("/v1/wechats/js-sdk", Func0(
|
||||
r.apiController.WechatJSSDK,
|
||||
))
|
||||
r.log.Debugf("Registering route: Post /v1/admin/auth -> apiController.AdminAuth")
|
||||
router.Post("/v1/admin/auth", Func0(
|
||||
r.apiController.AdminAuth,
|
||||
))
|
||||
r.log.Debugf("Registering route: Post /v1/admin/orders/:id/refund -> apiController.AdminOrderRefund")
|
||||
router.Post("/v1/admin/orders/:id/refund", Func1(
|
||||
r.apiController.AdminOrderRefund,
|
||||
PathParam[int]("id"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Post /v1/posts/:id/buy -> apiController.BuyPost")
|
||||
router.Post("/v1/posts/:id/buy", Func1(
|
||||
r.apiController.BuyPost,
|
||||
PathParam[int]("id"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Put /v1/users/username -> apiController.UpdateUsername")
|
||||
router.Put("/v1/users/username", Func1(
|
||||
r.apiController.UpdateUsername,
|
||||
Body[UpdateUsernameReq]("req"),
|
||||
))
|
||||
|
||||
r.log.Info("Successfully registered all routes")
|
||||
}
|
||||
17
backend/app/http/super/auth.go
Normal file
17
backend/app/http/super/auth.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package super
|
||||
|
||||
import (
|
||||
"quyun/v2/providers/app"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// @provider
|
||||
type authController struct {
|
||||
app *app.Config
|
||||
}
|
||||
|
||||
func (s *authController) auth(ctx fiber.Ctx) error {
|
||||
// user,err:=
|
||||
return nil
|
||||
}
|
||||
345
backend/app/http/super/controller.go
Normal file
345
backend/app/http/super/controller.go
Normal file
@@ -0,0 +1,345 @@
|
||||
package super
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"quyun/v2/app/errorx"
|
||||
"quyun/v2/providers/app"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// @provider
|
||||
type SuperController struct {
|
||||
DB *sql.DB
|
||||
App *app.Config
|
||||
}
|
||||
|
||||
func (s *SuperController) auth(ctx fiber.Ctx) error {
|
||||
token := ""
|
||||
if s.App != nil && s.App.Super != nil {
|
||||
token = strings.TrimSpace(s.App.Super.Token)
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
if s.App != nil && s.App.IsDevMode() {
|
||||
return nil
|
||||
}
|
||||
return errorx.ErrUnauthorized.WithMsg("missing super admin token")
|
||||
}
|
||||
|
||||
auth := strings.TrimSpace(ctx.Get(fiber.HeaderAuthorization))
|
||||
const prefix = "Bearer "
|
||||
if !strings.HasPrefix(auth, prefix) {
|
||||
return errorx.ErrUnauthorized.WithMsg("invalid authorization")
|
||||
}
|
||||
if strings.TrimSpace(strings.TrimPrefix(auth, prefix)) != token {
|
||||
return errorx.ErrUnauthorized.WithMsg("invalid token")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SuperStatisticsResp struct {
|
||||
TenantsTotal int64 `json:"tenants_total"`
|
||||
TenantsEnabled int64 `json:"tenants_enabled"`
|
||||
TenantAdminsTotal int64 `json:"tenant_admins_total"`
|
||||
TenantAdminsExpired int64 `json:"tenant_admins_expired"`
|
||||
}
|
||||
|
||||
// Statistics
|
||||
//
|
||||
// @Router /super/v1/statistics [get]
|
||||
func (s *SuperController) Statistics(ctx fiber.Ctx) error {
|
||||
if err := s.auth(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var out SuperStatisticsResp
|
||||
|
||||
if err := s.DB.QueryRowContext(ctx.Context(), `SELECT count(*) FROM tenants`).Scan(&out.TenantsTotal); err != nil {
|
||||
return errorx.ErrDatabaseError.WithMsg("database error").WithParams(err.Error())
|
||||
}
|
||||
if err := s.DB.QueryRowContext(ctx.Context(), `SELECT count(*) FROM tenants WHERE status = 0`).Scan(&out.TenantsEnabled); err != nil {
|
||||
return errorx.ErrDatabaseError.WithMsg("database error").WithParams(err.Error())
|
||||
}
|
||||
if err := s.DB.QueryRowContext(ctx.Context(), `SELECT count(*) FROM user_roles WHERE role_code = 'tenant_admin'`).Scan(&out.TenantAdminsTotal); err != nil {
|
||||
return errorx.ErrDatabaseError.WithMsg("database error").WithParams(err.Error())
|
||||
}
|
||||
if err := s.DB.QueryRowContext(ctx.Context(), `SELECT count(*) FROM user_roles WHERE role_code = 'tenant_admin' AND expires_at IS NOT NULL AND expires_at <= now()`).Scan(&out.TenantAdminsExpired); err != nil {
|
||||
return errorx.ErrDatabaseError.WithMsg("database error").WithParams(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(out)
|
||||
}
|
||||
|
||||
type TenantsQuery struct {
|
||||
Page int `query:"page"`
|
||||
Limit int `query:"limit"`
|
||||
Keyword string `query:"keyword"`
|
||||
}
|
||||
|
||||
type SuperTenantRow struct {
|
||||
ID int64 `json:"id"`
|
||||
TenantCode string `json:"tenant_code"`
|
||||
TenantUUID string `json:"tenant_uuid"`
|
||||
Name string `json:"name"`
|
||||
Status int16 `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
TenantAdminCount int64 `json:"tenant_admin_count"`
|
||||
TenantAdminExpireAt *time.Time `json:"tenant_admin_expire_at,omitempty"`
|
||||
}
|
||||
|
||||
// Tenants
|
||||
//
|
||||
// @Router /super/v1/tenants [get]
|
||||
// @Bind query query
|
||||
func (s *SuperController) Tenants(ctx fiber.Ctx, query *TenantsQuery) error {
|
||||
if err := s.auth(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
page := 1
|
||||
limit := 20
|
||||
keyword := ""
|
||||
if query != nil {
|
||||
if query.Page > 0 {
|
||||
page = query.Page
|
||||
}
|
||||
if query.Limit > 0 {
|
||||
limit = query.Limit
|
||||
}
|
||||
keyword = strings.TrimSpace(query.Keyword)
|
||||
}
|
||||
if limit > 200 {
|
||||
limit = 200
|
||||
}
|
||||
offset := (page - 1) * limit
|
||||
|
||||
where := "1=1"
|
||||
args := []any{}
|
||||
if keyword != "" {
|
||||
where += " AND (lower(t.tenant_code) LIKE $1 OR lower(t.name) LIKE $1)"
|
||||
args = append(args, "%"+strings.ToLower(keyword)+"%")
|
||||
}
|
||||
|
||||
countSQL := "SELECT count(*) FROM tenants t WHERE " + where
|
||||
var total int64
|
||||
if err := s.DB.QueryRowContext(ctx.Context(), countSQL, args...).Scan(&total); err != nil {
|
||||
return errorx.ErrDatabaseError.WithMsg("database error").WithParams(err.Error())
|
||||
}
|
||||
|
||||
args = append(args, limit, offset)
|
||||
limitArg := "$" + strconv.Itoa(len(args)-1)
|
||||
offsetArg := "$" + strconv.Itoa(len(args))
|
||||
|
||||
sqlStr := `
|
||||
SELECT
|
||||
t.id, t.tenant_code, t.tenant_uuid, t.name, t.status, t.created_at, t.updated_at,
|
||||
COALESCE(a.admin_count, 0) AS admin_count,
|
||||
a.max_expires_at
|
||||
FROM tenants t
|
||||
LEFT JOIN (
|
||||
SELECT tenant_id,
|
||||
count(*) AS admin_count,
|
||||
max(expires_at) AS max_expires_at
|
||||
FROM user_roles
|
||||
WHERE role_code = 'tenant_admin' AND tenant_id IS NOT NULL
|
||||
GROUP BY tenant_id
|
||||
) a ON a.tenant_id = t.id
|
||||
WHERE ` + where + `
|
||||
ORDER BY t.id DESC
|
||||
LIMIT ` + limitArg + ` OFFSET ` + offsetArg
|
||||
|
||||
rows, err := s.DB.QueryContext(ctx.Context(), sqlStr, args...)
|
||||
if err != nil {
|
||||
return errorx.ErrDatabaseError.WithMsg("database error").WithParams(err.Error())
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
items := make([]SuperTenantRow, 0, limit)
|
||||
for rows.Next() {
|
||||
var (
|
||||
it SuperTenantRow
|
||||
uuidStr string
|
||||
)
|
||||
if err := rows.Scan(
|
||||
&it.ID,
|
||||
&it.TenantCode,
|
||||
&uuidStr,
|
||||
&it.Name,
|
||||
&it.Status,
|
||||
&it.CreatedAt,
|
||||
&it.UpdatedAt,
|
||||
&it.TenantAdminCount,
|
||||
&it.TenantAdminExpireAt,
|
||||
); err != nil {
|
||||
return errorx.ErrDatabaseError.WithMsg("database error").WithParams(err.Error())
|
||||
}
|
||||
it.TenantUUID = uuidStr
|
||||
items = append(items, it)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return errorx.ErrDatabaseError.WithMsg("database error").WithParams(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(fiber.Map{
|
||||
"items": items,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
})
|
||||
}
|
||||
|
||||
type RoleRow struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Status int16 `json:"status"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Roles
|
||||
//
|
||||
// @Router /super/v1/roles [get]
|
||||
func (s *SuperController) Roles(ctx fiber.Ctx) error {
|
||||
if err := s.auth(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows, err := s.DB.QueryContext(ctx.Context(), `SELECT code, name, status, updated_at FROM roles ORDER BY code`)
|
||||
if err != nil {
|
||||
return errorx.ErrDatabaseError.WithMsg("database error").WithParams(err.Error())
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
items := make([]RoleRow, 0, 8)
|
||||
for rows.Next() {
|
||||
var it RoleRow
|
||||
if err := rows.Scan(&it.Code, &it.Name, &it.Status, &it.UpdatedAt); err != nil {
|
||||
return errorx.ErrDatabaseError.WithMsg("database error").WithParams(err.Error())
|
||||
}
|
||||
items = append(items, it)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return errorx.ErrDatabaseError.WithMsg("database error").WithParams(err.Error())
|
||||
}
|
||||
|
||||
return ctx.JSON(fiber.Map{"items": items})
|
||||
}
|
||||
|
||||
type UpdateRoleReq struct {
|
||||
Name *string `json:"name"`
|
||||
Status *int16 `json:"status"`
|
||||
}
|
||||
|
||||
// UpdateRole
|
||||
//
|
||||
// @Router /super/v1/roles/:code [put]
|
||||
// @Bind code path
|
||||
// @Bind req body
|
||||
func (s *SuperController) UpdateRole(ctx fiber.Ctx, code string, req *UpdateRoleReq) error {
|
||||
if err := s.auth(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
code = strings.TrimSpace(code)
|
||||
if code == "" {
|
||||
return errorx.ErrInvalidParameter.WithMsg("missing code")
|
||||
}
|
||||
if req == nil {
|
||||
return errorx.ErrInvalidJSON
|
||||
}
|
||||
|
||||
switch code {
|
||||
case "user", "tenant_admin", "super_admin":
|
||||
default:
|
||||
return errorx.ErrInvalidParameter.WithMsg("unknown role code")
|
||||
}
|
||||
|
||||
set := make([]string, 0, 2)
|
||||
args := make([]any, 0, 3)
|
||||
i := 1
|
||||
|
||||
if req.Name != nil {
|
||||
set = append(set, "name = $"+strconv.Itoa(i))
|
||||
args = append(args, strings.TrimSpace(*req.Name))
|
||||
i++
|
||||
}
|
||||
if req.Status != nil {
|
||||
set = append(set, "status = $"+strconv.Itoa(i))
|
||||
args = append(args, *req.Status)
|
||||
i++
|
||||
}
|
||||
if len(set) == 0 {
|
||||
return ctx.JSON(fiber.Map{"ok": true})
|
||||
}
|
||||
|
||||
set = append(set, "updated_at = now()")
|
||||
args = append(args, code)
|
||||
|
||||
sqlStr := "UPDATE roles SET " + strings.Join(set, ", ") + " WHERE code = $" + strconv.Itoa(i)
|
||||
res, err := s.DB.ExecContext(ctx.Context(), sqlStr, args...)
|
||||
if err != nil {
|
||||
return errorx.ErrDatabaseError.WithMsg("database error").WithParams(err.Error())
|
||||
}
|
||||
aff, _ := res.RowsAffected()
|
||||
if aff == 0 {
|
||||
return fiber.ErrNotFound
|
||||
}
|
||||
return ctx.JSON(fiber.Map{"ok": true})
|
||||
}
|
||||
|
||||
func resolveDistDir(primary, fallback string) string {
|
||||
if st, err := os.Stat(primary); err == nil && st.IsDir() {
|
||||
return primary
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func sendIndex(ctx fiber.Ctx, distDir string) error {
|
||||
indexPath := filepath.Join(distDir, "index.html")
|
||||
if st, err := os.Stat(indexPath); err == nil && !st.IsDir() {
|
||||
return ctx.SendFile(indexPath)
|
||||
}
|
||||
return fiber.ErrNotFound
|
||||
}
|
||||
|
||||
func sendAssetOrIndex(ctx fiber.Ctx, distDir, rel string) error {
|
||||
rel = filepath.Clean(strings.TrimSpace(rel))
|
||||
if rel == "." || rel == "/" {
|
||||
rel = ""
|
||||
}
|
||||
if strings.HasPrefix(rel, "..") {
|
||||
return fiber.ErrBadRequest
|
||||
}
|
||||
|
||||
if rel != "" {
|
||||
assetPath := filepath.Join(distDir, rel)
|
||||
if st, err := os.Stat(assetPath); err == nil && !st.IsDir() {
|
||||
return ctx.SendFile(assetPath)
|
||||
}
|
||||
}
|
||||
|
||||
return sendIndex(ctx, distDir)
|
||||
}
|
||||
|
||||
// SuperIndex
|
||||
//
|
||||
// @Router /super [get]
|
||||
func (s *SuperController) SuperIndex(ctx fiber.Ctx) error {
|
||||
dist := resolveDistDir("frontend/superadmin/dist", "../frontend/superadmin/dist")
|
||||
return sendIndex(ctx, dist)
|
||||
}
|
||||
|
||||
// SuperWildcard
|
||||
//
|
||||
// @Router /super/* [get]
|
||||
func (s *SuperController) SuperWildcard(ctx fiber.Ctx) error {
|
||||
dist := resolveDistDir("frontend/superadmin/dist", "../frontend/superadmin/dist")
|
||||
return sendAssetOrIndex(ctx, dist, ctx.Params("*"))
|
||||
}
|
||||
54
backend/app/http/super/provider.gen.go
Executable file
54
backend/app/http/super/provider.gen.go
Executable file
@@ -0,0 +1,54 @@
|
||||
package super
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"quyun/v2/providers/app"
|
||||
|
||||
"go.ipao.vip/atom"
|
||||
"go.ipao.vip/atom/container"
|
||||
"go.ipao.vip/atom/contracts"
|
||||
"go.ipao.vip/atom/opt"
|
||||
)
|
||||
|
||||
func Provide(opts ...opt.Option) error {
|
||||
if err := container.Container.Provide(func(
|
||||
app *app.Config,
|
||||
) (*authController, error) {
|
||||
obj := &authController{
|
||||
app: app,
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Container.Provide(func(
|
||||
App *app.Config,
|
||||
DB *sql.DB,
|
||||
) (*SuperController, error) {
|
||||
obj := &SuperController{
|
||||
App: App,
|
||||
DB: DB,
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Container.Provide(func(
|
||||
superController *SuperController,
|
||||
) (contracts.HttpRoute, error) {
|
||||
obj := &Routes{
|
||||
superController: superController,
|
||||
}
|
||||
if err := obj.Prepare(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}, atom.GroupRoutes); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
70
backend/app/http/super/routes.gen.go
Normal file
70
backend/app/http/super/routes.gen.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Code generated by atomctl. DO NOT EDIT.
|
||||
|
||||
// Package super provides HTTP route definitions and registration
|
||||
// for the quyun/v2 application.
|
||||
package super
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
_ "go.ipao.vip/atom"
|
||||
_ "go.ipao.vip/atom/contracts"
|
||||
. "go.ipao.vip/atom/fen"
|
||||
)
|
||||
|
||||
// Routes implements the HttpRoute contract and provides route registration
|
||||
// for all controllers in the super module.
|
||||
//
|
||||
// @provider contracts.HttpRoute atom.GroupRoutes
|
||||
type Routes struct {
|
||||
log *log.Entry `inject:"false"`
|
||||
// Controller instances
|
||||
superController *SuperController
|
||||
}
|
||||
|
||||
// Prepare initializes the routes provider with logging configuration.
|
||||
func (r *Routes) Prepare() error {
|
||||
r.log = log.WithField("module", "routes.super")
|
||||
r.log.Info("Initializing routes module")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the unique identifier for this routes provider.
|
||||
func (r *Routes) Name() string {
|
||||
return "super"
|
||||
}
|
||||
|
||||
// Register registers all HTTP routes with the provided fiber router.
|
||||
// Each route is registered with its corresponding controller action and parameter bindings.
|
||||
func (r *Routes) Register(router fiber.Router) {
|
||||
// Register routes for controller: SuperController
|
||||
r.log.Debugf("Registering route: Get /super -> superController.SuperIndex")
|
||||
router.Get("/super", Func0(
|
||||
r.superController.SuperIndex,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /super/* -> superController.SuperWildcard")
|
||||
router.Get("/super/*", Func0(
|
||||
r.superController.SuperWildcard,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /super/v1/roles -> superController.Roles")
|
||||
router.Get("/super/v1/roles", Func0(
|
||||
r.superController.Roles,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /super/v1/statistics -> superController.Statistics")
|
||||
router.Get("/super/v1/statistics", Func0(
|
||||
r.superController.Statistics,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /super/v1/tenants -> superController.Tenants")
|
||||
router.Get("/super/v1/tenants", Func1(
|
||||
r.superController.Tenants,
|
||||
Query[TenantsQuery]("query"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Put /super/v1/roles/:code -> superController.UpdateRole")
|
||||
router.Put("/super/v1/roles/:code", Func2(
|
||||
r.superController.UpdateRole,
|
||||
PathParam[string]("code"),
|
||||
Body[UpdateRoleReq]("req"),
|
||||
))
|
||||
|
||||
r.log.Info("Successfully registered all routes")
|
||||
}
|
||||
76
backend/app/http/v1/demo.go
Normal file
76
backend/app/http/v1/demo.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"mime/multipart"
|
||||
|
||||
"quyun/v2/app/errorx"
|
||||
"quyun/v2/app/requests"
|
||||
"quyun/v2/app/services"
|
||||
"quyun/v2/providers/jwt"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// @provider
|
||||
type demo struct{}
|
||||
|
||||
type FooUploadReq struct {
|
||||
Folder string `json:"folder" form:"folder"` // 上传到指定文件夹
|
||||
}
|
||||
|
||||
type FooQuery struct {
|
||||
Search string `query:"search"` // 搜索关键词
|
||||
}
|
||||
|
||||
type FooHeader struct {
|
||||
ContentType string `header:"Content-Type"` // 内容类型
|
||||
}
|
||||
type Filter struct {
|
||||
Name string `query:"name"` // 名称
|
||||
Age int `query:"age"` // 年龄
|
||||
}
|
||||
|
||||
type ResponseItem struct{}
|
||||
|
||||
// Foo
|
||||
//
|
||||
// @Summary Test
|
||||
// @Description Test
|
||||
// @Tags Test
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
//
|
||||
// @Param id path int true "ID"
|
||||
// @Param query query Filter true "Filter"
|
||||
// @Param pager query requests.Pagination true "Pager"
|
||||
// @Success 200 {object} requests.Pager{list=ResponseItem} "成功"
|
||||
//
|
||||
// @Router /v1/medias/:id [post]
|
||||
// @Bind query query
|
||||
// @Bind pager query
|
||||
// @Bind header header
|
||||
// @Bind id path
|
||||
// @Bind req body
|
||||
// @Bind file file
|
||||
// @Bind claim local
|
||||
func (d *demo) Foo(
|
||||
ctx fiber.Ctx,
|
||||
id int,
|
||||
pager *requests.Pagination,
|
||||
query *FooQuery,
|
||||
header *FooHeader,
|
||||
claim *jwt.Claims,
|
||||
file *multipart.FileHeader,
|
||||
req *FooUploadReq,
|
||||
) error {
|
||||
_, err := services.Test.Test(ctx)
|
||||
if err != nil {
|
||||
// 示例:在控制器层自定义错误消息/附加数据
|
||||
appErr := errorx.Wrap(err).
|
||||
WithMsg("获取测试失败").
|
||||
WithData(fiber.Map{"route": "/v1/test"}).
|
||||
WithParams("handler", "Test.Hello")
|
||||
return appErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
33
backend/app/http/v1/provider.gen.go
Executable file
33
backend/app/http/v1/provider.gen.go
Executable file
@@ -0,0 +1,33 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"go.ipao.vip/atom"
|
||||
"go.ipao.vip/atom/container"
|
||||
"go.ipao.vip/atom/contracts"
|
||||
"go.ipao.vip/atom/opt"
|
||||
)
|
||||
|
||||
func Provide(opts ...opt.Option) error {
|
||||
if err := container.Container.Provide(func() (*demo, error) {
|
||||
obj := &demo{}
|
||||
|
||||
return obj, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Container.Provide(func(
|
||||
demo *demo,
|
||||
) (contracts.HttpRoute, error) {
|
||||
obj := &Routes{
|
||||
demo: demo,
|
||||
}
|
||||
if err := obj.Prepare(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}, atom.GroupRoutes); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
57
backend/app/http/v1/routes.gen.go
Normal file
57
backend/app/http/v1/routes.gen.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Code generated by atomctl. DO NOT EDIT.
|
||||
|
||||
// Package v1 provides HTTP route definitions and registration
|
||||
// for the quyun/v2 application.
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
_ "go.ipao.vip/atom"
|
||||
_ "go.ipao.vip/atom/contracts"
|
||||
. "go.ipao.vip/atom/fen"
|
||||
"mime/multipart"
|
||||
"quyun/v2/app/requests"
|
||||
"quyun/v2/providers/jwt"
|
||||
)
|
||||
|
||||
// Routes implements the HttpRoute contract and provides route registration
|
||||
// for all controllers in the v1 module.
|
||||
//
|
||||
// @provider contracts.HttpRoute atom.GroupRoutes
|
||||
type Routes struct {
|
||||
log *log.Entry `inject:"false"`
|
||||
// Controller instances
|
||||
demo *demo
|
||||
}
|
||||
|
||||
// Prepare initializes the routes provider with logging configuration.
|
||||
func (r *Routes) Prepare() error {
|
||||
r.log = log.WithField("module", "routes.v1")
|
||||
r.log.Info("Initializing routes module")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the unique identifier for this routes provider.
|
||||
func (r *Routes) Name() string {
|
||||
return "v1"
|
||||
}
|
||||
|
||||
// Register registers all HTTP routes with the provided fiber router.
|
||||
// Each route is registered with its corresponding controller action and parameter bindings.
|
||||
func (r *Routes) Register(router fiber.Router) {
|
||||
// Register routes for controller: demo
|
||||
r.log.Debugf("Registering route: Post /v1/medias/:id -> demo.Foo")
|
||||
router.Post("/v1/medias/:id", Func7(
|
||||
r.demo.Foo,
|
||||
PathParam[int]("id"),
|
||||
Query[requests.Pagination]("pager"),
|
||||
Query[FooQuery]("query"),
|
||||
Header[FooHeader]("header"),
|
||||
Local[*jwt.Claims]("claim"),
|
||||
File[multipart.FileHeader]("file"),
|
||||
Body[FooUploadReq]("req"),
|
||||
))
|
||||
|
||||
r.log.Info("Successfully registered all routes")
|
||||
}
|
||||
78
backend/app/http/web/controller.go
Normal file
78
backend/app/http/web/controller.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
// @provider
|
||||
type WebController struct{}
|
||||
|
||||
func resolveDistDir(primary, fallback string) string {
|
||||
if st, err := os.Stat(primary); err == nil && st.IsDir() {
|
||||
return primary
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func sendIndex(ctx fiber.Ctx, distDir string) error {
|
||||
indexPath := filepath.Join(distDir, "index.html")
|
||||
if st, err := os.Stat(indexPath); err == nil && !st.IsDir() {
|
||||
return ctx.SendFile(indexPath)
|
||||
}
|
||||
return fiber.ErrNotFound
|
||||
}
|
||||
|
||||
func sendAssetOrIndex(ctx fiber.Ctx, distDir, rel string) error {
|
||||
rel = filepath.Clean(strings.TrimSpace(rel))
|
||||
if rel == "." || rel == "/" {
|
||||
rel = ""
|
||||
}
|
||||
if strings.HasPrefix(rel, "..") {
|
||||
return fiber.ErrBadRequest
|
||||
}
|
||||
|
||||
if rel != "" {
|
||||
assetPath := filepath.Join(distDir, rel)
|
||||
if st, err := os.Stat(assetPath); err == nil && !st.IsDir() {
|
||||
return ctx.SendFile(assetPath)
|
||||
}
|
||||
}
|
||||
|
||||
return sendIndex(ctx, distDir)
|
||||
}
|
||||
|
||||
// AdminIndex
|
||||
//
|
||||
// @Router /admin [get]
|
||||
func (w *WebController) AdminIndex(ctx fiber.Ctx) error {
|
||||
adminDist := resolveDistDir("frontend/admin/dist", "../frontend/admin/dist")
|
||||
return sendIndex(ctx, adminDist)
|
||||
}
|
||||
|
||||
// AdminWildcard
|
||||
//
|
||||
// @Router /admin/* [get]
|
||||
func (w *WebController) AdminWildcard(ctx fiber.Ctx) error {
|
||||
adminDist := resolveDistDir("frontend/admin/dist", "../frontend/admin/dist")
|
||||
return sendAssetOrIndex(ctx, adminDist, ctx.Params("*"))
|
||||
}
|
||||
|
||||
// UserIndex
|
||||
//
|
||||
// @Router / [get]
|
||||
func (w *WebController) UserIndex(ctx fiber.Ctx) error {
|
||||
userDist := resolveDistDir("frontend/user/dist", "../frontend/user/dist")
|
||||
return sendIndex(ctx, userDist)
|
||||
}
|
||||
|
||||
// UserWildcard
|
||||
//
|
||||
// @Router /* [get]
|
||||
func (w *WebController) UserWildcard(ctx fiber.Ctx) error {
|
||||
userDist := resolveDistDir("frontend/user/dist", "../frontend/user/dist")
|
||||
return sendAssetOrIndex(ctx, userDist, ctx.Params("*"))
|
||||
}
|
||||
33
backend/app/http/web/provider.gen.go
Executable file
33
backend/app/http/web/provider.gen.go
Executable file
@@ -0,0 +1,33 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"go.ipao.vip/atom"
|
||||
"go.ipao.vip/atom/container"
|
||||
"go.ipao.vip/atom/contracts"
|
||||
"go.ipao.vip/atom/opt"
|
||||
)
|
||||
|
||||
func Provide(opts ...opt.Option) error {
|
||||
if err := container.Container.Provide(func() (*WebController, error) {
|
||||
obj := &WebController{}
|
||||
|
||||
return obj, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Container.Provide(func(
|
||||
webController *WebController,
|
||||
) (contracts.HttpRoute, error) {
|
||||
obj := &Routes{
|
||||
webController: webController,
|
||||
}
|
||||
if err := obj.Prepare(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}, atom.GroupRoutes); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
59
backend/app/http/web/routes.gen.go
Normal file
59
backend/app/http/web/routes.gen.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Code generated by atomctl. DO NOT EDIT.
|
||||
|
||||
// Package web provides HTTP route definitions and registration
|
||||
// for the quyun/v2 application.
|
||||
package web
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
_ "go.ipao.vip/atom"
|
||||
_ "go.ipao.vip/atom/contracts"
|
||||
. "go.ipao.vip/atom/fen"
|
||||
)
|
||||
|
||||
// Routes implements the HttpRoute contract and provides route registration
|
||||
// for all controllers in the web module.
|
||||
//
|
||||
// @provider contracts.HttpRoute atom.GroupRoutes
|
||||
type Routes struct {
|
||||
log *log.Entry `inject:"false"`
|
||||
// Controller instances
|
||||
webController *WebController
|
||||
}
|
||||
|
||||
// Prepare initializes the routes provider with logging configuration.
|
||||
func (r *Routes) Prepare() error {
|
||||
r.log = log.WithField("module", "routes.web")
|
||||
r.log.Info("Initializing routes module")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the unique identifier for this routes provider.
|
||||
func (r *Routes) Name() string {
|
||||
return "web"
|
||||
}
|
||||
|
||||
// Register registers all HTTP routes with the provided fiber router.
|
||||
// Each route is registered with its corresponding controller action and parameter bindings.
|
||||
func (r *Routes) Register(router fiber.Router) {
|
||||
// Register routes for controller: WebController
|
||||
r.log.Debugf("Registering route: Get / -> webController.UserIndex")
|
||||
router.Get("/", Func0(
|
||||
r.webController.UserIndex,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /* -> webController.UserWildcard")
|
||||
router.Get("/*", Func0(
|
||||
r.webController.UserWildcard,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /admin -> webController.AdminIndex")
|
||||
router.Get("/admin", Func0(
|
||||
r.webController.AdminIndex,
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /admin/* -> webController.AdminWildcard")
|
||||
router.Get("/admin/*", Func0(
|
||||
r.webController.AdminWildcard,
|
||||
))
|
||||
|
||||
r.log.Info("Successfully registered all routes")
|
||||
}
|
||||
Reference in New Issue
Block a user