stage commit (pagination, sorting, a lil bit refactored)

This commit is contained in:
Vitaliy Pavlov 2024-07-04 04:19:51 +07:00
parent 848dd5df8a
commit 7ed9f67c37
41 changed files with 2044 additions and 320 deletions

View File

@ -1,15 +0,0 @@
package controllers
import (
"system-trace/core/auth"
"github.com/gofiber/fiber/v2"
)
func InitAuth(app *fiber.App, version *fiber.Router) {
v := *version
g := v.Group("/auth")
{
g.Post("/login", auth.ReqTokens)
}
}

View File

@ -1,17 +0,0 @@
package router
import (
"system-trace/core/app/controllers"
_ "system-trace/core/docs"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/swagger"
)
func Init(app *fiber.App) {
app.Get("/swagger/*", swagger.HandlerDefault)
v1 := app.Group("/v1")
{
controllers.InitAuth(app, &v1)
}
}

View File

@ -1,51 +0,0 @@
package app
import (
"os"
"system-trace/core/app/constants"
"system-trace/core/app/router"
"system-trace/core/app/router/middlewares"
"system-trace/core/environment"
"github.com/goccy/go-json"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/recover"
)
var Instance *fiber.App
func Serve() {
Instance = initServer()
Instance.Use(recover.New())
Instance.Use(cors.New())
Instance.Use(middlewares.ValidateSession)
router.Init(Instance)
Instance.Use(func(c *fiber.Ctx) error {
return c.
Status(fiber.StatusNotFound).
JSON(fiber.Map{
"error": constants.NOT_FOUND,
})
})
Instance.Listen(":" + os.Getenv("APP_PORT"))
}
func initServer() *fiber.App {
debug := environment.IsDebug()
return fiber.New(fiber.Config{
JSONEncoder: json.Marshal,
JSONDecoder: json.Unmarshal,
CaseSensitive: true,
EnablePrintRoutes: debug,
Prefork: true,
AppName: "System Trace API",
ErrorHandler: func(c *fiber.Ctx, err error) error {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": err.Error(),
})
},
})
}

View File

@ -1,7 +1,7 @@
package constants package constants
const ( const (
AUTH_FAILED = "User with specified email and password not found" AUTH_FAILED = "User with specified E-Mail and password not found"
NOT_FOUND = "Wrong request method or path" NOT_FOUND = "Wrong request method or path"
UNAUTHORIZED = "Unauthorized" UNAUTHORIZED = "Unauthorized"
) )

View File

@ -0,0 +1,42 @@
package permissions
const (
DASHBOARD_READ = "DASHBOARD_READ"
SETTINGS_READ = "SETTINGS_READ"
SETTINGS_UPDATE = "SETTINGS_UPDATE"
LOGS_READ = "LOGS_READ"
TASKS_READ = "TASKS_READ"
TASKS_UPDATE = "TASKS_UPDATE"
USERS_READ = "USERS_READ"
USERS_CREATE = "USERS_CREATE"
USERS_UPDATE = "USERS_UPDATE"
USERS_DELETE = "USERS_DELETE"
USERS_BLOCK = "USERS_BLOCK"
GROUPS_READ = "GROUPS_READ"
GROUPS_CREATE = "GROUPS_CREATE"
GROUPS_UPDATE = "GROUPS_UPDATE"
GROUPS_DELETE = "GROUPS_DELETE"
)
var All = map[string]int8{
DASHBOARD_READ: 1,
SETTINGS_READ: 2,
SETTINGS_UPDATE: 3,
LOGS_READ: 4,
TASKS_READ: 5,
TASKS_UPDATE: 6,
USERS_READ: 7,
USERS_CREATE: 9,
USERS_UPDATE: 10,
USERS_DELETE: 11,
USERS_BLOCK: 12,
GROUPS_READ: 13,
GROUPS_CREATE: 14,
GROUPS_UPDATE: 15,
GROUPS_DELETE: 16,
}

View File

