# Role Based Access Controller

Untuk memanajemen pengaturan hak akses

## Design Tabel

* Design rbac database pada schema/migrate.go dan schema/seed.go

```go
package schema

import (
    "database/sql"

    "github.com/GuiaBolso/darwin"
)

var migrations = []darwin.Migration{
    {
        Version:     1,
        Description: "Add users",
        Script: `
CREATE TABLE users (
    id   BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    username         CHAR(15) NOT NULL UNIQUE,
    password         varchar(255) NOT NULL,
    email     VARCHAR(255) NOT NULL UNIQUE,
    is_active TINYINT(1) NOT NULL DEFAULT '0',
    created TIMESTAMP NOT NULL DEFAULT NOW(),
    updated TIMESTAMP NOT NULL DEFAULT NOW(),
    PRIMARY KEY (id)
);`,
    },
    {
        Version:     2,
        Description: "Add access",
        Script: `
CREATE TABLE access (
    id   INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    parent_id         INT(10) UNSIGNED,
    name         varchar(255) NOT NULL UNIQUE,
    alias         varchar(255) NOT NULL UNIQUE,
    created TIMESTAMP NOT NULL DEFAULT NOW(),
    PRIMARY KEY (id)
);`,
    },
    {
        Version:     3,
        Description: "Add roles",
        Script: `
CREATE TABLE roles (
    id   INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    name         varchar(255) NOT NULL UNIQUE,
    created TIMESTAMP NOT NULL DEFAULT NOW(),
    PRIMARY KEY (id)
);`,
    },
    {
        Version:     4,
        Description: "Add access_roles",
        Script: `
CREATE TABLE access_roles (
    id   INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    access_id         INT(10) UNSIGNED NOT NULL,
    role_id         INT(10) UNSIGNED NOT NULL,
    created TIMESTAMP NOT NULL DEFAULT NOW(),
    PRIMARY KEY (id),
    UNIQUE KEY access_roles_unique (access_id, role_id),
    KEY access_roles_access_id (access_id),
    KEY access_roles_role_id (role_id),
    CONSTRAINT fk_access_roles_to_access FOREIGN KEY (access_id) REFERENCES access(id) ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT fk_access_roles_to_roles FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE ON UPDATE CASCADE
);`,
    },
    {
        Version:     5,
        Description: "Add roles_users",
        Script: `
CREATE TABLE roles_users (
    id   BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    role_id         INT(10) UNSIGNED NOT NULL,
    user_id         BIGINT(20) UNSIGNED NOT NULL,
    created TIMESTAMP NOT NULL DEFAULT NOW(),
    PRIMARY KEY (id),
    UNIQUE KEY roles_users_unique (role_id, user_id),
    KEY roles_users_role_id (role_id),
    KEY roles_users_user_id (user_id),
    CONSTRAINT fk_roles_users_to_roles FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT fk_roles_users_to_users FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE
);`,
    },
}

// Migrate attempts to bring the schema for db up to date with the migrations
// defined in this package.
func Migrate(db *sql.DB) error {
    driver := darwin.NewGenericDriver(db, darwin.MySQLDialect{})

    d := darwin.New(driver, migrations, nil)

    return d.Migrate()
}
```

```go
package schema

import (
    "database/sql"
    "fmt"
)

// seeds is a string constant containing all of the queries needed to get the
// db seeded to a useful state for development.
//
// Using a constant in a .go file is an easy way to ensure the queries are part
// of the compiled executable and avoids pathing issues with the working
// directory. It has the downside that it lacks syntax highlighting and may be
// harder to read for some cases compared to using .sql files. You may also
// consider a combined approach using a tool like packr or go-bindata.
//
// Note that database servers besides PostgreSQL may not support running
// multiple queries as part of the same execution so this single large constant
// may need to be broken up.

const seedUsers string = `
INSERT INTO users (id, username, password, email, is_active) VALUES
    (1, 'jackyhtg', '$2y$10$ekouPwVdtMEy5AFbogzfSeRloxHzUwEAsM7SyNJXnso/F9ds/XUYy', 'admin@admin.com', 1);
