swagger done

This commit is contained in:
Sense T 2024-04-19 12:47:00 +08:00
parent 34fb2a478b
commit 47335ca5e9
15 changed files with 2801 additions and 21 deletions

5
TODO
View File

@ -3,7 +3,10 @@
- [x] i18n - [x] i18n
- [x] modals - [x] modals
- [] debug - [] debug
- [] swagger - [x] swagger
- [] comments
- [] Nix Module - [] Nix Module
v2.0
- [] RBAC - [] RBAC
- [] Audit - [] Audit

View File

@ -3,7 +3,7 @@ package database
import ( import (
"errors" "errors"
"github.com/huandu/go-clone" clone "github.com/huandu/go-clone/generic"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -31,7 +31,7 @@ func (BaseDAO[T]) GetAll(db *gorm.DB, e T, cond ...T) ([]T, error) {
return nil, err return nil, err
} }
i := clone.Clone(e).(T) i := clone.Clone(e)
r = append(r, i) r = append(r, i)
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,573 @@
basePath: /api/v1
definitions:
models.Domain:
properties:
admin_email:
type: string
domain_name:
type: string
expiry_period:
type: integer
id:
type: integer
main_dns:
type: string
negative_ttl:
type: integer
refresh_interval:
type: integer
retry_interval:
type: integer
serial_number:
type: integer
type: object
models.Record-models_RecordContentDefault:
properties:
content:
allOf:
- $ref: '#/definitions/models.RecordContentDefault'
description: see https://github.com/cloud66-oss/coredns_mysql/blob/main/types.go
for content
id:
type: integer
name:
type: string
record_type:
type: string
ttl:
type: integer
zone:
type: string
type: object
models.RecordContentDefault:
additionalProperties: {}
type: object
server.Response:
properties:
data:
description: payload here
message:
description: error message
type: string
succeed:
description: '`true` for 2xx, else `false`'
type: boolean
type: object
info: info:
contact: {} contact: {}
paths: {} description: APIs for reCoreD-UI
title: reCoreD-UI API
version: "1.0"
paths:
/domains/:
get:
consumes:
- application/json
description: List all domains
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
items:
$ref: '#/definitions/models.Domain'
type: array
type: object
"401":
description: Unauthorized
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"500":
description: Internal Server Error
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
summary: List all domains
tags:
- domains
post:
description: Create a domain
parameters:
- description: content
in: body
name: object
required: true
schema:
$ref: '#/definitions/models.Domain'
responses:
"201":
description: Created
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
$ref: '#/definitions/models.Domain'
type: object
"400":
description: Bad Request
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"401":
description: Unauthorized
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"500":
description: Internal Server Error
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
summary: Create a domain
tags:
- domains
put:
consumes:
- application/json
description: Update a domain
parameters:
- description: content
in: body
name: object
required: true
schema:
$ref: '#/definitions/models.Domain'
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
$ref: '#/definitions/models.Domain'
type: object
"400":
description: Bad Request
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"401":
description: Unauthorized
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"404":
description: Not Found
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"500":
description: Internal Server Error
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
summary: Update a domain
tags:
- domains
/domains/{id}:
delete:
description: Delete a domain
parameters:
- description: Domain ID
in: path
name: id
required: true
type: integer
responses:
"204":
description: No Content
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"401":
description: Unauthorized
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"404":
description: Not Found
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"500":
description: Internal Server Error
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
summary: Delete a domain
tags:
- domains
/records/{domain}:
get:
description: List all records of a domain
parameters:
- description: domain
in: path
name: domain
required: true
type: string
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
items:
$ref: '#/definitions/models.Record-models_RecordContentDefault'
type: array
type: object
"401":
description: Unauthorized
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"404":
description: Not Found
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"500":
description: Internal Server Error
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
summary: List all records of a domain
tags:
- records
post:
consumes:
- application/json
description: Create a record of a domain
parameters:
- description: domain
in: path
name: domain
required: true
type: string
- description: content
in: body
name: object
required: true
schema:
$ref: '#/definitions/models.Record-models_RecordContentDefault'
responses:
"201":
description: Created
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
$ref: '#/definitions/models.Record-models_RecordContentDefault'
type: object
"400":
description: Bad Request
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"401":
description: Unauthorized
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"404":
description: Not Found
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"500":
description: Internal Server Error
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
summary: Create a record of a domain
tags:
- records
put:
consumes:
- application/json
description: Update a record of a domain
parameters:
- description: domain
in: path
name: domain
required: true
type: string
- description: content
in: body
name: object
required: true
schema:
$ref: '#/definitions/models.Record-models_RecordContentDefault'
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
$ref: '#/definitions/models.Record-models_RecordContentDefault'
type: object
"400":
description: Bad Request
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"401":
description: Unauthorized
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"404":
description: Not Found
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"500":
description: Internal Server Error
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
summary: Update a record of a domain
tags:
- records
/records/{domain}/{id}:
delete:
description: Delete a record of a domain, except SOA record.
parameters:
- description: domain
in: path
name: domain
required: true
type: string
- description: Record ID
in: path
name: id
required: true
type: integer
responses:
"204":
description: No Content
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"400":
description: Bad Request
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"401":
description: Unauthorized
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"404":
description: Not Found
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"500":
description: Internal Server Error
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
summary: Delete a record of a domain
tags:
- records
/records/{domain}/bulk:
post:
consumes:
- application/json
description: Create some records of a domain
parameters:
- description: domain
in: path
name: domain
required: true
type: string
- description: content
in: body
name: object
required: true
schema:
items:
$ref: '#/definitions/models.Record-models_RecordContentDefault'
type: array
responses:
"201":
description: Created
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
$ref: '#/definitions/models.Record-models_RecordContentDefault'
type: object
"400":
description: Bad Request
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"401":
description: Unauthorized
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"404":
description: Not Found
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
"500":
description: Internal Server Error
schema:
allOf:
- $ref: '#/definitions/server.Response'
- properties:
data:
type: object
type: object
summary: Create some records of a domain
tags:
- records
securityDefinitions:
BasicAuth:
type: basic
swagger: "2.0" swagger: "2.0"

1
go.mod
View File

@ -74,6 +74,7 @@ require (
require ( require (
github.com/cloud66-oss/coredns_mysql v0.0.0-20231116193749-de52e2924a6f github.com/cloud66-oss/coredns_mysql v0.0.0-20231116193749-de52e2924a6f
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1
github.com/huandu/go-clone/generic v1.7.2
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/swaggo/files v1.0.1 github.com/swaggo/files v1.0.1
github.com/urfave/cli/v2 v2.27.1 github.com/urfave/cli/v2 v2.27.1

2
go.sum
View File

@ -357,6 +357,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=
github.com/huandu/go-clone v1.7.2 h1:3+Aq0Ed8XK+zKkLjE2dfHg0XrpIfcohBE1K+c8Usxoo= github.com/huandu/go-clone v1.7.2 h1:3+Aq0Ed8XK+zKkLjE2dfHg0XrpIfcohBE1K+c8Usxoo=
github.com/huandu/go-clone v1.7.2/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE= github.com/huandu/go-clone v1.7.2/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE=
github.com/huandu/go-clone/generic v1.7.2 h1:47pQphxs1Xc9cVADjOHN+Bm5D0hNagwH9UXErbxgVKA=
github.com/huandu/go-clone/generic v1.7.2/go.mod h1:xgd9ZebcMsBWWcBx5mVMCoqMX24gLWr5lQicr+nVXNs=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=

View File

@ -15,6 +15,11 @@ func init() {
logrus.SetReportCaller(true) logrus.SetReportCaller(true)
} }
// @title reCoreD-UI API
// @version 1.0
// @description APIs for reCoreD-UI
// @BasePath /api/v1
// @securityDefinitions.basic BasicAuth
func main() { func main() {
flags := []cli.Flag{ flags := []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{

View File

@ -5,11 +5,11 @@ import (
"strings" "strings"
) )
// Domain domain data structure
type Domain struct { type Domain struct {
ID uint `gorm:"primaryKey" json:"id"` ID uint `gorm:"primaryKey" json:"id"`
DomainName string `gorm:"unique,not null,size:255" json:"domain_name"` DomainName string `gorm:"unique,not null,size:255" json:"domain_name"`
//SOA Info
MainDNS string `gorm:"not null;size:255" json:"main_dns"` MainDNS string `gorm:"not null;size:255" json:"main_dns"`
AdminEmail string `gorm:"not null;size:255" json:"admin_email"` AdminEmail string `gorm:"not null;size:255" json:"admin_email"`
SerialNumber int64 `gorm:"not null;default:1" json:"serial_number"` SerialNumber int64 `gorm:"not null;default:1" json:"serial_number"`

View File

@ -24,11 +24,13 @@ type recordContentTypes interface {
ARecord | AAAARecord | CNAMERecord | CAARecord | NSRecord | MXRecord | SOARecord | SRVRecord | TXTRecord | RecordContentDefault ARecord | AAAARecord | CNAMERecord | CAARecord | NSRecord | MXRecord | SOARecord | SRVRecord | TXTRecord | RecordContentDefault
} }
// Record dns records for coredns mysql plugin
type Record[T recordContentTypes] struct { type Record[T recordContentTypes] struct {
ID uint `gorm:"primaryKey" json:"id"` ID uint `gorm:"primaryKey" json:"id"`
Zone string `gorm:"not null;size:255" json:"zone"` Zone string `gorm:"not null;size:255" json:"zone"`
Name string `gorm:"not null;size:255" json:"name"` Name string `gorm:"not null;size:255" json:"name"`
Ttl int `json:"ttl"` Ttl int `json:"ttl"`
// see https://github.com/cloud66-oss/coredns_mysql/blob/main/types.go for content
Content T `gorm:"serializer:json;type:text" json:"content"` Content T `gorm:"serializer:json;type:text" json:"content"`
RecordType string `gorm:"not null;size:255" json:"record_type"` RecordType string `gorm:"not null;size:255" json:"record_type"`
} }

View File

@ -10,6 +10,7 @@ const (
SettingsKeyDNSServer = "dns.servers" SettingsKeyDNSServer = "dns.servers"
) )
// Settings settings for this app
type Settings struct { type Settings struct {
ID uint `gorm:"primaryKey"` ID uint `gorm:"primaryKey"`
Key string `gorm:"unique;not null;size:255"` Key string `gorm:"unique;not null;size:255"`

View File

@ -6,9 +6,21 @@ import (
"reCoreD-UI/models" "reCoreD-UI/models"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
_ "reCoreD-UI/docs"
) )
// GetDomains // GetDomains godoc
//
// @Router /domains/ [get]
// @Summary List all domains
// @Description List all domains
// @Tags domains
// @Accept json
// @Product json
// @Success 200 {object} Response{data=[]models.Domain}
// @Failure 401 {object} Response{data=nil}
// @Failure 500 {object} Response{data=nil}
func getDomains(c *gin.Context) { func getDomains(c *gin.Context) {
domains, err := controllers.GetDomains("") domains, err := controllers.GetDomains("")
if err != nil { if err != nil {
@ -22,6 +34,18 @@ func getDomains(c *gin.Context) {
}) })
} }
// CreateDomain godoc
//
// @Router /domains/ [post]
// @Summary Create a domain
// @Description Create a domain
// @Tags domains
// @Product json
// @Param object body models.Domain true "content"
// @Success 201 {object} Response{data=models.Domain}
// @Failure 400 {object} Response{data=nil}
// @Failure 401 {object} Response{data=nil}
// @Failure 500 {object} Response{data=nil}
func createDomain(c *gin.Context) { func createDomain(c *gin.Context) {
domain := &models.Domain{} domain := &models.Domain{}
@ -45,6 +69,20 @@ func createDomain(c *gin.Context) {
}) })
} }
// UpdateDomain godoc
//
// @Router /domains/ [put]
// @Summary Update a domain
// @Description Update a domain
// @Tags domains
// @Accept json
// @Product json
// @Param object body models.Domain true "content"
// @Success 200 {object} Response{data=models.Domain}
// @Failure 400 {object} Response{data=nil}
// @Failure 401 {object} Response{data=nil}
// @Failure 404 {object} Response{data=nil}
// @Failure 500 {object} Response{data=nil}
func updateDomain(c *gin.Context) { func updateDomain(c *gin.Context) {
domain := &models.Domain{} domain := &models.Domain{}
@ -66,6 +104,18 @@ func updateDomain(c *gin.Context) {
}) })
} }
// DeleteDomain godoc
//
// @Router /domains/{id} [delete]
// @Summary Delete a domain
// @Description Delete a domain
// @Tags domains
// @Product json
// @Param id path int true "Domain ID"
// @Success 204 {object} Response{data=nil}
// @Failure 401 {object} Response{data=nil}
// @Failure 404 {object} Response{data=nil}
// @Failure 500 {object} Response{data=nil}
func deleteDomain(c *gin.Context) { func deleteDomain(c *gin.Context) {
id := c.Param("id") id := c.Param("id")
if err := controllers.DeleteDomain(id); err != nil { if err := controllers.DeleteDomain(id); err != nil {

View File

@ -10,7 +10,6 @@ import (
) )
func validateRecord(r models.IRecord) error { func validateRecord(r models.IRecord) error {
switch r.GetType() { switch r.GetType() {
case models.RecordTypeA: case models.RecordTypeA:
record := &models.Record[models.ARecord]{} record := &models.Record[models.ARecord]{}
@ -71,6 +70,18 @@ func validateRecord(r models.IRecord) error {
} }
} }
// GetRecords godoc
//
// @Router /records/{domain} [get]
// @Summary List all records of a domain
// @Description List all records of a domain
// @Tags records
// @Product json
// @Param domain path string true "domain"
// @Success 200 {object} Response{data=[]models.Record[models.RecordContentDefault]}
// @Failure 401 {object} Response{data=nil}
// @Failure 404 {object} Response{data=nil}
// @Failure 500 {object} Response{data=nil}
func getRecords(c *gin.Context) { func getRecords(c *gin.Context) {
query := &models.Record[models.RecordContentDefault]{Content: make(models.RecordContentDefault)} query := &models.Record[models.RecordContentDefault]{Content: make(models.RecordContentDefault)}
if err := c.BindQuery(query); err != nil { if err := c.BindQuery(query); err != nil {
@ -95,6 +106,21 @@ func getRecords(c *gin.Context) {
}) })
} }
// CreateRecord godoc
//
// @Router /records/{domain} [post]
// @Summary Create a record of a domain
// @Description Create a record of a domain
// @Tags records
// @Accept json
// @Product json
// @Param domain path string true "domain"
// @Param object body models.Record[models.RecordContentDefault] true "content"
// @Success 201 {object} Response{data=models.Record[models.RecordContentDefault]}
// @Failure 400 {object} Response{data=nil}
// @Failure 401 {object} Response{data=nil}
// @Failure 404 {object} Response{data=nil}
// @Failure 500 {object} Response{data=nil}
func createRecord(c *gin.Context) { func createRecord(c *gin.Context) {
record := &models.Record[models.RecordContentDefault]{Content: make(models.RecordContentDefault)} record := &models.Record[models.RecordContentDefault]{Content: make(models.RecordContentDefault)}
if err := c.BindJSON(record); err != nil { if err := c.BindJSON(record); err != nil {
@ -134,6 +160,21 @@ func createRecord(c *gin.Context) {
}) })
} }
// CreateRecords godoc
//
// @Router /records/{domain}/bulk [post]
// @Summary Create some records of a domain
// @Description Create some records of a domain
// @Tags records
// @Accept json
// @Product json
// @Param domain path string true "domain"
// @Param object body []models.Record[models.RecordContentDefault] true "content"
// @Success 201 {object} Response{data=models.Record[models.RecordContentDefault]}
// @Failure 400 {object} Response{data=nil}
// @Failure 401 {object} Response{data=nil}
// @Failure 404 {object} Response{data=nil}
// @Failure 500 {object} Response{data=nil}
func createRecords(c *gin.Context) { func createRecords(c *gin.Context) {
var records []models.Record[models.RecordContentDefault] var records []models.Record[models.RecordContentDefault]
if err := c.BindJSON(&records); err != nil { if err := c.BindJSON(&records); err != nil {
@ -159,6 +200,21 @@ func createRecords(c *gin.Context) {
}) })
} }
// UpdateRecord godoc
//
// @Router /records/{domain} [put]
// @Summary Update a record of a domain
// @Description Update a record of a domain
// @Tags records
// @Accept json
// @Product json
// @Param domain path string true "domain"
// @Param object body models.Record[models.RecordContentDefault] true "content"
// @Success 200 {object} Response{data=models.Record[models.RecordContentDefault]}
// @Failure 400 {object} Response{data=nil}
// @Failure 401 {object} Response{data=nil}
// @Failure 404 {object} Response{data=nil}
// @Failure 500 {object} Response{data=nil}
func updateRecord(c *gin.Context) { func updateRecord(c *gin.Context) {
record := &models.Record[models.RecordContentDefault]{Content: make(models.RecordContentDefault)} record := &models.Record[models.RecordContentDefault]{Content: make(models.RecordContentDefault)}
if err := c.BindJSON(record); err != nil { if err := c.BindJSON(record); err != nil {
@ -196,6 +252,20 @@ func updateRecord(c *gin.Context) {
}) })
} }
// DeleteRecord godoc
//
// @Router /records/{domain}/{id} [delete]
// @Summary Delete a record of a domain
// @Description Delete a record of a domain, except SOA record.
// @Tags records
// @Product json
// @Param domain path string true "domain"
// @Param id path int true "Record ID"
// @Success 204 {object} Response{data=nil}
// @Failure 400 {object} Response{data=nil}
// @Failure 401 {object} Response{data=nil}
// @Failure 404 {object} Response{data=nil}
// @Failure 500 {object} Response{data=nil}
func deleteRecord(c *gin.Context) { func deleteRecord(c *gin.Context) {
domain := c.Param("domain") domain := c.Param("domain")
id := c.Param("id") id := c.Param("id")

View File

@ -10,9 +10,15 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
// Response common http response
type Response struct { type Response struct {
// `true` for 2xx, else `false`
Succeed bool `json:"succeed"` Succeed bool `json:"succeed"`
// error message
Message string `json:"message"` Message string `json:"message"`
// payload here
Data interface{} `json:"data"` Data interface{} `json:"data"`
} }

View File

@ -18,6 +18,7 @@ const (
swaggerPrefix = "/swagger" swaggerPrefix = "/swagger"
) )
func (s *Server) setupRoute() { func (s *Server) setupRoute() {
username, password, err := controllers.GetAdmin() username, password, err := controllers.GetAdmin()
if err != nil { if err != nil {