diff --git a/app/controllers/auth.go b/app/controllers/auth.go deleted file mode 100644 index 0197f74..0000000 --- a/app/controllers/auth.go +++ /dev/null @@ -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) - } -} diff --git a/app/router/router.go b/app/router/router.go deleted file mode 100644 index ca30799..0000000 --- a/app/router/router.go +++ /dev/null @@ -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) - } -} diff --git a/app/server.go b/app/server.go deleted file mode 100644 index 9ae5691..0000000 --- a/app/server.go +++ /dev/null @@ -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(), - }) - }, - }) -} diff --git a/app/constants/errors.go b/constants/errors.go similarity index 61% rename from app/constants/errors.go rename to constants/errors.go index 05fc778..bf375e1 100644 --- a/app/constants/errors.go +++ b/constants/errors.go @@ -1,7 +1,7 @@ package constants 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" UNAUTHORIZED = "Unauthorized" ) diff --git a/app/constants/misc.go b/constants/misc.go similarity index 100% rename from app/constants/misc.go rename to constants/misc.go diff --git a/constants/permissions/permissions.go b/constants/permissions/permissions.go new file mode 100644 index 0000000..2c4f58c --- /dev/null +++ b/constants/permissions/permissions.go @@ -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, +} diff --git a/auth/sql.go b/database/auth_token.go similarity index 52% rename from auth/sql.go rename to database/auth_token.go index 841a002..cb53980 100644 --- a/auth/sql.go +++ b/database/auth_token.go @@ -1,16 +1,22 @@ -package auth +package database import ( "context" - "system-trace/core/database" - "system-trace/core/database/entities" + "system-trace/core/types" ) -func GetPair(p *PairTokens) (*entities.AuthToken, error) { - aut := new(entities.AuthToken) +type AuthToken struct { + 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() - err := database.PG.NewSelect(). + err := PG.NewSelect(). Model(aut). Where("access_token = ?", p.AccessToken). Where("refresh_token = ?", p.RefreshToken). @@ -20,13 +26,29 @@ func GetPair(p *PairTokens) (*entities.AuthToken, error) { return aut, err } -func RevokePair(p *PairTokens) error { - aut := entities.AuthToken{ +func InsertPairOfTokens(id int32, at, rt string) error { + 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, } ctx := context.Background() - _, err := database.PG.NewUpdate(). + _, err := PG.NewUpdate(). Model(&aut). Column("is_revoked"). Where("access_token = ?", p.AccessToken). @@ -35,19 +57,3 @@ func RevokePair(p *PairTokens) error { 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 -} diff --git a/database/database.go b/database/database.go index 587973f..c595ac6 100644 --- a/database/database.go +++ b/database/database.go @@ -6,7 +6,6 @@ import ( "database/sql" "fmt" "os" - "system-trace/core/database/entities" "system-trace/core/environment" "time" @@ -41,7 +40,6 @@ func Connect() { if environment.IsDebug() { db.AddQueryHook(bundebug.NewQueryHook( bundebug.WithVerbose(true), - bundebug.FromEnv("BUNDEBUG"), )) } @@ -57,12 +55,12 @@ func Connect() { func createSchema(db *bun.DB) error { models := []interface{}{ - (*entities.Group)(nil), - (*entities.GroupPermission)(nil), - (*entities.User)(nil), - (*entities.Session)(nil), - (*entities.Server)(nil), - (*entities.AuthToken)(nil), + (*Group)(nil), + (*GroupPermission)(nil), + (*User)(nil), + (*Session)(nil), + (*Server)(nil), + (*AuthToken)(nil), } ctx := context.Background() diff --git a/database/entities/auth_token.go b/database/entities/auth_token.go deleted file mode 100644 index b34df64..0000000 --- a/database/entities/auth_token.go +++ /dev/null @@ -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"` -} diff --git a/database/entities/group.go b/database/entities/group.go deleted file mode 100644 index ef392f9..0000000 --- a/database/entities/group.go +++ /dev/null @@ -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"` -} diff --git a/database/entities/server.go b/database/entities/server.go deleted file mode 100644 index b04d774..0000000 --- a/database/entities/server.go +++ /dev/null @@ -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 -} diff --git a/database/entities/user.go b/database/entities/user.go deleted file mode 100644 index e80068f..0000000 --- a/database/entities/user.go +++ /dev/null @@ -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 -} diff --git a/database/group.go b/database/group.go new file mode 100644 index 0000000..0094e8c --- /dev/null +++ b/database/group.go @@ -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 +} diff --git a/database/entities/permission.go b/database/group_permission.go similarity index 54% rename from database/entities/permission.go rename to database/group_permission.go index ea1353f..167b0c1 100644 --- a/database/entities/permission.go +++ b/database/group_permission.go @@ -1,6 +1,6 @@ -package entities +package database type GroupPermission struct { GroupID int32 `bun:",notnull" json:"-"` - Value int8 `bun:",notnull"` + Value int8 `bun:",notnull" json:"value"` } diff --git a/database/server.go b/database/server.go new file mode 100644 index 0000000..dc4d8d3 --- /dev/null +++ b/database/server.go @@ -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 +} diff --git a/database/entities/session.go b/database/session.go similarity index 54% rename from database/entities/session.go rename to database/session.go index 882f105..81e252e 100644 --- a/database/entities/session.go +++ b/database/session.go @@ -1,4 +1,4 @@ -package entities +package database import ( "context" @@ -9,13 +9,13 @@ import ( type Session struct { ID int64 `bun:",pk,autoincrement"` - IssuerID int32 `bun:",notnull"` - Issuer *User `bun:"rel:belongs-to,join:issuer_id=id"` - Configuration map[string]interface{} `bun:"type:jsonb"` - CreatedAt time.Time `bun:",notnull,default:current_timestamp"` - FinishedAt time.Time - UpdatedAt time.Time - DeletedAt time.Time `bun:",soft_delete,nullzero"` + IssuerID int32 `bun:",notnull" json:"issuerId"` + Issuer *User `bun:"rel:belongs-to,join:issuer_id=id" json:"issuer"` + Configuration map[string]interface{} `bun:"type:jsonb" json:"configuration"` + CreatedAt time.Time `bun:",notnull,default:current_timestamp" json:"createdAt"` + FinishedAt time.Time `json:"finishedAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt time.Time `bun:",soft_delete,nullzero" json:"-"` } var _ bun.BeforeAppendModelHook = (*Session)(nil) diff --git a/database/user.go b/database/user.go new file mode 100644 index 0000000..74307cd --- /dev/null +++ b/database/user.go @@ -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 +} diff --git a/docs/docs.go b/docs/docs.go index eddbcca..2b10268 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -34,7 +34,7 @@ const docTemplate = `{ "in": "body", "required": true, "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": { - "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", "required": [ "email", @@ -60,15 +460,38 @@ const docTemplate = `{ }, "password": { "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": { - "Bearer": { + "accessToken=...;refreshToken=...": { "type": "apiKey", - "name": "Authorization", + "name": "Cookie", "in": "header" } } diff --git a/docs/swagger.json b/docs/swagger.json index 9b4357a..acaa78d 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -26,7 +26,7 @@ "in": "body", "required": true, "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": { - "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", "required": [ "email", @@ -52,15 +452,38 @@ }, "password": { "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": { - "Bearer": { + "accessToken=...;refreshToken=...": { "type": "apiKey", - "name": "Authorization", + "name": "Cookie", "in": "header" } } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 2fed0cc..4df5b73 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,17 +1,91 @@ basePath: /v1 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: email: example: john@proton.mail type: string password: - example: Aasdfgh1! + example: Aasdfg1! type: string required: - email - password 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: contact: name: https://peresvet.it @@ -27,7 +101,7 @@ paths: name: request required: true schema: - $ref: '#/definitions/auth.AuthBody' + $ref: '#/definitions/types.AuthBody' produces: - application/json responses: @@ -36,9 +110,216 @@ paths: summary: Request pair of tokens tags: - 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: - Bearer: + accessToken=...;refreshToken=...: in: header - name: Authorization + name: Cookie type: apiKey swagger: "2.0" diff --git a/go.mod b/go.mod index 804c069..185feee 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,11 @@ module system-trace/core go 1.22.3 require ( - github.com/go-playground/validator v9.31.0+incompatible github.com/go-playground/validator/v10 v10.20.0 github.com/goccy/go-json v0.10.2 github.com/gofiber/fiber v1.14.6 github.com/gofiber/fiber/v2 v2.52.4 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/joho/godotenv v1.5.1 github.com/swaggo/swag v1.16.3 diff --git a/go.sum b/go.sum index beb4295..6fe6137 100644 --- a/go.sum +++ b/go.sum @@ -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.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= 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/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator 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/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 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/utils v0.0.10 h1:3Mr7X7JdCUo7CWf/i5sajSaDmArEDtti8bM1JUVso2U= 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/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 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/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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 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/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-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 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/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.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 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.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 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/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= @@ -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/tetratelabs/wazero v1.7.0 h1:jg5qPydno59wqjpGrHph81lbtHzTrWzwwtD4cD88+hQ= 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/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= 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/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/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= 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/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-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= 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/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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= 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-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/main.go b/main.go index db132f8..7831bd5 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "system-trace/core/app" "system-trace/core/database" "system-trace/core/environment" "system-trace/core/validators" @@ -11,13 +10,13 @@ import ( // @version 1.0 // @contact.name https://peresvet.it // @BasePath /v1 -// @securityDefinitions.apikey Bearer +// @securityDefinitions.apikey accessToken=...;refreshToken=... // @in header -// @name Authorization +// @name Cookie // @externalDocs.description OpenAPI func main() { environment.Load() validators.RegisterValidators() database.Connect() - app.Serve() + serveApp() } diff --git a/app/router/middlewares/session.go b/middlewares/session.go similarity index 80% rename from app/router/middlewares/session.go rename to middlewares/session.go index f8d50b2..6a0d6f3 100644 --- a/app/router/middlewares/session.go +++ b/middlewares/session.go @@ -3,15 +3,17 @@ package middlewares import ( "net/http" "strings" - "system-trace/core/app/constants" - "system-trace/core/auth" + "system-trace/core/constants" + "system-trace/core/database" + "system-trace/core/modules/auth" + "system-trace/core/types" "system-trace/core/utils" "github.com/gofiber/fiber/v2" ) func ValidateSession(c *fiber.Ctx) error { - p := new(auth.PairTokens) + p := new(types.PairTokens) if err := c.CookieParser(p); err != nil { return c.Status(http.StatusBadRequest).JSON(fiber.Map{ "error": err.Error(), @@ -26,7 +28,7 @@ func ValidateSession(c *fiber.Ctx) error { 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 { return false } @@ -39,12 +41,12 @@ func validatePair(c *fiber.Ctx, p *auth.PairTokens) bool { return false } - pt, err := auth.GetPair(p) + pt, err := database.GetPairOfTokens(p) if err != nil { return false } - err = auth.RevokePair(p) + err = database.RevokePairOfTokens(p) if err != nil { return false } diff --git a/parsers/order_by.go b/parsers/order_by.go new file mode 100644 index 0000000..b210191 --- /dev/null +++ b/parsers/order_by.go @@ -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 +} diff --git a/parsers/pagination.go b/parsers/pagination.go new file mode 100644 index 0000000..fb83313 --- /dev/null +++ b/parsers/pagination.go @@ -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 +} diff --git a/router.go b/router.go new file mode 100644 index 0000000..5a25909 --- /dev/null +++ b/router.go @@ -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) + } + } +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..a03a6eb --- /dev/null +++ b/server.go @@ -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)) + } +} diff --git a/auth/README.md b/services/auth/README.md similarity index 100% rename from auth/README.md rename to services/auth/README.md diff --git a/auth/auth.go b/services/auth/auth.go similarity index 66% rename from auth/auth.go rename to services/auth/auth.go index 291aa65..86f0ab6 100644 --- a/auth/auth.go +++ b/services/auth/auth.go @@ -2,34 +2,41 @@ package auth import ( "errors" - "system-trace/core/app/constants" - "system-trace/core/users" + "system-trace/core/constants" + "system-trace/core/database" + "system-trace/core/services/users" + "system-trace/core/types" "system-trace/core/validators" "time" "github.com/gofiber/fiber/v2" ) -// ReqTokens godoc +// MARK: ReqTokens godoc // @Summary Request pair of tokens // @Description Returns pair of access and refresh tokens // @Tags auth // @Produce json -// @Param request body AuthBody true "Request body" -// @Header 200 {string} Token "accessToken=..." +// @Param request body types.AuthBody true "Request body" +// @Header 200 {string} Set-Cookie "accessToken=..." // @Header 200 {string} Set-Cookie "refreshToken=..." // @Success 200 // @Router /auth/login [post] -func ReqTokens(c *fiber.Ctx) error { - ab := new(AuthBody) +func ReqTokensHandler(c *fiber.Ctx) error { + ab := new(types.AuthBody) if err := c.BodyParser(ab); err != nil { - return err - } - if err := validators.Validate(c, ab); err != nil { - return err + return c.Status(fiber.StatusBadRequest).JSON(types.JSONError{ + Error: err.Error(), + }) } - 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 { e := err.Error() if e == "sql: no rows in result set" { @@ -37,17 +44,18 @@ func ReqTokens(c *fiber.Ctx) error { } return c. Status(fiber.StatusBadRequest). - JSON(fiber.Map{ - "error": e, + JSON(types.JSONError{ + Error: e, }) } + if u != nil { err = GeneratePairAndSetCookie(c, u.ID) if err != nil { return c. Status(fiber.StatusBadRequest). - JSON(fiber.Map{ - "error": err.Error(), + JSON(types.JSONError{ + Error: err.Error(), }) } @@ -55,8 +63,8 @@ func ReqTokens(c *fiber.Ctx) error { if err != nil { return c. Status(fiber.StatusBadRequest). - JSON(fiber.Map{ - "error": err.Error(), + JSON(types.JSONError{ + Error: err.Error(), }) } return c.SendStatus(fiber.StatusOK) @@ -75,7 +83,7 @@ func GeneratePairAndSetCookie(c *fiber.Ctx, id int32) error { return nil } -func setCookie(c *fiber.Ctx, p *PairTokens) { +func setCookie(c *fiber.Ctx, p *types.PairTokens) { // Access token atc := new(fiber.Cookie) atc.Name = "accessToken" diff --git a/auth/tokens.go b/services/auth/tokens.go similarity index 80% rename from auth/tokens.go rename to services/auth/tokens.go index fcd3885..784a9b5 100644 --- a/auth/tokens.go +++ b/services/auth/tokens.go @@ -2,7 +2,9 @@ package auth import ( "fmt" - "system-trace/core/app/constants" + "system-trace/core/constants" + "system-trace/core/database" + "system-trace/core/types" "system-trace/core/utils" "time" @@ -14,18 +16,18 @@ const ( RefreshTokenLifetime int8 = 24 ) -func genPair(id int32) (*PairTokens, error) { +func genPair(id int32) (*types.PairTokens, error) { at, rt, err := genTokens(id) if err != nil { return nil, err } - err = insertPair(id, at, rt) + err = database.InsertPairOfTokens(id, at, rt) if err != nil { return nil, err } - p := PairTokens{ + p := types.PairTokens{ AccessToken: at, RefreshToken: rt, } diff --git a/services/groups/groups.go b/services/groups/groups.go new file mode 100644 index 0000000..24c9deb --- /dev/null +++ b/services/groups/groups.go @@ -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) +} diff --git a/services/misc.go b/services/misc.go new file mode 100644 index 0000000..1fc3b5e --- /dev/null +++ b/services/misc.go @@ -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) +} diff --git a/services/users/users.go b/services/users/users.go new file mode 100644 index 0000000..a13a101 --- /dev/null +++ b/services/users/users.go @@ -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"}) +} diff --git a/types/order_by.go b/types/order_by.go new file mode 100644 index 0000000..de26e42 --- /dev/null +++ b/types/order_by.go @@ -0,0 +1,6 @@ +package types + +type OrderBy struct { + Key string `validate:"required"` + Order string `validate:"required,endswith=sc"` +} diff --git a/types/pagination.go b/types/pagination.go new file mode 100644 index 0000000..b0751e4 --- /dev/null +++ b/types/pagination.go @@ -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"` +} diff --git a/types/response.go b/types/response.go new file mode 100644 index 0000000..8b2f0df --- /dev/null +++ b/types/response.go @@ -0,0 +1,10 @@ +package types + +type JSONPagination struct { + Data interface{} `json:"data"` + Cursor *Cursor `json:"cursor"` +} + +type JSONError struct { + Error string `json:"error"` +} diff --git a/auth/types.go b/types/types.go similarity index 96% rename from auth/types.go rename to types/types.go index f9a06d2..2d687d8 100644 --- a/auth/types.go +++ b/types/types.go @@ -1,4 +1,4 @@ -package auth +package types type PairTokens struct { AccessToken string `json:"accessToken" cookie:"accessToken"` diff --git a/users/sql.go b/users/sql.go deleted file mode 100644 index 3c0d81e..0000000 --- a/users/sql.go +++ /dev/null @@ -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 -} diff --git a/users/users.go b/users/users.go deleted file mode 100644 index ea7865e..0000000 --- a/users/users.go +++ /dev/null @@ -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"}) -} diff --git a/validators/validator.go b/validators/validator.go index ee2d0a8..edb00c4 100644 --- a/validators/validator.go +++ b/validators/validator.go @@ -36,7 +36,7 @@ func Validate(c *fiber.Ctx, data interface{}) error { for _, err := range errs { errMsgs = append(errMsgs, fmt.Sprintf( - "field '%s' needs to implement '%s' rule", + "validator:'%s'/'%s'", err.FailedField, err.Tag, ))