`

const seedAccess string = `
INSERT INTO access (id, name, alias, created) VALUES (1, 'root', 'root', NOW());
`

const seedRoles string = `
INSERT INTO roles (id, name, created) VALUES (1, 'superadmin', NOW());
`

const seedAccessRoles string = `
INSERT INTO access_roles (access_id, role_id) VALUES (1, 1);
`

const seedRolesUsers string = `
INSERT INTO roles_users (role_id, user_id) VALUES (1, 1);
`

// Seed runs the set of seed-data queries against db. The queries are ran in a
// transaction and rolled back if any fail.
func Seed(db *sql.DB) error {
    seeds := []string{
        seedUsers,
        seedAccess,
        seedRoles,
        seedAccessRoles,
        seedRolesUsers,
    }

    tx, err := db.Begin()
    if err != nil {
        return err
    }

    for _, seed := range seeds {
        _, err = tx.Exec(seed)
        if err != nil {
            tx.Rollback()
            fmt.Println("error execute seed")
            return err
        }
    }

    return tx.Commit()
}
```

* go run cmd/main.go migrate && go run cmd/main.go seed untuk dump database

## Design Routing

* Buat routing untuk rbac

```go
    // Roles Routing
    {
        roles := controllers.Roles{Db: db, Log: log}
        app.Handle(http.MethodGet, "/roles", roles.List)
        app.Handle(http.MethodGet, "/roles/:id", roles.View)
        app.Handle(http.MethodPost, "/roles", roles.Create)
        app.Handle(http.MethodPut, "/roles/:id", roles.Update)
        app.Handle(http.MethodDelete, "/roles/:id", roles.Delete)
        app.Handle(http.MethodPost, "/roles/:id/access/:access_id", roles.Grant)
        app.Handle(http.MethodDelete, "/roles/:id/access/:access_id", roles.Revoke)
    }

    // Access Routing
    {
        access := controllers.Access{Db: db, Log: log}
        app.Handle(http.MethodGet, "/access", access.List)
    }
```

## Access

* Buat perintah scan-access pada libraries/auth/access.go

```go
package auth

import (
    "context"
    "database/sql"
    "essentials/libraries/array"
    "essentials/models"
    "fmt"
    "io/ioutil"
    "strings"
)

var aStr array.ArrString
var aUint32 array.ArrUint32

func ScanAccess(db *sql.DB) error {
    var existingAccess []uint32
    var err error

    // get existing access
    {
        a := models.Access{}
        existingAccess, err = a.GetIDs(context.Background(), db)
    }

    if err != nil {
        return err
    }

    // read routing file
    data, err := ioutil.ReadFile("routing/route.go")
    if err != nil {
        return err
    }

    // set transaction
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    // convert routing to access field
    datas := strings.Split(string(data), "\n")
    for _, env := range datas {
        env = strings.TrimSpace(env)
        if len(env) > 11 && env[:11] == "app.Handle(" {
            routings := strings.Split(env[11:(len(env)-1)], ",")
            httpMethod := routings[0][11:]
            url := strings.TrimSpace(routings[1])
            url = url[1:(len(url) - 1)]
            alias := strings.TrimSpace(routings[2])

            //store access except login route
            isExist, _ := aStr.InArray(url, []string{"/login", "/health"})
            if !isExist {
                urls := strings.Split(url, "/")
                controller := urls[1]
                access := strings.ToUpper(httpMethod) + " " + url
                existingAccess, err = storeAccess(existingAccess, tx, controller, access, alias)
                if err != nil {
                    tx.Rollback()
                    return err
                }
            }
        }
    }

    // remove existing access
    err = removeAccess(tx, existingAccess)
    if err != nil {
        tx.Rollback()
        return err
    }

    return tx.Commit()
}

func storeAccess(existingAccess []uint32, tx *sql.Tx, controller string, access string, alias string) ([]uint32, error) {
    ctx := context.Background()
    // get or store parent access
    existingAccess, id, err := storeController(existingAccess, ctx, tx, controller)
    if err != nil {
        return existingAccess, err
    }
    nullID := sql.NullInt64{Int64: int64(id), Valid: true}

    u := models.Access{ParentID: nullID, Name: access, Alias: alias}
    err = u.GetByName(ctx, tx)
    if err != sql.ErrNoRows && err != nil {
        return existingAccess, err
    }

    if err == sql.ErrNoRows {
        err = u.Create(ctx, tx)
        if err != nil {
            return existingAccess, err
        }
        println("store " + u.Name)
    } else {
        existingAccess = aUint32.Remove(existingAccess, u.ID)
    }

    return existingAccess, nil
}

func storeController(existingAccess []uint32, ctx context.Context, tx *sql.Tx, controller string) ([]uint32, uint32, error) {
    u := models.Access{Name: controller, Alias: controller}
    err := u.GetByName(ctx, tx)
    if err != sql.ErrNoRows && err != nil {
        return existingAccess, 0, err
    }

    if err == sql.ErrNoRows {
        u.ParentID = sql.NullInt64{Int64: 1, Valid: true}
        err = u.Create(ctx, tx)
        if err != nil {
            return existingAccess, 0, err
        }
        println("store " + u.Name)
    } else {
        existingAccess = aUint32.Remove(existingAccess, u.ID)
    }

    return existingAccess, u.ID, nil
}

func removeAccess(tx *sql.Tx, existingAccess []uint32) error {
    var err error
    ctx := context.Background()

    for _, i := range existingAccess {
        u := models.Access{ID: i}
        err = u.Get(ctx, tx)
        if err != nil {
            return err
        }

        name := u.Name

        err = u.Delete(ctx, tx)
        if err != nil {
            return err
        }

        fmt.Println("Deleted " + name)
    }

    return err
}
```

