feat: add Yuheng ticket bind, trial SMS off, shared bindings

Ship ticket-exchange and bind/policy for Z13, keep trial binds SMS-free, allow shared company bindings, and align SyncPage plus sync docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
whm
2026-08-05 15:09:49 +08:00
parent f90245db9c
commit d195aa4804
19 changed files with 957 additions and 96 deletions

View File

@@ -19,6 +19,8 @@ type Binding struct {
LocalDatabaseID string `json:"local_database_id"`
OnlineDBID string `json:"online_db_id"`
ChannelID string `json:"channel_id,omitempty"`
// Shared公司共享库Z12c-2同租户任意成员可 push/pull 该 online_db_id
Shared bool `json:"shared,omitempty"`
// 可读名(与宇恒 database_name / display_name 对齐;控制台优先展示)
DatabaseName string `json:"database_name,omitempty"` // 本地库可读名
DisplayName string `json:"display_name,omitempty"` // 线上库可读名
@@ -70,6 +72,7 @@ func (s *FileStore) EnsureBinding(b Binding) (Binding, error) {
if b.UserID > 0 {
list[i].UserID = b.UserID
}
list[i].Shared = b.Shared
list[i].UpdatedAt = now
if err := s.writeBindingsUnlocked(list); err != nil {
return b, err
@@ -117,18 +120,33 @@ func (s *FileStore) ListBindingsFiltered(tenantID, userID int64, localDatabaseID
return out, nil
}
// UserOwnsOnlineDB 用户是否登记了该 online_db_id可选限定 channel
// UserOwnsOnlineDB 用户是否登记了该 online_db_id可选限定 channel;或该库为公司 Shared
func (s *FileStore) UserOwnsOnlineDB(tenantID, userID int64, channelID, onlineDBID string) bool {
if tenantID <= 0 || userID <= 0 || strings.TrimSpace(onlineDBID) == "" {
return false
}
list, err := s.ListBindingsFiltered(tenantID, userID, "")
online := strings.TrimSpace(onlineDBID)
ch := strings.TrimSpace(channelID)
own, err := s.ListBindingsFiltered(tenantID, userID, "")
if err == nil {
for _, b := range own {
if strings.TrimSpace(b.OnlineDBID) != online {
continue
}
if ch != "" && b.ChannelID != "" && b.ChannelID != ch {
continue
}
return true
}
}
all, err := s.ListBindingsFiltered(tenantID, 0, "")
if err != nil {
return false
}
online := strings.TrimSpace(onlineDBID)
ch := strings.TrimSpace(channelID)
for _, b := range list {
for _, b := range all {
if !b.Shared {
continue
}
if strings.TrimSpace(b.OnlineDBID) != online {
continue
}
@@ -140,17 +158,28 @@ func (s *FileStore) UserOwnsOnlineDB(tenantID, userID int64, channelID, onlineDB
return false
}
// UserCanAccessChannel 用户是否登记了指向该通道的 Bindingchannel_id 空视为未限定通道)。
// UserCanAccessChannel 用户是否登记了指向该通道的 Bindingchannel_id 空视为未限定通道);含 Shared
func (s *FileStore) UserCanAccessChannel(tenantID, userID int64, channelID string) bool {
if tenantID <= 0 || userID <= 0 || strings.TrimSpace(channelID) == "" {
return false
}
ch := strings.TrimSpace(channelID)
list, err := s.ListBindingsFiltered(tenantID, userID, "")
if err != nil || len(list) == 0 {
if err == nil {
for _, b := range list {
if b.ChannelID == "" || b.ChannelID == ch {
return true
}
}
}
all, err := s.ListBindingsFiltered(tenantID, 0, "")
if err != nil {
return false
}
ch := strings.TrimSpace(channelID)
for _, b := range list {
for _, b := range all {
if !b.Shared {
continue
}
if b.ChannelID == "" || b.ChannelID == ch {
return true
}