From 84f53ecb1cd1aec3dafb933fbc6bfa8579f55f0f Mon Sep 17 00:00:00 2001 From: ali Date: Thu, 20 Feb 2025 13:46:37 +0500 Subject: [PATCH] feat: core modules `SortBy` filter for repos --- .../employee/employee_repository.go | 30 ++++--- .../domain/aggregates/role/role_repository.go | 16 +++- .../domain/aggregates/user/user_repository.go | 20 ++++- .../entities/currency/currency_repository.go | 18 ++++- .../permission/permission_repository.go | 17 +++- .../entities/position/position_repository.go | 17 +++- .../entities/session/session_repository.go | 16 +++- .../entities/upload/upload_repository.go | 15 +++- .../persistence/currency_repository.go | 71 +++++++++++++---- .../persistence/employee_repository.go | 32 +++++++- .../persistence/permission_repository.go | 65 +++++++-------- .../persistence/position_repository.go | 69 ++++++++++++---- .../persistence/role_repository.go | 30 +++++-- .../persistence/session_repository.go | 59 +++++++++----- .../persistence/upload_repository.go | 79 +++++++++++++------ .../persistence/user_repository.go | 28 ++++++- modules/core/interfaces/graph/generated.go | 53 ++++++++++--- modules/core/interfaces/graph/users.graphql | 2 +- .../core/interfaces/graph/users.resolvers.go | 7 +- .../controllers/employee_controller.go | 20 +++-- .../controllers/user_controller.go | 7 +- .../persistence/chat_repository.go | 10 +-- .../controllers/chat_controller.go | 2 +- 23 files changed, 498 insertions(+), 185 deletions(-) diff --git a/modules/core/domain/aggregates/employee/employee_repository.go b/modules/core/domain/aggregates/employee/employee_repository.go index e19c8a9a..a3983a42 100644 --- a/modules/core/domain/aggregates/employee/employee_repository.go +++ b/modules/core/domain/aggregates/employee/employee_repository.go @@ -2,18 +2,30 @@ package employee import "context" -type DateRange struct { - From string - To string +type Field int + +const ( + Id Field = iota + FirstName + LastName + MiddleName + Salary + HourlyRate + Coefficient + CreatedAt +) + +type SortBy struct { + Fields []Field + Ascending bool } type FindParams struct { - Limit int - Offset int - SortBy []string - Query string - Field string - CreatedAt DateRange + Limit int + Offset int + Query string + Field string + SortBy SortBy } type Repository interface { diff --git a/modules/core/domain/aggregates/role/role_repository.go b/modules/core/domain/aggregates/role/role_repository.go index 38975a0d..a3337e08 100644 --- a/modules/core/domain/aggregates/role/role_repository.go +++ b/modules/core/domain/aggregates/role/role_repository.go @@ -1,14 +1,26 @@ package role -import ( - "context" +import "context" + +type Field int + +const ( + Name Field = iota + Description + CreatedAt ) +type SortBy struct { + Fields []Field + Ascending bool +} + type FindParams struct { Name string AttachPermissions bool Limit int Offset int + SortBy SortBy } type Repository interface { diff --git a/modules/core/domain/aggregates/user/user_repository.go b/modules/core/domain/aggregates/user/user_repository.go index 499abc03..a27c9ed7 100644 --- a/modules/core/domain/aggregates/user/user_repository.go +++ b/modules/core/domain/aggregates/user/user_repository.go @@ -1,13 +1,27 @@ package user -import ( - "context" +import "context" + +type Field = int + +const ( + FirstName Field = iota + LastName + MiddleName + Email + LastLogin + CreatedAt ) +type SortBy struct { + Fields []Field + Ascending bool +} + type FindParams struct { Limit int Offset int - SortBy []string + SortBy SortBy } type Repository interface { diff --git a/modules/core/domain/entities/currency/currency_repository.go b/modules/core/domain/entities/currency/currency_repository.go index 06905783..92c0a896 100644 --- a/modules/core/domain/entities/currency/currency_repository.go +++ b/modules/core/domain/entities/currency/currency_repository.go @@ -1,14 +1,26 @@ package currency -import ( - "context" +import "context" + +type Field int + +const ( + FieldCode Field = iota + FieldName + FieldSymbol + FieldCreatedAt ) +type SortBy struct { + Fields []Field + Ascending bool +} + type FindParams struct { Code string Limit int Offset int - SortBy []string + SortBy SortBy } type Repository interface { diff --git a/modules/core/domain/entities/permission/permission_repository.go b/modules/core/domain/entities/permission/permission_repository.go index 6fa9d935..0739360d 100644 --- a/modules/core/domain/entities/permission/permission_repository.go +++ b/modules/core/domain/entities/permission/permission_repository.go @@ -1,13 +1,26 @@ package permission -import ( - "context" +import "context" + +type Field int + +const ( + FieldName Field = iota + FieldResource + FieldAction + FieldModifier ) +type SortBy struct { + Fields []Field + Ascending bool +} + type FindParams struct { Limit int Offset int RoleID uint + SortBy SortBy } type Repository interface { diff --git a/modules/core/domain/entities/position/position_repository.go b/modules/core/domain/entities/position/position_repository.go index 589a1918..d6dd3c23 100644 --- a/modules/core/domain/entities/position/position_repository.go +++ b/modules/core/domain/entities/position/position_repository.go @@ -1,14 +1,25 @@ package position -import ( - "context" +import "context" + +type Field int + +const ( + Name Field = iota + Descripton + CreatedAt ) +type SortBy struct { + Fields []Field + Ascending bool +} + type FindParams struct { ID int64 Limit int Offset int - SortBy []string + SortBy SortBy } type Repository interface { diff --git a/modules/core/domain/entities/session/session_repository.go b/modules/core/domain/entities/session/session_repository.go index 6772a0c8..58cca8f9 100644 --- a/modules/core/domain/entities/session/session_repository.go +++ b/modules/core/domain/entities/session/session_repository.go @@ -1,14 +1,24 @@ package session -import ( - "context" +import "context" + +type Field int + +const ( + ExpiresAt Field = iota + CreatedAt ) +type SortBy struct { + Fields []Field + Ascending bool +} + type FindParams struct { Limit int Offset int - SortBy []string Token string + SortBy SortBy } type Repository interface { diff --git a/modules/core/domain/entities/upload/upload_repository.go b/modules/core/domain/entities/upload/upload_repository.go index bcafb136..b74c2eed 100644 --- a/modules/core/domain/entities/upload/upload_repository.go +++ b/modules/core/domain/entities/upload/upload_repository.go @@ -1,17 +1,24 @@ package upload -import ( - "context" -) +import "context" + +type Field int + +const Size Field = iota + +type SortBy struct { + Fields []Field + Ascending bool +} type FindParams struct { ID uint Hash string Limit int Offset int - SortBy []string Search string Type string + SortBy SortBy } type Repository interface { diff --git a/modules/core/infrastructure/persistence/currency_repository.go b/modules/core/infrastructure/persistence/currency_repository.go index 73adad22..719fd7be 100644 --- a/modules/core/infrastructure/persistence/currency_repository.go +++ b/modules/core/infrastructure/persistence/currency_repository.go @@ -4,43 +4,47 @@ import ( "context" "errors" "fmt" - "github.com/iota-uz/iota-sdk/pkg/repo" - "strings" "github.com/iota-uz/iota-sdk/modules/core/domain/entities/currency" "github.com/iota-uz/iota-sdk/modules/core/infrastructure/persistence/models" "github.com/iota-uz/iota-sdk/pkg/composables" + "github.com/iota-uz/iota-sdk/pkg/repo" ) var ( ErrCurrencyNotFound = errors.New("currency not found") ) +const ( + selectCurrenciesQuery = ` + SELECT + c.code, + c.name, + c.symbol, + c.created_at, + c.updated_at, + FROM c + ` +) + type GormCurrencyRepository struct{} func NewCurrencyRepository() currency.Repository { return &GormCurrencyRepository{} } -func (g *GormCurrencyRepository) GetPaginated( - ctx context.Context, params *currency.FindParams, +func (g *GormCurrencyRepository) queryChats( + ctx context.Context, + query string, + args ...interface{}, ) ([]*currency.Currency, error) { pool, err := composables.UseTx(ctx) if err != nil { return nil, err } - where, args := []string{"1 = 1"}, []interface{}{} - if params.Code != "" { - where, args = append(where, fmt.Sprintf("code = $%d", len(args)+1)), append(args, params.Code) - } - - rows, err := pool.Query(ctx, ` - SELECT code, name, symbol, created_at, updated_at FROM currencies - WHERE `+strings.Join(where, " AND ")+` - `+repo.FormatLimitOffset(params.Limit, params.Offset)+` - `, args...) + rows, err := pool.Query(ctx, query, args...) if err != nil { return nil, err } @@ -73,6 +77,43 @@ func (g *GormCurrencyRepository) GetPaginated( return currencies, nil } +func (g *GormCurrencyRepository) GetPaginated( + ctx context.Context, params *currency.FindParams, +) ([]*currency.Currency, error) { + sortFields := []string{} + for _, f := range params.SortBy.Fields { + switch f { + case currency.FieldCode: + sortFields = append(sortFields, "c.name") + case currency.FieldName: + sortFields = append(sortFields, "c.code") + case currency.FieldSymbol: + sortFields = append(sortFields, "c.symbol") + case currency.FieldCreatedAt: + sortFields = append(sortFields, "c.created_at") + default: + return nil, fmt.Errorf("unknown sort field: %v", f) + } + } + + where, args := []string{"1 = 1"}, []interface{}{} + if params.Code != "" { + where = append(where, "c.code ILIKE $1") + args = append(args, "%"+params.Code+"%") + } + + return g.queryChats( + ctx, + repo.Join( + selectCurrenciesQuery, + repo.JoinWhere(where...), + repo.OrderBy(sortFields, params.SortBy.Ascending), + repo.FormatLimitOffset(params.Limit, params.Offset), + ), + args..., + ) +} + func (g *GormCurrencyRepository) Count(ctx context.Context) (uint, error) { pool, err := composables.UseTx(ctx) if err != nil { @@ -127,7 +168,7 @@ func (g *GormCurrencyRepository) Update(ctx context.Context, entity *currency.Cu } row := ToDBCurrency(entity) if _, err := tx.Exec(ctx, ` - UPDATE currencies + UPDATE currencies SET name = $1, symbol = $2 WHERE code = $3 `, row.Name, row.Symbol, row.Code); err != nil { diff --git a/modules/core/infrastructure/persistence/employee_repository.go b/modules/core/infrastructure/persistence/employee_repository.go index f83b6d8f..fbafa180 100644 --- a/modules/core/infrastructure/persistence/employee_repository.go +++ b/modules/core/infrastructure/persistence/employee_repository.go @@ -3,12 +3,13 @@ package persistence import ( "context" "fmt" - "github.com/go-faster/errors" - "github.com/iota-uz/iota-sdk/pkg/repo" "github.com/iota-uz/iota-sdk/modules/core/domain/aggregates/employee" "github.com/iota-uz/iota-sdk/modules/core/infrastructure/persistence/models" "github.com/iota-uz/iota-sdk/pkg/composables" + "github.com/iota-uz/iota-sdk/pkg/repo" + + "github.com/go-faster/errors" ) var ( @@ -81,9 +82,31 @@ func NewEmployeeRepository() employee.Repository { } func (g *GormEmployeeRepository) GetPaginated(ctx context.Context, params *employee.FindParams) ([]employee.Employee, error) { - var args []interface{} - where := []string{"1 = 1"} + sortFields := []string{} + for _, f := range params.SortBy.Fields { + switch f { + case employee.Id: + sortFields = append(sortFields, "e.id") + case employee.FirstName: + sortFields = append(sortFields, "e.first_name") + case employee.LastName: + sortFields = append(sortFields, "e.last_name") + case employee.MiddleName: + sortFields = append(sortFields, "e.middle_name") + case employee.Salary: + sortFields = append(sortFields, "e.salary") + case employee.HourlyRate: + sortFields = append(sortFields, "e.hourly_rate") + case employee.Coefficient: + sortFields = append(sortFields, "e.coefficient_rate") + case employee.CreatedAt: + sortFields = append(sortFields, "e.created_at") + default: + return nil, fmt.Errorf("unknown sort field: %v", f) + } + } + where, args := []string{"1 = 1"}, []interface{}{} if params.Query != "" && params.Field != "" { where = append(where, fmt.Sprintf("e.%s::VARCHAR ILIKE $%d", params.Field, len(where))) args = append(args, "%"+params.Query+"%") @@ -92,6 +115,7 @@ func (g *GormEmployeeRepository) GetPaginated(ctx context.Context, params *emplo q := repo.Join( employeeFindQuery, repo.JoinWhere(where...), + repo.OrderBy(sortFields, params.SortBy.Ascending), repo.FormatLimitOffset(params.Limit, params.Offset), ) return g.queryEmployees(ctx, q, args...) diff --git a/modules/core/infrastructure/persistence/permission_repository.go b/modules/core/infrastructure/persistence/permission_repository.go index 904575dc..22134bce 100644 --- a/modules/core/infrastructure/persistence/permission_repository.go +++ b/modules/core/infrastructure/persistence/permission_repository.go @@ -4,12 +4,12 @@ import ( "context" "errors" "fmt" + "github.com/google/uuid" "github.com/iota-uz/iota-sdk/modules/core/domain/entities/permission" "github.com/iota-uz/iota-sdk/modules/core/infrastructure/persistence/models" "github.com/iota-uz/iota-sdk/pkg/composables" "github.com/iota-uz/iota-sdk/pkg/repo" - "strings" ) var ( @@ -21,7 +21,7 @@ const ( permissionsCountQuery = `SELECT COUNT(*) FROM permissions` permissionsInsertQuery = ` INSERT INTO permissions (id, name, resource, action, modifier) - VALUES ($1, $2, $3, $4, $5) + VALUES ($1, $2, $3, $4, $5) ON CONFLICT (name) DO UPDATE SET resource = permissions.resource RETURNING id` permissionsUpdateQuery = ` @@ -40,45 +40,38 @@ func NewPermissionRepository() permission.Repository { func (g *GormPermissionRepository) GetPaginated( ctx context.Context, params *permission.FindParams, ) ([]*permission.Permission, error) { - pool, err := composables.UseTx(ctx) - if err != nil { - return nil, err + sortFields := []string{} + for _, f := range params.SortBy.Fields { + switch f { + case permission.FieldName: + sortFields = append(sortFields, "permissions.name") + case permission.FieldResource: + sortFields = append(sortFields, "permissions.resource") + case permission.FieldAction: + sortFields = append(sortFields, "permissions.action") + case permission.FieldModifier: + sortFields = append(sortFields, "permissions.modifier") + default: + return nil, fmt.Errorf("unknown sort field: %v", f) + } } - where, joins, args := []string{"1 = 1"}, []string{}, []interface{}{} + joins, args := []string{}, []interface{}{} if params.RoleID != 0 { - joins, args = append(joins, fmt.Sprintf("INNER JOIN role_permissions rp ON rp.permission_id = permissions.id and rp.role_id = $%d", len(args)+1)), append(args, params.RoleID) - } - rows, err := pool.Query(ctx, repo.Join( - permissionsSelectQuery, - strings.Join(joins, " "), - repo.JoinWhere(where...), - )) - if err != nil { - return nil, err - } - defer rows.Close() - permissions := make([]*permission.Permission, 0) - for rows.Next() { - var p models.Permission - if err := rows.Scan( - &p.ID, - &p.Name, - &p.Resource, - &p.Action, - &p.Modifier, - ); err != nil { - return nil, err - } - - domainPermission, err := toDomainPermission(&p) - if err != nil { - return nil, err - } - permissions = append(permissions, domainPermission) + joins = append(joins, fmt.Sprintf("INNER JOIN role_permissions rp ON rp.permission_id = permissions.id and rp.role_id = $%d", len(args)+1)) + args = append(args, params.RoleID) } - return permissions, nil + return g.queryPermissions( + ctx, + repo.Join( + permissionsSelectQuery, + repo.Join(joins...), + repo.OrderBy(sortFields, params.SortBy.Ascending), + repo.FormatLimitOffset(params.Limit, params.Offset), + ), + args..., + ) } func (g *GormPermissionRepository) Count(ctx context.Context) (int64, error) { diff --git a/modules/core/infrastructure/persistence/position_repository.go b/modules/core/infrastructure/persistence/position_repository.go index 0e1168ca..95ab020d 100644 --- a/modules/core/infrastructure/persistence/position_repository.go +++ b/modules/core/infrastructure/persistence/position_repository.go @@ -5,45 +5,51 @@ import ( "database/sql" "errors" "fmt" - "github.com/iota-uz/iota-sdk/pkg/repo" - "strings" "github.com/iota-uz/iota-sdk/modules/core/domain/entities/position" "github.com/iota-uz/iota-sdk/modules/core/infrastructure/persistence/models" "github.com/iota-uz/iota-sdk/pkg/composables" + "github.com/iota-uz/iota-sdk/pkg/repo" ) var ( ErrPositionNotFound = errors.New("position not found") ) +const ( + selectPositionsQuery = ` + SELECT + id, + name, + description, + created_at, + updated_at + FROM positions p + ` +) + type GormPositionRepository struct{} func NewPositionRepository() position.Repository { return &GormPositionRepository{} } -func (g *GormPositionRepository) GetPaginated( - ctx context.Context, params *position.FindParams, +func (g *GormPositionRepository) queryChats( + ctx context.Context, + query string, + args ...interface{}, ) ([]*position.Position, error) { pool, err := composables.UseTx(ctx) if err != nil { return nil, err } - where, args := []string{"1 = 1"}, []interface{}{} - if params.ID != 0 { - where, args = append(where, fmt.Sprintf("id = $%d", len(args)+1)), append(args, params.ID) - } - rows, err := pool.Query(ctx, ` - SELECT id, name, description, created_at, updated_at FROM positions - WHERE `+strings.Join(where, " AND ")+` - `+repo.FormatLimitOffset(params.Limit, params.Offset)+` - `, args...) + rows, err := pool.Query(ctx, query, args...) if err != nil { return nil, err } defer rows.Close() + positions := make([]*position.Position, 0) for rows.Next() { var p models.Position @@ -72,6 +78,41 @@ func (g *GormPositionRepository) GetPaginated( return positions, nil } +func (g *GormPositionRepository) GetPaginated( + ctx context.Context, params *position.FindParams, +) ([]*position.Position, error) { + sortFields := []string{} + for _, f := range params.SortBy.Fields { + switch f { + case position.Name: + sortFields = append(sortFields, "p.name") + case position.Descripton: + sortFields = append(sortFields, "p.description") + case position.CreatedAt: + sortFields = append(sortFields, "p.created_at") + default: + return nil, fmt.Errorf("unknown sort field: %v", f) + } + } + + where, args := []string{"1 = 1"}, []interface{}{} + if params.ID != 0 { + where = append(where, fmt.Sprintf("id = $%d", len(args)+1)) + args = append(args, params.ID) + } + + return g.queryChats( + ctx, + repo.Join( + selectPositionsQuery, + repo.JoinWhere(where...), + repo.OrderBy(sortFields, params.SortBy.Ascending), + repo.FormatLimitOffset(params.Limit, params.Offset), + ), + args..., + ) +} + func (g *GormPositionRepository) Count(ctx context.Context) (int64, error) { pool, err := composables.UseTx(ctx) if err != nil { @@ -127,7 +168,7 @@ func (g *GormPositionRepository) Update(ctx context.Context, data *position.Posi } dbRow := toDBPosition(data) if _, err := tx.Exec(ctx, ` - UPDATE positions + UPDATE positions SET name = $1, description = $2 WHERE id = $3 `, dbRow.Name, dbRow.Description, dbRow.ID); err != nil { diff --git a/modules/core/infrastructure/persistence/role_repository.go b/modules/core/infrastructure/persistence/role_repository.go index 366cac49..2a5977aa 100644 --- a/modules/core/infrastructure/persistence/role_repository.go +++ b/modules/core/infrastructure/persistence/role_repository.go @@ -3,6 +3,7 @@ package persistence import ( "context" "fmt" + "github.com/go-faster/errors" "github.com/iota-uz/iota-sdk/modules/core/domain/aggregates/role" "github.com/iota-uz/iota-sdk/modules/core/infrastructure/persistence/models" @@ -16,15 +17,15 @@ var ( const ( roleFindQuery = ` - SELECT - r.id, - r.name, - r.description, - r.created_at, + SELECT + r.id, + r.name, + r.description, + r.created_at, r.updated_at FROM roles r` rolePermissionsQuery = ` - SELECT + SELECT p.id, p.name, p.resource, @@ -39,7 +40,7 @@ const ( roleDeletePermissionsQuery = `DELETE FROM role_permissions WHERE role_id = $1` roleInsertPermissionQuery = ` INSERT INTO role_permissions (role_id, permission_id) - VALUES ($1, $2) + VALUES ($1, $2) ON CONFLICT (role_id, permission_id) DO NOTHING` roleDeleteQuery = `DELETE FROM roles WHERE id = $1` ) @@ -51,6 +52,20 @@ func NewRoleRepository() role.Repository { } func (g *GormRoleRepository) GetPaginated(ctx context.Context, params *role.FindParams) ([]role.Role, error) { + sortFields := []string{} + for _, f := range params.SortBy.Fields { + switch f { + case role.Name: + sortFields = append(sortFields, "r.name") + case role.Description: + sortFields = append(sortFields, "r.description") + case role.CreatedAt: + sortFields = append(sortFields, "r.created_at") + default: + return nil, fmt.Errorf("unknown sort field: %v", f) + } + } + where, args := []string{"1 = 1"}, []interface{}{} if params.Name != "" { where = append(where, fmt.Sprintf("r.name = $%d", len(args)+1)) @@ -60,6 +75,7 @@ func (g *GormRoleRepository) GetPaginated(ctx context.Context, params *role.Find query := repo.Join( roleFindQuery, repo.JoinWhere(where...), + repo.OrderBy(sortFields, params.SortBy.Ascending), repo.FormatLimitOffset(params.Limit, params.Offset), ) return g.queryRoles(ctx, query, args...) diff --git a/modules/core/infrastructure/persistence/session_repository.go b/modules/core/infrastructure/persistence/session_repository.go index 6798fef7..90f23ecb 100644 --- a/modules/core/infrastructure/persistence/session_repository.go +++ b/modules/core/infrastructure/persistence/session_repository.go @@ -3,6 +3,7 @@ package persistence import ( "context" "fmt" + "github.com/go-faster/errors" "github.com/iota-uz/iota-sdk/modules/core/domain/entities/session" "github.com/iota-uz/iota-sdk/modules/core/infrastructure/persistence/models" @@ -16,29 +17,30 @@ var ( const ( sessionFindQuery = ` - SELECT token, - user_id, - expires_at, - ip, - user_agent, - created_at - FROM sessions` + SELECT token, + user_id, + expires_at, + ip, + user_agent, + created_at + FROM sessions + ` sessionCountQuery = `SELECT COUNT(*) as count FROM sessions` sessionInsertQuery = ` INSERT INTO sessions ( - token, - user_id, - expires_at, - ip, + token, + user_id, + expires_at, + ip, user_agent, created_at ) VALUES ($1, $2, $3, $4, $5, $6)` sessionUpdateQuery = ` - UPDATE sessions - SET expires_at = $1, - ip = $2, - user_agent = $3 + UPDATE sessions + SET expires_at = $1, + ip = $2, + user_agent = $3 WHERE token = $4` sessionDeleteQuery = `DELETE FROM sessions WHERE token = $1` ) @@ -50,6 +52,18 @@ func NewSessionRepository() session.Repository { } func (g *GormSessionRepository) GetPaginated(ctx context.Context, params *session.FindParams) ([]*session.Session, error) { + sortFields := []string{} + for _, f := range params.SortBy.Fields { + switch f { + case session.ExpiresAt: + sortFields = append(sortFields, "sessions.expires_at") + case session.CreatedAt: + sortFields = append(sortFields, "sessions.created_at") + default: + return nil, fmt.Errorf("unknown sort field: %v", f) + } + } + var args []interface{} where := []string{"1 = 1"} @@ -58,13 +72,16 @@ func (g *GormSessionRepository) GetPaginated(ctx context.Context, params *sessio args = append(args, params.Token) } - q := repo.Join( - sessionFindQuery, - repo.JoinWhere(where...), - repo.FormatLimitOffset(params.Limit, params.Offset), + return g.querySessions( + ctx, + repo.Join( + sessionFindQuery, + repo.JoinWhere(where...), + repo.OrderBy(sortFields, params.SortBy.Ascending), + repo.FormatLimitOffset(params.Limit, params.Offset), + ), + args..., ) - - return g.querySessions(ctx, q, args...) } func (g *GormSessionRepository) Count(ctx context.Context) (int64, error) { diff --git a/modules/core/infrastructure/persistence/upload_repository.go b/modules/core/infrastructure/persistence/upload_repository.go index 92eb7fd1..aaf3ca62 100644 --- a/modules/core/infrastructure/persistence/upload_repository.go +++ b/modules/core/infrastructure/persistence/upload_repository.go @@ -4,12 +4,11 @@ import ( "context" "errors" "fmt" - "github.com/iota-uz/iota-sdk/pkg/repo" - "strings" "github.com/iota-uz/iota-sdk/modules/core/domain/entities/upload" "github.com/iota-uz/iota-sdk/modules/core/infrastructure/persistence/models" "github.com/iota-uz/iota-sdk/pkg/composables" + "github.com/iota-uz/iota-sdk/pkg/repo" ) var ( @@ -17,6 +16,17 @@ var ( ) const ( + selectUploadQuery = ` + SELECT + id, + hash, + path, + size, + mimetype, + created_at, + updated_at + FROM uploads u + ` insertUploadQuery = `INSERT INTO uploads (hash, path, size, mimetype, created_at) VALUES ($1, $2, $3, $4, $5) RETURNING id` updatedUploadQuery = `UPDATE uploads SET hash = $1, path = $2, size = $3, mimetype = $4, updated_at = $5 WHERE id = $6` ) @@ -27,36 +37,22 @@ func NewUploadRepository() upload.Repository { return &GormUploadRepository{} } -func (g *GormUploadRepository) GetPaginated( - ctx context.Context, params *upload.FindParams, +func (g *GormUploadRepository) queryUploads( + ctx context.Context, + query string, + args ...interface{}, ) ([]*upload.Upload, error) { pool, err := composables.UseTx(ctx) if err != nil { return nil, err } - where, args := []string{"1 = 1"}, []interface{}{} - if params.ID != 0 { - where, args = append(where, fmt.Sprintf("id = $%d", len(args)+1)), append(args, params.ID) - } - - if params.Hash != "" { - where, args = append(where, fmt.Sprintf("hash = $%d", len(args)+1)), append(args, params.Hash) - } - - if params.Type != "" { - where, args = append(where, fmt.Sprintf("mimetype = $%d", len(args)+1)), append(args, params.Type) - } - - rows, err := pool.Query(ctx, ` - SELECT id, hash, path, size, mimetype, created_at, updated_at FROM uploads - WHERE `+strings.Join(where, " AND ")+` - `+repo.FormatLimitOffset(params.Limit, params.Offset)+` - `, args...) + rows, err := pool.Query(ctx, query, args...) if err != nil { return nil, err } defer rows.Close() + uploads := make([]*upload.Upload, 0) for rows.Next() { var upload models.Upload @@ -73,7 +69,6 @@ func (g *GormUploadRepository) GetPaginated( } uploads = append(uploads, ToDomainUpload(&upload)) } - if err := rows.Err(); err != nil { return nil, err } @@ -81,6 +76,44 @@ func (g *GormUploadRepository) GetPaginated( return uploads, nil } +func (g *GormUploadRepository) GetPaginated( + ctx context.Context, params *upload.FindParams, +) ([]*upload.Upload, error) { + sortFields := []string{} + for _, f := range params.SortBy.Fields { + switch f { + case upload.Size: + sortFields = append(sortFields, "u.size") + default: + return nil, fmt.Errorf("unknown sort field: %v", f) + } + } + + where, args := []string{"1 = 1"}, []interface{}{} + if params.ID != 0 { + where, args = append(where, fmt.Sprintf("id = $%d", len(args)+1)), append(args, params.ID) + } + + if params.Hash != "" { + where, args = append(where, fmt.Sprintf("hash = $%d", len(args)+1)), append(args, params.Hash) + } + + if params.Type != "" { + where, args = append(where, fmt.Sprintf("mimetype = $%d", len(args)+1)), append(args, params.Type) + } + + return g.queryUploads( + ctx, + repo.Join( + selectUploadQuery, + repo.JoinWhere(where...), + repo.OrderBy(sortFields, params.SortBy.Ascending), + repo.FormatLimitOffset(params.Limit, params.Offset), + ), + args..., + ) +} + func (g *GormUploadRepository) Count(ctx context.Context) (int64, error) { pool, err := composables.UseTx(ctx) if err != nil { diff --git a/modules/core/infrastructure/persistence/user_repository.go b/modules/core/infrastructure/persistence/user_repository.go index 1a108e36..4904abb1 100644 --- a/modules/core/infrastructure/persistence/user_repository.go +++ b/modules/core/infrastructure/persistence/user_repository.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "errors" + "fmt" "github.com/iota-uz/iota-sdk/modules/core/domain/aggregates/role" "github.com/iota-uz/iota-sdk/modules/core/domain/aggregates/user" @@ -18,7 +19,7 @@ var ( const ( userFindQuery = ` - SELECT + SELECT u.id, u.first_name, u.last_name, @@ -88,10 +89,31 @@ func NewUserRepository() user.Repository { } func (g *GormUserRepository) GetPaginated(ctx context.Context, params *user.FindParams) ([]user.User, error) { + sortFields := []string{} + for _, f := range params.SortBy.Fields { + switch f { + case user.FirstName: + sortFields = append(sortFields, "u.first_name") + case user.LastName: + sortFields = append(sortFields, "u.last_name") + case user.MiddleName: + sortFields = append(sortFields, "u.middle_name") + case user.Email: + sortFields = append(sortFields, "u.email") + case user.LastLogin: + sortFields = append(sortFields, "u.last_login") + case user.CreatedAt: + sortFields = append(sortFields, "u.created_at") + default: + return nil, fmt.Errorf("unknown sort field: %v", f) + } + } + where, args := []string{"1 = 1"}, []interface{}{} query := repo.Join( userFindQuery, repo.JoinWhere(where...), + repo.OrderBy(sortFields, params.SortBy.Ascending), repo.FormatLimitOffset(params.Limit, params.Offset), ) return g.queryUsers(ctx, query, args...) @@ -352,12 +374,12 @@ func (g *GormUserRepository) userRoles(ctx context.Context, userID uint) ([]role } rows, err := tx.Query(ctx, ` - SELECT + SELECT r.id, r.name, r.description, r.created_at, - r.updated_at + r.updated_at FROM user_roles ur LEFT JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = $1 `, userID, diff --git a/modules/core/interfaces/graph/generated.go b/modules/core/interfaces/graph/generated.go index a6cf999e..9d67ef5d 100644 --- a/modules/core/interfaces/graph/generated.go +++ b/modules/core/interfaces/graph/generated.go @@ -65,7 +65,7 @@ type ComplexityRoot struct { Query struct { Hello func(childComplexity int, name *string) int User func(childComplexity int, id int64) int - Users func(childComplexity int, offset int, limit int, sortBy []string) int + Users func(childComplexity int, offset int, limit int, sortBy []int, ascending bool) int } Session struct { @@ -102,7 +102,7 @@ type MutationResolver interface { type QueryResolver interface { Hello(ctx context.Context, name *string) (*string, error) User(ctx context.Context, id int64) (*model.User, error) - Users(ctx context.Context, offset int, limit int, sortBy []string) (*model.PaginatedUsers, error) + Users(ctx context.Context, offset int, limit int, sortBy []int, ascending bool) (*model.PaginatedUsers, error) } type SubscriptionResolver interface { Counter(ctx context.Context) (<-chan int, error) @@ -219,7 +219,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.Users(childComplexity, args["offset"].(int), args["limit"].(int), args["sortBy"].([]string)), true + return e.complexity.Query.Users(childComplexity, args["offset"].(int), args["limit"].(int), args["sortBy"].([]int), args["ascending"].(bool)), true case "Session.createdAt": if e.complexity.Session.CreatedAt == nil { @@ -732,6 +732,11 @@ func (ec *executionContext) field_Query_users_args(ctx context.Context, rawArgs return nil, err } args["sortBy"] = arg2 + arg3, err := ec.field_Query_users_argsAscending(ctx, rawArgs) + if err != nil { + return nil, err + } + args["ascending"] = arg3 return args, nil } func (ec *executionContext) field_Query_users_argsOffset( @@ -781,22 +786,44 @@ func (ec *executionContext) field_Query_users_argsLimit( func (ec *executionContext) field_Query_users_argsSortBy( ctx context.Context, rawArgs map[string]interface{}, -) ([]string, error) { +) ([]int, error) { // We won't call the directive if the argument is null. // Set call_argument_directives_with_null to true to call directives // even if the argument is null. _, ok := rawArgs["sortBy"] if !ok { - var zeroVal []string + var zeroVal []int return zeroVal, nil } ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("sortBy")) if tmp, ok := rawArgs["sortBy"]; ok { - return ec.unmarshalOString2ᚕstringᚄ(ctx, tmp) + return ec.unmarshalOInt2ᚕintᚄ(ctx, tmp) } - var zeroVal []string + var zeroVal []int + return zeroVal, nil +} + +func (ec *executionContext) field_Query_users_argsAscending( + ctx context.Context, + rawArgs map[string]interface{}, +) (bool, error) { + // We won't call the directive if the argument is null. + // Set call_argument_directives_with_null to true to call directives + // even if the argument is null. + _, ok := rawArgs["ascending"] + if !ok { + var zeroVal bool + return zeroVal, nil + } + + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("ascending")) + if tmp, ok := rawArgs["ascending"]; ok { + return ec.unmarshalNBoolean2bool(ctx, tmp) + } + + var zeroVal bool return zeroVal, nil } @@ -1333,7 +1360,7 @@ func (ec *executionContext) _Query_users(ctx context.Context, field graphql.Coll }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Users(rctx, fc.Args["offset"].(int), fc.Args["limit"].(int), fc.Args["sortBy"].([]string)) + return ec.resolvers.Query().Users(rctx, fc.Args["offset"].(int), fc.Args["limit"].(int), fc.Args["sortBy"].([]int), fc.Args["ascending"].(bool)) }) if err != nil { ec.Error(ctx, err) @@ -5134,7 +5161,7 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast return res } -func (ec *executionContext) unmarshalOString2ᚕstringᚄ(ctx context.Context, v interface{}) ([]string, error) { +func (ec *executionContext) unmarshalOInt2ᚕintᚄ(ctx context.Context, v interface{}) ([]int, error) { if v == nil { return nil, nil } @@ -5143,10 +5170,10 @@ func (ec *executionContext) unmarshalOString2ᚕstringᚄ(ctx context.Context, v vSlice = graphql.CoerceList(v) } var err error - res := make([]string, len(vSlice)) + res := make([]int, len(vSlice)) for i := range vSlice { ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) - res[i], err = ec.unmarshalNString2string(ctx, vSlice[i]) + res[i], err = ec.unmarshalNInt2int(ctx, vSlice[i]) if err != nil { return nil, err } @@ -5154,13 +5181,13 @@ func (ec *executionContext) unmarshalOString2ᚕstringᚄ(ctx context.Context, v return res, nil } -func (ec *executionContext) marshalOString2ᚕstringᚄ(ctx context.Context, sel ast.SelectionSet, v []string) graphql.Marshaler { +func (ec *executionContext) marshalOInt2ᚕintᚄ(ctx context.Context, sel ast.SelectionSet, v []int) graphql.Marshaler { if v == nil { return graphql.Null } ret := make(graphql.Array, len(v)) for i := range v { - ret[i] = ec.marshalNString2string(ctx, sel, v[i]) + ret[i] = ec.marshalNInt2int(ctx, sel, v[i]) } for _, e := range ret { diff --git a/modules/core/interfaces/graph/users.graphql b/modules/core/interfaces/graph/users.graphql index 38161271..471bf759 100644 --- a/modules/core/interfaces/graph/users.graphql +++ b/modules/core/interfaces/graph/users.graphql @@ -15,5 +15,5 @@ type PaginatedUsers { extend type Query { user(id: ID!): User - users(offset: Int!, limit: Int!, sortBy: [String!]): PaginatedUsers! + users(offset: Int!, limit: Int!, sortBy: [Int!], ascending: Boolean!): PaginatedUsers! } diff --git a/modules/core/interfaces/graph/users.resolvers.go b/modules/core/interfaces/graph/users.resolvers.go index a6f16a66..532226f6 100644 --- a/modules/core/interfaces/graph/users.resolvers.go +++ b/modules/core/interfaces/graph/users.resolvers.go @@ -23,11 +23,14 @@ func (r *queryResolver) User(ctx context.Context, id int64) (*model.User, error) } // Users is the resolver for the users field. -func (r *queryResolver) Users(ctx context.Context, offset int, limit int, sortBy []string) (*model.PaginatedUsers, error) { +func (r *queryResolver) Users(ctx context.Context, offset int, limit int, sortBy []int, ascending bool) (*model.PaginatedUsers, error) { domainUsers, err := r.userService.GetPaginated(ctx, &user.FindParams{ Limit: limit, Offset: offset, - SortBy: sortBy, + SortBy: user.SortBy{ + Fields: sortBy, + Ascending: ascending, + }, }) if err != nil { return nil, err diff --git a/modules/core/presentation/controllers/employee_controller.go b/modules/core/presentation/controllers/employee_controller.go index 4027ef78..03f602cb 100644 --- a/modules/core/presentation/controllers/employee_controller.go +++ b/modules/core/presentation/controllers/employee_controller.go @@ -2,25 +2,26 @@ package controllers import ( "fmt" - "github.com/go-faster/errors" + "net/http" + "strconv" + "time" + "github.com/iota-uz/iota-sdk/modules/core/domain/aggregates/employee" "github.com/iota-uz/iota-sdk/modules/core/domain/entities/currency" "github.com/iota-uz/iota-sdk/modules/core/domain/value_objects/money" + "github.com/iota-uz/iota-sdk/modules/core/presentation/mappers" + "github.com/iota-uz/iota-sdk/modules/core/presentation/templates/pages/employees" "github.com/iota-uz/iota-sdk/modules/core/presentation/viewmodels" "github.com/iota-uz/iota-sdk/modules/core/services" "github.com/iota-uz/iota-sdk/pkg/application" + "github.com/iota-uz/iota-sdk/pkg/composables" "github.com/iota-uz/iota-sdk/pkg/mapping" "github.com/iota-uz/iota-sdk/pkg/middleware" "github.com/iota-uz/iota-sdk/pkg/shared" - "net/http" - "strconv" - "time" "github.com/a-h/templ" + "github.com/go-faster/errors" "github.com/gorilla/mux" - "github.com/iota-uz/iota-sdk/modules/core/presentation/mappers" - "github.com/iota-uz/iota-sdk/modules/core/presentation/templates/pages/employees" - "github.com/iota-uz/iota-sdk/pkg/composables" ) type EmployeeController struct { @@ -70,7 +71,10 @@ func (c *EmployeeController) List(w http.ResponseWriter, r *http.Request) { employeeEntities, err := c.employeeService.GetPaginated(r.Context(), &employee.FindParams{ Limit: params.Limit, Offset: params.Offset, - SortBy: []string{"id"}, + SortBy: employee.SortBy{ + Fields: []employee.Field{employee.Id}, + Ascending: true, + }, }) if err != nil { http.Error(w, errors.Wrap(err, "Error retrieving employees").Error(), http.StatusInternalServerError) diff --git a/modules/core/presentation/controllers/user_controller.go b/modules/core/presentation/controllers/user_controller.go index 6605870f..8f1064a1 100644 --- a/modules/core/presentation/controllers/user_controller.go +++ b/modules/core/presentation/controllers/user_controller.go @@ -3,8 +3,6 @@ package controllers import ( "net/http" - "github.com/a-h/templ" - "github.com/gorilla/mux" "github.com/iota-uz/iota-sdk/modules/core/domain/aggregates/user" "github.com/iota-uz/iota-sdk/modules/core/presentation/mappers" "github.com/iota-uz/iota-sdk/modules/core/presentation/templates/pages/users" @@ -15,6 +13,9 @@ import ( "github.com/iota-uz/iota-sdk/pkg/mapping" "github.com/iota-uz/iota-sdk/pkg/middleware" "github.com/iota-uz/iota-sdk/pkg/shared" + + "github.com/a-h/templ" + "github.com/gorilla/mux" ) type UsersController struct { @@ -67,7 +68,7 @@ func (c *UsersController) Users(w http.ResponseWriter, r *http.Request) { us, err := c.userService.GetPaginated(r.Context(), &user.FindParams{ Limit: params.Limit, Offset: params.Offset, - SortBy: []string{}, + SortBy: user.SortBy{Fields: []user.Field{}}, }) if err != nil { http.Error(w, "Error retrieving users", http.StatusInternalServerError) diff --git a/modules/crm/infrastructure/persistence/chat_repository.go b/modules/crm/infrastructure/persistence/chat_repository.go index 6bd14fd0..6ea083b5 100644 --- a/modules/crm/infrastructure/persistence/chat_repository.go +++ b/modules/crm/infrastructure/persistence/chat_repository.go @@ -20,7 +20,7 @@ var ( const ( selectChatQuery = ` - SELECT + SELECT c.id, c.created_at, c.last_message_at, @@ -46,7 +46,7 @@ const ( deleteChatQuery = `DELETE FROM chats WHERE id = $1` selectMessagesQuery = ` - SELECT + SELECT m.id, m.chat_id, m.message, @@ -63,7 +63,7 @@ const ( selectMessageClientSender = `SELECT id, first_name, last_name FROM clients WHERE id = $1` selectMessageAttachmentsQuery = ` - SELECT + SELECT u.id AS upload_id, u.hash, u.path, @@ -90,12 +90,12 @@ const ( ) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id` updateMessageQuery = ` - UPDATE messages SET + UPDATE messages SET chat_id = $1, message = $2, sender_user_id = $3, sender_client_id = $4, - is_read = $5, + is_read = $5, read_at = $6 WHERE id = $7 ` diff --git a/modules/crm/presentation/controllers/chat_controller.go b/modules/crm/presentation/controllers/chat_controller.go index ae3267ef..9e266165 100644 --- a/modules/crm/presentation/controllers/chat_controller.go +++ b/modules/crm/presentation/controllers/chat_controller.go @@ -21,7 +21,7 @@ import ( "github.com/iota-uz/iota-sdk/modules/crm/infrastructure/persistence" "github.com/iota-uz/iota-sdk/modules/crm/infrastructure/websocket" "github.com/iota-uz/iota-sdk/modules/crm/presentation/mappers" - "github.com/iota-uz/iota-sdk/modules/crm/presentation/templates/pages/chats" + chatsui "github.com/iota-uz/iota-sdk/modules/crm/presentation/templates/pages/chats" "github.com/iota-uz/iota-sdk/modules/crm/presentation/viewmodels" "github.com/iota-uz/iota-sdk/modules/crm/services" "github.com/iota-uz/iota-sdk/pkg/application"