* Library di atas butuh library array. Buat file libraries/array/string.go dan libraries/array/uint32.go

```go
package array

type ArrString string

func (s ArrString) InArray(val string, array []string) (exists bool, index int) {
    exists = false
    index = -1

    for i, s := range array {
        if s == val {
            exists = true
            index = i
            return
        }
    }

    return
}

func (s ArrString) Remove(array []string, value string) []string {
    isExist, index := s.InArray(value, array)
    if isExist {
        array = append(array[:index], array[(index+1):]...)
    }

    return array
}
```

```go
package array

type ArrUint32 uint32

func (s ArrUint32) InArray(val uint32, array []uint32) (exists bool, index int) {
    exists = false
    index = -1

    for i, s := range array {
        if s == val {
            exists = true
            index = i
            return
        }
    }

    return
}

func (s ArrUint32) Remove(array []uint32, value uint32) []uint32 {
    isExist, index := s.InArray(value, array)
    if isExist {
        array = append(array[:index], array[(index+1):]...)
    }

    return array
}

func (s ArrUint32) RemoveByIndex(array []uint32, index int) []uint32 {
    return append(array[:index], array[(index+1):]...)
}
```

* Buat model Access models/access.go

```go
package models

import (
    "context"
    "database/sql"
    "errors"
    "essentials/libraries/api"
    "essentials/libraries/token"
)

//Access : struct of Access
type Access struct {
    ID       uint32
    ParentID sql.NullInt64
    Name     string
    Alias    string
}

const qAccess = `SELECT id, parent_id, name, alias FROM access`

// List ...
func (u *Access) List(ctx context.Context, tx *sql.Tx) ([]Access, error) {
    list := []Access{}

    rows, err := tx.QueryContext(ctx, qAccess)
    if err != nil {
        return list, err
    }

    defer rows.Close()

    for rows.Next() {
        var access Access
        err = rows.Scan(access.getArgs()...)
        if err != nil {
            return list, err
        }

        list = append(list, access)
    }

    if err := rows.Err(); err != nil {
        return list, err
    }

    if len(list) <= 0 {
        return list, errors.New("Access not found")
    }

    return list, nil
}

//GetByName : get access by name
func (u *Access) GetByName(ctx context.Context, tx *sql.Tx) error {
    return tx.QueryRowContext(ctx, qAccess+" WHERE name=?", u.Name).Scan(u.getArgs()...)
}

//GetByAlias : get access by alias
func (u *Access) GetByAlias(ctx context.Context, tx *sql.Tx) error {
    return tx.QueryRowContext(ctx, qAccess+" WHERE alias=?", u.Alias).Scan(u.getArgs()...)
}

//Get : get access by id
func (u *Access) Get(ctx context.Context, tx *sql.Tx) error {
    return tx.QueryRowContext(ctx, qAccess+" WHERE id=?", u.ID).Scan(u.getArgs()...)
}

//Create new Access
func (u *Access) Create(ctx context.Context, tx *sql.Tx) error {
    const query = `
        INSERT INTO access (parent_id, name, alias, created)
        VALUES (?, ?, ?, NOW())
    `
    stmt, err := tx.PrepareContext(ctx, query)
    if err != nil {
        return err
    }

    res, err := stmt.ExecContext(ctx, u.ParentID, u.Name, u.Alias)
    if err != nil {
        return err
    }

    id, err := res.LastInsertId()
    if err != nil {
        return err
    }

    u.ID = uint32(id)

    return nil
}

//Delete : delete user
func (u *Access) Delete(ctx context.Context, tx *sql.Tx) error {
    stmt, err := tx.PrepareContext(ctx, `DELETE FROM access WHERE id = ?`)
    if err != nil {
        return err
    }

    _, err = stmt.ExecContext(ctx, u.ID)
    return err
}

// GetIDs : get array of access id
func (u *Access) GetIDs(ctx context.Context, db *sql.DB) ([]uint32, error) {
    var access []uint32

    rows, err := db.QueryContext(ctx, "SELECT id FROM access WHERE name != 'root'")
    if err != nil {
        return access, err
    }

    defer rows.Close()

    for rows.Next() {
        var id uint32
        err = rows.Scan(&id)
        if err != nil {
            return access, err
        }
        access = append(access, id)
    }

    return access, rows.Err()
}

// IsAuth for check user authorization
func (u *Access) IsAuth(ctx context.Context, db *sql.DB, tokenparam interface{}, controller string, route string) (bool, error) {
    query := `
    SELECT true
    FROM users
    JOIN roles_users ON users.id = roles_users.user_id
    JOIN roles ON roles_users.role_id = roles.id
    JOIN access_roles ON roles.id = access_roles.role_id
    JOIN access ON access_roles.access_id = access.id
    WHERE (access.name = 'root' OR access.name = ? OR access.name = ?)
    AND users.id = ?
    `
    var isAuth bool
    var err error

    if tokenparam == nil {
        return isAuth, api.ErrBadRequest(errors.New("Bad request for token"), "")
    }

    isValid, username := token.ValidateToken(tokenparam.(string))
    if !isValid {
        return isAuth, api.ErrBadRequest(errors.New("Bad request for invalid token"), "")
    }

    user := User{Username: username}
    err = user.GetByUsername(ctx, db)
    if err != nil {
        return isAuth, err
    }

    err = db.QueryRowContext(ctx, query, controller, route, user.ID).Scan(&isAuth)

    return isAuth, err
}

func (u *Access) getArgs() []interface{} {
    var args []interface{}
    args = append(args, &u.ID)
    args = append(args, &u.ParentID)
    args = append(args, &u.Name)
    args = append(args, &u.Alias)
    return args
}
```

