Fix meeting list and media search issues
This commit is contained in:
@@ -405,6 +405,7 @@ type TenantBusinessAdapter interface {
|
||||
TenantOrganizationMembersAll(t.TenantID, t.Uid, string, string, string, int) ([]t.OrganizationMember, string, error)
|
||||
TenantOrganizationSearch(t.TenantID, t.Uid, string, string, string, int) ([]t.OrganizationSearchItem, string, error)
|
||||
TenantMeetingCreate(t.TenantID, t.Uid, *t.Meeting, []t.Uid) (*t.Meeting, error)
|
||||
TenantMeetingList(t.TenantID, t.Uid, string, int) ([]t.Meeting, string, error)
|
||||
TenantMeetingGet(t.TenantID, t.Uid, string) (*t.Meeting, error)
|
||||
TenantMeetingUpdate(t.TenantID, t.Uid, string, t.MeetingPatch, string) (*t.Meeting, error)
|
||||
TenantMeetingCancel(t.TenantID, t.Uid, string, string) (*t.Meeting, error)
|
||||
|
||||
@@ -113,9 +113,23 @@ func (a *adapter) getContextForTx() (context.Context, context.CancelFunc) {
|
||||
return context.Background(), nil
|
||||
}
|
||||
|
||||
func normalizeMySQLDSN(dsn string) string {
|
||||
dsn = strings.TrimSpace(dsn)
|
||||
if strings.HasPrefix(dsn, "mysql://") {
|
||||
return strings.TrimPrefix(dsn, "mysql://")
|
||||
}
|
||||
if strings.HasPrefix(dsn, "mysql:") {
|
||||
credentials, _, _ := strings.Cut(strings.TrimPrefix(dsn, "mysql:"), "@")
|
||||
if strings.Count(credentials, ":") >= 1 {
|
||||
return strings.TrimPrefix(dsn, "mysql:")
|
||||
}
|
||||
}
|
||||
return dsn
|
||||
}
|
||||
|
||||
func openMySQLDB(config configType, dsn, dbName string) (*sqlx.DB, string, error) {
|
||||
if strings.TrimSpace(dsn) != "" {
|
||||
cfg, err := ms.ParseDSN(strings.TrimPrefix(dsn, "mysql://"))
|
||||
cfg, err := ms.ParseDSN(normalizeMySQLDSN(dsn))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -180,7 +194,7 @@ func (a *adapter) Open(jsonconfig json.RawMessage) error {
|
||||
// Note: this method is deprecated.
|
||||
if config.DSN != "" {
|
||||
// Remove optional schema.
|
||||
a.dsn = strings.TrimPrefix(config.DSN, "mysql://")
|
||||
a.dsn = normalizeMySQLDSN(config.DSN)
|
||||
} else {
|
||||
a.dsn = defaultDSN
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
//go:build mysql
|
||||
// +build mysql
|
||||
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
ms "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
func TestNormalizeMySQLDSN(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dsn string
|
||||
user string
|
||||
}{
|
||||
{
|
||||
name: "go driver dsn",
|
||||
dsn: "root:secret@tcp(127.0.0.1:3306)/tinode?parseTime=true",
|
||||
user: "root",
|
||||
},
|
||||
{
|
||||
name: "url scheme dsn",
|
||||
dsn: "mysql://root:secret@tcp(127.0.0.1:3306)/tinode?parseTime=true",
|
||||
user: "root",
|
||||
},
|
||||
{
|
||||
name: "legacy scheme dsn",
|
||||
dsn: "mysql:root:secret@tcp(127.0.0.1:3306)/tinode?parseTime=true",
|
||||
user: "root",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg, err := ms.ParseDSN(normalizeMySQLDSN(tc.dsn))
|
||||
if err != nil {
|
||||
t.Fatalf("ParseDSN failed: %v", err)
|
||||
}
|
||||
if cfg.User != tc.user {
|
||||
t.Fatalf("user=%q, want %q", cfg.User, tc.user)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2308,7 +2308,7 @@ func (a *adapter) TenantMessageSearch(id t.TenantID, topic string, uid t.Uid, qu
|
||||
args = append(args, "%"+queryText+"%")
|
||||
}
|
||||
if messageType != "" && messageType != "all" {
|
||||
where = append(where, "(JSON_UNQUOTE(JSON_EXTRACT(m.head,'$.type'))=? OR JSON_UNQUOTE(JSON_EXTRACT(m.head,'$.x-im-type'))=?)")
|
||||
where = append(where, "(JSON_UNQUOTE(JSON_EXTRACT(m.head,'$.type'))=? OR JSON_UNQUOTE(JSON_EXTRACT(m.head,'$.\"x-im-type\"'))=?)")
|
||||
args = append(args, messageType, messageType)
|
||||
}
|
||||
ctx, cancel := a.getContext()
|
||||
@@ -3110,6 +3110,105 @@ func (a *adapter) TenantMeetingCreate(id t.TenantID, uid t.Uid, meeting *t.Meeti
|
||||
return a.TenantMeetingGet(id, uid, meeting.MeetingID)
|
||||
}
|
||||
|
||||
func (a *adapter) TenantMeetingList(id t.TenantID, uid t.Uid, cursor string, limit int) ([]t.Meeting, string, error) {
|
||||
if !a.tenantScopedDB {
|
||||
tenantAdp, err := a.routedTenantAdapter(id)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if tenantAdp != a {
|
||||
return tenantAdp.TenantMeetingList(id, uid, cursor, limit)
|
||||
}
|
||||
}
|
||||
if err := validTenant(id); err != nil || uid.IsZero() {
|
||||
return nil, "", t.ErrMalformed
|
||||
}
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 20
|
||||
}
|
||||
where := []string{`(m.organizer_id=? OR EXISTS (
|
||||
SELECT 1 FROM im_meeting_participants AS p WHERE p.meeting_id=m.meeting_id AND p.user_id=?
|
||||
))`}
|
||||
args := []any{decodeUID(uid), decodeUID(uid)}
|
||||
if strings.TrimSpace(cursor) != "" {
|
||||
parsed, err := strconv.ParseInt(strings.TrimSpace(cursor), 10, 64)
|
||||
if err != nil || parsed <= 0 {
|
||||
return nil, "", t.ErrMalformed
|
||||
}
|
||||
where = append(where, "m.id<?")
|
||||
args = append(args, parsed)
|
||||
}
|
||||
ctx, cancel := a.getContext()
|
||||
if cancel != nil {
|
||||
defer cancel()
|
||||
}
|
||||
args = append(args, limit+1)
|
||||
rows, err := a.db.QueryxContext(ctx, `SELECT m.* FROM im_meetings AS m
|
||||
WHERE `+strings.Join(where, " AND ")+`
|
||||
ORDER BY m.start_at DESC,m.id DESC LIMIT ?`, args...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
defer rows.Close()
|
||||
meetings := make([]t.Meeting, 0, limit)
|
||||
nextCursor := ""
|
||||
for rows.Next() {
|
||||
row := struct {
|
||||
ID int64
|
||||
MeetingID string `db:"meeting_id"`
|
||||
OrganizerID int64 `db:"organizer_id"`
|
||||
Title string
|
||||
StartAt time.Time `db:"start_at"`
|
||||
EndAt time.Time `db:"end_at"`
|
||||
Location string
|
||||
State string
|
||||
ReminderMinutes []byte `db:"reminder_minutes"`
|
||||
IdempotencyKey sql.NullString `db:"idempotency_key"`
|
||||
Version int
|
||||
CanceledAt *time.Time `db:"canceled_at"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}{}
|
||||
if err = rows.StructScan(&row); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
meeting := t.Meeting{
|
||||
ID: row.ID,
|
||||
MeetingID: row.MeetingID,
|
||||
OrganizerID: store.EncodeUid(row.OrganizerID),
|
||||
Title: row.Title,
|
||||
StartAt: row.StartAt,
|
||||
EndAt: row.EndAt,
|
||||
Location: row.Location,
|
||||
State: row.State,
|
||||
Version: row.Version,
|
||||
CanceledAt: row.CanceledAt,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
}
|
||||
if row.IdempotencyKey.Valid {
|
||||
meeting.IdempotencyKey = row.IdempotencyKey.String
|
||||
}
|
||||
if len(row.ReminderMinutes) > 0 {
|
||||
_ = json.Unmarshal(row.ReminderMinutes, &meeting.ReminderMinutes)
|
||||
}
|
||||
if len(meetings) == limit {
|
||||
nextCursor = strconv.FormatInt(row.ID, 10)
|
||||
continue
|
||||
}
|
||||
participants, err := a.meetingParticipants(ctx, row.MeetingID)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
meeting.Participants = participants
|
||||
meetings = append(meetings, meeting)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return meetings, nextCursor, nil
|
||||
}
|
||||
|
||||
func (a *adapter) TenantMeetingGet(id t.TenantID, uid t.Uid, meetingID string) (*t.Meeting, error) {
|
||||
if !a.tenantScopedDB {
|
||||
tenantAdp, err := a.routedTenantAdapter(id)
|
||||
|
||||
Reference in New Issue
Block a user