feat: switch client API branding to MeetingIM
This commit is contained in:
@@ -275,6 +275,9 @@ func (a *adapter) Open(jsonconfig json.RawMessage) error {
|
||||
if err = ensureIMFileReviewTables(a.db); err != nil {
|
||||
return errors.New("mysql adapter failed to ensure file review schema: " + err.Error())
|
||||
}
|
||||
if err = ensureIMOrganizationVisibilityTables(a.db); err != nil {
|
||||
return errors.New("mysql adapter failed to ensure organization visibility schema: " + err.Error())
|
||||
}
|
||||
if err = ensureSnowflakePrimaryKeys(a.db); err != nil {
|
||||
return errors.New("mysql adapter failed to ensure snowflake primary keys: " + err.Error())
|
||||
}
|
||||
@@ -507,6 +510,10 @@ func (a *adapter) TenantAdapterFor(tenantID t.TenantID) (dbadapter.TenantBusines
|
||||
db.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
if err = ensureIMOrganizationVisibilityTables(db); err != nil {
|
||||
db.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
if err = ensureSnowflakePrimaryKeys(db); err != nil {
|
||||
db.Close()
|
||||
return nil, nil, err
|
||||
@@ -1187,6 +1194,18 @@ func createIMOrganizationTables(tx *sql.Tx) error {
|
||||
INDEX idx_im_org_members_display_name(display_name),
|
||||
CONSTRAINT fk_im_org_members_department FOREIGN KEY(department_id) REFERENCES im_org_departments(department_id) ON DELETE CASCADE
|
||||
)`,
|
||||
`CREATE TABLE im_org_visibility_overrides(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
user_id BIGINT NOT NULL,
|
||||
visibility_scope VARCHAR(16) NOT NULL,
|
||||
state VARCHAR(16) NOT NULL DEFAULT 'active',
|
||||
remark VARCHAR(256) NOT NULL DEFAULT '',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_org_visibility_user(user_id),
|
||||
INDEX idx_im_org_visibility_scope(visibility_scope,state)
|
||||
)`,
|
||||
}
|
||||
for _, stmt := range statements {
|
||||
if _, err := tx.Exec(stmt); err != nil {
|
||||
@@ -1196,6 +1215,22 @@ func createIMOrganizationTables(tx *sql.Tx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureIMOrganizationVisibilityTables(exec sqlExecutor) error {
|
||||
_, err := exec.Exec(`CREATE TABLE IF NOT EXISTS im_org_visibility_overrides(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
user_id BIGINT NOT NULL,
|
||||
visibility_scope VARCHAR(16) NOT NULL,
|
||||
state VARCHAR(16) NOT NULL DEFAULT 'active',
|
||||
remark VARCHAR(256) NOT NULL DEFAULT '',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE INDEX uk_im_org_visibility_user(user_id),
|
||||
INDEX idx_im_org_visibility_scope(visibility_scope,state)
|
||||
)`)
|
||||
return err
|
||||
}
|
||||
|
||||
func createIMMeetingTables(tx *sql.Tx) error {
|
||||
statements := []string{
|
||||
`CREATE TABLE im_meetings(
|
||||
|
||||
@@ -2681,12 +2681,28 @@ func (a *adapter) TenantOrganizationDepartments(id t.TenantID, uid t.Uid, parent
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
rows, err := a.db.QueryxContext(ctx, `SELECT d.id,d.department_id,d.name,d.parent_id,d.sort_order,d.version,d.state,d.created_at,d.updated_at,
|
||||
(SELECT COUNT(*) FROM im_org_departments AS child WHERE child.parent_id=d.department_id AND child.state='active') AS child_count,
|
||||
(SELECT COUNT(*) FROM im_org_members AS m WHERE m.department_id=d.department_id) AS member_count
|
||||
visibility, err := a.organizationVisibility(ctx, uid)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
cte, cteArgs := visibility.departmentCTE()
|
||||
memberCountSQL, memberCountArgs := visibility.memberCountSQL("m")
|
||||
if visibility.Scope != orgVisibilityAll {
|
||||
where = append(where, "d.department_id IN (SELECT department_id FROM visible_depts)")
|
||||
}
|
||||
queryArgs := append([]any{}, cteArgs...)
|
||||
queryArgs = append(queryArgs, memberCountArgs...)
|
||||
queryArgs = append(queryArgs, args...)
|
||||
childCountSQL := `(SELECT COUNT(*) FROM im_org_departments AS child WHERE child.parent_id=d.department_id AND child.state='active')`
|
||||
if visibility.Scope != orgVisibilityAll {
|
||||
childCountSQL = `(SELECT COUNT(*) FROM im_org_departments AS child WHERE child.parent_id=d.department_id AND child.state='active' AND child.department_id IN (SELECT department_id FROM visible_depts))`
|
||||
}
|
||||
rows, err := a.db.QueryxContext(ctx, cte+`SELECT d.id,d.department_id,d.name,d.parent_id,d.sort_order,d.version,d.state,d.created_at,d.updated_at,
|
||||
`+childCountSQL+` AS child_count,
|
||||
(SELECT COUNT(*) FROM im_org_members AS m WHERE m.department_id=d.department_id AND m.employment_state='active'`+memberCountSQL+`) AS member_count
|
||||
FROM im_org_departments AS d
|
||||
WHERE `+strings.Join(where, " AND ")+`
|
||||
ORDER BY d.sort_order ASC,d.id ASC LIMIT ?`, args...)
|
||||
ORDER BY d.sort_order ASC,d.id ASC LIMIT ?`, queryArgs...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -2726,7 +2742,7 @@ func (a *adapter) TenantOrganizationMembers(id t.TenantID, uid t.Uid, department
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 50
|
||||
}
|
||||
where := []string{"m.department_id=?"}
|
||||
where := []string{"m.department_id=?", "m.employment_state='active'"}
|
||||
args := []any{strings.TrimSpace(departmentID)}
|
||||
if cursor != "" {
|
||||
parsed, err := strconv.ParseInt(cursor, 10, 64)
|
||||
@@ -2741,6 +2757,15 @@ func (a *adapter) TenantOrganizationMembers(id t.TenantID, uid t.Uid, department
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
visibility, err := a.organizationVisibility(ctx, uid)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
visibilitySQL, visibilityArgs := visibility.memberWhereSQL("m")
|
||||
if visibilitySQL != "" {
|
||||
where = append(where, visibilitySQL)
|
||||
args = append(args, visibilityArgs...)
|
||||
}
|
||||
rows, err := a.db.QueryxContext(ctx, `SELECT m.id,m.department_id,m.user_id,m.display_name,m.position,m.avatar_file_id,m.mobile,m.email,
|
||||
m.employment_state,m.contactable,m.sort_order,m.version,m.created_at,m.updated_at
|
||||
FROM im_org_members AS m
|
||||
@@ -2809,6 +2834,15 @@ func (a *adapter) TenantOrganizationMembersAll(id t.TenantID, uid t.Uid, departm
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
visibility, err := a.organizationVisibility(ctx, uid)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
visibilitySQL, visibilityArgs := visibility.memberWhereSQL("m")
|
||||
if visibilitySQL != "" {
|
||||
where = append(where, visibilitySQL)
|
||||
args = append(args, visibilityArgs...)
|
||||
}
|
||||
rows, err := a.db.QueryxContext(ctx, `SELECT m.id,m.department_id,m.user_id,m.display_name,m.position,m.avatar_file_id,m.mobile,m.email,
|
||||
m.employment_state,m.contactable,m.sort_order,m.version,m.created_at,m.updated_at
|
||||
FROM im_org_members AS m
|
||||
@@ -2866,29 +2900,45 @@ func (a *adapter) TenantOrganizationSearch(id t.TenantID, uid t.Uid, queryText,
|
||||
return nil, "", t.ErrMalformed
|
||||
}
|
||||
like := "%" + strings.TrimSpace(queryText) + "%"
|
||||
parts := make([]string, 0, 2)
|
||||
args := make([]any, 0, 6)
|
||||
if includeDepartments {
|
||||
parts = append(parts, `SELECT 'department' AS result_type,d.id,d.department_id,d.name,d.parent_id,d.sort_order,d.version,d.state,d.created_at,d.updated_at,
|
||||
(SELECT COUNT(*) FROM im_org_departments AS child WHERE child.parent_id=d.department_id AND child.state='active') AS child_count,
|
||||
(SELECT COUNT(*) FROM im_org_members AS m WHERE m.department_id=d.department_id) AS member_count,
|
||||
0 AS user_id,'' AS display_name,'' AS position,'' AS avatar_file_id,'' AS mobile,'' AS email,'' AS employment_state,0 AS contactable
|
||||
FROM im_org_departments AS d WHERE d.state='active' AND d.name LIKE ?`)
|
||||
args = append(args, like)
|
||||
}
|
||||
if includeUsers {
|
||||
parts = append(parts, `SELECT 'user' AS result_type,m.id,'' AS department_id,'' AS name,m.department_id AS parent_id,m.sort_order,m.version,'' AS state,m.created_at,m.updated_at,
|
||||
0 AS child_count,0 AS member_count,m.user_id,m.display_name,m.position,m.avatar_file_id,m.mobile,m.email,m.employment_state,m.contactable
|
||||
FROM im_org_members AS m
|
||||
WHERE m.display_name LIKE ? OR m.mobile LIKE ? OR m.email LIKE ? OR CAST(m.user_id AS CHAR) LIKE ?`)
|
||||
args = append(args, like, like, like, like)
|
||||
}
|
||||
args = append(args, offset, limit+1)
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
rows, err := a.db.QueryxContext(ctx, `SELECT * FROM (`+strings.Join(parts, " UNION ALL ")+`) AS results
|
||||
visibility, err := a.organizationVisibility(ctx, uid)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
cte, cteArgs := visibility.departmentCTE()
|
||||
parts := make([]string, 0, 2)
|
||||
args := make([]any, 0, 10)
|
||||
args = append(args, cteArgs...)
|
||||
if includeDepartments {
|
||||
departmentWhere := "d.state='active' AND d.name LIKE ?"
|
||||
if visibility.Scope != orgVisibilityAll {
|
||||
departmentWhere += " AND d.department_id IN (SELECT department_id FROM visible_depts)"
|
||||
}
|
||||
parts = append(parts, `SELECT 'department' AS result_type,d.id,d.department_id,d.name,d.parent_id,d.sort_order,d.version,d.state,d.created_at,d.updated_at,
|
||||
(SELECT COUNT(*) FROM im_org_departments AS child WHERE child.parent_id=d.department_id AND child.state='active') AS child_count,
|
||||
(SELECT COUNT(*) FROM im_org_members AS m WHERE m.department_id=d.department_id) AS member_count,
|
||||
0 AS user_id,'' AS display_name,'' AS position,'' AS avatar_file_id,'' AS mobile,'' AS email,'' AS employment_state,0 AS contactable
|
||||
FROM im_org_departments AS d WHERE `+departmentWhere)
|
||||
args = append(args, like)
|
||||
}
|
||||
if includeUsers {
|
||||
userWhere := "(m.display_name LIKE ? OR m.mobile LIKE ? OR m.email LIKE ? OR CAST(m.user_id AS CHAR) LIKE ?) AND m.employment_state='active'"
|
||||
visibilitySQL, visibilityArgs := visibility.memberWhereSQL("m")
|
||||
if visibilitySQL != "" {
|
||||
userWhere += " AND " + visibilitySQL
|
||||
}
|
||||
parts = append(parts, `SELECT 'user' AS result_type,m.id,'' AS department_id,'' AS name,m.department_id AS parent_id,m.sort_order,m.version,'' AS state,m.created_at,m.updated_at,
|
||||
0 AS child_count,0 AS member_count,m.user_id,m.display_name,m.position,m.avatar_file_id,m.mobile,m.email,m.employment_state,m.contactable
|
||||
FROM im_org_members AS m
|
||||
WHERE `+userWhere)
|
||||
args = append(args, like, like, like, like)
|
||||
args = append(args, visibilityArgs...)
|
||||
}
|
||||
args = append(args, offset, limit+1)
|
||||
rows, err := a.db.QueryxContext(ctx, cte+`SELECT * FROM (`+strings.Join(parts, " UNION ALL ")+`) AS results
|
||||
ORDER BY result_type ASC,id ASC LIMIT ?,?`, args...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
@@ -2913,6 +2963,106 @@ func (a *adapter) TenantOrganizationSearch(id t.TenantID, uid t.Uid, queryText,
|
||||
return items, nextCursor, nil
|
||||
}
|
||||
|
||||
const (
|
||||
orgVisibilityAll = "all"
|
||||
orgVisibilityFriends = "friends"
|
||||
orgVisibilitySelf = "self"
|
||||
)
|
||||
|
||||
type organizationVisibility struct {
|
||||
Scope string
|
||||
UserID int64
|
||||
}
|
||||
|
||||
func (a *adapter) organizationVisibility(ctx context.Context, uid t.Uid) (organizationVisibility, error) {
|
||||
userID := decodeUID(uid)
|
||||
visibility := organizationVisibility{Scope: orgVisibilitySelf, UserID: userID}
|
||||
if userID <= 0 {
|
||||
return visibility, t.ErrMalformed
|
||||
}
|
||||
|
||||
var override string
|
||||
err := a.db.QueryRowxContext(ctx, `SELECT visibility_scope FROM im_org_visibility_overrides
|
||||
WHERE user_id=? AND state='active' LIMIT 1`, userID).Scan(&override)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return visibility, err
|
||||
}
|
||||
if err == nil {
|
||||
switch strings.TrimSpace(override) {
|
||||
case orgVisibilityAll, orgVisibilityFriends, orgVisibilitySelf:
|
||||
visibility.Scope = strings.TrimSpace(override)
|
||||
return visibility, nil
|
||||
default:
|
||||
return visibility, t.ErrMalformed
|
||||
}
|
||||
}
|
||||
|
||||
var phone sql.NullString
|
||||
err = a.db.QueryRowxContext(ctx, `SELECT phone FROM im_users WHERE id=? AND state<>? LIMIT 1`, userID, t.StateDeleted).Scan(&phone)
|
||||
if err == sql.ErrNoRows {
|
||||
return visibility, t.ErrNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return visibility, err
|
||||
}
|
||||
var inviteUsed int
|
||||
err = a.db.QueryRowxContext(ctx, `SELECT EXISTS(SELECT 1 FROM im_registration_invite_uses WHERE user_id=?)`, userID).Scan(&inviteUsed)
|
||||
if err != nil {
|
||||
return visibility, err
|
||||
}
|
||||
if inviteUsed > 0 {
|
||||
visibility.Scope = orgVisibilitySelf
|
||||
return visibility, nil
|
||||
}
|
||||
if phone.Valid && strings.TrimSpace(phone.String) != "" {
|
||||
visibility.Scope = orgVisibilityFriends
|
||||
}
|
||||
return visibility, nil
|
||||
}
|
||||
|
||||
func (v organizationVisibility) memberWhereSQL(alias string) (string, []any) {
|
||||
alias = strings.TrimSpace(alias)
|
||||
if alias == "" {
|
||||
alias = "m"
|
||||
}
|
||||
switch v.Scope {
|
||||
case orgVisibilityAll:
|
||||
return "", nil
|
||||
case orgVisibilityFriends:
|
||||
return "(" + alias + ".user_id=? OR EXISTS (SELECT 1 FROM im_friendships AS fs WHERE fs.user_id=? AND fs.peer_id=" + alias + ".user_id AND fs.status=?))",
|
||||
[]any{v.UserID, v.UserID, t.RelationshipFriend}
|
||||
default:
|
||||
return alias + ".user_id=?", []any{v.UserID}
|
||||
}
|
||||
}
|
||||
|
||||
func (v organizationVisibility) memberCountSQL(alias string) (string, []any) {
|
||||
where, args := v.memberWhereSQL(alias)
|
||||
if where == "" {
|
||||
return "", nil
|
||||
}
|
||||
return " AND " + where, args
|
||||
}
|
||||
|
||||
func (v organizationVisibility) departmentCTE() (string, []any) {
|
||||
if v.Scope == orgVisibilityAll {
|
||||
return "", nil
|
||||
}
|
||||
memberWhere, args := v.memberWhereSQL("m")
|
||||
return `WITH RECURSIVE visible_member_depts AS (
|
||||
SELECT DISTINCT m.department_id FROM im_org_members AS m
|
||||
WHERE m.employment_state='active' AND ` + memberWhere + `
|
||||
), visible_depts AS (
|
||||
SELECT d.department_id,d.parent_id FROM im_org_departments AS d
|
||||
JOIN visible_member_depts AS vmd ON vmd.department_id=d.department_id
|
||||
WHERE d.state='active'
|
||||
UNION DISTINCT
|
||||
SELECT parent.department_id,parent.parent_id FROM im_org_departments AS parent
|
||||
JOIN visible_depts AS child ON child.parent_id=parent.department_id
|
||||
WHERE parent.state='active'
|
||||
) `, args
|
||||
}
|
||||
|
||||
func organizationSearchIncludes(resultTypes string) (bool, bool) {
|
||||
if strings.TrimSpace(resultTypes) == "" {
|
||||
return true, true
|
||||
|
||||
Reference in New Issue
Block a user