package access import ( "antrian-operasi/internal/database" queryUtils "antrian-operasi/internal/utils/query" "context" "database/sql" "log" "time" "github.com/google/uuid" "github.com/jmoiron/sqlx" ) const DB_NAME = "db_role_access" const TBL_USER = "role_users" const TBL_PERMISSION = "role_permission" const TBL_USER_PERMISSION = "role_user_permission" const TBL_ROLE_PAGES = "role_pages" const TBL_PAGE_PERMISSION = "role_page_permission" type IAccessRepository interface { FindUserByKeycloakId(c context.Context, id string) ([]RoleUserModel, error) CreateUserPermission(c context.Context, req SyncKeycloakRoleRequest) error UpdateUserPermission(c context.Context, userId string, req SyncKeycloakRoleRequest) error GetAvailablePageByKeycloakId(c context.Context, keycloakId string) ([]RolePageModel, error) ListUserRole(c context.Context, q QueryListUserRole) (ListUserRolePaginateResponse, error) ListRolePermission(c context.Context, q QueryListRolePermission) (ListRolePermissionPaginateResponse, error) DetailRolePermission(c context.Context, permission_id string) (DetailRolePageResponse, error) UpdateRolePermission(c context.Context, req UpdateRolePageRequest) error } type accessRepo struct { queryBuilder *queryUtils.QueryBuilder db database.Service } func NewRepository(dbService database.Service) IAccessRepository { queryBuilder := queryUtils.NewQueryBuilder(queryUtils.DBTypePostgreSQL).SetAllowedColumns([]string{ "id", "name", "is_active", "keycloak_id", "id_user", "id_permission", "email", "created_at", "id_page", }).SetAllowedTables([]string{TBL_USER}) queryBuilder.SetSecurityOptions(false, 100) return accessRepo{ queryBuilder: queryBuilder, db: dbService, } } func (r accessRepo) FindUserByKeycloakId(c context.Context, id string) ([]RoleUserModel, error) { var result []RoleUserModel query := queryUtils.DynamicQuery{ From: TBL_USER, Fields: []queryUtils.SelectField{ {Expression: "id"}, {Expression: "keycloak_id"}, }, Filters: []queryUtils.FilterGroup{ { Filters: []queryUtils.DynamicFilter{ {Column: "keycloak_id", Operator: queryUtils.OpEqual, Value: id}, }, LogicOp: "AND", }, }, } dbconn, err := r.db.GetSQLXDB(DB_NAME) if err != nil { log.Printf("Unable to connect db : %s", err) return result, err } err = r.queryBuilder.ExecuteQuery(c, dbconn, query, &result) if err != nil { log.Printf("Unable to execute query search user : %s", err) return result, err } return result, err } func (r accessRepo) CreateUserPermission(c context.Context, req SyncKeycloakRoleRequest) error { db, err := r.db.GetSQLXDB(DB_NAME) if err != nil { return err } // START TRANSACTION tx, err := db.BeginTx(c, nil) if err != nil { return err } // insert user userId, err := r.insertUser(c, tx, req) if err != nil { tx.Rollback() return err } err = r.getOrCreateUserPermission(c, db, tx, userId, req.Roles) if err != nil { tx.Rollback() return err } tx.Commit() return nil } func (r accessRepo) UpdateUserPermission(c context.Context, userId string, req SyncKeycloakRoleRequest) error { db, err := r.db.GetSQLXDB(DB_NAME) if err != nil { return err } // START TRANSACTION tx, err := db.BeginTx(c, nil) if err != nil { return err } // delete all user permission err = r.deletePermissionByUserId(c, tx, userId) if err != nil { tx.Rollback() return err } err = r.getOrCreateUserPermission(c, db, tx, userId, req.Roles) if err != nil { tx.Rollback() return err } tx.Commit() return nil } func (r accessRepo) GetAvailablePageByKeycloakId(c context.Context, keycloakId string) ([]RolePageModel, error) { var result []RolePageModel dbconn, err := r.db.GetSQLXDB(DB_NAME) if err != nil { log.Printf("Unable to connect db : %s", err) return result, err } fetchUsers, err := r.FindUserByKeycloakId(c, keycloakId) if err != nil { return nil, err } if len(fetchUsers) == 0 { log.Printf("user not found, keycloak id : %s", keycloakId) return nil, err } user := &fetchUsers[0] var permissionIds []string fetchUserPermission, err := r.getUserActivePermissionByUserId(c, dbconn, user.ID) if err != nil { return nil, err } for _, p := range fetchUserPermission { permissionIds = append(permissionIds, p.IdPermission) } var pageIds []string fetchPagePermission, err := r.getPageIdsByPermissionIds(c, dbconn, permissionIds) if err != nil { return nil, err } for _, pg := range fetchPagePermission { pageIds = append(pageIds, pg.IdPage) } result, err = r.getPageByIds(c, dbconn, pageIds) if err != nil { return nil, err } return result, nil } func (r accessRepo) ListUserRole(c context.Context, q QueryListUserRole) (ListUserRolePaginateResponse, error) { var result ListUserRolePaginateResponse query := queryUtils.DynamicQuery{ From: TBL_USER, Fields: []queryUtils.SelectField{ {Expression: "id"}, {Expression: "name"}, {Expression: "email"}, }, Sort: []queryUtils.SortField{ {Column: "created_at", Order: "DESC"}, }, } if q.Search != "" { searchFilters := []queryUtils.DynamicFilter{ {Column: "name", Operator: queryUtils.OpILike, Value: "%" + q.Search + "%"}, {Column: "email", Operator: queryUtils.OpILike, Value: "%" + q.Search + "%"}, } query.Filters = append(query.Filters, queryUtils.FilterGroup{Filters: searchFilters, LogicOp: "OR"}) } dbconn, err := r.db.GetSQLXDB(DB_NAME) if err != nil { log.Printf("Unable to connect db : %s", err) return result, err } // query count countData, err := r.queryBuilder.ExecuteCount(c, dbconn, query) if err != nil { log.Printf("Unable to execute query count : %s ", err) return result, err } result.Paging.Limit = q.Limit result.Paging.Offset = q.Offset result.Paging.Total = int(countData) result.Paging.CalculatePagingInfo() // query data queryData := query queryData.Limit = q.Limit queryData.Offset = q.Offset err = r.queryBuilder.ExecuteQuery( c, dbconn, queryData, &result.Data) if err != nil { log.Printf("Unable to execute query data : %s", err) return result, err } userIds := make([]string, 0, len(result.Data)) for _, item := range result.Data { userIds = append(userIds, item.ID) } // fetch role userPermission, err := r.getPermissionNameByUserIds(c, dbconn, userIds) if err != nil { return result, err } groupPermissionByUser := make(map[string][]string) for _, p := range userPermission { groupPermissionByUser[p.IdUser] = append(groupPermissionByUser[p.IdUser], *p.PermissionName) } // join result with role for idx := range result.Data { result.Data[idx].HakAkses = groupPermissionByUser[result.Data[idx].ID] } return result, nil } func (r accessRepo) ListRolePermission(c context.Context, q QueryListRolePermission) (ListRolePermissionPaginateResponse, error) { var result ListRolePermissionPaginateResponse query := queryUtils.DynamicQuery{ From: TBL_PERMISSION, Fields: []queryUtils.SelectField{ {Expression: "id"}, {Expression: "name"}, {Expression: "is_active"}, }, Sort: []queryUtils.SortField{ {Column: "created_at", Order: "DESC"}, }, } if q.Search != "" { searchFilters := []queryUtils.DynamicFilter{ {Column: "name", Operator: queryUtils.OpILike, Value: "%" + q.Search + "%"}, } query.Filters = append(query.Filters, queryUtils.FilterGroup{Filters: searchFilters, LogicOp: "OR"}) } dbconn, err := r.db.GetSQLXDB(DB_NAME) if err != nil { log.Printf("Unable to connect db : %s", err) return result, err } // query count countData, err := r.queryBuilder.ExecuteCount(c, dbconn, query) if err != nil { log.Printf("Unable to execute query count : %s ", err) return result, err } result.Paging.Limit = q.Limit result.Paging.Offset = q.Offset result.Paging.Total = int(countData) result.Paging.CalculatePagingInfo() // query data queryData := query queryData.Limit = q.Limit queryData.Offset = q.Offset err = r.queryBuilder.ExecuteQuery( c, dbconn, queryData, &result.Data) if err != nil { log.Printf("Unable to execute query data : %s", err) return result, err } return result, nil } func (r accessRepo) DetailRolePermission(c context.Context, permission_id string) (DetailRolePageResponse, error) { var result DetailRolePageResponse // fetch role query := queryUtils.DynamicQuery{ From: TBL_PERMISSION, Fields: []queryUtils.SelectField{ {Expression: "id"}, {Expression: "name"}, {Expression: "is_active"}, }, Filters: []queryUtils.FilterGroup{ { Filters: []queryUtils.DynamicFilter{ {Column: "id", Operator: queryUtils.OpEqual, Value: permission_id}, }, LogicOp: "AND", }, }, } dbconn, err := r.db.GetSQLXDB(DB_NAME) if err != nil { log.Printf("Unable to connect db : %s", err) return result, err } err = r.queryBuilder.ExecuteQueryRow( c, dbconn, query, &result) if err != nil { log.Printf("Unable to execute query data : %s", err) return result, err } // fetch all pages -> compare with eligible pages -> map to DetailRolePageResponse.AccessPage allPages, err := r.getAllPages(c, dbconn) if err != nil { return result, err } // fetch eligible page for role eligiblePagesMap := make(map[string]bool) eligiblePages, err := r.getPageIdsByPermissionIds(c, dbconn, []string{permission_id}) if err != nil { return result, err } for _, p := range eligiblePages { eligiblePagesMap[p.IdPage] = true } for _, p := range allPages { isActive := false if eligiblePagesMap[p.ID] { isActive = true } result.AccessPage = append(result.AccessPage, AccessPage{ ID: p.ID, Page: p.Name, IsActive: isActive, Sort: p.Sort, ParentId: &p.ParentId.String, }) } return result, nil } func (r accessRepo) UpdateRolePermission(c context.Context, req UpdateRolePageRequest) error { db, err := r.db.GetSQLXDB(DB_NAME) if err != nil { return err } // START TRANSACTION tx, err := db.BeginTx(c, nil) if err != nil { return err } // delete all page permission by permission id err = r.deletePageIdsByPermissionId(c, tx, req.ID) if err != nil { tx.Rollback() return err } // updating permission err = r.updateActivePermissionById(c, tx, req.ID, req.Status) if err != nil { tx.Rollback() return err } // select active page var pageIds []string for _, page := range req.AccessPage { if page.IsActive { pageIds = append(pageIds, page.ID) } } // inserting new permission err = r.insertPagePermission(c, tx, req.ID, pageIds) if err != nil { tx.Rollback() return err } tx.Commit() return nil } // PRIVATE FUNCTIONS // Table user function func (r accessRepo) insertUser(c context.Context, tx *sql.Tx, req SyncKeycloakRoleRequest) (string, error) { newUserId := uuid.New().String() insertUserQuery := queryUtils.InsertData{ Columns: []string{ "id", "name", "email", "keycloak_id", "created_at", }, Values: []interface{}{ newUserId, req.Username, req.Email, req.KeycloakId, time.Now(), }, } returningCols := []string{ "id", } sql, args, err := r.queryBuilder.BuildInsertQuery(TBL_USER, insertUserQuery, returningCols...) if err != nil { log.Printf("error building query create user %s", err) return newUserId, err } _, err = tx.ExecContext(c, sql, args...) if err != nil { log.Printf("error executing query create user %s", err) return newUserId, err } log.Printf(sql, args) log.Printf("success insert user") return newUserId, nil } // End Table user function // Table permission functions func (r accessRepo) findPermissionByRoleName(c context.Context, dbconn *sqlx.DB, roles []string) ([]RolePermissionModel, error) { var result []RolePermissionModel query := queryUtils.DynamicQuery{ From: TBL_PERMISSION, Fields: []queryUtils.SelectField{ {Expression: "id"}, {Expression: "name"}, }, Filters: []queryUtils.FilterGroup{ { Filters: []queryUtils.DynamicFilter{ {Column: "name", Operator: queryUtils.OpIn, Value: roles}, }, LogicOp: "AND", }, }, } err := r.queryBuilder.ExecuteQuery(c, dbconn, query, &result) if err != nil { return result, err } return result, nil } func (r accessRepo) insertPermissionByRoleName(c context.Context, tx *sql.Tx, newRoles []string) ([]string, error) { var idRoles []string if len(newRoles) > 0 { var valuePermission [][]interface{} for _, role := range newRoles { id := uuid.New().String() itemValues := []interface{}{id, role, false} idRoles = append(idRoles, id) valuePermission = append(valuePermission, itemValues) } insertPermissionQuery := queryUtils.InsertBulkData{ Columns: []string{ "id", "name", "is_active", }, Values: valuePermission, } returningCols := []string{ "id", } sql, args, err := r.queryBuilder.BuildBulkInsertQuery(TBL_PERMISSION, insertPermissionQuery, returningCols...) if err != nil { log.Printf("error building query insert new permission %s", err) return idRoles, err } _, err = tx.ExecContext(c, sql, args...) if err != nil { log.Println(err) return idRoles, err } log.Printf(sql, args) log.Printf("success insert new permission") } return idRoles, nil } func (r accessRepo) updateActivePermissionById(c context.Context, tx *sql.Tx, permissionId string, isActive bool) error { updateData := queryUtils.UpdateData{ Columns: []string{"is_active"}, Values: []interface{}{isActive}, } filters := []queryUtils.FilterGroup{ { Filters: []queryUtils.DynamicFilter{ {Column: "id", Operator: queryUtils.OpEqual, Value: permissionId}, }, }, } sql, args, err := r.queryBuilder.BuildUpdateQuery(TBL_PERMISSION, updateData, filters) if err != nil { log.Printf("failed create update permission query : %v", err) return err } _, err = tx.ExecContext(c, sql, args...) if err != nil { log.Printf("failed executing update permission : %v", err) return err } return nil } // END table permission functions // Table user permission functions func (r accessRepo) deletePermissionByUserId(c context.Context, tx *sql.Tx, userId string) error { filters := []queryUtils.FilterGroup{ { Filters: []queryUtils.DynamicFilter{ {Column: "id_user", Operator: queryUtils.OpEqual, Value: userId}, }, }, } sql, args, err := r.queryBuilder.BuildDeleteQuery(TBL_USER_PERMISSION, filters) if err != nil { log.Printf("Unable to create delete user permission query : %v", err) return err } _, err = tx.ExecContext(c, sql, args...) if err != nil { log.Printf("Unable to executing delete user permission : %v", err) return err } return nil } func (r accessRepo) insertUserPermission(c context.Context, tx *sql.Tx, userId string, roleIds []string) error { if len(roleIds) > 0 { var valueUserPermission [][]interface{} for _, roleId := range roleIds { id := uuid.New().String() itemValues := []interface{}{id, userId, roleId} valueUserPermission = append(valueUserPermission, itemValues) } insertUserPermissionQuery := queryUtils.InsertBulkData{ Columns: []string{ "id", "id_user", "id_permission", }, Values: valueUserPermission, } returningCols := []string{ "id", } sql, args, err := r.queryBuilder.BuildBulkInsertQuery(TBL_USER_PERMISSION, insertUserPermissionQuery, returningCols...) if err != nil { log.Printf("error building query insert user permission %s", err) return err } _, err = tx.ExecContext(c, sql, args...) if err != nil { log.Println(err) return err } log.Printf(sql, args) log.Printf("success insert user permission") } return nil } func (r accessRepo) getOrCreateUserPermission(c context.Context, db *sqlx.DB, tx *sql.Tx, userId string, roles []string) error { var permissionIds []string mapPermissionExist := make(map[string]string) var newRoles []string // fetch permission by name, append the permission ids fetchRoles, err := r.findPermissionByRoleName(c, db, roles) if err != nil { return err } if len(fetchRoles) > 0 { for _, item := range fetchRoles { permissionIds = append(permissionIds, item.ID) mapPermissionExist[item.Name] = item.ID } } // if permission not exist, insert non-existed permission first then append the permission ids for _, role := range roles { if mapPermissionExist[role] == "" { newRoles = append(newRoles, role) } } // insert new , non existed permission if len(newRoles) > 0 { newRoleIds, err := r.insertPermissionByRoleName(c, tx, newRoles) if err != nil { return err } else { // append new role ids to permission ids permissionIds = append(permissionIds, newRoleIds...) } } // insert all permission to user err = r.insertUserPermission(c, tx, userId, permissionIds) if err != nil { return err } return nil } func (r accessRepo) getUserActivePermissionByUserId(c context.Context, db *sqlx.DB, userId string) ([]RoleUserPermissionModel, error) { var result []RoleUserPermissionModel query := queryUtils.DynamicQuery{ From: TBL_USER_PERMISSION, Aliases: "up", Fields: []queryUtils.SelectField{ {Expression: "up.id"}, {Expression: "id_user"}, {Expression: "id_permission"}, }, Joins: []queryUtils.Join{ { Type: "LEFT", Table: TBL_PERMISSION, Alias: "p", OnConditions: queryUtils.FilterGroup{ Filters: []queryUtils.DynamicFilter{ { Column: "p.id", Operator: queryUtils.OpEqual, Value: "up.id_permission", }, }, }, }, }, Filters: []queryUtils.FilterGroup{ { Filters: []queryUtils.DynamicFilter{ {Column: "id_user", Operator: queryUtils.OpEqual, Value: userId}, {Column: "p.is_active", Operator: queryUtils.OpEqual, Value: true}, }, LogicOp: "AND", }, }, } err := r.queryBuilder.ExecuteQuery(c, db, query, &result) if err != nil { log.Printf("error executing fetch user permission : %v", err) return nil, err } return result, nil } func (r accessRepo) getPermissionNameByUserIds(c context.Context, db *sqlx.DB, userIds []string) ([]RoleUserPermissionModel, error) { var result []RoleUserPermissionModel query := queryUtils.DynamicQuery{ From: TBL_USER_PERMISSION, Aliases: "up", Fields: []queryUtils.SelectField{ {Expression: "up.id", Alias: "id"}, {Expression: "up.id_user", Alias: "id_user"}, {Expression: "up.id_permission", Alias: "id_permission"}, {Expression: "p.name", Alias: "permission_name"}, }, Joins: []queryUtils.Join{ { Type: "LEFT", Table: TBL_PERMISSION, Alias: "p", OnConditions: queryUtils.FilterGroup{ Filters: []queryUtils.DynamicFilter{ { Column: "p.id", Operator: queryUtils.OpEqual, Value: "up.id_permission", }, }, }, }, }, Filters: []queryUtils.FilterGroup{ { Filters: []queryUtils.DynamicFilter{ {Column: "id_user", Operator: queryUtils.OpIn, Value: userIds}, }, LogicOp: "AND", }, }, } err := r.queryBuilder.ExecuteQuery(c, db, query, &result) if err != nil { log.Printf("error executing fetch user permission : %v", err) return nil, err } return result, nil } // End Table user permission functions // Table role page permission functions func (r accessRepo) getPageIdsByPermissionIds(c context.Context, db *sqlx.DB, permissionIds []string) ([]RolePagePermissionModel, error) { var result []RolePagePermissionModel query := queryUtils.DynamicQuery{ From: TBL_PAGE_PERMISSION, Fields: []queryUtils.SelectField{ {Expression: "id"}, {Expression: "id_permission"}, {Expression: "id_page"}, }, Filters: []queryUtils.FilterGroup{ { Filters: []queryUtils.DynamicFilter{ {Column: "id_permission", Operator: queryUtils.OpIn, Value: permissionIds}, }, LogicOp: "AND", }, }, } err := r.queryBuilder.ExecuteQuery(c, db, query, &result) if err != nil { log.Printf("error executing fetch user permission : %v", err) return nil, err } return result, nil } func (r accessRepo) deletePageIdsByPermissionId(c context.Context, tx *sql.Tx, permissionId string) error { filters := []queryUtils.FilterGroup{ { Filters: []queryUtils.DynamicFilter{ {Column: "id_permission", Operator: queryUtils.OpEqual, Value: permissionId}, }, }, } sql, args, err := r.queryBuilder.BuildDeleteQuery(TBL_PAGE_PERMISSION, filters) if err != nil { log.Printf("Unable to create delete page permission query : %v", err) return err } _, err = tx.ExecContext(c, sql, args...) if err != nil { log.Printf("Unable to executing delete page permission : %v", err) return err } return nil } func (r accessRepo) insertPagePermission(c context.Context, tx *sql.Tx, permissionId string, pageIds []string) error { if len(pageIds) > 0 { var valueUserPermission [][]interface{} for _, pageId := range pageIds { id := uuid.New().String() itemValues := []interface{}{id, permissionId, pageId} valueUserPermission = append(valueUserPermission, itemValues) } insertPagePermissionQuery := queryUtils.InsertBulkData{ Columns: []string{ "id", "id_permission", "id_page", }, Values: valueUserPermission, } returningCols := []string{ "id", } sql, args, err := r.queryBuilder.BuildBulkInsertQuery(TBL_PAGE_PERMISSION, insertPagePermissionQuery, returningCols...) if err != nil { log.Printf("error building query insert page permission %s", err) return err } _, err = tx.ExecContext(c, sql, args...) if err != nil { log.Println(err) return err } log.Printf(sql, args) log.Printf("success insert page permission") } return nil } // End role page permission functions // Table pages functions func (r accessRepo) getPageByIds(c context.Context, db *sqlx.DB, ids []string) ([]RolePageModel, error) { var result []RolePageModel query := queryUtils.DynamicQuery{ From: TBL_ROLE_PAGES, Fields: []queryUtils.SelectField{ {Expression: "id"}, {Expression: "name"}, {Expression: "icon"}, {Expression: "url"}, {Expression: "level"}, {Expression: "sort"}, {Expression: "parent"}, }, Filters: []queryUtils.FilterGroup{ { Filters: []queryUtils.DynamicFilter{ {Column: "id", Operator: queryUtils.OpIn, Value: ids}, }, LogicOp: "AND", }, }, } err := r.queryBuilder.ExecuteQuery(c, db, query, &result) if err != nil { log.Printf("error executing fetch pages : %v", err) return nil, err } return result, nil } func (r accessRepo) getAllPages(c context.Context, db *sqlx.DB) ([]RolePageModel, error) { var result []RolePageModel query := queryUtils.DynamicQuery{ From: TBL_ROLE_PAGES, Fields: []queryUtils.SelectField{ {Expression: "id"}, {Expression: "name"}, {Expression: "icon"}, {Expression: "url"}, {Expression: "level"}, {Expression: "sort"}, {Expression: "parent"}, }, } err := r.queryBuilder.ExecuteQuery(c, db, query, &result) if err != nil { log.Printf("error executing fetch pages : %v", err) return nil, err } return result, nil } // End page functions