feat: implement user profile retrieval with roles and permissions caching

This commit is contained in:
Tran Anh Tuan
2026-05-12 14:36:50 +07:00
parent e81a248a61
commit 902caa222f
17 changed files with 671 additions and 19 deletions

View File

@@ -10,20 +10,20 @@ const (
)
const (
API_GROUP_AUTH = "/auth"
API_GROUP_WAREHOUSE = "/warehouses"
API_GROUP_ROOM = "/rooms"
API_GROUP_CABINET = "/cabinets"
API_GROUP_SHELF = "/shelves"
API_GROUP_CONTAINER = "/containers"
API_GROUP_COMPONENT_TYPE = "/component-types"
API_GROUP_COMPONENT = "/components"
API_GROUP_COMPONENT_CODE = "/component-codes"
API_GROUP_COMPONENT_ITEM = "/component-items"
API_GROUP_INVOICE_CONFIG = "/invoice-configs"
API_GROUP_INVOICE_CONFIG_ITEM = "/invoice-config-items"
API_GROUP_INVOICE = "/invoices"
API_GROUP_ALTERNATIVE_COMPONENT = "/alternative-components"
API_GROUP_AUTH = "/auth"
API_GROUP_WAREHOUSE = "/warehouses"
API_GROUP_ROOM = "/rooms"
API_GROUP_CABINET = "/cabinets"
API_GROUP_SHELF = "/shelves"
API_GROUP_CONTAINER = "/containers"
API_GROUP_COMPONENT_TYPE = "/component-types"
API_GROUP_COMPONENT = "/components"
API_GROUP_COMPONENT_CODE = "/component-codes"
API_GROUP_COMPONENT_ITEM = "/component-items"
API_GROUP_INVOICE_CONFIG = "/invoice-configs"
API_GROUP_INVOICE_CONFIG_ITEM = "/invoice-config-items"
API_GROUP_INVOICE = "/invoices"
API_GROUP_ALTERNATIVE_COMPONENT = "/alternative-components"
)
const (
@@ -31,4 +31,5 @@ const (
API_PATH_DOCS = "/swagger/*any"
API_PATH_AUTH_REGISTER = "/register"
API_PATH_AUTH_LOGIN = "/login"
API_PATH_PROFILE = "/profile"
)

View File

@@ -0,0 +1,6 @@
-- name: GetPermissionsByUserID :many
SELECT DISTINCT p.name, p.description
FROM user_roles ur
JOIN role_permissions rp ON rp.role_id = ur.role_id
JOIN permissions p ON p.id = rp.permission_id
WHERE ur.user_id = sqlc.arg(user_id);

View File

@@ -1,10 +1,54 @@
package middlewares
import "github.com/gin-gonic/gin"
import (
"net/http"
"strings"
"wm-backend/pkg/helper"
"wm-backend/response"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
)
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 1. Get the Authorization header
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
response.UnauthorizedError(c, http.StatusUnauthorized, "Missing Authorization header")
c.Abort()
return
}
// 2. Extract Bearer token
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
response.UnauthorizedError(c, http.StatusUnauthorized, "Invalid Authorization header format")
c.Abort()
return
}
tokenString := parts[1]
// 3. Parse JWT token
claims, err := helper.ParseToken(tokenString)
if err != nil {
log.Error().Err(err).Msg("Failed to parse JWT token")
response.UnauthorizedError(c, http.StatusUnauthorized, "Invalid or expired token")
c.Abort()
return
}
// 4. Extract user_id from claims
userID, ok := claims["user_id"].(string)
if !ok || userID == "" {
response.UnauthorizedError(c, http.StatusUnauthorized, "Invalid token: missing user_id")
c.Abort()
return
}
// 5. Set user_id in gin context for downstream handlers
c.Set("user_id", userID)
c.Next()
}
}

View File