@ -1,16 +1,22 @@
package auth package database
import ( import (
"context" "context"
"system-trace/core/database" "system-trace/core/types"
"system-trace/core/database/entities"
) )
func GetPair(p *PairTokens) (*entities.AuthToken, error) { type AuthToken struct {
aut := new(entities.AuthToken) UserID int32
AccessToken string `bun:"type:varchar"`
RefreshToken string `bun:"type:varchar"`
IsRevoked bool `bun:",default:false"`
}
func GetPairOfTokens(p *types.PairTokens) (*AuthToken, error) {
aut := new(AuthToken)
ctx := context.Background() ctx := context.Background()
err := database.PG.NewSelect(). err := PG.NewSelect().
Model(aut). Model(aut).
Where("access_token = ?", p.AccessToken). Where("access_token = ?", p.AccessToken).
Where("refresh_token = ?", p.RefreshToken). Where("refresh_token = ?", p.RefreshToken).
@ -20,13 +26,29 @@ func GetPair(p *PairTokens) (*entities.AuthToken, error) {
return aut, err return aut, err
} }
func RevokePair(p *PairTokens) error { func InsertPairOfTokens(id int32, at, rt string) error {
aut := entities.AuthToken{ p := AuthToken{
UserID: id,
AccessToken: at,
RefreshToken: rt,
}
ctx := context.Background()
_, err := PG.NewInsert().
Model(&p).
Returning("NULL").
Exec(ctx)
return err
}
func RevokePairOfTokens(p *types.PairTokens) error {
aut := AuthToken{
IsRevoked: true, IsRevoked: true,
} }
ctx := context.Background() ctx := context.Background()
_, err := database.PG.NewUpdate(). _, err := PG.NewUpdate().
Model(&aut). Model(&aut).
Column("is_revoked"). Column("is_revoked").
Where("access_token = ?", p.AccessToken). Where("access_token = ?", p.AccessToken).
@ -35,19 +57,3 @@ func RevokePair(p *PairTokens) error {
return err return err
} }
func insertPair(id int32, at, rt string) error {
p := entities.AuthToken{
UserID: id,
AccessToken: at,
RefreshToken: rt,
}
ctx := context.Background()
_, err := database.PG.NewInsert().
Model(&p).
Returning("NULL").
Exec(ctx)
return err
}

View File

@ -6,7 +6,6 @@ import (
"database/sql" "database/sql"
"fmt" "fmt"
"os" "os"
"system-trace/core/database/entities"
"system-trace/core/environment" "system-trace/core/environment"
"time" "time"
@ -41,7 +40,6 @@ func Connect() {
if environment.IsDebug() { if environment.IsDebug() {
db.AddQueryHook(bundebug.NewQueryHook( db.AddQueryHook(bundebug.NewQueryHook(
bundebug.WithVerbose(true), bundebug.WithVerbose(true),
bundebug.FromEnv("BUNDEBUG"),
)) ))
} }
@ -57,12 +55,12 @@ func Connect() {
func createSchema(db *bun.DB) error { func createSchema(db *bun.DB) error {
models := []interface{}{ models := []interface{}{
(*entities.Group)(nil), (*Group)(nil),
(*entities.GroupPermission)(nil), (*GroupPermission)(nil),
(*entities.User)(nil), (*User)(nil),
(*entities.Session)(nil), (*Session)(nil),
(*entities.Server)(nil), (*Server)(nil),
(*entities.AuthToken)(nil), (*AuthToken)(nil),
} }
ctx := context.Background() ctx := context.Background()

View File

@ -1,8 +0,0 @@
package entities
type AuthToken struct {
UserID int32
AccessToken string `bun:"type:varchar"`
RefreshToken string `bun:"type:varchar"`
IsRevoked bool `bun:",default:false"`
}

View File

@ -1,16 +0,0 @@
package entities
import "time"
type Group struct {
ID int32 `bun:",pk,autoincrement"`
IssuerID int32 `bun:",notnull"`
Issuer *User `bun:"rel:belongs-to,join:issuer_id=id"`
Name string `bun:",notnull,unique"`
GroupID int32 `bun:",notnull"`
Users []*User `bun:"rel:has-many,join:id=group_id"`
Permissions []*GroupPermission `bun:"rel:has-many,join:id=group_id"`
CreatedAt time.Time `bun:",notnull,default:current_timestamp"`
UpdatedAt time.Time
DeletedAt time.Time `bun:",soft_delete,nullzero"`
}

View File

@ -1,30 +0,0 @@
package entities
import (
"context"
"time"
"github.com/uptrace/bun"
)
type Server struct {
ID int64 `bun:",pk,autoincrement"`
Hostname string `bun:",notnull"`
IP string `bun:",notnull"`
IsOnline bool `bun:",notnull,default:true"`
LastOnline time.Time
CreatedAt time.Time `bun:",notnull,default:current_timestamp"`
UpdatedAt time.Time
}
var _ bun.BeforeAppendModelHook = (*Server)(nil)
func (s *Server) BeforeAppendModel(ctx context.Context, query bun.Query) error {
switch query.(type) {
case *bun.InsertQuery:
s.LastOnline = time.Now()
case *bun.UpdateQuery:
s.UpdatedAt = time.Now()
}
return nil
}

View File

@ -1,34 +0,0 @@
package entities
import (
"context"
"time"
"github.com/uptrace/bun"
)
type User struct {
ID int32 `bun:",pk,autoincrement"`
Email string `bun:",notnull,unique"`
PasswordHash string `bun:",notnull,type:varchar(64)"`
PasswordLength int8 `bun:",notnull"`
RealName string `bun:",notnull"`
GroupID int32 `bun:",notnull"`
Group *Group `bun:"rel:belongs-to,join:group_id=id"`
IsRequiredToSetPassword bool `bun:",notnull,default:true"`
IsActive bool `bun:",notnull,default:true"`
LastLogin time.Time
CreatedAt time.Time `bun:",notnull,default:current_timestamp"`
UpdatedAt time.Time
DeletedAt time.Time `bun:",soft_delete,nullzero"`
}
var _ bun.BeforeAppendModelHook = (*User)(nil)
func (u *User) BeforeAppendModel(ctx context.Context, query bun.Query) error {
switch query.(type) {
case *bun.UpdateQuery:
u.UpdatedAt = time.Now()
}
return nil
}

95
database/group.go Normal file
View File

@ -0,0 +1,95 @@
package database
import (
"context"
"fmt"
"math"
"system-trace/core/types"
"time"
"github.com/uptrace/bun"
)
type Group struct {
ID int32 `bun:",pk,autoincrement"`
IssuerID int32 `bun:",notnull" json:"issuerId"`
Issuer *User `bun:"rel:belongs-to,join:issuer_id=id" json:"issuer"`
Name string `bun:",notnull,unique" json:"name"`
Users []*User `bun:"rel:has-many,join:id=group_id,array" json:"users"`
Permissions []*GroupPermission `bun:"rel:has-many,join:id=group_id,array" json:"permissions"`
CreatedAt time.Time `bun:",notnull,default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt time.Time `bun:",soft_delete,nullzero" json:"-"`
}
var _ bun.BeforeAppendModelHook = (*Group)(nil)
func (g *Group) BeforeAppendModel(ctx context.Context, query bun.Query) error {
switch query.(type) {
case *bun.UpdateQuery:
g.UpdatedAt = time.Now()
}
return nil
}
func FindGroups(p *types.Pagination, ob *types.OrderBy) (groups *[]Group, cursor *types.Cursor, err error) {
ctx := context.Background()
groups = new([]Group)
count, err := PG.NewSelect().
Model(groups).
Relation("Users").
Relation("Permissions").
Offset(p.Page*p.Count - p.Count).
Limit(p.Count).
Order(fmt.Sprintf("%s %s", ob.Key, ob.Order)).
ScanAndCount(ctx)
return groups, &types.Cursor{
Count: len(*groups),
CurrentPage: p.Page,
TotalPages: math.Ceil(float64(count) / float64(p.Count)),
TotalRows: float64(count),
}, err
}
func FindGroupByID(id int) (group *Group, err error) {
ctx := context.Background()
group = new(Group)
err = PG.NewSelect().
Model(group).
Where("id = ?", id).
Scan(ctx)
return group, err
}
func InsertGroup(g *Group) error {
ctx := context.Background()
_, err := PG.NewInsert().
Model(g).
Returning("NULL").
Exec(ctx)
return err
}
func UpdateGroup(g *Group, cols []string) error {
ctx := context.Background()
_, err := PG.NewUpdate().
Model(g).
Column(cols...).
WherePK().
Exec(ctx)
return err
}
func DeleteGroup(id int) error {
ctx := context.Background()
_, err := PG.NewDelete().
Model(new(Group)).
Where("id = ?", id).
Exec(ctx)
return err
}

View File

@ -1,6 +1,6 @@
package entities package database
type GroupPermission struct { type GroupPermission struct {
GroupID int32 `bun:",notnull" json:"-"` GroupID int32 `bun:",notnull" json:"-"`
Value int8 `bun:",notnull"` Value int8 `bun:",notnull" json:"value"`
} }

30
database/server.go Normal file
View File

@ -0,0 +1,30 @@
package database
import (
"context"
"time"
"github.com/uptrace/bun"
)
type Server struct {
ID int64 `bun:",pk,autoincrement"`
Hostname string `bun:",notnull" json:"hostname"`
IP string `bun:",notnull"`
IsOnline bool `bun:",notnull,default:true" json:"isOnline"`
LastOnline time.Time `json:"lastOnline"`
CreatedAt time.Time `bun:",notnull,default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
var _ bun.BeforeAppendModelHook = (*Server)(nil)
func (s *Server) BeforeAppendModel(ctx context.Context, query bun.Query) error {
switch query.(type) {
case *bun.InsertQuery:
s.LastOnline = time.Now()
case *bun.UpdateQuery:
s.UpdatedAt = time.Now()
}
return nil
}

View File

@ -1,4 +1,4 @@
package entities package database
import ( import (
"context" "context"
@ -9,13 +9,13 @@ import (
type Session struct { type Session struct {
ID int64 `bun:",pk,autoincrement"` ID int64 `bun:",pk,autoincrement"`
IssuerID int32 `bun:",notnull"` IssuerID int32 `bun:",notnull" json:"issuerId"`
Issuer *User `bun:"rel:belongs-to,join:issuer_id=id"` Issuer *User `bun:"rel:belongs-to,join:issuer_id=id" json:"issuer"`
Configuration map[string]interface{} `bun:"type:jsonb"` Configuration map[string]interface{} `bun:"type:jsonb" json:"configuration"`
CreatedAt time.Time `bun:",notnull,default:current_timestamp"` CreatedAt time.Time `bun:",notnull,default:current_timestamp" json:"createdAt"`
FinishedAt time.Time FinishedAt time.Time `json:"finishedAt"`
UpdatedAt time.Time UpdatedAt time.Time `json:"updatedAt"`
DeletedAt time.Time `bun:",soft_delete,nullzero"` DeletedAt time.Time `bun:",soft_delete,nullzero" json:"-"`
} }
var _ bun.BeforeAppendModelHook = (*Session)(nil) var _ bun.BeforeAppendModelHook = (*Session)(nil)

112
database/user.go Normal file
View File

@ -0,0 +1,112 @@
package database
import (
"context"
"fmt"
"math"
"system-trace/core/types"
"system-trace/core/utils"
"time"
"github.com/uptrace/bun"
)
type User struct {
ID int32 `bun:",pk,autoincrement"`
Email string `bun:",notnull,unique" json:"email"`
PasswordHash string `bun:",notnull,type:varchar(64)" json:"-"`
PasswordLength int8 `bun:",notnull" json:"passwordLength"`
RealName string `bun:",notnull" json:"realName"`
GroupID int32 `bun:",notnull" json:"groupId"`
Group *Group `bun:"rel:belongs-to,join:group_id=id" json:"group"`
IsRequiredToSetPassword bool `bun:",notnull,default:true" json:"isRequiredToSetPassword"`
IsActive bool `bun:",notnull,default:true" json:"isActive"`
LastLogin time.Time `json:"lastLogin"`
CreatedAt time.Time `bun:",notnull,default:current_timestamp" json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt time.Time `bun:",soft_delete,nullzero" json:"deletedAt"`
}
var _ bun.BeforeAppendModelHook = (*User)(nil)
func (u *User) BeforeAppendModel(ctx context.Context, query bun.Query) error {
switch query.(type) {
case *bun.UpdateQuery:
u.UpdatedAt = time.Now()
}
return nil
}
func FindByEmailAndPassword(email, password string) (*User, error) {
passwordHash := utils.SHA256(password)
ctx := context.Background()
u := new(User)
err := PG.NewSelect().
Model(u).
Where("email = ?", email).
Where("password_hash = ?", passwordHash).
Scan(ctx)
return u, err
}
func FindUsers(p *types.Pagination, ob *types.OrderBy) (users *[]User, cursor *types.Cursor, err error) {
ctx := context.Background()
users = new([]User)
count, err := PG.NewSelect().
Model(users).
Relation("Group").
Offset(p.Page*p.Count - p.Count).
Limit(p.Count).
Order(fmt.Sprintf("%s %s", ob.Key, ob.Order)).
ScanAndCount(ctx)
return users, &types.Cursor{
Count: len(*users),
CurrentPage: p.Page,
TotalPages: math.Ceil(float64(count) / float64(p.Count)),
TotalRows: float64(count),
}, err
}
func FindUserByID(id int) (user *User, err error) {
ctx := context.Background()
user = new(User)
err = PG.NewSelect().
Model(user).
Where("id = ?", id).
Scan(ctx)
return user, err
}
func InsertUser(u *User) error {
ctx := context.Background()
_, err := PG.NewInsert().
Model(u).
Returning("NULL").
Exec(ctx)
return err
}
func UpdateUser(u *User, cols []string) error {
ctx := context.Background()
_, err := PG.NewUpdate().
Model(u).
Column(cols...).
WherePK().
Exec(ctx)
return err
}
func DeleteUser(id int) error {
ctx := context.Background()
_, err := PG.NewDelete().
Model(new(User)).
Where("id = ?", id).
Exec(ctx)
return err
}

View File

@ -34,7 +34,7 @@ const docTemplate = `{
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/auth.AuthBody" "$ref": "#/definitions/types.AuthBody"
} }
} }
], ],
@ -44,10 +44,410 @@ const docTemplate = `{
} }
} }
} }
},
"/groups": {
"get": {
"description": "Returns array of groups and count",
"produces": [
"application/json"
],
"tags": [
"groups"
],
"summary": "Get groups (with pagination)",
"parameters": [
{
"maximum": 100,
"minimum": 10,
"type": "integer",
"description": "Count of rows",
"name": "count",
"in": "query",
"required": true
},
{
"type": "integer",
"description": "Rows to skip",
"name": "offset",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.JSONPagination"
}
}
}
},
"post": {
"description": "Create group with specified data",
"produces": [
"application/json"
],
"tags": [
"groups"
],
"summary": "Create group",
"parameters": [
{
"description": "Request body",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/database.Group"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
},
"patch": {
"description": "Update group with specified data",
"produces": [
"application/json"
],
"tags": [
"groups"
],
"summary": "Update group",
"parameters": [
{
"description": "Request body",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/database.Group"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/groups/:id": {
"get": {
"description": "Returns group instance",
"produces": [
"application/json"
],
"tags": [
"groups"
],
"summary": "Get group by ID",
"parameters": [
{
"minimum": 1,
"type": "integer",
"description": "Group ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/database.Group"
}
}
}
},
"delete": {
"description": "Delete group by ID",
"produces": [
"application/json"
],
"tags": [
"groups"
],
"summary": "Delete group",
"parameters": [
{
"minimum": 1,
"type": "integer",
"description": "Group ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/permissions": {
"get": {
"description": "Returns key-value map with permissions",
"produces": [
"application/json"
],
"summary": "Get list of permissions",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": {
"type": "integer"
}
}
}
}
}
},
"/users": {
"get": {
"description": "Returns array of users and count",
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Get users (with pagination)",
"parameters": [
{
"maximum": 100,
"minimum": 10,
"type": "integer",
"description": "Count of rows",
"name": "count",
"in": "query",
"required": true
},
{
"type": "integer",
"description": "Rows to skip",
"name": "offset",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.JSONPagination"
}
}
}
},
"post": {
"description": "Create user with specified data",
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Create user",
"parameters": [
{
"description": "Request body",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/database.User"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
},
"patch": {
"description": "Update user with specified data",
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Update user",
"parameters": [
{
"description": "Request body",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/database.User"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/users/:id": {
"get": {
"description": "Returns user instance",
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Get user by ID",
"parameters": [
{
"minimum": 1,
"type": "integer",
"description": "User ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/database.User"
}
}
}
},
"delete": {
"description": "Delete user by ID",
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Delete user",
"parameters": [
{
"minimum": 1,
"type": "integer",
"description": "User ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
} }
}, },
"definitions": { "definitions": {
"auth.AuthBody": { "database.Group": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"deletedAt": {
"type": "string"
},
"id": {
"type": "integer"
},
"issuer": {
"$ref": "#/definitions/database.User"
},
"issuerID": {
"type": "integer"
},
"name": {
"type": "string"
},
"permissions": {
"type": "array",
"items": {
"$ref": "#/definitions/database.GroupPermission"
}
},
"updatedAt": {
"type": "string"
},
"users": {
"type": "array",
"items": {
"$ref": "#/definitions/database.User"
}
}
}
},
"database.GroupPermission": {
"type": "object",
"properties": {
"value": {
"type": "integer"
}
}
},
"database.User": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"deletedAt": {
"type": "string"
},
"email": {
"type": "string"
},
"group": {
"$ref": "#/definitions/database.Group"
},
"groupID": {
"type": "integer"
},
"id": {
"type": "integer"
},
"isActive": {
"type": "boolean"
},
"isRequiredToSetPassword": {
"type": "boolean"
},
"lastLogin": {
"type": "string"
},
"passwordHash": {
"type": "string"
},
"passwordLength": {
"type": "integer"
},
"realName": {
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
"types.AuthBody": {
"type": "object", "type": "object",
"required": [ "required": [
"email", "email",
@ -60,15 +460,38 @@ const docTemplate = `{
}, },
"password": { "password": {
"type": "string", "type": "string",
"example": "Aasdfgh1!" "example": "Aasdfg1!"
} }
} }
},
"types.Cursor": {
"type": "object",
"properties": {
"count": {
"type": "integer"
},
"currentPage": {
"type": "integer"
},
"totalPages": {
"type": "number"
}
}
},
"types.JSONPagination": {
"type": "object",
"properties": {
"cursor": {
"$ref": "#/definitions/types.Cursor"
},
"data": {}
}
} }
}, },
"securityDefinitions": { "securityDefinitions": {
"Bearer": { "accessToken=...;refreshToken=...": {
"type": "apiKey", "type": "apiKey",
"name": "Authorization", "name": "Cookie",
"in": "header" "in": "header"
} }
} }