* Ubah file cmd/main.go

```go
    switch flag.Arg(0) {
    case "migrate":
        if err := schema.Migrate(db); err != nil {
            return fmt.Errorf("applying migrations: %v", err)
        }
        fmt.Println("Migrations complete")

    case "seed":
        if err := schema.Seed(db); err != nil {
            return fmt.Errorf("seeding database: %v", err)
        }
        fmt.Println("Seed data complete")

    case "scan-access":
        if err := auth.ScanAccess(db); err != nil {
            return fmt.Errorf("scan access : %v", err)
        }
        fmt.Println("Scan access complete")
    }
```

* `go run cmd/main.go scan-access` untuk insert routing ke tabel access
* Buat file controllers/access.go

```go
package controllers

import (
    "database/sql"
    "essentials/libraries/api"
    "essentials/models"
    "essentials/payloads/response"
    "fmt"
    "log"
    "net/http"
)

//Access : struct for set Access Dependency Injection
type Access struct {
    Db  *sql.DB
    Log *log.Logger
}

//List : http handler for returning list of access
func (u *Access) List(w http.ResponseWriter, r *http.Request) {
    var access models.Access
    tx, err := u.Db.Begin()
    if err != nil {
        u.Log.Printf("Begin tx : %+v", err)
        api.ResponseError(w, fmt.Errorf("getting access list: %v", err))
        return
    }
    list, err := access.List(r.Context(), tx)
    if err != nil {
        tx.Rollback()
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("getting access list: %v", err))
        return
    }

    var listResponse []*response.AccessResponse
    for _, a := range list {
        var accessResponse response.AccessResponse
        accessResponse.Transform(&a)
        listResponse = append(listResponse, &accessResponse)
    }

    api.ResponseOK(w, listResponse, http.StatusOK)
}
```

