WIP: Migrate from GORM to sqlc - partial migration, build broken
This commit is contained in:
parent
560b40503a
commit
dad96978e0
20 changed files with 1241 additions and 709 deletions
|
|
@ -1,206 +1,129 @@
|
|||
package db
|
||||
|
||||
// =============================================================================
|
||||
// Admin User (ImcUser) Operations
|
||||
// These functions manage admin users who can log into the web interface.
|
||||
// =============================================================================
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
// GetImcUserByUsername looks up an admin user by their username.
|
||||
// Returns the user with their associated domain preloaded.
|
||||
// username: the username to search for.
|
||||
// Returns: the user and any error.
|
||||
func (d *DB) GetImcUserByUsername(username string) (*ImcUser, error) {
|
||||
var user ImcUser
|
||||
// Preload loads the related Domain to avoid N+1 queries.
|
||||
// Where + First is the generics API pattern.
|
||||
err := d.Where("username = ?", username).Preload("Domain").First(&user).Error
|
||||
if err != nil {
|
||||
return nil, err // Return nil user and the error
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
imcdb "git.workaround.org/chaas/imc/backend/internal/db/sqlc"
|
||||
)
|
||||
|
||||
// GetImcUserByID looks up an admin user by their ID.
|
||||
// Returns the user with their associated domain preloaded.
|
||||
// id: the user's primary key ID.
|
||||
// Returns: the user and any error.
|
||||
func (d *DB) GetImcUserByID(id uint) (*ImcUser, error) {
|
||||
var user ImcUser
|
||||
// Where + First looks up by primary key using generics API.
|
||||
err := d.Where("id = ?", id).Preload("Domain").First(&user).Error
|
||||
var ErrNotFound = errors.New("record not found")
|
||||
|
||||
func (d *DB) GetImcUserByUsername(ctx context.Context, username string) (*imcdb.ImcUser, error) {
|
||||
user, err := d.Queries.GetImcUserByUsername(ctx, username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// CreateImcUser creates a new admin user account.
|
||||
// username: the login username (must be unique).
|
||||
// passwordHash: the bcrypt hash of the password (NOT the plain password).
|
||||
// role: "admin" or "user".
|
||||
// domainID: the ID of the domain this user can manage.
|
||||
// Returns: the created user and any error.
|
||||
func (d *DB) CreateImcUser(username, passwordHash, role string, domainID uint) (*ImcUser, error) {
|
||||
user := ImcUser{
|
||||
func (d *DB) GetImcUserByID(ctx context.Context, id uint32) (*imcdb.ImcUser, error) {
|
||||
user, err := d.Queries.GetImcUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (d *DB) CreateImcUser(ctx context.Context, username, passwordHash, role string, domainID uint32) error {
|
||||
roleNull := imcdb.NullImcUsersRole{ImcUsersRole: imcdb.ImcUsersRole(role), Valid: true}
|
||||
return d.Queries.CreateImcUser(ctx, imcdb.CreateImcUserParams{
|
||||
Username: username,
|
||||
PasswordHash: passwordHash,
|
||||
Role: role,
|
||||
Role: roleNull,
|
||||
DomainID: domainID,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *DB) UpdateImcUserPassword(ctx context.Context, id uint32, passwordHash string) error {
|
||||
return d.Queries.UpdateImcUserPassword(ctx, imcdb.UpdateImcUserPasswordParams{
|
||||
PasswordHash: passwordHash,
|
||||
ID: id,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *DB) DeleteImcUser(ctx context.Context, id uint32) error {
|
||||
return d.Queries.DeleteImcUser(ctx, id)
|
||||
}
|
||||
|
||||
func (d *DB) UpsertAdminUser(ctx context.Context, username, passwordHash string, domainID uint32) error {
|
||||
user, err := d.Queries.GetImcUserByUsername(ctx, username)
|
||||
if err == nil && user.ID != 0 {
|
||||
return d.Queries.UpdateImcUserPassword(ctx, imcdb.UpdateImcUserPasswordParams{
|
||||
PasswordHash: passwordHash,
|
||||
ID: user.ID,
|
||||
})
|
||||
}
|
||||
// Create inserts the record into the database.
|
||||
err := d.Create(&user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
return d.CreateImcUser(ctx, username, passwordHash, "admin", domainID)
|
||||
}
|
||||
|
||||
// UpdateImcUserPassword updates the password hash for a user.
|
||||
// id: the user's ID.
|
||||
// passwordHash: the new bcrypt hash of the password.
|
||||
func (d *DB) UpdateImcUserPassword(id uint, passwordHash string) error {
|
||||
// Model specifies which table/struct to update.
|
||||
// Updates with map for generics API.
|
||||
return d.Model(&ImcUser{}).Updates(map[string]any{"password_hash": passwordHash}).Error
|
||||
}
|
||||
|
||||
// DeleteImcUser removes an admin user from the database.
|
||||
// id: the user's ID to delete.
|
||||
func (d *DB) DeleteImcUser(id uint) error {
|
||||
return d.Delete(&ImcUser{}, id).Error // Delete by primary key
|
||||
}
|
||||
|
||||
// UpsertAdminUser creates the admin user if it doesn't exist, or updates the password if it does.
|
||||
func (d *DB) UpsertAdminUser(username, passwordHash string, domainID uint) error {
|
||||
var user ImcUser
|
||||
err := d.Where("username = ? AND role = ?", username, "admin").First(&user).Error
|
||||
if err == nil {
|
||||
return d.Model(&ImcUser{}).Updates(map[string]any{"password_hash": passwordHash}).Error
|
||||
}
|
||||
_, err = d.CreateImcUser(username, passwordHash, "admin", domainID)
|
||||
return err
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Domain Access Control
|
||||
// These functions manage which domains users can access.
|
||||
// =============================================================================
|
||||
|
||||
// GetUserAccessibleDomains returns all domains a user can access.
|
||||
// Admin users can access all domains; regular users get their primary domain plus any from many-to-many.
|
||||
// userID: the ID of the user.
|
||||
// isAdmin: whether the user has admin privileges.
|
||||
// Returns: a list of accessible domains.
|
||||
func (d *DB) GetUserAccessibleDomains(userID uint, isAdmin bool) ([]VirtualDomain, error) {
|
||||
// Admins get access to all domains.
|
||||
func (d *DB) GetUserAccessibleDomains(ctx context.Context, userID uint32, isAdmin bool) ([]imcdb.VirtualDomain, error) {
|
||||
if isAdmin {
|
||||
var domains []VirtualDomain
|
||||
err := d.Order("name ASC").Find(&domains).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return domains, nil
|
||||
return d.Queries.GetAllDomains(ctx)
|
||||
}
|
||||
|
||||
// Regular users get their primary domain from imc_users table.
|
||||
var user ImcUser
|
||||
err := d.Where("id = ?", userID).First(&user).Error
|
||||
user, err := d.Queries.GetImcUserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var primaryDomain VirtualDomain
|
||||
err = d.Where("id = ?", user.DomainID).First(&primaryDomain).Error
|
||||
primaryDomain, err := d.Queries.GetDomainByID(ctx, user.DomainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Also get additional domains from many-to-many table.
|
||||
var additionalDomains []VirtualDomain
|
||||
err = d.Table("imc_users2domains").
|
||||
Select("virtual_domains.*").
|
||||
Joins("JOIN virtual_domains ON virtual_domains.id = imc_users2domains.domain_id").
|
||||
Where("imc_users2domains.user_id = ?", userID).
|
||||
Find(&additionalDomains).Error
|
||||
additionalDomains, err := d.Queries.GetAdditionalDomainsForUser(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Merge primary domain with additional domains (avoiding duplicates).
|
||||
domainMap := make(map[uint]VirtualDomain)
|
||||
domainMap := make(map[uint32]imcdb.VirtualDomain)
|
||||
domainMap[primaryDomain.ID] = primaryDomain
|
||||
for _, d := range additionalDomains {
|
||||
domainMap[d.ID] = d
|
||||
}
|
||||
|
||||
result := make([]VirtualDomain, 0, len(domainMap))
|
||||
result := make([]imcdb.VirtualDomain, 0, len(domainMap))
|
||||
for _, d := range domainMap {
|
||||
result = append(result, d)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CanAccessDomain checks if a user can access a specific domain.
|
||||
// userID: the ID of the user.
|
||||
// domainName: the name of the domain (e.g., "example.org").
|
||||
// isAdmin: whether the user has admin privileges.
|
||||
// Returns: true if the user can access the domain, false otherwise.
|
||||
func (d *DB) CanAccessDomain(userID uint, domainName string, isAdmin bool) (bool, error) {
|
||||
// Admins can access all domains.
|
||||
func (d *DB) CanAccessDomain(ctx context.Context, userID uint32, domainName string, isAdmin bool) (bool, error) {
|
||||
if isAdmin {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Regular users check their primary domain first.
|
||||
var user ImcUser
|
||||
err := d.Where("id = ?", userID).First(&user).Error
|
||||
user, err := d.Queries.GetImcUserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var primaryDomain VirtualDomain
|
||||
err = d.Where("id = ? AND name = ?", user.DomainID, domainName).First(&primaryDomain).Error
|
||||
if err == nil {
|
||||
return true, nil // Primary domain matches
|
||||
primaryDomain, err := d.Queries.GetDomainByID(ctx, user.DomainID)
|
||||
if err == nil && primaryDomain.Name == domainName {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Also check the many-to-many table.
|
||||
var count int64
|
||||
err = d.Table("imc_users2domains").
|
||||
Select("COUNT(*)").
|
||||
Joins("JOIN virtual_domains ON virtual_domains.id = imc_users2domains.domain_id").
|
||||
Where("imc_users2domains.user_id = ? AND virtual_domains.name = ?", userID, domainName).
|
||||
Count(&count).Error
|
||||
count, err := d.Queries.IsUserInDomain(ctx, imcdb.IsUserInDomainParams{UserID: userID})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// GetUsersForDomain returns all admin users who can access a specific domain.
|
||||
// domainID: the ID of the domain.
|
||||
// Returns: a list of users who can access this domain (via primary domain or many-to-many).
|
||||
func (d *DB) GetUsersForDomain(domainID uint) ([]ImcUser, error) {
|
||||
var users []ImcUser
|
||||
// First get users who have this as their primary domain.
|
||||
err := d.Where("domain_id = ?", domainID).Find(&users).Error
|
||||
func (d *DB) GetUsersForDomain(ctx context.Context, domainID uint32) ([]imcdb.ImcUser, error) {
|
||||
users, err := d.Queries.GetUsersForDomain(ctx, domainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Also get users from the many-to-many table.
|
||||
var additionalUsers []ImcUser
|
||||
err = d.Table("imc_users2domains").
|
||||
Select("imc_users.*").
|
||||
Joins("JOIN imc_users ON imc_users.id = imc_users2domains.user_id").
|
||||
Where("imc_users2domains.domain_id = ?", domainID).
|
||||
Find(&additionalUsers).Error
|
||||
additionalUsers, err := d.Queries.GetUsersForDomainViaJoin(ctx, domainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Merge the results (avoiding duplicates).
|
||||
userMap := make(map[uint]ImcUser)
|
||||
userMap := make(map[uint32]imcdb.ImcUser)
|
||||
for _, u := range users {
|
||||
userMap[u.ID] = u
|
||||
}
|
||||
|
|
@ -210,37 +133,34 @@ func (d *DB) GetUsersForDomain(domainID uint) ([]ImcUser, error) {
|
|||
}
|
||||
}
|
||||
|
||||
result := make([]ImcUser, 0, len(userMap))
|
||||
result := make([]imcdb.ImcUser, 0, len(userMap))
|
||||
for _, u := range userMap {
|
||||
result = append(result, u)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// AddUserToDomain grants a user access to a domain via many-to-many relationship.
|
||||
// userID: the ID of the user.
|
||||
// domainID: the ID of the domain.
|
||||
func (d *DB) AddUserToDomain(userID, domainID uint) error {
|
||||
ud := ImcUserDomain{
|
||||
func (d *DB) AddUserToDomain(ctx context.Context, userID, domainID uint32) error {
|
||||
return d.Queries.AddUserToDomain(ctx, imcdb.AddUserToDomainParams{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *DB) RemoveUserFromDomain(ctx context.Context, userID, domainID uint32) error {
|
||||
return d.Queries.RemoveUserFromDomain(ctx, imcdb.RemoveUserFromDomainParams{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *DB) IsUserInDomain(ctx context.Context, userID, domainID uint32) (bool, error) {
|
||||
count, err := d.Queries.IsUserInDomain(ctx, imcdb.IsUserInDomainParams{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return d.Create(&ud).Error
|
||||
}
|
||||
|
||||
// RemoveUserFromDomain revokes a user's access to a domain via many-to-many relationship.
|
||||
// userID: the ID of the user.
|
||||
// domainID: the ID of the domain.
|
||||
func (d *DB) RemoveUserFromDomain(userID, domainID uint) error {
|
||||
return d.Where("user_id = ? AND domain_id = ?", userID, domainID).Delete(&ImcUserDomain{}).Error
|
||||
}
|
||||
|
||||
// IsUserInDomain checks if a user already has access to a domain via many-to-many.
|
||||
// userID: the ID of the user.
|
||||
// domainID: the ID of the domain.
|
||||
// Returns: true if the user already has access, false otherwise.
|
||||
func (d *DB) IsUserInDomain(userID, domainID uint) (bool, error) {
|
||||
var count int64
|
||||
err := d.Model(&ImcUserDomain{}).Where("user_id = ? AND domain_id = ?", userID, domainID).Count(&count).Error
|
||||
return count > 0, err
|
||||
return count > 0, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue