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
|
||||
|
||||
+3
-3
@@ -300,7 +300,7 @@ func cacheControlHandler(maxAge int, handler http.Handler) http.Handler {
|
||||
// Get API key from an HTTP request.
|
||||
func getAPIKey(req *http.Request) string {
|
||||
// Check header.
|
||||
apikey := req.Header.Get("X-Tinode-APIKey")
|
||||
apikey := req.Header.Get("X-MeetingIM-APIKey")
|
||||
if apikey != "" {
|
||||
return apikey
|
||||
}
|
||||
@@ -328,8 +328,8 @@ func getAPIKey(req *http.Request) string {
|
||||
// Extracts authorization credentials from an HTTP request.
|
||||
// Returns authentication method and secret.
|
||||
func getHttpAuth(req *http.Request) (method, secret string) {
|
||||
// Check X-Tinode-Auth header.
|
||||
if parts := strings.Split(req.Header.Get("X-Tinode-Auth"), " "); len(parts) == 2 {
|
||||
// Check MeetingIM product auth header.
|
||||
if parts := strings.Split(req.Header.Get("X-MeetingIM-Auth"), " "); len(parts) == 2 {
|
||||
method, secret = parts[0], parts[1]
|
||||
return
|
||||
}
|
||||
|
||||
+18
-18
@@ -110,7 +110,7 @@ func withIMHTTPProtocol(prefix string, next http.Handler) http.Handler {
|
||||
wrt.Header().Set("X-Request-ID", requestID)
|
||||
wrt.Header().Set("X-IM-Schema", "1")
|
||||
|
||||
if valid, _ := checkAPIKey(strings.TrimSpace(req.Header.Get("X-Tinode-APIKey"))); !valid {
|
||||
if valid, _ := checkAPIKey(strings.TrimSpace(getAPIKey(req))); !valid {
|
||||
writeIMError(wrt, req, http.StatusForbidden, 30003, imReasonAPIKeyRequired, "valid API key required", nil)
|
||||
return
|
||||
}
|
||||
@@ -7283,24 +7283,24 @@ func meetingPayloadWithUserSummaries(meeting *types.Meeting, viewer types.Uid, u
|
||||
startAt := meeting.StartAt.UTC()
|
||||
endAt := meeting.EndAt.UTC()
|
||||
data := map[string]any{
|
||||
"meeting_id": meeting.MeetingID,
|
||||
"title": meeting.Title,
|
||||
"organizer_id": meeting.OrganizerID.UserId(),
|
||||
"organizer": organizer,
|
||||
"start_at": startAt.Format(time.RFC3339Nano),
|
||||
"end_at": endAt.Format(time.RFC3339Nano),
|
||||
"meeting_time": meetingTimeRange(startAt, endAt),
|
||||
"time_range": meetingTimeRange(startAt, endAt),
|
||||
"location": meeting.Location,
|
||||
"state": meeting.State,
|
||||
"reminder_minutes": meeting.ReminderMinutes,
|
||||
"version": meeting.Version,
|
||||
"participants": participants,
|
||||
"meeting_id": meeting.MeetingID,
|
||||
"title": meeting.Title,
|
||||
"organizer_id": meeting.OrganizerID.UserId(),
|
||||
"organizer": organizer,
|
||||
"start_at": startAt.Format(time.RFC3339Nano),
|
||||
"end_at": endAt.Format(time.RFC3339Nano),
|
||||
"meeting_time": meetingTimeRange(startAt, endAt),
|
||||
"time_range": meetingTimeRange(startAt, endAt),
|
||||
"location": meeting.Location,
|
||||
"state": meeting.State,
|
||||
"reminder_minutes": meeting.ReminderMinutes,
|
||||
"version": meeting.Version,
|
||||
"participants": participants,
|
||||
"participant_count": len(participants),
|
||||
"participant_names": meetingParticipantNames(participants),
|
||||
"permissions": meetingPermissionsPayload(meeting, viewer),
|
||||
"created_at": meeting.CreatedAt.UTC().Format(time.RFC3339Nano),
|
||||
"updated_at": meeting.UpdatedAt.UTC().Format(time.RFC3339Nano),
|
||||
"permissions": meetingPermissionsPayload(meeting, viewer),
|
||||
"created_at": meeting.CreatedAt.UTC().Format(time.RFC3339Nano),
|
||||
"updated_at": meeting.UpdatedAt.UTC().Format(time.RFC3339Nano),
|
||||
}
|
||||
if displayName, ok := organizer["display_name"]; ok {
|
||||
data["organizer_display_name"] = displayName
|
||||
@@ -7681,7 +7681,7 @@ func imCanViewLastSeen(tenantID types.TenantID, viewer, target types.Uid) bool {
|
||||
if viewer == target {
|
||||
return true
|
||||
}
|
||||
// Legacy deployments without the product privacy stores retain Tinode's
|
||||
// Legacy deployments without the product privacy stores retain MeetingIM's
|
||||
// original presence behavior.
|
||||
if store.Profiles == nil || store.Relations == nil {
|
||||
return true
|
||||
|
||||
+41
-18
@@ -1226,7 +1226,7 @@ func TestIMHTTPProtocolHeaders(t *testing.T) {
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodGet, "/v1/im/bootstrap", nil)
|
||||
req.Header.Set("X-Tinode-APIKey", apiKey)
|
||||
req.Header.Set("X-MeetingIM-APIKey", apiKey)
|
||||
rec = httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusBadRequest || decodeIMHTTPResponse(t, rec).Reason != imReasonSchemaUnsupported {
|
||||
@@ -1234,7 +1234,7 @@ func TestIMHTTPProtocolHeaders(t *testing.T) {
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodGet, "/v1/im/bootstrap", nil)
|
||||
req.Header.Set("X-Tinode-APIKey", apiKey)
|
||||
req.Header.Set("X-MeetingIM-APIKey", apiKey)
|
||||
req.Header.Set("X-IM-Schema", "1")
|
||||
req.Header.Set("X-Request-ID", "request-1")
|
||||
rec = httptest.NewRecorder()
|
||||
@@ -1242,6 +1242,29 @@ func TestIMHTTPProtocolHeaders(t *testing.T) {
|
||||
if rec.Code != http.StatusOK || called != 1 || rec.Header().Get("X-Request-ID") != "request-1" {
|
||||
t.Fatalf("valid protocol request: status=%d called=%d request_id=%q", rec.Code, called, rec.Header().Get("X-Request-ID"))
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodGet, "/v1/im/bootstrap", nil)
|
||||
req.Header.Set("X-MeetingIM-APIKey", apiKey)
|
||||
req.Header.Set("X-IM-Schema", "1")
|
||||
rec = httptest.NewRecorder()
|
||||
handler.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK || called != 2 {
|
||||
t.Fatalf("legacy protocol request: status=%d called=%d", rec.Code, called)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeetingIMHTTPAuthHeaders(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/v0/file/s/file1", nil)
|
||||
req.Header.Set("X-MeetingIM-APIKey", "new-api-key")
|
||||
req.Header.Set("X-MeetingIM-Auth", "token new-token")
|
||||
|
||||
if got := getAPIKey(req); got != "new-api-key" {
|
||||
t.Fatalf("getAPIKey new header=%q", got)
|
||||
}
|
||||
method, secret := getHttpAuth(req)
|
||||
if method != "token" || secret != "new-token" {
|
||||
t.Fatalf("getHttpAuth new header method=%q secret=%q", method, secret)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIMHTTPIdempotencyReplayAndConflict(t *testing.T) {
|
||||
@@ -1263,7 +1286,7 @@ func TestIMHTTPIdempotencyReplayAndConflict(t *testing.T) {
|
||||
request := func(body string) *httptest.ResponseRecorder {
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/test", strings.NewReader(body))
|
||||
req.Header.Set("Authorization", "Bearer token-1")
|
||||
req.Header.Set("X-Tinode-APIKey", apiKey)
|
||||
req.Header.Set("X-MeetingIM-APIKey", apiKey)
|
||||
req.Header.Set("X-IM-Schema", "1")
|
||||
req.Header.Set("Idempotency-Key", "same-key")
|
||||
rec := httptest.NewRecorder()
|
||||
@@ -1298,7 +1321,7 @@ func TestServeIMTenantResolveSuccessFromHeader(t *testing.T) {
|
||||
globals.servingAt = "https://im.example.com/"
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/tenants/resolve", nil)
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", " acme ")
|
||||
req.Header.Set("X-Request-ID", "req-1")
|
||||
rec := httptest.NewRecorder()
|
||||
@@ -1331,7 +1354,7 @@ func TestIMHTTPProtocolDoesNotRequireIdempotencyForTenantResolve(t *testing.T) {
|
||||
writeIMSuccess(w, req, map[string]any{"ok": true})
|
||||
})
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/tenants/resolve", nil)
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-IM-Schema", "1")
|
||||
req.Header.Set("Authorization", "Bearer test-token")
|
||||
rec := httptest.NewRecorder()
|
||||
@@ -1362,7 +1385,7 @@ func TestServeIMTenantResolveUsesForwardedPublicHost(t *testing.T) {
|
||||
req.Host = "internal:6060"
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
req.Header.Set("X-Forwarded-Host", "api.118im.com")
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", "acme")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -1393,7 +1416,7 @@ func TestServeIMTenantResolveSuccessFromBody(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/tenants/resolve",
|
||||
bytes.NewBufferString(`{"tenant_code":"acme","platform":"android","app_version":"1.0.0"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMTenantResolve(rec, req)
|
||||
@@ -1441,7 +1464,7 @@ func TestServeIMTenantResolveRequiresAPIKey(t *testing.T) {
|
||||
func TestServeIMTenantResolveRejectsMalformedTenantCode(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/tenants/resolve",
|
||||
bytes.NewBufferString(`{"tenant_code":"`+strings.Repeat("a", 65)+`"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMTenantResolve(rec, req)
|
||||
@@ -1467,7 +1490,7 @@ func TestServeIMTenantResolveHidesUnavailableTenantState(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/tenants/resolve",
|
||||
bytes.NewBufferString(`{"tenant_code":"acme"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMTenantResolve(rec, req)
|
||||
@@ -1488,7 +1511,7 @@ func TestServeIMTenantResolveHidesMissingTenant(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/tenants/resolve",
|
||||
bytes.NewBufferString(`{"tenant_code":"acme"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMTenantResolve(rec, req)
|
||||
@@ -1639,7 +1662,7 @@ func TestServeIMVerificationCodesCreatesChallenge(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/verification-codes",
|
||||
bytes.NewBufferString(`{"scene":"register","phone":"+8613800138000"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", "acme")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -1689,7 +1712,7 @@ func TestServeIMVerificationCodesSkipsDeliveryWhenRegistrationSMSDisabled(t *tes
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/verification-codes",
|
||||
bytes.NewBufferString(`{"scene":"register","phone":"+8613800138000"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", "acme")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -1729,7 +1752,7 @@ func TestServeIMPasswordResetCodeFindsPhoneWithoutTelCredential(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/verification-codes",
|
||||
bytes.NewBufferString(`{"scene":"reset_password","phone":"13800138000"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", "acme")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -1768,7 +1791,7 @@ func TestServeIMVerificationCodesKeepsChallengeWhenDeliveryUnavailable(t *testin
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/verification-codes",
|
||||
bytes.NewBufferString(`{"scene":"reset_password","phone":"+8613800138000"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", "acme")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -1786,7 +1809,7 @@ func TestServeIMVerificationCodesKeepsChallengeWhenDeliveryUnavailable(t *testin
|
||||
func TestServeIMVerificationCodesRequiresTenantCode(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/verification-codes",
|
||||
bytes.NewBufferString(`{"scene":"register","phone":"+8613800138000"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
serveIMVerificationCodes(rec, req)
|
||||
@@ -1825,7 +1848,7 @@ func TestServeIMVerificationCodesRateLimited(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/verification-codes",
|
||||
bytes.NewBufferString(`{"scene":"register","phone":"+8613800138000"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", "acme")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -1868,7 +1891,7 @@ func TestServeIMVerificationCodeVerifySuccess(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/verification-codes/challenge-1/verify",
|
||||
bytes.NewBufferString(`{"code":"123456"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", "acme")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -1913,7 +1936,7 @@ func TestServeIMVerificationCodeVerifyInvalidCode(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/verification-codes/challenge-1/verify",
|
||||
bytes.NewBufferString(`{"code":"654321"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", "acme")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
|
||||
+1
-1
@@ -341,7 +341,7 @@ func main() {
|
||||
|
||||
logFlags := flag.String("log_flags", "stdFlags",
|
||||
"Comma-separated list of log flags (as defined in https://golang.org/pkg/log/#pkg-constants without the L prefix)")
|
||||
configfile := flag.String("config", "tinode.conf", "Path to config file.")
|
||||
configfile := flag.String("config", "meetingim.conf", "Path to config file.")
|
||||
// Path to static content.
|
||||
staticPath := flag.String("static_data", defaultStaticPath, "File path to directory with static files to be served.")
|
||||
listenOn := flag.String("listen", "", "Override address and port to listen on for HTTP(S) clients.")
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
{
|
||||
"listen": ":6060",
|
||||
"api_path": "/",
|
||||
"cache_control": 39600,
|
||||
"ws_compression_disabled": false,
|
||||
"static_mount": "/",
|
||||
"static_data": "static",
|
||||
"grpc_listen": ":16060",
|
||||
"grpc_keepalive_enabled": true,
|
||||
|
||||
"api_key_salt": "T713/rYYgW7g4m3vG6zGRh7+FM1t0T8j13koXScOAj4=",
|
||||
"max_message_size": 131072,
|
||||
"max_subscriber_count": 128,
|
||||
"max_tag_count": 16,
|
||||
"permanent_accounts": false,
|
||||
"expvar": "/debug/vars",
|
||||
"server_status": "-",
|
||||
"use_x_forwarded_for": true,
|
||||
"x_frame_options": "SAMEORIGIN",
|
||||
"default_country_code": "CN",
|
||||
"p2p_delete_enabled": true,
|
||||
"msg_delete_age": 600,
|
||||
"alias_tag": "alias",
|
||||
|
||||
"media": {
|
||||
"use_handler": "fs",
|
||||
"max_size": 104857600,
|
||||
"gc_period": 3600,
|
||||
"gc_block_size": 100,
|
||||
"handlers": {
|
||||
"fs": {
|
||||
"upload_dir": "uploads",
|
||||
"cache_control": "max-age=86400",
|
||||
"cors_origins": ["*"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"webrtc": {
|
||||
"enabled": true,
|
||||
"call_establishment_timeout": 30,
|
||||
"ice_servers": [
|
||||
{
|
||||
"urls": ["stun:stun.l.google.com:19302"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"tls": {
|
||||
"enabled": false,
|
||||
"http_redirect": "",
|
||||
"strict_max_age": 0
|
||||
},
|
||||
|
||||
"auth_config": {
|
||||
"logical_names": [],
|
||||
"basic": {
|
||||
"add_to_tags": false,
|
||||
"min_login_length": 1,
|
||||
"min_password_length": 6
|
||||
},
|
||||
"token": {
|
||||
"expire_in": 1209600,
|
||||
"serial_num": 1,
|
||||
"key": "wfaY2RgF2S1OQI/ZlK+LSrp1KB2jwAdGAIHQ7JZn+Kc="
|
||||
},
|
||||
"code": {
|
||||
"expire_in": 900,
|
||||
"max_retries": 3,
|
||||
"code_length": 6
|
||||
}
|
||||
},
|
||||
|
||||
"store_config": {
|
||||
"uid_key": "la6YsO+bNX/+XIkOqc5Svw==",
|
||||
"max_results": 1024,
|
||||
"use_adapter": "mysql",
|
||||
"adapters": {
|
||||
"mysql": {
|
||||
"dsn": "mysql://root:oWxz*2=sb),ouW%p@tcp(64.118.137.182:53306)/tinode?parseTime=true&collation=utf8mb4_0900_ai_ci",
|
||||
"tenant_registry_db": "hotgo",
|
||||
"tenant_db_routing": true,
|
||||
"max_open_conns": 64,
|
||||
"max_idle_conns": 64,
|
||||
"conn_max_lifetime": 60,
|
||||
"sql_timeout": 10
|
||||
}
|
||||
},
|
||||
"redis": {
|
||||
"addr": "64.118.137.182:56379",
|
||||
"username": "",
|
||||
"password": "HMTJzkm.h>Y+E9ep",
|
||||
"db": 0,
|
||||
"key_prefix": "im",
|
||||
"dial_timeout_ms": 2000,
|
||||
"read_timeout_ms": 2000,
|
||||
"write_timeout_ms": 2000,
|
||||
"pending_ttl_seconds": 120,
|
||||
"done_ttl_seconds": 86400,
|
||||
"failed_ttl_seconds": 600,
|
||||
"hot_message_ttl_seconds": 86400,
|
||||
"presence_ttl_seconds": 120,
|
||||
"cache_ttl_seconds": 300,
|
||||
"short_state_ttl_seconds": 600,
|
||||
"group_fanout_enabled": true
|
||||
}
|
||||
},
|
||||
|
||||
"kafka": {
|
||||
"enabled": true,
|
||||
"brokers": ["64.118.137.182:9094"],
|
||||
"client_id": "im-server",
|
||||
"consumer_group": "im-server",
|
||||
"dial_timeout_ms": 3000,
|
||||
"write_timeout_ms": 3000,
|
||||
"read_timeout_ms": 3000,
|
||||
"required_acks": 1,
|
||||
"allow_auto_topic_creation": false,
|
||||
"tls_enabled": false,
|
||||
"sasl_enabled": true,
|
||||
"sasl_mechanism": "SCRAM-SHA-512",
|
||||
"sasl_username": "kafka-external",
|
||||
"sasl_password": "gr58tNnlZOVGW3AmMbL2",
|
||||
"max_retry_count": 5,
|
||||
"retry_backoff_ms": [10000, 30000, 60000, 300000, 900000],
|
||||
"dead_letter_retention_seconds": 7776000,
|
||||
"topics": {
|
||||
"admin_command": "im.admin.command",
|
||||
"admin_command_result": "im.admin.command.result",
|
||||
"config_event": "im.config.event",
|
||||
"business_event": "im.business.event",
|
||||
"message_persist": "im.message.persist",
|
||||
"message_push": "im.message.push",
|
||||
"file_security": "im.file.security",
|
||||
"file_lifecycle": "im.file.lifecycle",
|
||||
"workbench_event": "im.workbench.event",
|
||||
"cache_invalidate": "im.cache.invalidate",
|
||||
"audit_event": "im.audit.event",
|
||||
"dead_letter": "im.dead.letter"
|
||||
}
|
||||
},
|
||||
"internal_api": {
|
||||
"enabled": true,
|
||||
"time_window_seconds": 300,
|
||||
"services": {
|
||||
"hotgo-admin": {
|
||||
"secret": "8569f5626a54950539eece9f731c9e3bf073daa74af8d23735d19afacac40f4b",
|
||||
"allow": ["admin_query"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"admin_internal_api": {
|
||||
"enabled": true,
|
||||
"base_url": "http://im-admin:8000",
|
||||
"service_name": "im-server",
|
||||
"secret": "b96832c04dbfb6dd768930ec9f082ecd90d87a1ffdaa493aa070617c48de9097",
|
||||
"timeout_ms": 3000
|
||||
},
|
||||
|
||||
"acc_validation": {
|
||||
"email": {
|
||||
"add_to_tags": false,
|
||||
"required": [],
|
||||
"config": {
|
||||
"host_url": "http://64.118.137.182:6060/",
|
||||
"sender": "MeetingIM <noreply@118im.com>",
|
||||
"validation_templ": "server/templ/email-validation-en.templ",
|
||||
"reset_secret_templ": "server/templ/email-password-reset-en.templ",
|
||||
"debug_response": "123456",
|
||||
"max_retries": 3
|
||||
}
|
||||
},
|
||||
"tel": {
|
||||
"add_to_tags": false,
|
||||
"required": [],
|
||||
"config": {
|
||||
"host_url": "http://64.118.137.182:6060/",
|
||||
"sender": "MeetingIM",
|
||||
"universal_templ": "server/templ/sms-universal-en.templ",
|
||||
"debug_response": "123456",
|
||||
"max_retries": 3
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"acc_gc_config": {
|
||||
"enabled": true,
|
||||
"gc_period": 3600,
|
||||
"gc_block_size": 10,
|
||||
"gc_min_account_age": 30
|
||||
},
|
||||
|
||||
"push": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"config": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fcm",
|
||||
"config": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tnpg",
|
||||
"config": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
"cluster_config": {
|
||||
"self": "",
|
||||
"nodes": [],
|
||||
"failover": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
|
||||
"plugins": []
|
||||
}
|
||||
@@ -9,7 +9,7 @@ HTTP_BASE_PORT=6060
|
||||
# Port where the first node will listen for gRPC intra-cluster connections.
|
||||
GRPC_BASE_PORT=16060
|
||||
|
||||
USAGE="Usage: $0 [ --config <path_to_tinode.conf> ] {start|stop}"
|
||||
USAGE="Usage: $0 [ --config <path_to_meetingim.conf> ] {start|stop}"
|
||||
|
||||
# Your server binary may have a different name and location.
|
||||
SERVER='./server'
|
||||
@@ -34,9 +34,9 @@ while [[ $# -gt 0 ]]; do
|
||||
;;
|
||||
start)
|
||||
if [ ! -z "$config" ] ; then
|
||||
TINODE_CONF=$config
|
||||
MEETINGIM_CONF=$config
|
||||
else
|
||||
TINODE_CONF="tinode.conf"
|
||||
MEETINGIM_CONF="meetingim.conf"
|
||||
fi
|
||||
if [ ! -z "${static_data+x}" ] ; then
|
||||
STATIC_DATA_DIR=$static_data
|
||||
@@ -51,10 +51,10 @@ while [[ $# -gt 0 ]]; do
|
||||
for NODE_NAME in "${ALL_NODE_NAMES[@]}"
|
||||
do
|
||||
# Start the node
|
||||
$SERVER -config=${TINODE_CONF} -cluster_self=${NODE_NAME} -listen=:${HTTP_PORT} -grpc_listen=:${GRPC_PORT} -static_data=${STATIC_DATA_DIR} -log_flags=stdFlags,shortfile &
|
||||
$SERVER -config=${MEETINGIM_CONF} -cluster_self=${NODE_NAME} -listen=:${HTTP_PORT} -grpc_listen=:${GRPC_PORT} -static_data=${STATIC_DATA_DIR} -log_flags=stdFlags,shortfile &
|
||||
# Save PID of the node to a temp file.
|
||||
# /var/tmp/ does not requre root access.
|
||||
echo $!> "/var/tmp/tinode-${NODE_NAME}.pid"
|
||||
echo $!> "/var/tmp/meetingim-${NODE_NAME}.pid"
|
||||
# Increment ports for the next node.
|
||||
HTTP_PORT=$((HTTP_PORT+1))
|
||||
GRPC_PORT=$((GRPC_PORT+1))
|
||||
@@ -67,9 +67,9 @@ while [[ $# -gt 0 ]]; do
|
||||
for NODE_NAME in "${ALL_NODE_NAMES[@]}"
|
||||
do
|
||||
# Read PIDs of running nodes from temp files and kill them.
|
||||
kill `cat /var/tmp/tinode-${NODE_NAME}.pid`
|
||||
kill `cat /var/tmp/meetingim-${NODE_NAME}.pid`
|
||||
# Clean up: delete temp files.
|
||||
rm "/var/tmp/tinode-${NODE_NAME}.pid"
|
||||
rm "/var/tmp/meetingim-${NODE_NAME}.pid"
|
||||
done
|
||||
exit 0
|
||||
;;
|
||||
|
||||
+12
-12
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
BINARY_PATH=$GOPATH/bin
|
||||
TINODE_BINARY=$BINARY_PATH/server
|
||||
MEETINGIM_BINARY=$BINARY_PATH/server
|
||||
|
||||
# Kills and removes any running containers.
|
||||
cleanup() {
|
||||
@@ -45,23 +45,23 @@ setup() {
|
||||
echo -n "."
|
||||
sleep 1
|
||||
done
|
||||
# Make sure there's no Tinode server binary in the current directory.
|
||||
# Make sure there's no MeetingIM server binary in the current directory.
|
||||
if [ -f "./server" ]; then
|
||||
rm ./server
|
||||
fi
|
||||
}
|
||||
|
||||
# Compiles Tinode binaries.
|
||||
# Compiles MeetingIM binaries.
|
||||
build() {
|
||||
go install -tags mysql -ldflags "-X main.buildstamp=`date -u '+%Y%m%dT%H:%M:%SZ'`" \
|
||||
github.com/tinode/chat/tinode-db \
|
||||
github.com/tinode/chat/server && \
|
||||
ln -s $TINODE_BINARY
|
||||
ln -s $MEETINGIM_BINARY
|
||||
}
|
||||
|
||||
# Initializes Tinode database.
|
||||
# Initializes MeetingIM database.
|
||||
init-db() {
|
||||
$GOPATH/bin/tinode-db -config=./tinode.conf -data=../tinode-db/data.json
|
||||
$GOPATH/bin/tinode-db -config=./meetingim.conf -data=../tinode-db/data.json
|
||||
}
|
||||
|
||||
wait-for() {
|
||||
@@ -71,7 +71,7 @@ wait-for() {
|
||||
done
|
||||
}
|
||||
|
||||
# Brings up a three-node Tinode cluster.
|
||||
# Brings up a three-node MeetingIM cluster.
|
||||
run-server() {
|
||||
./run-cluster.sh -s "" start && wait-for 16060
|
||||
}
|
||||
@@ -80,7 +80,7 @@ send-requests() {
|
||||
local expect=12
|
||||
local port=$1
|
||||
local id=$2
|
||||
local outfile=$(mktemp /tmp/tinode-${id}.txt)
|
||||
local outfile=$(mktemp /tmp/meetingim-${id}.txt)
|
||||
pushd .
|
||||
cd ../tn-cli
|
||||
python3 tn-cli.py --host=localhost:${port} --no-login < sample-script.txt > $outfile || fail "Test script failed (instance port ${port})"
|
||||
@@ -103,14 +103,14 @@ trap 'cleanup ; fail "For Unexpected Reasons"'\
|
||||
|
||||
run_id=`date +%s`
|
||||
echo "+----------------------------------------------------+"
|
||||
echo "| Tinode sanity test. |"
|
||||
echo "| MeetingIM sanity test. |"
|
||||
echo "+----------------------------------------------------+"
|
||||
echo "Timestamp = ${run_id}"
|
||||
|
||||
setup || fail "Test setup failed."
|
||||
build || fail "Could not build Tinode binaries"
|
||||
init-db || fail "Could not initialize Tinode database"
|
||||
run-server || fail "Could not start tinode"
|
||||
build || fail "Could not build MeetingIM binaries"
|
||||
init-db || fail "Could not initialize MeetingIM database"
|
||||
run-server || fail "Could not start meetingim"
|
||||
|
||||
# Test requests.
|
||||
send-requests 16060 $run_id
|
||||
|
||||
@@ -60,7 +60,7 @@ func openAdapter(workerId int, jsonconf json.RawMessage) error {
|
||||
adp = v
|
||||
}
|
||||
} else {
|
||||
return errors.New("store: db adapter is not specified. Please set `store_config.use_adapter` in `tinode.conf`")
|
||||
return errors.New("store: db adapter is not specified. Please set `store_config.use_adapter` in `meetingim.conf`")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
{{define "subject" -}}
|
||||
Reset Tinode password
|
||||
Reset MeetingIM password
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -17,7 +17,7 @@ Reset Tinode password
|
||||
|
||||
<p>Hello.</p>
|
||||
|
||||
<p>You recently requested to reset the password for your <a href="{{.HostUrl}}">Tinode</a> account.
|
||||
<p>You recently requested to reset the password for your <a href="{{.HostUrl}}">MeetingIM</a> account.
|
||||
Use the link or code below to reset it. The link and code are valid for the next 24 hours only.</p>
|
||||
|
||||
<blockquote><a href="{{.HostUrl}}#reset?scheme={{.Scheme}}&code={{.Code}}&cred={{.Cred}}">Click</a> to reset your password.</blockquote>
|
||||
@@ -36,7 +36,7 @@ Use the link or code below to reset it. The link and code are valid for the next
|
||||
|
||||
<p>If you did not request a password reset, please ignore this message.</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Tinode Team</a></p>
|
||||
<p><a href="https://118im.com/">MeetingIM Team</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -46,7 +46,7 @@ Use the link or code below to reset it. The link and code are valid for the next
|
||||
|
||||
Hello.
|
||||
|
||||
You recently requested to reset the password for your Tinode account ({{.HostUrl}}).
|
||||
You recently requested to reset the password for your MeetingIM account ({{.HostUrl}}).
|
||||
Use the link or code below to reset it. The link and code are valid for the next 24 hours only.
|
||||
|
||||
{{.HostUrl}}#reset?scheme={{.Scheme}}&code={{.Code}}&cred={{.Cred}}
|
||||
@@ -62,7 +62,7 @@ In case you have forgotten, here is your login: {{.}}.
|
||||
|
||||
If you did not request a password reset, please ignore this message.
|
||||
|
||||
Tinode Team
|
||||
https://tinode.co/
|
||||
MeetingIM Team
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
{{define "subject" -}}
|
||||
Reestablecer contraseña de Tinode
|
||||
Reestablecer contraseña de MeetingIM
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -17,7 +17,7 @@ Reestablecer contraseña de Tinode
|
||||
|
||||
<p>Hola.</p>
|
||||
|
||||
<p>Recientemente solicitaste reestablecer la contraseña para tu cuenta <a href="{{.HostUrl}}">Tinode</a>.
|
||||
<p>Recientemente solicitaste reestablecer la contraseña para tu cuenta <a href="{{.HostUrl}}">MeetingIM</a>.
|
||||
Usa el enlace de abajo para reestablecerla. El enlace es válido solamente por las siguientes 24 horas.</p>
|
||||
|
||||
<blockquote><a href="{{.HostUrl}}#reset?scheme={{.Scheme}}&token={{.Token}}">Clic</a> para reestablecer contraseña.</blockquote>
|
||||
@@ -36,7 +36,7 @@ Usa el enlace de abajo para reestablecerla. El enlace es válido solamente por l
|
||||
|
||||
<p>Si no solicitaste el reestablecimiento de tu contrseña, por favor ignora este mensaje.</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Equipo de Tinode</a></p>
|
||||
<p><a href="https://118im.com/">Equipo de MeetingIM</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -46,7 +46,7 @@ Usa el enlace de abajo para reestablecerla. El enlace es válido solamente por l
|
||||
|
||||
Hola.
|
||||
|
||||
Recientemente solicitaste reestablecer la contraseña para tu cuenta Tinode ({{.HostUrl}}).
|
||||
Recientemente solicitaste reestablecer la contraseña para tu cuenta MeetingIM ({{.HostUrl}}).
|
||||
Usa el enlace de abajo para reestablecerla. El enlace es válido solamente por las siguientes 24 horas.
|
||||
|
||||
{{.HostUrl}}#reset?scheme={{.Scheme}}&token={{.Token}}
|
||||
@@ -62,7 +62,7 @@ En caso de que lo hayas olvidado, tu usuario es: {{.}}.
|
||||
|
||||
Si no solicitaste el reestablecimiento de tu contrseña, por favor ignora este mensaje.
|
||||
|
||||
Equipo de Tinode
|
||||
https://tinode.co/
|
||||
Equipo de MeetingIM
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
{{define "subject" -}}
|
||||
Réinitialiser votre mot de passe Tinode
|
||||
Réinitialiser votre mot de passe MeetingIM
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -17,7 +17,7 @@ Réinitialiser votre mot de passe Tinode
|
||||
|
||||
<p>Bonjour.</p>
|
||||
|
||||
<p>Vous avez récemment demandé à réinitialiser le mot de passe de votre compte <a href="{{.HostUrl}}">Tinode</a>.
|
||||
<p>Vous avez récemment demandé à réinitialiser le mot de passe de votre compte <a href="{{.HostUrl}}">MeetingIM</a>.
|
||||
Utilisez le lien ou le code ci-dessous pour le réinitialiser. Le lien et le code sont valides pour les prochaines 24 heures seulement.</p>
|
||||
|
||||
<blockquote><a href="{{.HostUrl}}#reset?scheme={{.Scheme}}&token={{.Token}}">Cliquer ici</a> pour réinitialiser votre mot de passe.</blockquote>
|
||||
@@ -36,7 +36,7 @@ Utilisez le lien ou le code ci-dessous pour le réinitialiser. Le lien et le cod
|
||||
|
||||
<p>Si vous n'avez pas demandé la réinitialisation de votre mot de passe, veuillez ignorer ce message.</p>
|
||||
|
||||
<p><a href="https://tinode.co/">L'équipe Tinode</a></p>
|
||||
<p><a href="https://118im.com/">L'équipe MeetingIM</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -62,7 +62,7 @@ Au cas où vous l'auriez oublié, voici votre login : {{.}}.
|
||||
|
||||
Si vous n'avez pas demandé la réinitialisation de votre mot de passe, veuillez ignorer ce message.
|
||||
|
||||
L'équipe Tinode
|
||||
https://tinode.co/
|
||||
L'équipe MeetingIM
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
{{define "subject" -}}
|
||||
Redefinir senha Tinode
|
||||
Redefinir senha MeetingIM
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -17,7 +17,7 @@ Redefinir senha Tinode
|
||||
|
||||
<p>Olá.</p>
|
||||
|
||||
<p>Você solicitou recentemente a redefinição da sua senha <a href="{{.HostUrl}}">Tinode</a>.
|
||||
<p>Você solicitou recentemente a redefinição da sua senha <a href="{{.HostUrl}}">MeetingIM</a>.
|
||||
Use o link ou código abaixo para redefini-lo. O link e o código são válidos apenas pelas próximas 24 horas.</p>
|
||||
|
||||
<blockquote><a href="{{.HostUrl}}#reset?scheme={{.Scheme}}&token={{.Token}}">Clic</a> para redefinir sua senha.</blockquote>
|
||||
@@ -36,7 +36,7 @@ Use o link ou código abaixo para redefini-lo. O link e o código são válidos
|
||||
|
||||
<p>Se não solicitaste a redefinição da senha, por favor ignorar essa mensagem.</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Equipe Tinode</a></p>
|
||||
<p><a href="https://118im.com/">Equipe MeetingIM</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -46,7 +46,7 @@ Use o link ou código abaixo para redefini-lo. O link e o código são válidos
|
||||
|
||||
Olá.
|
||||
|
||||
Você solicitou recentemente a redefinição da sua senha Tinode ({{.HostUrl}}).
|
||||
Você solicitou recentemente a redefinição da sua senha MeetingIM ({{.HostUrl}}).
|
||||
Use o link ou código abaixo para redefini-lo. O link e o código são válidos apenas pelas próximas 24 horas.
|
||||
|
||||
{{.HostUrl}}#reset?scheme={{.Scheme}}&token={{.Token}}
|
||||
@@ -62,7 +62,7 @@ Em caso de esquecimento, aqui seu login: {{.}}.
|
||||
|
||||
Se não solicitaste a redefinição da senha, por favor ignorar essa mensagem..
|
||||
|
||||
Equipe Tinode
|
||||
https://tinode.co/
|
||||
Equipe MeetingIM
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
{{define "subject" -}}
|
||||
Изменить пароль Tinode
|
||||
Изменить пароль MeetingIM
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<p>Здравствуйте.</p>
|
||||
|
||||
<p>Вы прислали запрос на изменение пароля для вашего аккаунта <a href="{{.HostUrl}}">Tinode</a>.
|
||||
<p>Вы прислали запрос на изменение пароля для вашего аккаунта <a href="{{.HostUrl}}">MeetingIM</a>.
|
||||
Используйте ссылку или код ниже, чтобы сбросить его. Ссылка и код действительны только в течение следующих 24 часов.</p>
|
||||
|
||||
<blockquote><a href="{{.HostUrl}}#reset?scheme={{.Scheme}}&token={{.Token}}&hl=RU">Кликните</a> для изменения пароля.</blockquote>
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<p>Если вы не отправляли запрос на изменение пароля, просто игнорируйте это сообщение.</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Команда Tinode</a></p>
|
||||
<p><a href="https://118im.com/">Команда MeetingIM</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
Здравствуйте.
|
||||
|
||||
Вы прислали запрос на изменение пароля для вашего аккаунта Tinode ({{.HostUrl}}).
|
||||
Вы прислали запрос на изменение пароля для вашего аккаунта MeetingIM ({{.HostUrl}}).
|
||||
Используйте ссылку или код ниже, чтобы сбросить его. Ссылка и код действительны только в течение следующих 24 часов.
|
||||
|
||||
{{.HostUrl}}#reset?scheme={{.Scheme}}&token={{.Token}}&hl=RU
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
Если вы не отправляли запрос на изменение пароля, просто игнорируйте это сообщение.
|
||||
|
||||
Команда Tinode
|
||||
https://tinode.co/
|
||||
Команда MeetingIM
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
{{define "subject" -}}
|
||||
Змінити пароль Tinode
|
||||
Змінити пароль MeetingIM
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<p>Доброго дня.</p>
|
||||
|
||||
<p>Ви надіслали запит на зміну пароля для вашого акаунта <a href="{{.HostUrl}}">Tinode</a>.
|
||||
<p>Ви надіслали запит на зміну пароля для вашого акаунта <a href="{{.HostUrl}}">MeetingIM</a>.
|
||||
Щоб скинути його, скористайтеся посиланням або кодом нижче. Посилання та код дійсні лише протягом наступних 24 годин.</p>
|
||||
|
||||
<blockquote><a href="{{.HostUrl}}#reset?scheme={{.Scheme}}&token={{.Token}}&hl=UK">Натисніть</a> для зміни пароля.</blockquote>
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<p>Якщо ви не надсилали запит на зміну пароля, просто ігноруйте це повідомлення.</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Команда Tinode</a></p>
|
||||
<p><a href="https://118im.com/">Команда MeetingIM</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
Доброго дня.
|
||||
|
||||
Ви надіслали запит на зміну пароля для вашого акаунта Tinode ({{.HostUrl}}).
|
||||
Ви надіслали запит на зміну пароля для вашого акаунта MeetingIM ({{.HostUrl}}).
|
||||
Щоб скинути його, скористайтеся посиланням або кодом нижче. Посилання та код дійсні лише протягом наступних 24 годин.
|
||||
|
||||
{{.HostUrl}}#reset?scheme={{.Scheme}}&token={{.Token}}&hl=UK
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
Якщо ви не надсилали запит на зміну пароля, просто ігноруйте це повідомлення.
|
||||
|
||||
Команда Tinode
|
||||
https://tinode.co/
|
||||
Команда MeetingIM
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
{{define "subject" -}}
|
||||
Đặt lại mật khẩu Tinode
|
||||
Đặt lại mật khẩu MeetingIM
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<p>Xin chào.</p>
|
||||
|
||||
<p>Bạn vừa gửi yêu cầu đặt lại mật khẩu cho tài khoản <a href="{{.HostUrl}}">Tinode</a> của bạn.
|
||||
<p>Bạn vừa gửi yêu cầu đặt lại mật khẩu cho tài khoản <a href="{{.HostUrl}}">MeetingIM</a> của bạn.
|
||||
Sử dụng liên kết hoặc mã dưới đây để thiết lập lại nó. Liên kết và mã chỉ có hiệu lực trong 24 giờ tới.</p>
|
||||
|
||||
<blockquote><a href="{{.HostUrl}}#reset?scheme={{.Scheme}}&token={{.Token}}">Bấm</a> để đặt lại mật khẩu.</blockquote>
|
||||
@@ -36,7 +36,7 @@ Sử dụng liên kết hoặc mã dưới đây để thiết lập lại nó.
|
||||
|
||||
<p>Nếu bạn không yêu cầu cấp lại mật khẩu, Vui lòng bỏ qua tin nhắn này.</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Tinode Team</a></p>
|
||||
<p><a href="https://118im.com/">MeetingIM Team</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -46,7 +46,7 @@ Sử dụng liên kết hoặc mã dưới đây để thiết lập lại nó.
|
||||
|
||||
Xin chào.
|
||||
|
||||
Bạn vừa gửi yêu cầu đặt lại mật khẩu cho tài khoản Tinode ({{.HostUrl}}).
|
||||
Bạn vừa gửi yêu cầu đặt lại mật khẩu cho tài khoản MeetingIM ({{.HostUrl}}).
|
||||
Sử dụng liên kết hoặc mã dưới đây để thiết lập lại nó. Liên kết và mã chỉ có hiệu lực trong 24 giờ tới.
|
||||
|
||||
{{.HostUrl}}#reset?scheme={{.Scheme}}&token={{.Token}}
|
||||
@@ -62,7 +62,7 @@ Trong trường hợp bạn quên, thì đây là tên đăng nhập của bạn
|
||||
|
||||
Nếu bạn không yêu cầu cấp lại mật khẩu, Vui lòng bỏ qua tin nhắn này.
|
||||
|
||||
Tinode Team
|
||||
https://tinode.co/
|
||||
MeetingIM Team
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
{{define "subject" -}}
|
||||
重設 Tinode 密碼
|
||||
重設 MeetingIM 密碼
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<p>您好。</p>
|
||||
|
||||
<p>您最近請求重設您的 <a href="{{.HostUrl}}">Tinode</a> 帳戶密碼。
|
||||
<p>您最近請求重設您的 <a href="{{.HostUrl}}">MeetingIM</a> 帳戶密碼。
|
||||
請使用以下連結或驗證碼進行重設。此連結和驗證碼僅在接下來的 24 小時內有效。</p>
|
||||
|
||||
<blockquote><a href="{{.HostUrl}}#reset?scheme={{.Scheme}}&code={{.Code}}&cred={{.Cred}}">點擊</a>重設您的密碼。</blockquote>
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<p>如果您沒有請求密碼重設,請忽略此訊息。</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Tinode 團隊</a></p>
|
||||
<p><a href="https://118im.com/">MeetingIM 團隊</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
您好。
|
||||
|
||||
您最近請求重設您的 Tinode 帳戶密碼 ({{.HostUrl}})。
|
||||
您最近請求重設您的 MeetingIM 帳戶密碼 ({{.HostUrl}})。
|
||||
請使用以下連結或驗證碼進行重設。此連結和驗證碼僅在接下來的 24 小時內有效。
|
||||
|
||||
{{.HostUrl}}#reset?scheme={{.Scheme}}&code={{.Code}}&cred={{.Cred}}
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
如果您沒有請求密碼重設,請忽略此訊息。
|
||||
|
||||
Tinode 團隊
|
||||
https://tinode.co/
|
||||
MeetingIM 團隊
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
{{define "subject" -}}
|
||||
重置 Tinode 密码
|
||||
重置 MeetingIM 密码
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<p>您好!</p>
|
||||
|
||||
<p>您正在申请重置 <a href="{{.HostUrl}}">Tinode</a> 使用下面的链接或代码重置它。 链接和代码仅在接下来的 24 小时内有效。</p>
|
||||
<p>您正在申请重置 <a href="{{.HostUrl}}">MeetingIM</a> 使用下面的链接或代码重置它。 链接和代码仅在接下来的 24 小时内有效。</p>
|
||||
|
||||
<blockquote><a href="{{.HostUrl}}#reset?scheme={{.Scheme}}&token={{.Token}}">重置密码</a></blockquote>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
<p>如非您没有申请重置密码,请忽略这条消息。</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Tinode 团队</a></p>
|
||||
<p><a href="https://118im.com/">MeetingIM 团队</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
您好!
|
||||
|
||||
您正在申请重置 Tinode ({{.HostUrl}}) 账号密码。
|
||||
您正在申请重置 MeetingIM ({{.HostUrl}}) 账号密码。
|
||||
使用下面的链接或代码重置它。 链接和代码仅在接下来的 24 小时内有效。
|
||||
|
||||
{{.HostUrl}}#reset?scheme={{.Scheme}}&token={{.Token}}
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
如非您没有申请重置密码,请忽略这条消息。
|
||||
|
||||
Tinode 团队
|
||||
https://tinode.co/
|
||||
MeetingIM 团队
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/}}
|
||||
|
||||
{{define "subject" -}}
|
||||
Tinode registration: confirm email
|
||||
MeetingIM registration: confirm email
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -24,7 +24,7 @@ Tinode registration: confirm email
|
||||
<p>Hello.</p>
|
||||
|
||||
<p>You're receiving this message because someone used your email to register at
|
||||
<a href="{{.HostUrl}}">Tinode</a>.</p>
|
||||
<a href="{{.HostUrl}}">MeetingIM</a>.</p>
|
||||
|
||||
<p><a href="{{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}}">Click to confirm</a>
|
||||
or go to
|
||||
@@ -33,9 +33,9 @@ and enter the following code:</p>
|
||||
<blockquote><big>{{.Code}}</big></blockquote>
|
||||
<p>You may need to enter login and password.</p>
|
||||
|
||||
<p>If you did not register at Tinode just ignore this message.</p>
|
||||
<p>If you did not register at MeetingIM just ignore this message.</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Tinode Team</a></p>
|
||||
<p><a href="https://118im.com/">MeetingIM Team</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -45,7 +45,7 @@ and enter the following code:</p>
|
||||
|
||||
Hello.
|
||||
|
||||
You're receiving this message because someone used your email to register at Tinode ({{.HostUrl}}).
|
||||
You're receiving this message because someone used your email to register at MeetingIM ({{.HostUrl}}).
|
||||
|
||||
Click the link {{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}} to confirm or go to {{.HostUrl}}#cred?what=email
|
||||
and enter the following code:
|
||||
@@ -54,9 +54,9 @@ and enter the following code:
|
||||
|
||||
You may need to enter login and password.
|
||||
|
||||
If you did not register at Tinode just ignore this message.
|
||||
If you did not register at MeetingIM just ignore this message.
|
||||
|
||||
Tinode Team
|
||||
https://tinode.co/
|
||||
MeetingIM Team
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/}}
|
||||
|
||||
{{define "subject" -}}
|
||||
Registro Tinode: Correo de confirmación
|
||||
Registro MeetingIM: Correo de confirmación
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -15,7 +15,7 @@ Registro Tinode: Correo de confirmación
|
||||
<p>Hola.</p>
|
||||
|
||||
<p>Estás recibiendo este correo porque alguien uso tu correo para registrarse en
|
||||
<a href="{{.HostUrl}}">Tinode</a>.</p>
|
||||
<a href="{{.HostUrl}}">MeetingIM</a>.</p>
|
||||
|
||||
<p><a href="{{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}}">Clic para confirmar</a>
|
||||
o ve a
|
||||
@@ -24,9 +24,9 @@ e ingresa el siguiente código:</p>
|
||||
<blockquote><big>{{.Code}}</big></blockquote>
|
||||
<p>Necesitas ingresar tu usuario y contraseña.</p>
|
||||
|
||||
<p>Si tú no te registraste en Tinode solo ignora este mensaje.</p>
|
||||
<p>Si tú no te registraste en MeetingIM solo ignora este mensaje.</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Equipo de Tinode</a></p>
|
||||
<p><a href="https://118im.com/">Equipo de MeetingIM</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -36,7 +36,7 @@ e ingresa el siguiente código:</p>
|
||||
|
||||
Hola.
|
||||
|
||||
Estás recibiendo este correo porque alguien uso tu correo para registrarse en Tinode ({{.HostUrl}}).
|
||||
Estás recibiendo este correo porque alguien uso tu correo para registrarse en MeetingIM ({{.HostUrl}}).
|
||||
|
||||
Da clic en el enlace {{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}} para confirmar o ve a {{.HostUrl}}#cred?what=email
|
||||
e ingresa el siguiente código:
|
||||
@@ -45,9 +45,9 @@ e ingresa el siguiente código:
|
||||
|
||||
Necesitas ingresar tu usuario y contraseña.
|
||||
|
||||
Si tú no te registraste en Tinode solo ignora este mensaje.
|
||||
Si tú no te registraste en MeetingIM solo ignora este mensaje.
|
||||
|
||||
Equipo de Tinode
|
||||
https://tinode.co/
|
||||
Equipo de MeetingIM
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/}}
|
||||
|
||||
{{define "subject" -}}
|
||||
Tinode enregistrement : confirmer l'adresse e-mail
|
||||
MeetingIM enregistrement : confirmer l'adresse e-mail
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -15,7 +15,7 @@ Tinode enregistrement : confirmer l'adresse e-mail
|
||||
<p>Bonjour.</p>
|
||||
|
||||
<p>Vous recevez ce message car quelqu'un a utilisé votre adresse électronique pour s'inscrire sur le site
|
||||
<a href="{{.HostUrl}}">Tinode</a>.</p>
|
||||
<a href="{{.HostUrl}}">MeetingIM</a>.</p>
|
||||
|
||||
<p><a href="{{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}}">Cliquer ici pour confimer</a>
|
||||
ou rendez-vous à l'adresse
|
||||
@@ -24,9 +24,9 @@ et entrez le code suivant :</p>
|
||||
<blockquote><big>{{.Code}}</big></blockquote>
|
||||
<p>Vous devrez peut-être entrer un login et un mot de passe.</p>
|
||||
|
||||
<p>Si vous ne vous êtes pas inscrit à Tinode, ignorez ce message.</p>
|
||||
<p>Si vous ne vous êtes pas inscrit à MeetingIM, ignorez ce message.</p>
|
||||
|
||||
<p><a href="https://tinode.co/">L'équipe Tinode</a></p>
|
||||
<p><a href="https://118im.com/">L'équipe MeetingIM</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -36,7 +36,7 @@ et entrez le code suivant :</p>
|
||||
|
||||
Bonjour.
|
||||
|
||||
Vous recevez ce message car quelqu'un a utilisé votre adresse électronique pour s'inscrire sur le site Tinode ({{.HostUrl}}).
|
||||
Vous recevez ce message car quelqu'un a utilisé votre adresse électronique pour s'inscrire sur le site MeetingIM ({{.HostUrl}}).
|
||||
|
||||
Cliquer sur le lien {{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}} pour confimer ou rendez-vous à l'adresse {{.HostUrl}}#cred?what=email
|
||||
et entrez le code suivant :
|
||||
@@ -45,9 +45,9 @@ et entrez le code suivant :
|
||||
|
||||
Vous devrez peut-être entrer un login et un mot de passe.
|
||||
|
||||
Si vous ne vous êtes pas inscrit à Tinode, ignorez ce message.
|
||||
Si vous ne vous êtes pas inscrit à MeetingIM, ignorez ce message.
|
||||
|
||||
L'équipe Tinode
|
||||
https://tinode.co/
|
||||
L'équipe MeetingIM
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/}}
|
||||
|
||||
{{define "subject" -}}
|
||||
Registro Tinode: E-mail de confirmação
|
||||
Registro MeetingIM: E-mail de confirmação
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -15,7 +15,7 @@ Registro Tinode: E-mail de confirmação
|
||||
<p>Olá.</p>
|
||||
|
||||
<p> Você está recebendo esse e-mail porque alguém usou seu e-mail para registrar-se em
|
||||
<a href="{{.HostUrl}}">Tinode</a>.</p>
|
||||
<a href="{{.HostUrl}}">MeetingIM</a>.</p>
|
||||
|
||||
<p><a href="{{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}}">Clique para confirmar</a>
|
||||
ou acesse
|
||||
@@ -24,9 +24,9 @@ e entre com o seguinte código:</p>
|
||||
<blockquote><big>{{.Code}}</big></blockquote>
|
||||
<p>Necessário entrar com login e senha.</p>
|
||||
|
||||
<p>Se você não se registrou em Tinode apenas ignore essa mensagem.</p>
|
||||
<p>Se você não se registrou em MeetingIM apenas ignore essa mensagem.</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Equipe Tinode</a></p>
|
||||
<p><a href="https://118im.com/">Equipe MeetingIM</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -36,7 +36,7 @@ e entre com o seguinte código:</p>
|
||||
|
||||
Olá.
|
||||
|
||||
Você está recebendo este e-mail porque alguém usou seu e-mail para registrar-se em Tinode ({{.HostUrl}}).
|
||||
Você está recebendo este e-mail porque alguém usou seu e-mail para registrar-se em MeetingIM ({{.HostUrl}}).
|
||||
|
||||
Clique no link {{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}} para confirmar {{.HostUrl}}#cred?what=email
|
||||
e entre com o seguinte código:
|
||||
@@ -45,9 +45,9 @@ e entre com o seguinte código:
|
||||
|
||||
Necessário entrar com login e senha.
|
||||
|
||||
Se você não se registrou em Tinode apenas ignore essa mensagem..
|
||||
Se você não se registrou em MeetingIM apenas ignore essa mensagem..
|
||||
|
||||
Equipe Tinode
|
||||
https://tinode.co/
|
||||
Equipe MeetingIM
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/}}
|
||||
|
||||
{{define "subject" -}}
|
||||
Регистрация Tinode: подтвердите емейл
|
||||
Регистрация MeetingIM: подтвердите емейл
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<p>Здравствуйте.</p>
|
||||
|
||||
<p>Вы получили это сообщение потому, что зарегистрировались в <a href="{{.HostUrl}}">Tinode</a>.</p>
|
||||
<p>Вы получили это сообщение потому, что зарегистрировались в <a href="{{.HostUrl}}">MeetingIM</a>.</p>
|
||||
|
||||
<p><a href="{{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}}&hl=RU">Кликните здесь чтобы подтвердить</a>
|
||||
регистрацию или перейдите по сслыке
|
||||
@@ -23,9 +23,9 @@
|
||||
<blockquote><big>{{.Code}}</big></blockquote>
|
||||
<p>Возможно, вам потребуется ввести логин и пароль.</p>
|
||||
|
||||
<p>Если вы не регистрировались в Tinode, просто игнорируйте это сообщение.</p>
|
||||
<p>Если вы не регистрировались в MeetingIM, просто игнорируйте это сообщение.</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Команда Tinode</a></p>
|
||||
<p><a href="https://118im.com/">Команда MeetingIM</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
Здравствуйте.
|
||||
|
||||
Вы получили это сообщение потому, что зарегистрировались в Tinode ({{.HostUrl}}).
|
||||
Вы получили это сообщение потому, что зарегистрировались в MeetingIM ({{.HostUrl}}).
|
||||
|
||||
Кликните на {{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}}&hl=RU чтобы подтвердить
|
||||
регистрацию или перейдите по сслыке {{.HostUrl}}#cred?what=email">{{.HostUrl}}#cred?method=email&hl=RU
|
||||
@@ -45,9 +45,9 @@
|
||||
|
||||
Возможно, вам также потребуется ввести логин и пароль.
|
||||
|
||||
Если вы не регистрировались в Tinode, просто игнорируйте это сообщение.
|
||||
Если вы не регистрировались в MeetingIM, просто игнорируйте это сообщение.
|
||||
|
||||
Команда Tinode
|
||||
https://tinode.co/
|
||||
Команда MeetingIM
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/}}
|
||||
|
||||
{{define "subject" -}}
|
||||
Реєстрація Tinode: підтвердіть емейл
|
||||
Реєстрація MeetingIM: підтвердіть емейл
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<p>Доброго дня.</p>
|
||||
|
||||
<p>Ви отримали це повідомлення тому, що зареєструвалися в <a href="{{.HostUrl}}">Tinode</a>.</p>
|
||||
<p>Ви отримали це повідомлення тому, що зареєструвалися в <a href="{{.HostUrl}}">MeetingIM</a>.</p>
|
||||
|
||||
<p><a href="{{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}}&hl=UK">Натисніть тут, щоб підтвердити</a>
|
||||
реєстрацію або перейдіть за посиланням
|
||||
@@ -23,9 +23,9 @@
|
||||
<blockquote><big>{{.Code}}</big></blockquote>
|
||||
<p>Можливо, вам потрібно буде ввести логін та пароль.</p>
|
||||
|
||||
<p>Якщо ви не реєструвалися в Tinode, просто ігноруйте це повідомлення.</p>
|
||||
<p>Якщо ви не реєструвалися в MeetingIM, просто ігноруйте це повідомлення.</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Команда Tinode</a></p>
|
||||
<p><a href="https://118im.com/">Команда MeetingIM</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
Доброго дня.
|
||||
|
||||
Ви отримали це повідомлення тому, що зареєструвалися в Tinode ({{.HostUrl}}).
|
||||
Ви отримали це повідомлення тому, що зареєструвалися в MeetingIM ({{.HostUrl}}).
|
||||
|
||||
Натисніть на {{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}}&hl=UK щоб підтвердити
|
||||
реєстрацію або перейдіть за посиланням {{.HostUrl}}#cred?what=email">{{.HostUrl}}#cred?method=email&hl=UK
|
||||
@@ -45,9 +45,9 @@
|
||||
|
||||
Можливо, вам потрібно буде ввести логін та пароль.
|
||||
|
||||
Якщо ви не реєструвалися в Tinode, просто ігноруйте це повідомлення.
|
||||
Якщо ви не реєструвалися в MeetingIM, просто ігноруйте це повідомлення.
|
||||
|
||||
Команда Tinode
|
||||
https://tinode.co/
|
||||
Команда MeetingIM
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/}}
|
||||
|
||||
{{define "subject" -}}
|
||||
Xác thực đăng ký tài khoản Tinode
|
||||
Xác thực đăng ký tài khoản MeetingIM
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -15,7 +15,7 @@ Xác thực đăng ký tài khoản Tinode
|
||||
<p>Xin chào.</p>
|
||||
|
||||
<p>Bạn nhận được tin nhắn này bởi vì có ai đó đã dùng email này để đăng ký tài khoản tại
|
||||
<a href="{{.HostUrl}}">Tinode</a>.</p>
|
||||
<a href="{{.HostUrl}}">MeetingIM</a>.</p>
|
||||
|
||||
<p><a href="{{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}}">Bấm để xác nhận</a>
|
||||
hoặc đi tới liên kết
|
||||
@@ -24,9 +24,9 @@ và nhập mã xác thực:</p>
|
||||
<blockquote><big>{{.Code}}</big></blockquote>
|
||||
<p>Có thể bạn cần nhập tên đăng nhập và mật khẩu.</p>
|
||||
|
||||
<p>Nếu bạn không đăng ký tài khoản tại Tinode vui lòng bỏ qua tin nhắn này.</p>
|
||||
<p>Nếu bạn không đăng ký tài khoản tại MeetingIM vui lòng bỏ qua tin nhắn này.</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Tinode Team</a></p>
|
||||
<p><a href="https://118im.com/">MeetingIM Team</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -36,7 +36,7 @@ và nhập mã xác thực:</p>
|
||||
|
||||
Xin chào.
|
||||
|
||||
Bạn nhận được tin nhắn này bởi vì có ai đó đã dùng email này để đăng ký tài khoản tại Tinode ({{.HostUrl}}).
|
||||
Bạn nhận được tin nhắn này bởi vì có ai đó đã dùng email này để đăng ký tài khoản tại MeetingIM ({{.HostUrl}}).
|
||||
|
||||
Bấm vào liên kết {{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}} để xác nhận hoặc đi tới {{.HostUrl}}#cred?what=email
|
||||
và nhập mã xác thực
|
||||
@@ -45,9 +45,9 @@ và nhập mã xác thực
|
||||
|
||||
Có thể bạn sẽ cần nhập tên đăng nhập và mật khẩu.
|
||||
|
||||
Nếu bạn không đăng ký tài khoản tại Tinode vui lòng bỏ qua tin nhắn này.
|
||||
Nếu bạn không đăng ký tài khoản tại MeetingIM vui lòng bỏ qua tin nhắn này.
|
||||
|
||||
Tinode Team
|
||||
https://tinode.co/
|
||||
MeetingIM Team
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/}}
|
||||
|
||||
{{define "subject" -}}
|
||||
Tinode 註冊:確認電子郵件
|
||||
MeetingIM 註冊:確認電子郵件
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -25,7 +25,7 @@ Tinode 註冊:確認電子郵件
|
||||
<p>您好。</p>
|
||||
|
||||
<p>您收到此訊息是因為有人使用您的電子郵件在
|
||||
<a href="{{.HostUrl}}">Tinode</a> 註冊。</p>
|
||||
<a href="{{.HostUrl}}">MeetingIM</a> 註冊。</p>
|
||||
|
||||
<p><a href="{{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}}">點擊確認</a>
|
||||
或前往
|
||||
@@ -34,9 +34,9 @@ Tinode 註冊:確認電子郵件
|
||||
<blockquote><big>{{.Code}}</big></blockquote>
|
||||
<p>您可能需要輸入登入名稱和密碼。</p>
|
||||
|
||||
<p>如果您沒有在 Tinode 註冊,請忽略此訊息。</p>
|
||||
<p>如果您沒有在 MeetingIM 註冊,請忽略此訊息。</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Tinode 團隊</a></p>
|
||||
<p><a href="https://118im.com/">MeetingIM 團隊</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -46,7 +46,7 @@ Tinode 註冊:確認電子郵件
|
||||
|
||||
您好。
|
||||
|
||||
您收到此訊息是因為有人使用您的電子郵件在 Tinode ({{.HostUrl}}) 註冊。
|
||||
您收到此訊息是因為有人使用您的電子郵件在 MeetingIM ({{.HostUrl}}) 註冊。
|
||||
|
||||
點擊連結 {{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}} 進行確認,或前往 {{.HostUrl}}#cred?what=email
|
||||
並輸入以下驗證碼:
|
||||
@@ -55,9 +55,9 @@ Tinode 註冊:確認電子郵件
|
||||
|
||||
您可能需要輸入登入名稱和密碼。
|
||||
|
||||
如果您沒有在 Tinode 註冊,請忽略此訊息。
|
||||
如果您沒有在 MeetingIM 註冊,請忽略此訊息。
|
||||
|
||||
Tinode 團隊
|
||||
https://tinode.co/
|
||||
MeetingIM 團隊
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/}}
|
||||
|
||||
{{define "subject" -}}
|
||||
Tinode 注册: 确认邮件
|
||||
MeetingIM 注册: 确认邮件
|
||||
{{- end}}
|
||||
|
||||
{{define "body_html" -}}
|
||||
@@ -23,7 +23,7 @@ Tinode 注册: 确认邮件
|
||||
|
||||
<p>您好!</p>
|
||||
|
||||
<p>您收到此消息是因为您注册了<a href="{{.HostUrl}}">Tinode</a>。</p>
|
||||
<p>您收到此消息是因为您注册了<a href="{{.HostUrl}}">MeetingIM</a>。</p>
|
||||
|
||||
<p><a href="{{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}}">确认注册</a>
|
||||
或者跳转至链接
|
||||
@@ -32,9 +32,9 @@ Tinode 注册: 确认邮件
|
||||
<blockquote><big>{{.Code}}</big></blockquote>
|
||||
<p>您可能需要输入登录名和密码。</p>
|
||||
|
||||
<p>如果您没有注册Tinode,请忽略这条消息。</p>
|
||||
<p>如果您没有注册MeetingIM,请忽略这条消息。</p>
|
||||
|
||||
<p><a href="https://tinode.co/">Tinode 团队</a></p>
|
||||
<p><a href="https://118im.com/">MeetingIM 团队</a></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -44,7 +44,7 @@ Tinode 注册: 确认邮件
|
||||
|
||||
您好!
|
||||
|
||||
您收到此消息是因为您注册了 Tinode ({{.HostUrl}})。
|
||||
您收到此消息是因为您注册了 MeetingIM ({{.HostUrl}})。
|
||||
|
||||
点击链接 {{.HostUrl}}#cred?method=email&code={{.Code}}&token={{.Token}} 确认注册或者跳转至链接 {{.HostUrl}}#cred?what=email
|
||||
并输入验证码:
|
||||
@@ -53,9 +53,9 @@ Tinode 注册: 确认邮件
|
||||
|
||||
您可能需要输入登录名和密码。
|
||||
|
||||
如果您没有注册Tinode,请忽略这条消息。
|
||||
如果您没有注册MeetingIM,请忽略这条消息。
|
||||
|
||||
Tinode 团队
|
||||
https://tinode.co/
|
||||
MeetingIM 团队
|
||||
https://118im.com/
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
Universal confirmation and password reset template for SMS.
|
||||
*/}}
|
||||
|
||||
Tinode confirmation code: {{.Code}}
|
||||
MeetingIM confirmation code: {{.Code}}
|
||||
{{.HostUrl}}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
Universal confirmation and password reset template for SMS.
|
||||
*/}}
|
||||
|
||||
Código de confirmación de Tinode: {{.Code}}
|
||||
Código de confirmación de MeetingIM: {{.Code}}
|
||||
{{.HostUrl}}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
Modèle universel de confirmation et de réinitialisation du mot de passe pour SMS.
|
||||
*/}}
|
||||
|
||||
Code de confirmation Tinode : {{.Code}}
|
||||
Code de confirmation MeetingIM : {{.Code}}
|
||||
{{.HostUrl}}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
Modelo universal de confirmação e redefinição de senha para SMS.
|
||||
*/}}
|
||||
|
||||
Código de confirmação Tinode: {{.Code}}
|
||||
Código de confirmação MeetingIM: {{.Code}}
|
||||
{{.HostUrl}}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
Универсальный шаблон подтверждения и сброса пароля для СМС.
|
||||
*/}}
|
||||
|
||||
Код подтверждения Tinode: {{.Code}}
|
||||
Код подтверждения MeetingIM: {{.Code}}
|
||||
{{.HostUrl}}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
Універсальний шаблон підтвердження та скидання пароля для СМС.
|
||||
*/}}
|
||||
|
||||
Код підтвердження Tinode: {{.Code}}
|
||||
Код підтвердження MeetingIM: {{.Code}}
|
||||
{{.HostUrl}}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
Universal confirmation and password reset template for SMS.
|
||||
*/}}
|
||||
|
||||
Mã xác thực từ Tinode: {{.Code}}
|
||||
Mã xác thực từ MeetingIM: {{.Code}}
|
||||
{{.HostUrl}}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
通用確認和密碼重設簡訊模板。
|
||||
*/}}
|
||||
|
||||
Tinode 確認驗證碼:{{.Code}}
|
||||
MeetingIM 確認驗證碼:{{.Code}}
|
||||
{{.HostUrl}}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
Universal confirmation and password reset template for SMS.
|
||||
*/}}
|
||||
|
||||
【Tinode】验证码: {{.Code}}
|
||||
【MeetingIM】验证码: {{.Code}}
|
||||
{{.HostUrl}}
|
||||
|
||||
+2
-2
@@ -163,7 +163,7 @@
|
||||
"required": [],
|
||||
"config": {
|
||||
"host_url": "http://64.118.137.182:6060/",
|
||||
"sender": "Tinode <noreply@118im.com>",
|
||||
"sender": "MeetingIM <noreply@118im.com>",
|
||||
"validation_templ": "server/templ/email-validation-en.templ",
|
||||
"reset_secret_templ": "server/templ/email-password-reset-en.templ",
|
||||
"debug_response": "123456",
|
||||
@@ -175,7 +175,7 @@
|
||||
"required": [],
|
||||
"config": {
|
||||
"host_url": "http://64.118.137.182:6060/",
|
||||
"sender": "Tinode",
|
||||
"sender": "MeetingIM",
|
||||
"universal_templ": "server/templ/sms-universal-en.templ",
|
||||
"debug_response": "123456",
|
||||
"max_retries": 3
|
||||
|
||||
@@ -124,7 +124,7 @@ func TestServeIMPasswordResetVerify(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/password-reset/challenges/challenge-1/verify",
|
||||
bytes.NewBufferString(`{"code":"123456"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", "acme")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -197,7 +197,7 @@ func TestServeIMPasswordResetConfirm(t *testing.T) {
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/im/auth/password-reset/confirm",
|
||||
bytes.NewBufferString(`{"reset_token":"challenge-1.`+token+`","new_password":"new-password"}`))
|
||||
req.Header.Set("X-Tinode-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-MeetingIM-APIKey", makeTestAPIKey(t))
|
||||
req.Header.Set("X-Tenant-Code", "acme")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ const (
|
||||
// Default code length when one is not provided in the config
|
||||
defaultCodeLength = 6
|
||||
|
||||
defaultSender = "Tinode"
|
||||
defaultSender = "MeetingIM"
|
||||
)
|
||||
|
||||
func (v *validator) Init(jsonconf string) error {
|
||||
|
||||
Reference in New Issue
Block a user