* Buat file payloads/response/access\_response.go

```go
package response

import (
    "essentials/models"
)

//AccessResponse : format json response for user
type AccessResponse struct {
    ID       uint32 `json:"id"`
    ParentID uint32 `json:"parent_id,omitempty"`
    Name     string `json:"name"`
    Alias    string `json:"alias"`
}

//Transform from Access model to Access response
func (u *AccessResponse) Transform(access *models.Access) {
    u.ID = access.ID
    u.ParentID = uint32(access.ParentID.Int64)
    u.Name = access.Name
    u.Alias = access.Alias
}
```

## Roles

* Buat models/role.go

```go
package models

import (
    "context"
    "database/sql"
    "errors"
)

// Role : struct of Role
type Role struct {
    ID   uint32
    Name string
}

const qRoles = `SELECT id, name FROM roles`

// List of roles
func (u *Role) List(ctx context.Context, db *sql.DB) ([]Role, error) {
    list := []Role{}

    rows, err := db.QueryContext(ctx, qRoles)
    if err != nil {
        return list, err
    }

    defer rows.Close()

    for rows.Next() {
        var role Role
        err = rows.Scan(role.getArgs()...)
        if err != nil {
            return list, err
        }

        list = append(list, role)
    }

    if err := rows.Err(); err != nil {
        return list, err
    }

    if len(list) <= 0 {
        return list, errors.New("Role not found")
    }

    return list, nil
}

// Get role by id
func (u *Role) Get(ctx context.Context, db *sql.DB) error {
    return db.QueryRowContext(ctx, qRoles+" WHERE id=?", u.ID).Scan(u.getArgs()...)
}

// Create new role
func (u *Role) Create(ctx context.Context, db *sql.DB) error {
    const query = `
        INSERT INTO roles (name, created)
        VALUES (?, NOW())
    `
    stmt, err := db.PrepareContext(ctx, query)
    if err != nil {
        return err
    }

    res, err := stmt.ExecContext(ctx, u.Name)
    if err != nil {
        return err
    }

    id, err := res.LastInsertId()
    if err != nil {
        return err
    }

    u.ID = uint32(id)

    return nil
}

// Update role
func (u *Role) Update(ctx context.Context, db *sql.DB) error {

    stmt, err := db.PrepareContext(ctx, `
        UPDATE roles 
        SET name = ?
        WHERE id = ?
    `)
    if err != nil {
        return err
    }

    _, err = stmt.ExecContext(ctx, u.Name, u.ID)
    return err
}

// Delete role
func (u *Role) Delete(ctx context.Context, db *sql.DB) error {
    stmt, err := db.PrepareContext(ctx, `DELETE FROM roles WHERE id = ?`)
    if err != nil {
        return err
    }

    _, err = stmt.ExecContext(ctx, u.ID)
    return err
}

// Grant access to role
func (u *Role) Grant(ctx context.Context, db *sql.DB, accessID uint32) error {
    stmt, err := db.PrepareContext(ctx, `INSERT INTO access_roles (access_id, role_id) VALUES (?, ?)`)
    if err != nil {
        return err
    }
    _, err = stmt.ExecContext(ctx, accessID, u.ID)
    return err
}

// Revoke access from role
func (u *Role) Revoke(ctx context.Context, db *sql.DB, accessID uint32) error {
    stmt, err := db.PrepareContext(ctx, `DELETE FROM access_roles WHERE access_id= ? AND role_id = ?`)
    if err != nil {
        return err
    }
    _, err = stmt.ExecContext(ctx, accessID, u.ID)
    return err
}

func (u *Role) getArgs() []interface{} {
    var args []interface{}
    args = append(args, &u.ID)
    args = append(args, &u.Name)
    return args
}
```

