chore: import clean Agent Desk snapshot (3/7)
This commit is contained in:
304
internal/services/webhook_sync_service.go
Normal file
304
internal/services/webhook_sync_service.go
Normal file
@@ -0,0 +1,304 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"agent-desk/internal/models"
|
||||
"agent-desk/internal/pkg/config"
|
||||
"agent-desk/internal/pkg/dto/request"
|
||||
"agent-desk/internal/pkg/enums"
|
||||
"agent-desk/internal/pkg/errorsx"
|
||||
"agent-desk/internal/repositories"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
)
|
||||
|
||||
var WebhookSyncService = newWebhookSyncService()
|
||||
|
||||
func newWebhookSyncService() *webhookSyncService {
|
||||
return &webhookSyncService{}
|
||||
}
|
||||
|
||||
type webhookSyncService struct{}
|
||||
|
||||
func (s *webhookSyncService) VerifySignature(payload []byte, signature string) bool {
|
||||
secret := strings.TrimSpace(config.Current().Webhook.OrgSyncSecret)
|
||||
if secret == "" {
|
||||
secret = strings.TrimSpace(config.Current().Webhook.DOSOrgSyncSecret)
|
||||
}
|
||||
if secret == "" {
|
||||
secret = strings.TrimSpace(config.Current().OIDC.ClientSecret)
|
||||
}
|
||||
if secret == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
sig := strings.TrimSpace(signature)
|
||||
if strings.HasPrefix(sig, "sha256=") {
|
||||
sig = strings.TrimPrefix(sig, "sha256=")
|
||||
}
|
||||
if sig == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
mac.Write(payload)
|
||||
expected := hex.EncodeToString(mac.Sum(nil))
|
||||
|
||||
return hmac.Equal([]byte(sig), []byte(expected))
|
||||
}
|
||||
|
||||
func (s *webhookSyncService) HandleOrgSync(req request.OrgSyncWebhookRequest) error {
|
||||
event := strings.TrimSpace(req.Event)
|
||||
data := req.Data
|
||||
|
||||
orgCode := strings.TrimSpace(data.OrgID)
|
||||
if orgCode == "" {
|
||||
return errorsx.InvalidParam("org_id is required")
|
||||
}
|
||||
|
||||
switch event {
|
||||
case "org.created", "org.updated":
|
||||
return s.handleOrgUpsert(data)
|
||||
case "org.deleted":
|
||||
return s.handleOrgDelete(orgCode)
|
||||
case "org.member_added", "org.member_updated":
|
||||
return s.handleMemberUpsert(data)
|
||||
case "org.member_removed":
|
||||
return s.handleMemberRemove(data)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *webhookSyncService) HandleDOSOrgSync(req request.DOSOrgSyncWebhookRequest) error {
|
||||
return s.HandleOrgSync(req)
|
||||
}
|
||||
|
||||
func (s *webhookSyncService) handleOrgUpsert(data request.OrgSyncEventData) error {
|
||||
now := time.Now()
|
||||
orgCode := strings.TrimSpace(data.OrgID)
|
||||
orgName := strings.TrimSpace(data.OrgName)
|
||||
if orgName == "" {
|
||||
orgName = orgCode
|
||||
}
|
||||
plan := strings.TrimSpace(data.Plan)
|
||||
if plan == "" {
|
||||
plan = "free"
|
||||
}
|
||||
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
org := repositories.OrganizationRepository.GetByCode(ctx.Tx, orgCode)
|
||||
if org == nil {
|
||||
org = &models.Organization{
|
||||
Code: orgCode,
|
||||
Name: orgName,
|
||||
Plan: plan,
|
||||
Status: enums.StatusOk,
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
CreateUserID: 0,
|
||||
CreateUserName: "webhook-sync",
|
||||
UpdatedAt: now,
|
||||
UpdateUserID: 0,
|
||||
UpdateUserName: "webhook-sync",
|
||||
},
|
||||
}
|
||||
return repositories.OrganizationRepository.Create(ctx.Tx, org)
|
||||
}
|
||||
|
||||
updates := map[string]any{
|
||||
"name": orgName,
|
||||
"status": enums.StatusOk,
|
||||
"update_user_id": 0,
|
||||
"update_user_name": "webhook-sync",
|
||||
"updated_at": now,
|
||||
}
|
||||
if plan != "" {
|
||||
updates["plan"] = plan
|
||||
}
|
||||
return repositories.OrganizationRepository.Updates(ctx.Tx, org.ID, updates)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *webhookSyncService) handleOrgDelete(orgCode string) error {
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
org := repositories.OrganizationRepository.GetByCode(ctx.Tx, orgCode)
|
||||
if org == nil {
|
||||
return nil
|
||||
}
|
||||
return repositories.OrganizationRepository.UpdateColumn(ctx.Tx, org.ID, "status", enums.StatusDeleted)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *webhookSyncService) handleMemberUpsert(data request.OrgSyncEventData) error {
|
||||
now := time.Now()
|
||||
orgCode := strings.TrimSpace(data.OrgID)
|
||||
userSubject := strings.TrimSpace(data.UserID)
|
||||
userEmail := strings.TrimSpace(strings.ToLower(data.UserEmail))
|
||||
userName := strings.TrimSpace(data.UserName)
|
||||
role := strings.ToUpper(strings.TrimSpace(data.Role))
|
||||
if role == "" {
|
||||
role = "MEMBER"
|
||||
}
|
||||
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
org := repositories.OrganizationRepository.GetByCode(ctx.Tx, orgCode)
|
||||
if org == nil {
|
||||
orgName := strings.TrimSpace(data.OrgName)
|
||||
if orgName == "" {
|
||||
orgName = orgCode
|
||||
}
|
||||
org = &models.Organization{
|
||||
Code: orgCode,
|
||||
Name: orgName,
|
||||
Plan: "free",
|
||||
Status: enums.StatusOk,
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
CreateUserID: 0,
|
||||
CreateUserName: "webhook-sync",
|
||||
UpdatedAt: now,
|
||||
UpdateUserID: 0,
|
||||
UpdateUserName: "webhook-sync",
|
||||
},
|
||||
}
|
||||
if err := repositories.OrganizationRepository.Create(ctx.Tx, org); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var user *models.User
|
||||
if userSubject != "" {
|
||||
identity := repositories.UserIdentityRepository.GetBy(ctx.Tx, enums.ThirdProviderOIDC, "", userSubject)
|
||||
if identity != nil {
|
||||
user = repositories.UserRepository.Get(ctx.Tx, identity.UserID)
|
||||
}
|
||||
}
|
||||
if user == nil && userEmail != "" {
|
||||
user = repositories.UserRepository.GetByEmail(ctx.Tx, userEmail)
|
||||
}
|
||||
if user == nil {
|
||||
username := userEmail
|
||||
if username == "" {
|
||||
username = "u_" + userSubject
|
||||
}
|
||||
user = &models.User{
|
||||
Username: username,
|
||||
Nickname: userName,
|
||||
Email: &userEmail,
|
||||
UserType: enums.UserTypeEmployee,
|
||||
Status: enums.StatusOk,
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
CreateUserID: 0,
|
||||
CreateUserName: "webhook-sync",
|
||||
UpdatedAt: now,
|
||||
UpdateUserID: 0,
|
||||
UpdateUserName: "webhook-sync",
|
||||
},
|
||||
}
|
||||
if err := repositories.UserRepository.Create(ctx.Tx, user); err != nil {
|
||||
return err
|
||||
}
|
||||
if userSubject != "" {
|
||||
_ = repositories.UserIdentityRepository.Create(ctx.Tx, &models.UserIdentity{
|
||||
UserID: user.ID,
|
||||
Provider: enums.ThirdProviderOIDC,
|
||||
ProviderUserID: userSubject,
|
||||
ProviderName: "OIDC",
|
||||
Status: enums.StatusOk,
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
CreateUserID: user.ID,
|
||||
CreateUserName: user.Username,
|
||||
UpdatedAt: now,
|
||||
UpdateUserID: user.ID,
|
||||
UpdateUserName: user.Username,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
member := repositories.OrganizationMemberRepository.GetByOrgAndUser(ctx.Tx, org.ID, user.ID)
|
||||
if member == nil {
|
||||
member = &models.OrganizationMember{
|
||||
OrganizationID: org.ID,
|
||||
UserID: user.ID,
|
||||
Role: role,
|
||||
Status: enums.StatusOk,
|
||||
AuditFields: models.AuditFields{
|
||||
CreatedAt: now,
|
||||
CreateUserID: user.ID,
|
||||
CreateUserName: user.Username,
|
||||
UpdatedAt: now,
|
||||
UpdateUserID: user.ID,
|
||||
UpdateUserName: user.Username,
|
||||
},
|
||||
}
|
||||
if err := repositories.OrganizationMemberRepository.Create(ctx.Tx, member); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
_ = repositories.OrganizationMemberRepository.Updates(ctx.Tx, member.ID, map[string]any{
|
||||
"role": role,
|
||||
"status": enums.StatusOk,
|
||||
"update_user_id": user.ID,
|
||||
"update_user_name": user.Username,
|
||||
"updated_at": now,
|
||||
})
|
||||
}
|
||||
|
||||
if user.ActiveOrgID == 0 {
|
||||
_ = repositories.UserRepository.UpdateColumn(ctx.Tx, user.ID, "active_org_id", org.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *webhookSyncService) handleMemberRemove(data request.OrgSyncEventData) error {
|
||||
orgCode := strings.TrimSpace(data.OrgID)
|
||||
userSubject := strings.TrimSpace(data.UserID)
|
||||
userEmail := strings.TrimSpace(strings.ToLower(data.UserEmail))
|
||||
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
org := repositories.OrganizationRepository.GetByCode(ctx.Tx, orgCode)
|
||||
if org == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var user *models.User
|
||||
if userSubject != "" {
|
||||
identity := repositories.UserIdentityRepository.GetBy(ctx.Tx, enums.ThirdProviderOIDC, "", userSubject)
|
||||
if identity != nil {
|
||||
user = repositories.UserRepository.Get(ctx.Tx, identity.UserID)
|
||||
}
|
||||
}
|
||||
if user == nil && userEmail != "" {
|
||||
user = repositories.UserRepository.GetByEmail(ctx.Tx, userEmail)
|
||||
}
|
||||
if user == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
member := repositories.OrganizationMemberRepository.GetByOrgAndUser(ctx.Tx, org.ID, user.ID)
|
||||
if member != nil {
|
||||
_ = repositories.OrganizationMemberRepository.UpdateColumn(ctx.Tx, member.ID, "status", enums.StatusDeleted)
|
||||
}
|
||||
|
||||
if user.ActiveOrgID == org.ID {
|
||||
remaining := repositories.OrganizationMemberRepository.Find(ctx.Tx, sqls.NewCnd().Eq("user_id", user.ID).Eq("status", enums.StatusOk).Where("organization_id <> ?", org.ID))
|
||||
var newActiveOrgID int64 = 0
|
||||
if len(remaining) > 0 {
|
||||
newActiveOrgID = remaining[0].OrganizationID
|
||||
}
|
||||
_ = repositories.UserRepository.UpdateColumn(ctx.Tx, user.ID, "active_org_id", newActiveOrgID)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user