View File

@ -26,7 +26,7 @@
"in": "body", "in": "body",
"required": true, "required": true,
"schema": { "schema": {
"$ref": "#/definitions/auth.AuthBody" "$ref": "#/definitions/types.AuthBody"
} }
} }
], ],
@ -36,10 +36,410 @@
} }
} }
} }
},
"/groups": {
"get": {
"description": "Returns array of groups and count",
"produces": [
"application/json"
],
"tags": [
"groups"
],
"summary": "Get groups (with pagination)",
"parameters": [
{
"maximum": 100,
"minimum": 10,
"type": "integer",
"description": "Count of rows",
"name": "count",
"in": "query",
"required": true
},
{
"type": "integer",
"description": "Rows to skip",
"name": "offset",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.JSONPagination"
}
}
}
},
"post": {
"description": "Create group with specified data",
"produces": [
"application/json"
],
"tags": [
"groups"
],
"summary": "Create group",
"parameters": [
{
"description": "Request body",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/database.Group"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
},
"patch": {
"description": "Update group with specified data",
"produces": [
"application/json"
],
"tags": [
"groups"
],
"summary": "Update group",
"parameters": [
{
"description": "Request body",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/database.Group"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/groups/:id": {
"get": {
"description": "Returns group instance",
"produces": [
"application/json"
],
"tags": [
"groups"
],
"summary": "Get group by ID",
"parameters": [
{
"minimum": 1,
"type": "integer",
"description": "Group ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/database.Group"
}
}
}
},
"delete": {
"description": "Delete group by ID",
"produces": [
"application/json"
],
"tags": [
"groups"
],
"summary": "Delete group",
"parameters": [
{
"minimum": 1,
"type": "integer",
"description": "Group ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/permissions": {
"get": {
"description": "Returns key-value map with permissions",
"produces": [
"application/json"
],
"summary": "Get list of permissions",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"additionalProperties": {
"type": "integer"
}
}
}
}
}
},
"/users": {
"get": {
"description": "Returns array of users and count",
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Get users (with pagination)",
"parameters": [
{
"maximum": 100,
"minimum": 10,
"type": "integer",
"description": "Count of rows",
"name": "count",
"in": "query",
"required": true
},
{
"type": "integer",
"description": "Rows to skip",
"name": "offset",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/types.JSONPagination"
}
}
}
},
"post": {
"description": "Create user with specified data",
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Create user",
"parameters": [
{
"description": "Request body",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/database.User"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
},
"patch": {
"description": "Update user with specified data",
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Update user",
"parameters": [
{
"description": "Request body",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/database.User"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/users/:id": {
"get": {
"description": "Returns user instance",
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Get user by ID",
"parameters": [
{
"minimum": 1,
"type": "integer",
"description": "User ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/database.User"
}
}
}
},
"delete": {
"description": "Delete user by ID",
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Delete user",
"parameters": [
{
"minimum": 1,
"type": "integer",
"description": "User ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
} }
}, },
"definitions": { "definitions": {
"auth.AuthBody": { "database.Group": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"deletedAt": {
"type": "string"
},
"id": {
"type": "integer"
},
"issuer": {
"$ref": "#/definitions/database.User"
},
"issuerID": {
"type": "integer"
},
"name": {
"type": "string"
},
"permissions": {
"type": "array",
"items": {
"$ref": "#/definitions/database.GroupPermission"
}
},
"updatedAt": {
"type": "string"
},
"users": {
"type": "array",
"items": {
"$ref": "#/definitions/database.User"
}
}
}
},
"database.GroupPermission": {
"type": "object",
"properties": {
"value": {
"type": "integer"
}
}
},
"database.User": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"deletedAt": {
"type": "string"
},
"email": {
"type": "string"
},
"group": {
"$ref": "#/definitions/database.Group"
},
"groupID": {
"type": "integer"
},
"id": {
"type": "integer"
},
"isActive": {
"type": "boolean"
},
"isRequiredToSetPassword": {
"type": "boolean"
},
"lastLogin": {
"type": "string"
},
"passwordHash": {
"type": "string"
},
"passwordLength": {
"type": "integer"
},
"realName": {
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
"types.AuthBody": {
"type": "object", "type": "object",
"required": [ "required": [
"email", "email",
@ -52,15 +452,38 @@
}, },
"password": { "password": {
"type": "string", "type": "string",
"example": "Aasdfgh1!" "example": "Aasdfg1!"
} }
} }
},
"types.Cursor": {
"type": "object",
"properties": {
"count": {
"type": "integer"
},
"currentPage": {
"type": "integer"
},
"totalPages": {
"type": "number"
}
}
},
"types.JSONPagination": {
"type": "object",
"properties": {
"cursor": {
"$ref": "#/definitions/types.Cursor"
},
"data": {}
}
} }
}, },
"securityDefinitions": { "securityDefinitions": {
"Bearer": { "accessToken=...;refreshToken=...": {
"type": "apiKey", "type": "apiKey",
"name": "Authorization", "name": "Cookie",
"in": "header" "in": "header"
} }
} }

View File

@ -1,17 +1,91 @@
basePath: /v1 basePath: /v1
definitions: definitions:
auth.AuthBody: database.Group:
properties:
createdAt:
type: string
deletedAt:
type: string
id:
type: integer
issuer:
$ref: '#/definitions/database.User'
issuerID:
type: integer
name:
type: string
permissions:
items:
$ref: '#/definitions/database.GroupPermission'
type: array
updatedAt:
type: string
users:
items:
$ref: '#/definitions/database.User'
type: array
type: object
database.GroupPermission:
properties:
value:
type: integer
type: object
database.User:
properties:
createdAt:
type: string
deletedAt:
type: string
email:
type: string
group:
$ref: '#/definitions/database.Group'
groupID:
type: integer
id:
type: integer
isActive:
type: boolean
isRequiredToSetPassword:
type: boolean
lastLogin:
type: string
passwordHash:
type: string
passwordLength:
type: integer
realName:
type: string
updatedAt:
type: string
type: object
types.AuthBody:
properties: properties:
email: email:
example: john@proton.mail example: john@proton.mail
type: string type: string
password: password:
example: Aasdfgh1! example: Aasdfg1!
type: string type: string
required: required:
- email - email
- password - password
type: object type: object
types.Cursor:
properties:
count:
type: integer
currentPage:
type: integer
totalPages:
type: number
type: object
types.JSONPagination:
properties:
cursor:
$ref: '#/definitions/types.Cursor'
data: {}
type: object
info: info:
contact: contact:
name: https://peresvet.it name: https://peresvet.it
@ -27,7 +101,7 @@ paths:
name: request name: request
required: true required: true
schema: schema:
$ref: '#/definitions/auth.AuthBody' $ref: '#/definitions/types.AuthBody'
produces: produces:
- application/json - application/json
responses: responses:
@ -36,9 +110,216 @@ paths:
summary: Request pair of tokens summary: Request pair of tokens
tags: tags:
- auth - auth
/groups:
get:
description: Returns array of groups and count
parameters:
- description: Count of rows
in: query
maximum: 100
minimum: 10
name: count
required: true
type: integer
- description: Rows to skip
in: query
name: offset
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/types.JSONPagination'
summary: Get groups (with pagination)
tags:
- groups
patch:
description: Update group with specified data
parameters:
- description: Request body
in: body
name: request
required: true
schema:
$ref: '#/definitions/database.Group'
produces:
- application/json
responses:
"200":
description: OK
summary: Update group
tags:
- groups
post:
description: Create group with specified data
parameters:
- description: Request body
in: body
name: request
required: true
schema:
$ref: '#/definitions/database.Group'
produces:
- application/json
responses:
"200":
description: OK
summary: Create group
tags:
- groups
/groups/:id:
delete:
description: Delete group by ID
parameters:
- description: Group ID
in: path
minimum: 1
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
summary: Delete group
tags:
- groups
get:
description: Returns group instance
parameters:
- description: Group ID
in: path
minimum: 1
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/database.Group'
summary: Get group by ID
tags:
- groups
/permissions:
get:
description: Returns key-value map with permissions
produces:
- application/json
responses:
"200":
description: OK
schema:
additionalProperties:
type: integer
type: object
summary: Get list of permissions
/users:
get:
description: Returns array of users and count
parameters:
- description: Count of rows
in: query
maximum: 100
minimum: 10
name: count
required: true
type: integer
- description: Rows to skip
in: query
name: offset
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/types.JSONPagination'
summary: Get users (with pagination)
tags:
- users
patch:
description: Update user with specified data
parameters:
- description: Request body
in: body
name: request
required: true
schema:
$ref: '#/definitions/database.User'
produces:
- application/json
responses:
"200":
description: OK
summary: Update user
tags:
- users
post:
description: Create user with specified data
parameters:
- description: Request body
in: body
name: request
required: true
schema:
$ref: '#/definitions/database.User'
produces:
- application/json
responses:
"200":
description: OK
summary: Create user
tags:
- users
/users/:id:
delete:
description: Delete user by ID
parameters:
- description: User ID
in: path
minimum: 1
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
summary: Delete user
tags:
- users
get:
description: Returns user instance
parameters:
- description: User ID
in: path
minimum: 1
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/database.User'
summary: Get user by ID
tags:
- users
securityDefinitions: securityDefinitions:
Bearer: accessToken=...;refreshToken=...:
in: header in: header
name: Authorization name: Cookie
type: apiKey type: apiKey
swagger: "2.0" swagger: "2.0"

2
go.mod
View File

@ -3,13 +3,11 @@ module system-trace/core
go 1.22.3 go 1.22.3
require ( require (
github.com/go-playground/validator v9.31.0+incompatible
github.com/go-playground/validator/v10 v10.20.0 github.com/go-playground/validator/v10 v10.20.0
github.com/goccy/go-json v0.10.2 github.com/goccy/go-json v0.10.2
github.com/gofiber/fiber v1.14.6 github.com/gofiber/fiber v1.14.6
github.com/gofiber/fiber/v2 v2.52.4 github.com/gofiber/fiber/v2 v2.52.4
github.com/gofiber/swagger v1.0.0 github.com/gofiber/swagger v1.0.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang-jwt/jwt/v5 v5.2.1
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/swaggo/swag v1.16.3 github.com/swaggo/swag v1.16.3

16
go.sum
View File

@ -27,13 +27,12 @@ github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
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/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 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA=
github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
@ -46,8 +45,6 @@ github.com/gofiber/swagger v1.0.0 h1:BzUzDS9ZT6fDUa692kxmfOjc1DZiloLiPK/W5z1H1tc
github.com/gofiber/swagger v1.0.0/go.mod h1:QrYNF1Yrc7ggGK6ATsJ6yfH/8Zi5bu9lA7wB8TmCecg= github.com/gofiber/swagger v1.0.0/go.mod h1:QrYNF1Yrc7ggGK6ATsJ6yfH/8Zi5bu9lA7wB8TmCecg=
github.com/gofiber/utils v0.0.10 h1:3Mr7X7JdCUo7CWf/i5sajSaDmArEDtti8bM1JUVso2U= github.com/gofiber/utils v0.0.10 h1:3Mr7X7JdCUo7CWf/i5sajSaDmArEDtti8bM1JUVso2U=
github.com/gofiber/utils v0.0.10/go.mod h1:9J5aHFUIjq0XfknT4+hdSMG6/jzfaAgCu4HEbWDeBlo= github.com/gofiber/utils v0.0.10/go.mod h1:9J5aHFUIjq0XfknT4+hdSMG6/jzfaAgCu4HEbWDeBlo=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@ -66,6 +63,7 @@ github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ib
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 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/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
@ -84,13 +82,16 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -105,6 +106,7 @@ github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
github.com/tetratelabs/wazero v1.7.0 h1:jg5qPydno59wqjpGrHph81lbtHzTrWzwwtD4cD88+hQ= github.com/tetratelabs/wazero v1.7.0 h1:jg5qPydno59wqjpGrHph81lbtHzTrWzwwtD4cD88+hQ=
github.com/tetratelabs/wazero v1.7.0/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= github.com/tetratelabs/wazero v1.7.0/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
github.com/uptrace/bun v1.2.1 h1:2ENAcfeCfaY5+2e7z5pXrzFKy3vS8VXvkCag6N2Yzfk= github.com/uptrace/bun v1.2.1 h1:2ENAcfeCfaY5+2e7z5pXrzFKy3vS8VXvkCag6N2Yzfk=
@ -130,10 +132,14 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wasilibs/go-re2 v1.5.2 h1:fDO2TJrRzRrv3jD0gzOvmZ2UM4Yt9YXOEdLrlNc/Ies= github.com/wasilibs/go-re2 v1.5.2 h1:fDO2TJrRzRrv3jD0gzOvmZ2UM4Yt9YXOEdLrlNc/Ies=
github.com/wasilibs/go-re2 v1.5.2/go.mod h1:UqqxQ1O99boQUm1r61H/IYGiGQOS/P88K7hU5nLNkEg= github.com/wasilibs/go-re2 v1.5.2/go.mod h1:UqqxQ1O99boQUm1r61H/IYGiGQOS/P88K7hU5nLNkEg=
github.com/wasilibs/nottinygc v0.4.0 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2exJQ=
github.com/wasilibs/nottinygc v0.4.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK8jetlB3HIo=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
@ -151,6 +157,7 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@ -161,6 +168,7 @@ golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"system-trace/core/app"
"system-trace/core/database" "system-trace/core/database"
"system-trace/core/environment" "system-trace/core/environment"
"system-trace/core/validators" "system-trace/core/validators"
@ -11,13 +10,13 @@ import (
// @version 1.0 // @version 1.0
// @contact.name https://peresvet.it // @contact.name https://peresvet.it
// @BasePath /v1 // @BasePath /v1
// @securityDefinitions.apikey Bearer // @securityDefinitions.apikey accessToken=...;refreshToken=...
// @in header // @in header
// @name Authorization // @name Cookie
// @externalDocs.description OpenAPI // @externalDocs.description OpenAPI
func main() { func main() {
environment.Load() environment.Load()
validators.RegisterValidators() validators.RegisterValidators()
database.Connect() database.Connect()
app.Serve() serveApp()
} }

View File

@ -3,15 +3,17 @@ package middlewares
import ( import (
"net/http" "net/http"
"strings" "strings"
"system-trace/core/app/constants" "system-trace/core/constants"
"system-trace/core/auth" "system-trace/core/database"
"system-trace/core/modules/auth"
"system-trace/core/types"
"system-trace/core/utils" "system-trace/core/utils"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
func ValidateSession(c *fiber.Ctx) error { func ValidateSession(c *fiber.Ctx) error {
p := new(auth.PairTokens) p := new(types.PairTokens)
if err := c.CookieParser(p); err != nil { if err := c.CookieParser(p); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{ return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"error": err.Error(), "error": err.Error(),
@ -26,7 +28,7 @@ func ValidateSession(c *fiber.Ctx) error {
return c.Next() return c.Next()
} }
func validatePair(c *fiber.Ctx, p *auth.PairTokens) bool { func validatePair(c *fiber.Ctx, p *types.PairTokens) bool {
if len(p.AccessToken) <= 0 || len(p.RefreshToken) <= 0 { if len(p.AccessToken) <= 0 || len(p.RefreshToken) <= 0 {
return false return false
} }
@ -39,12 +41,12 @@ func validatePair(c *fiber.Ctx, p *auth.PairTokens) bool {
return false return false
} }
pt, err := auth.GetPair(p) pt, err := database.GetPairOfTokens(p)
if err != nil { if err != nil {
return false return false
} }
err = auth.RevokePair(p) err = database.RevokePairOfTokens(p)
if err != nil { if err != nil {
return false return false
} }

21
parsers/order_by.go Normal file
View File

@ -0,0 +1,21 @@
package parsers
import (
"system-trace/core/types"
"system-trace/core/validators"
"github.com/gofiber/fiber/v2"
)
func ParseOrder(c *fiber.Ctx) (*types.OrderBy, error) {
ob := new(types.OrderBy)
if err := c.QueryParser(ob); err != nil {
return nil, err
}
if err := validators.Validate(c, ob); err != nil {
return nil, err
}
return ob, nil
}

21
parsers/pagination.go Normal file
View File

@ -0,0 +1,21 @@
package parsers
import (
"system-trace/core/types"
"system-trace/core/validators"
"github.com/gofiber/fiber/v2"
)
func ParsePagination(c *fiber.Ctx) (*types.Pagination, error) {
p := new(types.Pagination)
if err := c.QueryParser(p); err != nil {
return nil, err
}
if err := validators.Validate(c, p); err != nil {
return nil, err
}
return p, nil
}

44
router.go Normal file
View File

@ -0,0 +1,44 @@
package main
import (
_ "system-trace/core/docs"
"system-trace/core/services"
"system-trace/core/services/auth"
"system-trace/core/services/groups"
"system-trace/core/services/users"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/swagger"
)
func initRouter(app *fiber.App) {
app.Get("/swagger/*", swagger.HandlerDefault)
v1 := app.Group("/v1")
{
v1.Get("/permissions", services.GetPermissions)
ag := v1.Group("/auth")
{
ag.Post("/login", auth.ReqTokensHandler)
}
gg := v1.Group("/groups")
{
gg.Get("", groups.GetGroupsHandler)
gg.Get("/:id", groups.GetGroupByIDHandler)
gg.Post("", groups.CreateGroupHandler)
gg.Patch("", groups.UpdateGroupHandler)
gg.Delete("/:id", groups.DeleteGroupHandler)
}
ug := v1.Group("/users")
{
ug.Get("", users.GetUsersHandler)
ug.Get("/:id", users.GetUserByIDHandler)
ug.Post("", users.CreateUserHandler)
ug.Patch("", users.UpdateUserHandler)
ug.Delete("/:id", users.DeleteUserHandler)
}
}
}

58
server.go Normal file
View File

@ -0,0 +1,58 @@
package main
import (
"fmt"
"os"
"system-trace/core/constants"
"system-trace/core/environment"
"system-trace/core/types"
"github.com/goccy/go-json"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/recover"
)
var Instance *fiber.App
func serveApp() {
Instance = initServer()
Instance.Use(recover.New())
Instance.Use(cors.New())
// Instance.Use(middlewares.ValidateSession)
initRouter(Instance)
Instance.Use(func(c *fiber.Ctx) error {
return c.
Status(fiber.StatusNotFound).
JSON(types.JSONError{
Error: constants.NOT_FOUND,
})
})
printRoutes(Instance)
Instance.Listen(":" + os.Getenv("APP_PORT"))
}
func initServer() *fiber.App {
return fiber.New(fiber.Config{
JSONEncoder: json.Marshal,
JSONDecoder: json.Unmarshal,
CaseSensitive: true,
Prefork: true,
AppName: "System Trace API",
ErrorHandler: func(c *fiber.Ctx, err error) error {
return c.Status(fiber.StatusInternalServerError).JSON(types.JSONError{
Error: err.Error(),
})
},
})
}
func printRoutes(app *fiber.App) {
debug := environment.IsDebug()
if !fiber.IsChild() && debug {
data, _ := json.MarshalIndent(app.GetRoutes(true), "", " ")
fmt.Print(string(data))
}
}

View File

@ -2,34 +2,41 @@ package auth
import ( import (
"errors" "errors"
"system-trace/core/app/constants" "system-trace/core/constants"
"system-trace/core/users" "system-trace/core/database"
"system-trace/core/services/users"
"system-trace/core/types"
"system-trace/core/validators" "system-trace/core/validators"
"time" "time"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
// ReqTokens godoc // MARK: ReqTokens godoc
// @Summary Request pair of tokens // @Summary Request pair of tokens
// @Description Returns pair of access and refresh tokens // @Description Returns pair of access and refresh tokens
// @Tags auth // @Tags auth
// @Produce json // @Produce json
// @Param request body AuthBody true "Request body" // @Param request body types.AuthBody true "Request body"
// @Header 200 {string} Token "accessToken=..." // @Header 200 {string} Set-Cookie "accessToken=..."
// @Header 200 {string} Set-Cookie "refreshToken=..." // @Header 200 {string} Set-Cookie "refreshToken=..."
// @Success 200 // @Success 200
// @Router /auth/login [post] // @Router /auth/login [post]
func ReqTokens(c *fiber.Ctx) error { func ReqTokensHandler(c *fiber.Ctx) error {
ab := new(AuthBody) ab := new(types.AuthBody)
if err := c.BodyParser(ab); err != nil { if err := c.BodyParser(ab); err != nil {
return err return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
} Error: err.Error(),
if err := validators.Validate(c, ab); err != nil { })
return err
} }
u, err := users.FindByEmailAndPassword(ab.Email, ab.Password) if err := validators.Validate(c, ab); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
u, err := database.FindByEmailAndPassword(ab.Email, ab.Password)
if err != nil { if err != nil {
e := err.Error() e := err.Error()
if e == "sql: no rows in result set" { if e == "sql: no rows in result set" {
@ -37,17 +44,18 @@ func ReqTokens(c *fiber.Ctx) error {
} }
return c. return c.
Status(fiber.StatusBadRequest). Status(fiber.StatusBadRequest).
JSON(fiber.Map{ JSON(types.JSONError{
"error": e, Error: e,
}) })
} }
if u != nil { if u != nil {
err = GeneratePairAndSetCookie(c, u.ID) err = GeneratePairAndSetCookie(c, u.ID)
if err != nil { if err != nil {
return c. return c.
Status(fiber.StatusBadRequest). Status(fiber.StatusBadRequest).
JSON(fiber.Map{ JSON(types.JSONError{
"error": err.Error(), Error: err.Error(),
}) })
} }
@ -55,8 +63,8 @@ func ReqTokens(c *fiber.Ctx) error {
if err != nil { if err != nil {
return c. return c.
Status(fiber.StatusBadRequest). Status(fiber.StatusBadRequest).
JSON(fiber.Map{ JSON(types.JSONError{
"error": err.Error(), Error: err.Error(),
}) })
} }
return c.SendStatus(fiber.StatusOK) return c.SendStatus(fiber.StatusOK)
@ -75,7 +83,7 @@ func GeneratePairAndSetCookie(c *fiber.Ctx, id int32) error {
return nil return nil
} }
func setCookie(c *fiber.Ctx, p *PairTokens) { func setCookie(c *fiber.Ctx, p *types.PairTokens) {
// Access token // Access token
atc := new(fiber.Cookie) atc := new(fiber.Cookie)
atc.Name = "accessToken" atc.Name = "accessToken"

View File

@ -2,7 +2,9 @@ package auth
import ( import (
"fmt" "fmt"
"system-trace/core/app/constants" "system-trace/core/constants"
"system-trace/core/database"
"system-trace/core/types"
"system-trace/core/utils" "system-trace/core/utils"
"time" "time"
@ -14,18 +16,18 @@ const (
RefreshTokenLifetime int8 = 24 RefreshTokenLifetime int8 = 24
) )
func genPair(id int32) (*PairTokens, error) { func genPair(id int32) (*types.PairTokens, error) {
at, rt, err := genTokens(id) at, rt, err := genTokens(id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = insertPair(id, at, rt) err = database.InsertPairOfTokens(id, at, rt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
p := PairTokens{ p := types.PairTokens{
AccessToken: at, AccessToken: at,
RefreshToken: rt, RefreshToken: rt,
} }

160
services/groups/groups.go Normal file
View File

@ -0,0 +1,160 @@
package groups
import (
"system-trace/core/database"
"system-trace/core/parsers"
"system-trace/core/types"
"github.com/gofiber/fiber/v2"
)
// MARK: GetGroups godoc
// @Summary Get groups (with pagination)
// @Description Returns array of groups and count
// @Tags groups
// @Produce json
// @Param count query int true "Count of rows" minimum(10) maximum(100)
// @Param offset query int true "Rows to skip" minumum(0)
// @Header 200 {string} Token "accessToken=...;refreshToken=..."
// @Success 200 {object} types.JSONPagination
// @Router /groups [get]
func GetGroupsHandler(c *fiber.Ctx) error {
// TODO permission validate
p, err := parsers.ParsePagination(c)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
ob, err := parsers.ParseOrder(c)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
groups, cursor, err := database.FindGroups(p, ob)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(types.JSONPagination{
Data: groups,
Cursor: cursor,
})
}
// MARK: GetGroupByID godoc
// @Summary Get group by ID
// @Description Returns group instance
// @Tags groups
// @Produce json
// @Param id path int true "Group ID" minimum(1)
// @Header 200 {string} Token "accessToken=...;refreshToken=..."
// @Success 200 {object} database.Group
// @Router /groups/:id [get]
func GetGroupByIDHandler(c *fiber.Ctx) error {
// TODO permission validate
id, err := c.ParamsInt("id")
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
group, err := database.FindGroupByID(id)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(group)
}
// MARK: CreateGroup godoc
// @Summary Create group
// @Description Create group with specified data
// @Tags groups
// @Produce json
// @Param request body database.Group true "Request body"
// @Header 200 {string} Token "accessToken=...;refreshToken=..."
// @Success 200
// @Router /groups [post]
func CreateGroupHandler(c *fiber.Ctx) error {
// TODO permission validate
g := new(database.Group)
if err := c.BodyParser(g); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
err := database.InsertGroup(g)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
return c.SendStatus(fiber.StatusOK)
}
// MARK: UpdateGroup godoc
// @Summary Update group
// @Description Update group with specified data
// @Tags groups
// @Produce json
// @Param request body database.Group true "Request body"
// @Header 200 {string} Token "accessToken=...;refreshToken=..."
// @Success 200
// @Router /groups [patch]
func UpdateGroupHandler(c *fiber.Ctx) error {
// TODO permission validate
g := new(database.Group)
if err := c.BodyParser(g); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
err := database.UpdateGroup(g, []string{"*"})
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
return c.SendStatus(fiber.StatusOK)
}
// MARK: DeleteGroup godoc
// @Summary Delete group
// @Description Delete group by ID
// @Tags groups
// @Produce json
// @Param id path int true "Group ID" minimum(1)
// @Header 200 {string} Token "accessToken=...;refreshToken=..."
// @Success 200
// @Router /groups/:id [delete]
func DeleteGroupHandler(c *fiber.Ctx) error {
// TODO permission validate
id, err := c.ParamsInt("id")
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
err = database.DeleteGroup(id)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
return c.SendStatus(fiber.StatusOK)
}

18
services/misc.go Normal file
View File

@ -0,0 +1,18 @@
package services
import (
"system-trace/core/constants/permissions"
"github.com/gofiber/fiber/v2"
)
// MARK: GetPermissions godoc
// @Summary Get list of permissions
// @Description Returns key-value map with permissions
// @Produce json
// @Header 200 {string} Token "accessToken=...;refreshToken=..."
// @Success 200 {object} map[string]int8
// @Router /permissions [get]
func GetPermissions(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(permissions.All)
}

166
services/users/users.go Normal file
View File

@ -0,0 +1,166 @@
package users
import (
"system-trace/core/database"
"system-trace/core/parsers"
"system-trace/core/types"
"time"
"github.com/gofiber/fiber/v2"
)
// MARK: GetUsers godoc
// @Summary Get users (with pagination)
// @Description Returns array of users and count
// @Tags users
// @Produce json
// @Param count query int true "Count of rows" minimum(10) maximum(100)
// @Param offset query int true "Rows to skip" minumum(0)
// @Header 200 {string} Token "accessToken=...;refreshToken=..."
// @Success 200 {object} types.JSONPagination
// @Router /users [get]
func GetUsersHandler(c *fiber.Ctx) error {
// TODO permission validate
p, err := parsers.ParsePagination(c)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
ob, err := parsers.ParseOrder(c)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
users, cursor, err := database.FindUsers(p, ob)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(types.JSONPagination{
Data: users,
Cursor: cursor,
})
}
// MARK: GetUserByID godoc
// @Summary Get user by ID
// @Description Returns user instance
// @Tags users
// @Produce json
// @Param id path int true "User ID" minimum(1)
// @Header 200 {string} Token "accessToken=...;refreshToken=..."
// @Success 200 {object} database.User
// @Router /users/:id [get]
func GetUserByIDHandler(c *fiber.Ctx) error {
// TODO permission validate
id, err := c.ParamsInt("id")
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
user, err := database.FindUserByID(id)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(user)
}
// MARK: CreateUser godoc
// @Summary Create user
// @Description Create user with specified data
// @Tags users
// @Produce json
// @Param request body database.User true "Request body"
// @Header 200 {string} Token "accessToken=...;refreshToken=..."
// @Success 200
// @Router /users [post]
func CreateUserHandler(c *fiber.Ctx) error {
// TODO permission validate
u := new(database.User)
if err := c.BodyParser(u); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
err := database.InsertUser(u)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
return c.SendStatus(fiber.StatusOK)
}
// MARK: UpdateUser godoc
// @Summary Update user
// @Description Update user with specified data
// @Tags users
// @Produce json
// @Param request body database.User true "Request body"
// @Header 200 {string} Token "accessToken=...;refreshToken=..."
// @Success 200
// @Router /users [patch]
func UpdateUserHandler(c *fiber.Ctx) error {
// TODO permission validate
u := new(database.User)
if err := c.BodyParser(u); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
err := database.UpdateUser(u, []string{"*"})
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
return c.SendStatus(fiber.StatusOK)
}
// MARK: DeleteUser godoc
// @Summary Delete user
// @Description Delete user by ID
// @Tags users
// @Produce json
// @Param id path int true "User ID" minimum(1)
// @Header 200 {string} Token "accessToken=...;refreshToken=..."
// @Success 200
// @Router /users/:id [delete]
func DeleteUserHandler(c *fiber.Ctx) error {
// TODO permission validate
id, err := c.ParamsInt("id")
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
err = database.DeleteUser(id)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{
Error: err.Error(),
})
}
return c.SendStatus(fiber.StatusOK)
}
func SetLoginTime(u *database.User) error {
u.LastLogin = time.Now()
return database.UpdateUser(u, []string{"last_login"})
}

6
types/order_by.go Normal file
View File

@ -0,0 +1,6 @@
package types
type OrderBy struct {
Key string `validate:"required"`
Order string `validate:"required,endswith=sc"`
}

13
types/pagination.go Normal file
View File

@ -0,0 +1,13 @@
package types
type Pagination struct {
Count int `query:"count" validate:"required,min=10,max=100"`
Page int `query:"page" validate:"required,min=1"`
}
type Cursor struct {
Count int `json:"count"`
CurrentPage int `json:"currentPage"`
TotalPages float64 `json:"totalPages"`
TotalRows float64 `json:"totalRows"`
}

10
types/response.go Normal file
View File

@ -0,0 +1,10 @@
package types
type JSONPagination struct {
Data interface{} `json:"data"`
Cursor *Cursor `json:"cursor"`
}
type JSONError struct {
Error string `json:"error"`
}

View File

@ -1,4 +1,4 @@
package auth package types
type PairTokens struct { type PairTokens struct {
AccessToken string `json:"accessToken" cookie:"accessToken"` AccessToken string `json:"accessToken" cookie:"accessToken"`

View File

@ -1,32 +0,0 @@
package users
import (
"context"
"system-trace/core/database"
"system-trace/core/database/entities"
"system-trace/core/utils"
)
func FindByEmailAndPassword(email, password string) (*entities.User, error) {
passwordHash := utils.SHA256(password)
ctx := context.Background()
u := new(entities.User)
err := database.PG.NewSelect().
Model(u).
Where("email = ?", email).
Where("password_hash = ?", passwordHash).
Scan(ctx)
return u, err
}
func UpdateUser(u *entities.User, cols []string) error {
ctx := context.Background()
_, err := database.PG.NewUpdate().
Model(u).
Column(cols...).
WherePK().
Exec(ctx)
return err
}

View File

@ -1,17 +0,0 @@
package users
import (
"system-trace/core/database/entities"
"time"
"github.com/gofiber/fiber/v2"
)
func Get(c *fiber.Ctx) error {
return nil
}
func SetLoginTime(u *entities.User) error {
u.LastLogin = time.Now()
return UpdateUser(u, []string{"last_login"})
}

View File

@ -36,7 +36,7 @@ func Validate(c *fiber.Ctx, data interface{}) error {
for _, err := range errs { for _, err := range errs {
errMsgs = append(errMsgs, fmt.Sprintf( errMsgs = append(errMsgs, fmt.Sprintf(
"field '%s' needs to implement '%s' rule", "validator:'%s'/'%s'",
err.FailedField, err.FailedField,
err.Tag, err.Tag,
)) ))