* Buat file payloads/request/role\_request.go

```go
package request

import (
    "essentials/models"
)

//NewRoleRequest : format json request for new role
type NewRoleRequest struct {
    Name string `json:"name" validate:"required"`
}

//Transform NewRoleRequest to Role
func (u *NewRoleRequest) Transform() *models.Role {
    var role models.Role
    role.Name = u.Name

    return &role
}

//RoleRequest : format json request for role
type RoleRequest struct {
    ID   uint32 `json:"id,omitempty"  validate:"required"`
    Name string `json:"name,omitempty"  validate:"required"`
}

//Transform RoleRequest to Role
func (u *RoleRequest) Transform(role *models.Role) *models.Role {
    if u.ID == role.ID {
        if len(u.Name) > 0 {
            role.Name = u.Name
        }
    }
    return role
}
```

* Buat file payloads/response/role\_response.go

```go
package response

import (
    "essentials/models"
)

//RoleResponse : format json response for role
type RoleResponse struct {
    ID   uint32 `json:"id"`
    Name string `json:"name"`
}

//Transform from Role model to Role response
func (u *RoleResponse) Transform(role *models.Role) {
    u.ID = role.ID
    u.Name = role.Name
}
```

* Buat file controllers/roles.go

```go
package controllers

import (
    "database/sql"
    "essentials/libraries/api"
    "essentials/models"
    "essentials/payloads/request"
    "essentials/payloads/response"
    "fmt"
    "log"
    "net/http"
    "strconv"

    "github.com/julienschmidt/httprouter"
)

//Roles : struct for set Roles Dependency Injection
type Roles struct {
    Db  *sql.DB
    Log *log.Logger
}

//List : http handler for returning list of roles
func (u *Roles) List(w http.ResponseWriter, r *http.Request) {
    var role models.Role
    list, err := role.List(r.Context(), u.Db)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("getting roles list: %v", err))
        return
    }

    var listResponse []*response.RoleResponse
    for _, role := range list {
        var roleResponse response.RoleResponse
        roleResponse.Transform(&role)
        listResponse = append(listResponse, &roleResponse)
    }

    api.ResponseOK(w, listResponse, http.StatusOK)
}

//View : http handler for retrieve role by id
func (u *Roles) View(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    paramID := ctx.Value("ps").(httprouter.Params).ByName("id")

    id, err := strconv.Atoi(paramID)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("type casting: %v", err))
        return
    }

    var role models.Role
    role.ID = uint32(id)
    err = role.Get(ctx, u.Db)

    if err == sql.ErrNoRows {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, api.ErrNotFound(err, ""))
        return
    }

    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Get Role: %v", err))
        return
    }

    var response response.RoleResponse
    response.Transform(&role)
    api.ResponseOK(w, response, http.StatusOK)
}

//Create : http handler for create new role
func (u *Roles) Create(w http.ResponseWriter, r *http.Request) {
    var roleRequest request.NewRoleRequest
    err := api.Decode(r, &roleRequest, true)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("decode role: %v", err))
        return
    }

    role := roleRequest.Transform()
    err = role.Create(r.Context(), u.Db)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Create Role: %v", err))
        return
    }

    var response response.RoleResponse
    response.Transform(role)
    api.ResponseOK(w, response, http.StatusCreated)
}

//Update : http handler for update role by id
func (u *Roles) Update(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    paramID := ctx.Value("ps").(httprouter.Params).ByName("id")

    id, err := strconv.Atoi(paramID)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("type casting paramID: %v", err))
        return
    }

    var role models.Role
    role.ID = uint32(id)
    err = role.Get(ctx, u.Db)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Get Role: %v", err))
        return
    }

    var roleRequest request.RoleRequest
    err = api.Decode(r, &roleRequest, true)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Decode Role: %v", err))
        return
    }

    if roleRequest.ID <= 0 {
        roleRequest.ID = role.ID
    }
    roleUpdate := roleRequest.Transform(&role)
    err = roleUpdate.Update(ctx, u.Db)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Update role: %v", err))
        return
    }

    var response response.RoleResponse
    response.Transform(roleUpdate)
    api.ResponseOK(w, response, http.StatusOK)
}

// Delete : http handler for delete role by id
func (u *Roles) Delete(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    paramID := ctx.Value("ps").(httprouter.Params).ByName("id")

    id, err := strconv.Atoi(paramID)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("type casting paramID: %v", err))
        return
    }

    var role models.Role
    role.ID = uint32(id)
    err = role.Get(ctx, u.Db)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Get role: %v", err))
        return
    }

    err = role.Delete(ctx, u.Db)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Delete role: %v", err))
        return
    }

    api.ResponseOK(w, nil, http.StatusNoContent)
}

//Grant : http handler for grant access to role
func (u *Roles) Grant(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    ps := ctx.Value("ps").(httprouter.Params)
    paramID := ps.ByName("id")
    paramAccessID := ps.ByName("access_id")

    id, err := strconv.Atoi(paramID)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("type casting paramID: %v", err))
        return
    }

    accessID, err := strconv.Atoi(paramAccessID)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("type casting paramAccessID: %v", err))
        return
    }

    var role models.Role
    role.ID = uint32(id)
    err = role.Get(ctx, u.Db)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Get role: %v", err))
        return
    }

    var access models.Access
    access.ID = uint32(accessID)
    tx, err := u.Db.Begin()
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Begin tx: %v", err))
        return
    }

    err = access.Get(ctx, tx)
    if err != nil {
        tx.Rollback()
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Get access: %v", err))
        return
    }
    tx.Commit()

    err = role.Grant(ctx, u.Db, access.ID)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Grant role: %v", err))
        return
    }

    api.ResponseOK(w, nil, http.StatusOK)
}

//Revoke : http handler for revoke access from role
func (u *Roles) Revoke(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    ps := ctx.Value("ps").(httprouter.Params)
    paramID := ps.ByName("id")
    paramAccessID := ps.ByName("access_id")

    id, err := strconv.Atoi(paramID)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("type casting paramID: %v", err))
        return
    }

    accessID, err := strconv.Atoi(paramAccessID)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("type casting paramAccessID: %v", err))
        return
    }

    var role models.Role
    role.ID = uint32(id)
    err = role.Get(ctx, u.Db)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Get role: %v", err))
        return
    }

    var access models.Access
    access.ID = uint32(accessID)
    tx, err := u.Db.Begin()
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Begin tx: %v", err))
        return
    }

    err = access.Get(ctx, tx)
    if err != nil {
        tx.Rollback()
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Get access: %v", err))
        return
    }
    tx.Commit()

    err = role.Revoke(ctx, u.Db, access.ID)
    if err != nil {
        u.Log.Printf("ERROR : %+v", err)
        api.ResponseError(w, fmt.Errorf("Revoke role: %v", err))
        return
    }

    api.ResponseOK(w, nil, http.StatusNoContent)
}
```

* Update users agar support roles/multi-roles


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://go-docs.waresix.com/build-rest-api-framework/rbac.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
