193 lines
5.3 KiB
Go
193 lines
5.3 KiB
Go
package repository
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/wmms/wmms/internal/model"
|
|
)
|
|
|
|
var ErrInvalidOrganizations = errors.New("公司授权或默认公司无效,请选择启用且已授权的公司")
|
|
|
|
type userOrganizationState struct {
|
|
UserID int64 `json:"user_id"`
|
|
OrganizationIDs []int64 `json:"organization_ids"`
|
|
DefaultOrganizationID *int64 `json:"default_organization_id"`
|
|
}
|
|
|
|
func replaceUserOrganizationsTx(tx *sql.Tx, userID int64, input model.UserOrganizationInput, creating bool) error {
|
|
if input.OrganizationIDs == nil && len(input.DefaultOrganizationID) == 0 && !creating {
|
|
return nil
|
|
}
|
|
ids := []int64{}
|
|
if input.OrganizationIDs != nil {
|
|
for _, id := range *input.OrganizationIDs {
|
|
if id <= 0 {
|
|
return ErrInvalidOrganizations
|
|
}
|
|
ids = append(ids, id)
|
|
}
|
|
ids = uniqueRoleIDs(ids)
|
|
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
|
}
|
|
old := userOrganizationState{UserID: userID, OrganizationIDs: []int64{}}
|
|
rows, err := tx.Query(`SELECT organization_id, is_default FROM user_organizations WHERE user_id = ? ORDER BY organization_id FOR UPDATE`, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for rows.Next() {
|
|
var id int64
|
|
var def bool
|
|
if err := rows.Scan(&id, &def); err != nil {
|
|
rows.Close()
|
|
return err
|
|
}
|
|
old.OrganizationIDs = append(old.OrganizationIDs, id)
|
|
if def {
|
|
value := id
|
|
old.DefaultOrganizationID = &value
|
|
}
|
|
}
|
|
err = rows.Err()
|
|
rows.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if input.OrganizationIDs == nil {
|
|
ids = append(ids, old.OrganizationIDs...)
|
|
}
|
|
// Decode into an independent value so replacing the default cannot mutate
|
|
// the original state retained for the audit record.
|
|
var def *int64
|
|
if old.DefaultOrganizationID != nil {
|
|
value := *old.DefaultOrganizationID
|
|
def = &value
|
|
}
|
|
if len(input.DefaultOrganizationID) > 0 {
|
|
if err := json.Unmarshal(input.DefaultOrganizationID, &def); err != nil {
|
|
return ErrInvalidOrganizations
|
|
}
|
|
}
|
|
if def != nil && *def <= 0 {
|
|
return ErrInvalidOrganizations
|
|
}
|
|
var admin bool
|
|
if err := tx.QueryRow(`SELECT EXISTS(SELECT 1 FROM user_roles ur JOIN roles r ON r.id=ur.role_id WHERE ur.user_id=? AND r.code='super_admin' AND r.enabled=1)`, userID).Scan(&admin); err != nil {
|
|
return err
|
|
}
|
|
contains := func(values []int64, id int64) bool {
|
|
for _, value := range values {
|
|
if value == id {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
if !admin {
|
|
if creating && (len(ids) == 0 || def == nil) {
|
|
return ErrInvalidOrganizations
|
|
}
|
|
if len(ids) > 0 && def == nil {
|
|
return ErrInvalidOrganizations
|
|
}
|
|
if def != nil && !contains(ids, *def) {
|
|
return ErrInvalidOrganizations
|
|
}
|
|
} else if def != nil && !contains(ids, *def) {
|
|
ids = append(ids, *def)
|
|
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
|
}
|
|
if len(ids) > 0 {
|
|
args := make([]interface{}, len(ids))
|
|
for i, id := range ids {
|
|
args[i] = id
|
|
}
|
|
placeholders := strings.TrimSuffix(strings.Repeat("?,", len(ids)), ",")
|
|
rows, err := tx.Query(fmt.Sprintf(`SELECT id, status FROM organizations WHERE id IN (%s) ORDER BY id FOR UPDATE`, placeholders), args...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
count := 0
|
|
invalid := false
|
|
for rows.Next() {
|
|
var id int64
|
|
var status string
|
|
if err := rows.Scan(&id, &status); err != nil {
|
|
rows.Close()
|
|
return err
|
|
}
|
|
count++
|
|
if status != "active" && (!contains(old.OrganizationIDs, id) || (def != nil && *def == id)) {
|
|
invalid = true
|
|
}
|
|
}
|
|
err = rows.Err()
|
|
rows.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if invalid || count != len(ids) {
|
|
return ErrInvalidOrganizations
|
|
}
|
|
}
|
|
if _, err := tx.Exec(`DELETE FROM user_organizations WHERE user_id = ?`, userID); err != nil {
|
|
return err
|
|
}
|
|
for _, id := range ids {
|
|
if _, err := tx.Exec(`INSERT INTO user_organizations (user_id, organization_id, is_default) VALUES (?, ?, ?)`, userID, id, def != nil && *def == id); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
next := userOrganizationState{UserID: userID, OrganizationIDs: ids, DefaultOrganizationID: def}
|
|
oldJSON, err := json.Marshal(old)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
newJSON, err := json.Marshal(next)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writeOrganizationAudit(tx, &model.OrganizationAuditLog{ActorUserID: input.ActorUserID, Action: "organization_access_changed", OldValue: oldJSON, NewValue: newJSON})
|
|
}
|
|
|
|
func loadOrganizationsForUsers(q interface {
|
|
Query(string, ...interface{}) (*sql.Rows, error)
|
|
}, users []*model.User) error {
|
|
if len(users) == 0 {
|
|
return nil
|
|
}
|
|
args := make([]interface{}, len(users))
|
|
byID := map[int64]*model.User{}
|
|
for i, user := range users {
|
|
user.OrganizationIDs = []int64{}
|
|
user.DefaultOrganizationID = nil
|
|
args[i] = user.ID
|
|
byID[user.ID] = user
|
|
}
|
|
placeholders := strings.TrimSuffix(strings.Repeat("?, ", len(users)), ", ")
|
|
rows, err := q.Query(fmt.Sprintf(`SELECT user_id, organization_id, is_default FROM user_organizations WHERE user_id IN (%s) ORDER BY user_id, organization_id`, placeholders), args...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var userID, id int64
|
|
var def bool
|
|
if err := rows.Scan(&userID, &id, &def); err != nil {
|
|
return err
|
|
}
|
|
if user := byID[userID]; user != nil {
|
|
user.OrganizationIDs = append(user.OrganizationIDs, id)
|
|
if def {
|
|
value := id
|
|
user.DefaultOrganizationID = &value
|
|
}
|
|
}
|
|
}
|
|
return rows.Err()
|
|
}
|