@@ -16,8 +16,8 @@ type AdminConfig struct {
}
type JWTConfig struct {
SecretKey string
ExpirationHours int
SecretKey string `mapstructure:"secretkey"`
ExpirationHours int `mapstructure:"expirehours"`
}
type DatabaseConfig struct {

View File

@@ -0,0 +1,7 @@
package models
// Permission represents a system permission (e.g., "read:warehouse", "write:component")
type Permission struct {
Name string `json:"name"`
Description string `json:"description"`
}

View File

@@ -3,6 +3,25 @@ package responses
type BodyRegisterResponse struct {
ID string `json:"id"`
}
type BodyLoginResponse struct {
Token string `json:"token"`
}
// RoleItem represents a role assigned to the user in the profile response.
type RoleItem struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
}
// BodyProfileResponse is the response body for GET /profile.
type BodyProfileResponse struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
FullName string `json:"fullName"`
IsActive bool `json:"isActive"`
Roles []RoleItem `json:"roles"`
Permissions []string `json:"permissions"`
}

View File

@@ -7,6 +7,7 @@ import (
"wm-backend/internal/models"
db "wm-backend/sqlc_gen"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
@@ -43,3 +44,20 @@ func CreateUser(ctx context.Context, queries *db.Queries, params db.CreateUserPa
}
return id.String(), nil
}
// GetUserByID retrieves a user by their ID using SQLC-generated queries.
// Returns nil, nil if no user is found.
func GetUserByID(ctx context.Context, queries *db.Queries, id string) (*models.User, error) {
uid, err := uuid.Parse(id)
if err != nil {
return nil, err
}
user, err := queries.GetUserByID(ctx, uid)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, nil
}
return nil, err
}
return mapper.ToDomainUser(user), nil
}

View File

@@ -0,0 +1,29 @@
package repositories
import (
"context"
"wm-backend/internal/models"
db "wm-backend/sqlc_gen"
"github.com/google/uuid"
)
// GetPermissionsByUserID retrieves all permission names for a given user.
func GetPermissionsByUserID(ctx context.Context, queries *db.Queries, userID string) ([]models.Permission, error) {
uid, err := uuid.Parse(userID)
if err != nil {
return nil, err
}
rows, err := queries.GetPermissionsByUserID(ctx, uid)
if err != nil {
return nil, err
}
permissions := make([]models.Permission, 0, len(rows))
for _, row := range rows {
permissions = append(permissions, models.Permission{
Name: row.Name,
Description: row.Description.String,
})
}
return permissions, nil
}

View File

@@ -1 +1,79 @@
package redis
import (
"context"
"encoding/json"
"fmt"
"time"
"wm-backend/global"
"github.com/rs/zerolog/log"
)
const (
userRBACPrefix = "user:rbac:"
defaultTTL = 60 * time.Minute
)
// RBACCachedData represents the cached roles and permissions for a user.
type RBACCachedData struct {
Roles []RoleCacheItem `json:"roles"`
Permissions []string `json:"permissions"`
}
// RoleCacheItem represents a cached role item.
type RoleCacheItem struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
}
func buildKey(userID string) string {
return fmt.Sprintf("%s%s", userRBACPrefix, userID)
}
// CacheUserPermissions stores the user's roles and permissions in Redis with TTL.
func CacheUserPermissions(ctx context.Context, userID string, data RBACCachedData, ttl time.Duration) error {
jsonData, err := json.Marshal(data)
if err != nil {
log.Error().Err(err).Str("userID", userID).Msg("Failed to marshal RBAC data for caching")
return err
}
if err := global.Cache.Set(ctx, buildKey(userID), jsonData, ttl).Err(); err != nil {
log.Error().Err(err).Str("userID", userID).Msg("Failed to cache user permissions in Redis")
return err
}
return nil
}
// GetCachedUserPermissions retrieves the cached RBAC data from Redis.
// Returns (nil, false) if not found or on error (graceful fallback).
func GetCachedUserPermissions(ctx context.Context, userID string) (*RBACCachedData, bool) {
val, err := global.Cache.Get(ctx, buildKey(userID)).Result()
if err != nil {
// Key doesn't exist or Redis error; silently fallback to DB
return nil, false
}
var data RBACCachedData
if err := json.Unmarshal([]byte(val), &data); err != nil {
log.Error().Err(err).Str("userID", userID).Msg("Failed to unmarshal cached RBAC data")
return nil, false
}
return &data, true
}
// RefreshTTL resets the TTL on a cached RBAC entry when a cache hit occurs.
func RefreshTTL(ctx context.Context, userID string, ttl time.Duration) {
if err := global.Cache.Expire(ctx, buildKey(userID), ttl).Err(); err != nil {
log.Error().Err(err).Str("userID", userID).Msg("Failed to refresh TTL for RBAC cache")
}
}
// DeleteUserPermissionsCache removes the cached RBAC data for a user (for cache invalidation).
func DeleteUserPermissionsCache(ctx context.Context, userID string) {
if err := global.Cache.Del(ctx, buildKey(userID)).Err(); err != nil {
log.Error().Err(err).Str("userID", userID).Msg("Failed to delete RBAC cache")
}
}

View File

@@ -5,6 +5,8 @@ import (
"wm-backend/internal/mapper"
"wm-backend/internal/models"
db "wm-backend/sqlc_gen"
"github.com/google/uuid"
)
func CreateRole(ctx context.Context, queries *db.Queries, body models.Role) (models.Role, error) {
@@ -14,3 +16,24 @@ func CreateRole(ctx context.Context, queries *db.Queries, body models.Role) (mod
}
return *mapper.ToDomainRole(role), nil
}
// GetUserRolesByUserID retrieves all roles assigned to a user with role info.
func GetUserRolesByUserID(ctx context.Context, queries *db.Queries, userID string) ([]models.Role, error) {
uid, err := uuid.Parse(userID)
if err != nil {
return nil, err
}
rows, err := queries.GetUserRolesByUserID(ctx, uid)
if err != nil {
return nil, err
}
roles := make([]models.Role, 0, len(rows))
for _, row := range rows {
roles = append(roles, models.Role{
ID: row.RoleID.String(),
Name: row.RoleName,
Description: row.RoleDescription.String,
})
}
return roles, nil
}

View File

@@ -29,6 +29,13 @@ func NewRouter() *gin.Engine {
auth.POST(constants.API_PATH_AUTH_LOGIN, utils.AsyncHandler(services.Login))
}
// Protected routes (require JWT authentication)
protected := v1.Group("")
protected.Use(middlewares.AuthMiddleware())
{
protected.GET(constants.API_PATH_PROFILE, utils.AsyncHandler(services.GetProfile))
}
warehouse := v1.Group(constants.API_GROUP_WAREHOUSE)
{
warehouse.GET("", utils.AsyncHandler(services.WareHouseList))

View File

@@ -1,12 +1,15 @@
package services
import (
"context"
"net/http"
"time"
"wm-backend/global"
"wm-backend/internal/models"
"wm-backend/internal/models/requests"
"wm-backend/internal/models/responses"
"wm-backend/internal/repositories"
redisRepo "wm-backend/internal/repositories/redis"
"wm-backend/pkg/helper"
"wm-backend/response"
db "wm-backend/sqlc_gen"
@@ -120,9 +123,51 @@ func Login(c *gin.Context) error {
return nil
}
// 5. Return token
// 5. Cache roles & permissions in Redis
go cacheUserRBAC(user.ID)
// 6. Return token
response.Ok(c, "Login successful", responses.BodyLoginResponse{
Token: token,
})
return nil
}
// cacheUserRBAC fetches roles and permissions for a user and stores them in Redis.
func cacheUserRBAC(userID string) {
ctx := context.Background()
roles, err := repositories.GetUserRolesByUserID(ctx, global.Queries, userID)
if err != nil {
log.Error().Err(err).Str("userID", userID).Msg("Failed to fetch roles for caching")
return
}
permissions, err := repositories.GetPermissionsByUserID(ctx, global.Queries, userID)
if err != nil {
log.Error().Err(err).Str("userID", userID).Msg("Failed to fetch permissions for caching")
return
}
roleItems := make([]redisRepo.RoleCacheItem, 0, len(roles))
permNames := make([]string, 0, len(permissions))
for _, r := range roles {
roleItems = append(roleItems, redisRepo.RoleCacheItem{
ID: r.ID,
Name: r.Name,
Description: r.Description,
})
}
for _, p := range permissions {
permNames = append(permNames, p.Name)
}
data := redisRepo.RBACCachedData{
Roles: roleItems,
Permissions: permNames,
}
if err := redisRepo.CacheUserPermissions(ctx, userID, data, 60*time.Minute); err != nil {
log.Error().Err(err).Str("userID", userID).Msg("Failed to cache RBAC data")
}
}

View File

@@ -0,0 +1,121 @@
package services
import (
"context"
"net/http"
"time"
"wm-backend/global"
"wm-backend/internal/models/responses"
"wm-backend/internal/repositories"
redisRepo "wm-backend/internal/repositories/redis"
"wm-backend/response"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
)
// GetProfile returns the authenticated user's profile including roles and permissions.
// It first attempts to read from Redis cache; on miss it queries the database.
//
// @Summary Get current user profile
// @Description Returns user info with roles and permissions (requires auth)
// @Tags auth
// @Security BearerAuth
// @Produce json
// @Success 200 {object} response.SuccessResponse{data=responses.BodyProfileResponse}
// @Failure 401 {object} response.ErrorResponse
// @Router /profile [get]
func GetProfile(c *gin.Context) error {
userID := c.GetString("user_id")
ctx := c.Request.Context()
var roles []responses.RoleItem
var permissions []string
// 1. Try Redis cache first (graceful fallback on error)
cachedData, found := redisRepo.GetCachedUserPermissions(ctx, userID)
if found {
// Cache hit - refresh TTL
redisRepo.RefreshTTL(ctx, userID, 60*time.Minute)
roles = make([]responses.RoleItem, 0, len(cachedData.Roles))
for _, r := range cachedData.Roles {
roles = append(roles, responses.RoleItem{
ID: r.ID,
Name: r.Name,
Description: r.Description,
})
}
permissions = cachedData.Permissions
} else {
// 2. Cache miss - fetch from DB
dbRoles, err := repositories.GetUserRolesByUserID(ctx, global.Queries, userID)
if err != nil {
log.Error().Err(err).Str("userID", userID).Msg("Failed to fetch roles from DB")
response.InternalServerError(c, http.StatusInternalServerError)
return nil
}
roles = make([]responses.RoleItem, 0, len(dbRoles))
for _, r := range dbRoles {
roles = append(roles, responses.RoleItem{
ID: r.ID,
Name: r.Name,
Description: r.Description,
})
}
dbPerms, err := repositories.GetPermissionsByUserID(ctx, global.Queries, userID)
if err != nil {
log.Error().Err(err).Str("userID", userID).Msg("Failed to fetch permissions from DB")
response.InternalServerError(c, http.StatusInternalServerError)
return nil
}
permissions = make([]string, 0, len(dbPerms))
for _, p := range dbPerms {
permissions = append(permissions, p.Name)
}
// 3. Save to Redis cache for future requests (use background context)
cacheRoles := make([]redisRepo.RoleCacheItem, 0, len(dbRoles))
for _, r := range dbRoles {
cacheRoles = append(cacheRoles, redisRepo.RoleCacheItem{
ID: r.ID,
Name: r.Name,
Description: r.Description,
})
}
go func() {
bgCtx := context.Background()
if err := redisRepo.CacheUserPermissions(bgCtx, userID, redisRepo.RBACCachedData{
Roles: cacheRoles,
Permissions: permissions,
}, 60*time.Minute); err != nil {
log.Error().Err(err).Str("userID", userID).Msg("Failed to cache RBAC data in profile")
}
}()
}
// 4. Fetch user info from DB
user, err := repositories.GetUserByID(ctx, global.Queries, userID)
if err != nil {
log.Error().Err(err).Str("userID", userID).Msg("Failed to fetch user from DB")
response.InternalServerError(c, http.StatusInternalServerError)
return nil
}
if user == nil {
response.NotFoundError(c, http.StatusNotFound, "User not found")
return nil
}
// 5. Return response
response.Ok(c, "Profile fetched", responses.BodyProfileResponse{
ID: user.ID,
Username: user.Username,
Email: user.Email,
FullName: user.FullName,
IsActive: user.IsActive,
Roles: roles,
Permissions: permissions,
})
return nil
}

View File

@@ -6,6 +6,7 @@ import (
"wm-backend/global"
"github.com/golang-jwt/jwt/v5"
"github.com/rs/zerolog/log"
)
// GenerateToken tạo JWT token cho user
@@ -21,19 +22,23 @@ func GenerateToken(userID string) (string, error) {
}
func ParseToken(tokenString string) (jwt.MapClaims, error) {
log.Debug().Str("token", tokenString).Msg("Parsing JWT token")
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(global.Cfg.JWT.SecretKey), nil // <-- lấy từ config
})
log.Debug().Interface("token", token).Msg("Parsed JWT token object")
if err != nil {
return nil, err
}
log.Debug().Interface("claims", token.Claims).Msg("Parsed JWT claims")
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
log.Debug().Interface("claims", claims).Msg("Valid JWT claims extracted")
return claims, nil
}
log.Error().Msg("Invalid JWT token: claims not valid or token not valid")
return nil, fmt.Errorf("invalid token")
}

View File

@@ -0,0 +1,197 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: invoice_item.sql
package db
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createInvoiceItem = `-- name: CreateInvoiceItem :one
INSERT INTO invoice_items (invoice_id,component_id,original_component_id, required_quantity,actual_quantity, is_substituted, is_short, shortage_quantity, note, metadata)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10
)
RETURNING id, invoice_id, component_id, original_component_id, required_quantity, actual_quantity, is_substituted, is_short, shortage_quantity, note, metadata
`
type CreateInvoiceItemParams struct {
InvoiceID int64 `db:"invoice_id" json:"invoiceId"`
ComponentID int64 `db:"component_id" json:"componentId"`
OriginalComponentID pgtype.Int8 `db:"original_component_id" json:"originalComponentId"`
RequiredQuantity int32 `db:"required_quantity" json:"requiredQuantity"`
ActualQuantity int32 `db:"actual_quantity" json:"actualQuantity"`
IsSubstituted bool `db:"is_substituted" json:"isSubstituted"`
IsShort bool `db:"is_short" json:"isShort"`
ShortageQuantity int32 `db:"shortage_quantity" json:"shortageQuantity"`
Note pgtype.Text `db:"note" json:"note"`
Metadata []byte `db:"metadata" json:"metadata"`
}
func (q *Queries) CreateInvoiceItem(ctx context.Context, arg CreateInvoiceItemParams) (InvoiceItem, error) {
row := q.db.QueryRow(ctx, createInvoiceItem,
arg.InvoiceID,
arg.ComponentID,
arg.OriginalComponentID,
arg.RequiredQuantity,
arg.ActualQuantity,
arg.IsSubstituted,
arg.IsShort,
arg.ShortageQuantity,
arg.Note,
arg.Metadata,
)
var i InvoiceItem
err := row.Scan(
&i.ID,
&i.InvoiceID,
&i.ComponentID,
&i.OriginalComponentID,
&i.RequiredQuantity,
&i.ActualQuantity,
&i.IsSubstituted,
&i.IsShort,
&i.ShortageQuantity,
&i.Note,
&i.Metadata,
)
return i, err
}
const deleteInvoiceItem = `-- name: DeleteInvoiceItem :execrows
DELETE FROM invoice_items
WHERE id = $1
`
func (q *Queries) DeleteInvoiceItem(ctx context.Context, id int64) (int64, error) {
result, err := q.db.Exec(ctx, deleteInvoiceItem, id)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const getInvoiceItemByID = `-- name: GetInvoiceItemByID :one
SELECT id, invoice_id, component_id, original_component_id, required_quantity, actual_quantity, is_substituted, is_short, shortage_quantity, note, metadata FROM invoice_items
WHERE id = $1
`
func (q *Queries) GetInvoiceItemByID(ctx context.Context, id int64) (InvoiceItem, error) {
row := q.db.QueryRow(ctx, getInvoiceItemByID, id)
var i InvoiceItem
err := row.Scan(
&i.ID,
&i.InvoiceID,
&i.ComponentID,
&i.OriginalComponentID,
&i.RequiredQuantity,
&i.ActualQuantity,
&i.IsSubstituted,
&i.IsShort,
&i.ShortageQuantity,
&i.Note,
&i.Metadata,
)
return i, err
}
const listInvoiceItems = `-- name: ListInvoiceItems :many
SELECT id, invoice_id, component_id, original_component_id, required_quantity, actual_quantity, is_substituted, is_short, shortage_quantity, note, metadata FROM invoice_items
`
func (q *Queries) ListInvoiceItems(ctx context.Context) ([]InvoiceItem, error) {
rows, err := q.db.Query(ctx, listInvoiceItems)
if err != nil {
return nil, err
}
defer rows.Close()
var items []InvoiceItem
for rows.Next() {
var i InvoiceItem
if err := rows.Scan(
&i.ID,
&i.InvoiceID,
&i.ComponentID,
&i.OriginalComponentID,
&i.RequiredQuantity,
&i.ActualQuantity,
&i.IsSubstituted,
&i.IsShort,
&i.ShortageQuantity,
&i.Note,
&i.Metadata,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateInvoiceItem = `-- name: UpdateInvoiceItem :one
UPDATE invoice_items
SET required_quantity = coalesce($1, required_quantity),
actual_quantity = coalesce($2, actual_quantity),
is_substituted = coalesce($3, is_substituted),
is_short = coalesce($4, is_short),
shortage_quantity = coalesce($5, shortage_quantity),
note = coalesce($6, note),
metadata = coalesce($7, metadata)
WHERE id = $8
RETURNING id, invoice_id, component_id, original_component_id, required_quantity, actual_quantity, is_substituted, is_short, shortage_quantity, note, metadata
`
type UpdateInvoiceItemParams struct {
RequiredQuantity int32 `db:"required_quantity" json:"requiredQuantity"`
ActualQuantity int32 `db:"actual_quantity" json:"actualQuantity"`
IsSubstituted bool `db:"is_substituted" json:"isSubstituted"`
IsShort bool `db:"is_short" json:"isShort"`
ShortageQuantity int32 `db:"shortage_quantity" json:"shortageQuantity"`
Note pgtype.Text `db:"note" json:"note"`
Metadata []byte `db:"metadata" json:"metadata"`
ID int64 `db:"id" json:"id"`
}
func (q *Queries) UpdateInvoiceItem(ctx context.Context, arg UpdateInvoiceItemParams) (InvoiceItem, error) {
row := q.db.QueryRow(ctx, updateInvoiceItem,
arg.RequiredQuantity,
arg.ActualQuantity,
arg.IsSubstituted,
arg.IsShort,
arg.ShortageQuantity,
arg.Note,
arg.Metadata,
arg.ID,
)
var i InvoiceItem
err := row.Scan(
&i.ID,
&i.InvoiceID,
&i.ComponentID,
&i.OriginalComponentID,
&i.RequiredQuantity,
&i.ActualQuantity,
&i.IsSubstituted,
&i.IsShort,
&i.ShortageQuantity,
&i.Note,
&i.Metadata,
)
return i, err
}

View File

@@ -0,0 +1,46 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: permission.sql
package db
import (
"context"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
)
const getPermissionsByUserID = `-- name: GetPermissionsByUserID :many
SELECT DISTINCT p.name, p.description
FROM user_roles ur
JOIN role_permissions rp ON rp.role_id = ur.role_id
JOIN permissions p ON p.id = rp.permission_id
WHERE ur.user_id = $1
`
type GetPermissionsByUserIDRow struct {
Name string `db:"name" json:"name"`
Description pgtype.Text `db:"description" json:"description"`
}
func (q *Queries) GetPermissionsByUserID(ctx context.Context, userID uuid.UUID) ([]GetPermissionsByUserIDRow, error) {
rows, err := q.db.Query(ctx, getPermissionsByUserID, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetPermissionsByUserIDRow
for rows.Next() {
var i GetPermissionsByUserIDRow
if err := rows.Scan(&i.Name, &i.Description); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@@ -24,6 +24,7 @@ type Querier interface {
CreateInvoice(ctx context.Context, arg CreateInvoiceParams) (Invoice, error)
CreateInvoiceConfig(ctx context.Context, arg CreateInvoiceConfigParams) (InvoiceConfig, error)
CreateInvoiceConfigItem(ctx context.Context, arg CreateInvoiceConfigItemParams) (InvoiceConfigItem, error)
CreateInvoiceItem(ctx context.Context, arg CreateInvoiceItemParams) (InvoiceItem, error)
CreateRole(ctx context.Context, arg CreateRoleParams) (Role, error)
CreateRoom(ctx context.Context, arg CreateRoomParams) (Room, error)
CreateShelve(ctx context.Context, arg CreateShelveParams) (Shelf, error)
@@ -39,6 +40,7 @@ type Querier interface {
DeleteInvoice(ctx context.Context, id int64) (int64, error)
DeleteInvoiceConfig(ctx context.Context, id int64) (int64, error)
DeleteInvoiceConfigItem(ctx context.Context, id int64) (int64, error)
DeleteInvoiceItem(ctx context.Context, id int64) (int64, error)
DeleteRole(ctx context.Context, id uuid.UUID) (int64, error)
DeleteRoom(ctx context.Context, id int64) (int64, error)
DeleteShelve(ctx context.Context, id int64) (int64, error)
@@ -55,6 +57,8 @@ type Querier interface {
GetInvoiceByID(ctx context.Context, id int64) (Invoice, error)
GetInvoiceConfigByID(ctx context.Context, id int64) (InvoiceConfig, error)
GetInvoiceConfigItemByID(ctx context.Context, id int64) (InvoiceConfigItem, error)
GetInvoiceItemByID(ctx context.Context, id int64) (InvoiceItem, error)
GetPermissionsByUserID(ctx context.Context, userID uuid.UUID) ([]GetPermissionsByUserIDRow, error)
GetRoleByID(ctx context.Context, id uuid.UUID) (Role, error)
GetRoomByID(ctx context.Context, id int64) (Room, error)
GetShelveByID(ctx context.Context, id int64) (Shelf, error)
@@ -74,6 +78,7 @@ type Querier interface {
ListContainers(ctx context.Context) ([]Container, error)
ListInvoiceConfigItems(ctx context.Context) ([]InvoiceConfigItem, error)
ListInvoiceConfigs(ctx context.Context) ([]InvoiceConfig, error)
ListInvoiceItems(ctx context.Context) ([]InvoiceItem, error)
ListInvoices(ctx context.Context) ([]Invoice, error)
ListRoles(ctx context.Context) ([]Role, error)
ListRooms(ctx context.Context) ([]Room, error)
@@ -93,6 +98,7 @@ type Querier interface {
UpdateInvoice(ctx context.Context, arg UpdateInvoiceParams) (Invoice, error)
UpdateInvoiceConfig(ctx context.Context, arg UpdateInvoiceConfigParams) (InvoiceConfig, error)
UpdateInvoiceConfigItem(ctx context.Context, arg UpdateInvoiceConfigItemParams) (InvoiceConfigItem, error)
UpdateInvoiceItem(ctx context.Context, arg UpdateInvoiceItemParams) (InvoiceItem, error)
UpdateRole(ctx context.Context, arg UpdateRoleParams) (Role, error)
UpdateRoom(ctx context.Context, arg UpdateRoomParams) (Room, error)
UpdateShelve(ctx context.Context, arg UpdateShelveParams) (Shelf, error)