mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
Serve the HTTP API from /v1/team instead of /api/v4
The product namespace is team, and /api/ is not used as a path prefix here — API hosts carry the api.* name and paths carry the version. This moves the one constant every route mounts on, so the public router, the local router and the websocket endpoint all follow it. The two 404 catch-alls had the old prefix written out by hand, which meant the constant was not actually the single source of truth; they now derive from it, as do the emoji, image-proxy, remote-cluster and self-ping URLs that were each carrying their own copy of the literal. The remote-cluster path constants gain a leading slash from the shared constant, so the two call sites that joined them with an explicit separator would have produced a double slash and are adjusted. APIURLSuffixV4 was a bare alias of the value and APIURLSuffixV5 mounted a subrouter that no route ever registered on, so both are gone rather than carried forward under names that no longer describe anything. APIURLSuffixV1 is not ours at all — it addresses the external push notification service — so it keeps its value and is renamed PushURLSuffix to stop it reading as a version of this API. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -834,7 +834,7 @@ func searchAccessControlPolicies(c *Context, w http.ResponseWriter, r *http.Requ
|
||||
// updateActiveStatus updates the active status of a single access control policy.
|
||||
//
|
||||
// Deprecated: This endpoint is deprecated and will be removed in a future release.
|
||||
// Use PUT /api/v4/access_control/policies/activate instead, which supports batch updates.
|
||||
// Use PUT /v1/team/access_control/policies/activate instead, which supports batch updates.
|
||||
func updateActiveStatus(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequirePolicyId()
|
||||
if c.Err != nil {
|
||||
@@ -896,7 +896,7 @@ func updateActiveStatus(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Set deprecation header to inform clients
|
||||
w.Header().Set("Deprecation", "true")
|
||||
w.Header().Set("Link", "</api/v4/access_control/policies/activate>; rel=\"successor-version\"")
|
||||
w.Header().Set("Link", "<"+model.APIURLSuffix+"/access_control/policies/activate>; rel=\"successor-version\"")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
|
||||
@@ -12,11 +12,11 @@ import (
|
||||
)
|
||||
|
||||
func (api *API) InitAgents() {
|
||||
// GET /api/v4/agents
|
||||
// GET /v1/team/agents
|
||||
api.BaseRoutes.Agents.Handle("", api.APISessionRequired(getAgents)).Methods(http.MethodGet)
|
||||
// GET /api/v4/agents/status
|
||||
// GET /v1/team/agents/status
|
||||
api.BaseRoutes.Agents.Handle("/status", api.APISessionRequired(getAgentsStatus)).Methods(http.MethodGet)
|
||||
// GET /api/v4/llmservices
|
||||
// GET /v1/team/llmservices
|
||||
api.BaseRoutes.LLMServices.Handle("", api.APISessionRequired(getLLMServices)).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
|
||||
+114
-116
@@ -16,166 +16,165 @@ import (
|
||||
)
|
||||
|
||||
type Routes struct {
|
||||
Root *mux.Router // ''
|
||||
APIRoot *mux.Router // 'api/v4'
|
||||
APIRoot5 *mux.Router // 'api/v5'
|
||||
Root *mux.Router // ''
|
||||
APIRoot *mux.Router // 'v1/team'
|
||||
|
||||
Users *mux.Router // 'api/v4/users'
|
||||
User *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}'
|
||||
UserByUsername *mux.Router // 'api/v4/users/username/{username:[A-Za-z0-9\\_\\-\\.]+}'
|
||||
UserByEmail *mux.Router // 'api/v4/users/email/{email:.+}'
|
||||
Users *mux.Router // 'v1/team/users'
|
||||
User *mux.Router // 'v1/team/users/{user_id:[A-Za-z0-9]+}'
|
||||
UserByUsername *mux.Router // 'v1/team/users/username/{username:[A-Za-z0-9\\_\\-\\.]+}'
|
||||
UserByEmail *mux.Router // 'v1/team/users/email/{email:.+}'
|
||||
|
||||
Bots *mux.Router // 'api/v4/bots'
|
||||
Bot *mux.Router // 'api/v4/bots/{bot_user_id:[A-Za-z0-9]+}'
|
||||
Bots *mux.Router // 'v1/team/bots'
|
||||
Bot *mux.Router // 'v1/team/bots/{bot_user_id:[A-Za-z0-9]+}'
|
||||
|
||||
Teams *mux.Router // 'api/v4/teams'
|
||||
TeamsForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams'
|
||||
Team *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}'
|
||||
TeamForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}'
|
||||
UserThreads *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}/threads'
|
||||
UserThread *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}/threads/{thread_id:[A-Za-z0-9]+}'
|
||||
TeamByName *mux.Router // 'api/v4/teams/name/{team_name:[A-Za-z0-9_-]+}'
|
||||
TeamMembers *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}/members'
|
||||
TeamMember *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}/members/{user_id:[A-Za-z0-9]+}'
|
||||
TeamMembersForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams/members'
|
||||
Teams *mux.Router // 'v1/team/teams'
|
||||
TeamsForUser *mux.Router // 'v1/team/users/{user_id:[A-Za-z0-9]+}/teams'
|
||||
Team *mux.Router // 'v1/team/teams/{team_id:[A-Za-z0-9]+}'
|
||||
TeamForUser *mux.Router // 'v1/team/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}'
|
||||
UserThreads *mux.Router // 'v1/team/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}/threads'
|
||||
UserThread *mux.Router // 'v1/team/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}/threads/{thread_id:[A-Za-z0-9]+}'
|
||||
TeamByName *mux.Router // 'v1/team/teams/name/{team_name:[A-Za-z0-9_-]+}'
|
||||
TeamMembers *mux.Router // 'v1/team/teams/{team_id:[A-Za-z0-9]+}/members'
|
||||
TeamMember *mux.Router // 'v1/team/teams/{team_id:[A-Za-z0-9]+}/members/{user_id:[A-Za-z0-9]+}'
|
||||
TeamMembersForUser *mux.Router // 'v1/team/users/{user_id:[A-Za-z0-9]+}/teams/members'
|
||||
|
||||
Channels *mux.Router // 'api/v4/channels'
|
||||
Channel *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}'
|
||||
ChannelForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/channels/{channel_id:[A-Za-z0-9]+}'
|
||||
ChannelByName *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}/channels/name/{channel_name:[A-Za-z0-9_-]+}'
|
||||
ChannelByNameForTeamName *mux.Router // 'api/v4/teams/name/{team_name:[A-Za-z0-9_-]+}/channels/name/{channel_name:[A-Za-z0-9_-]+}'
|
||||
ChannelsForTeam *mux.Router // 'api/v4/teams/{team_id:[A-Za-z0-9]+}/channels'
|
||||
ChannelMembers *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/members'
|
||||
ChannelMember *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/members/{user_id:[A-Za-z0-9]+}'
|
||||
ChannelMembersForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}/channels/members'
|
||||
ChannelModerations *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/moderations'
|
||||
ChannelCategories *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}/channels/categories'
|
||||
ChannelBookmarks *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/bookmarks'
|
||||
ChannelBookmark *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/bookmarks/{bookmark_id:[A-Za-z0-9]+}'
|
||||
ChannelViews *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/views'
|
||||
ChannelView *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/views/{view_id:[A-Za-z0-9]+}'
|
||||
ChannelViewPosts *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/views/{view_id:[A-Za-z0-9]+}/posts'
|
||||
Channels *mux.Router // 'v1/team/channels'
|
||||
Channel *mux.Router // 'v1/team/channels/{channel_id:[A-Za-z0-9]+}'
|
||||
ChannelForUser *mux.Router // 'v1/team/users/{user_id:[A-Za-z0-9]+}/channels/{channel_id:[A-Za-z0-9]+}'
|
||||
ChannelByName *mux.Router // 'v1/team/teams/{team_id:[A-Za-z0-9]+}/channels/name/{channel_name:[A-Za-z0-9_-]+}'
|
||||
ChannelByNameForTeamName *mux.Router // 'v1/team/teams/name/{team_name:[A-Za-z0-9_-]+}/channels/name/{channel_name:[A-Za-z0-9_-]+}'
|
||||
ChannelsForTeam *mux.Router // 'v1/team/teams/{team_id:[A-Za-z0-9]+}/channels'
|
||||
ChannelMembers *mux.Router // 'v1/team/channels/{channel_id:[A-Za-z0-9]+}/members'
|
||||
ChannelMember *mux.Router // 'v1/team/channels/{channel_id:[A-Za-z0-9]+}/members/{user_id:[A-Za-z0-9]+}'
|
||||
ChannelMembersForUser *mux.Router // 'v1/team/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}/channels/members'
|
||||
ChannelModerations *mux.Router // 'v1/team/channels/{channel_id:[A-Za-z0-9]+}/moderations'
|
||||
ChannelCategories *mux.Router // 'v1/team/users/{user_id:[A-Za-z0-9]+}/teams/{team_id:[A-Za-z0-9]+}/channels/categories'
|
||||
ChannelBookmarks *mux.Router // 'v1/team/channels/{channel_id:[A-Za-z0-9]+}/bookmarks'
|
||||
ChannelBookmark *mux.Router // 'v1/team/channels/{channel_id:[A-Za-z0-9]+}/bookmarks/{bookmark_id:[A-Za-z0-9]+}'
|
||||
ChannelViews *mux.Router // 'v1/team/channels/{channel_id:[A-Za-z0-9]+}/views'
|
||||
ChannelView *mux.Router // 'v1/team/channels/{channel_id:[A-Za-z0-9]+}/views/{view_id:[A-Za-z0-9]+}'
|
||||
ChannelViewPosts *mux.Router // 'v1/team/channels/{channel_id:[A-Za-z0-9]+}/views/{view_id:[A-Za-z0-9]+}/posts'
|
||||
|
||||
Posts *mux.Router // 'api/v4/posts'
|
||||
Post *mux.Router // 'api/v4/posts/{post_id:[A-Za-z0-9]+}'
|
||||
PostsForChannel *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/posts'
|
||||
PostsForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/posts'
|
||||
PostForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/posts/{post_id:[A-Za-z0-9]+}'
|
||||
Posts *mux.Router // 'v1/team/posts'
|
||||
Post *mux.Router // 'v1/team/posts/{post_id:[A-Za-z0-9]+}'
|
||||
PostsForChannel *mux.Router // 'v1/team/channels/{channel_id:[A-Za-z0-9]+}/posts'
|
||||
PostsForUser *mux.Router // 'v1/team/users/{user_id:[A-Za-z0-9]+}/posts'
|
||||
PostForUser *mux.Router // 'v1/team/users/{user_id:[A-Za-z0-9]+}/posts/{post_id:[A-Za-z0-9]+}'
|
||||
|
||||
Files *mux.Router // 'api/v4/files'
|
||||
File *mux.Router // 'api/v4/files/{file_id:[A-Za-z0-9]+}'
|
||||
Files *mux.Router // 'v1/team/files'
|
||||
File *mux.Router // 'v1/team/files/{file_id:[A-Za-z0-9]+}'
|
||||
|
||||
Uploads *mux.Router // 'api/v4/uploads'
|
||||
Upload *mux.Router // 'api/v4/uploads/{upload_id:[A-Za-z0-9]+}'
|
||||
Uploads *mux.Router // 'v1/team/uploads'
|
||||
Upload *mux.Router // 'v1/team/uploads/{upload_id:[A-Za-z0-9]+}'
|
||||
|
||||
Plugins *mux.Router // 'api/v4/plugins'
|
||||
Plugin *mux.Router // 'api/v4/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}'
|
||||
Plugins *mux.Router // 'v1/team/plugins'
|
||||
Plugin *mux.Router // 'v1/team/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}'
|
||||
|
||||
PublicFile *mux.Router // '/files/{file_id:[A-Za-z0-9]+}/public'
|
||||
|
||||
Commands *mux.Router // 'api/v4/commands'
|
||||
Command *mux.Router // 'api/v4/commands/{command_id:[A-Za-z0-9]+}'
|
||||
Commands *mux.Router // 'v1/team/commands'
|
||||
Command *mux.Router // 'v1/team/commands/{command_id:[A-Za-z0-9]+}'
|
||||
|
||||
Hooks *mux.Router // 'api/v4/hooks'
|
||||
IncomingHooks *mux.Router // 'api/v4/hooks/incoming'
|
||||
IncomingHook *mux.Router // 'api/v4/hooks/incoming/{hook_id:[A-Za-z0-9]+}'
|
||||
OutgoingHooks *mux.Router // 'api/v4/hooks/outgoing'
|
||||
OutgoingHook *mux.Router // 'api/v4/hooks/outgoing/{hook_id:[A-Za-z0-9]+}'
|
||||
Hooks *mux.Router // 'v1/team/hooks'
|
||||
IncomingHooks *mux.Router // 'v1/team/hooks/incoming'
|
||||
IncomingHook *mux.Router // 'v1/team/hooks/incoming/{hook_id:[A-Za-z0-9]+}'
|
||||
OutgoingHooks *mux.Router // 'v1/team/hooks/outgoing'
|
||||
OutgoingHook *mux.Router // 'v1/team/hooks/outgoing/{hook_id:[A-Za-z0-9]+}'
|
||||
|
||||
OAuth *mux.Router // 'api/v4/oauth'
|
||||
OAuthApps *mux.Router // 'api/v4/oauth/apps'
|
||||
OAuthApp *mux.Router // 'api/v4/oauth/apps/{app_id:[A-Za-z0-9]+}'
|
||||
OAuth *mux.Router // 'v1/team/oauth'
|
||||
OAuthApps *mux.Router // 'v1/team/oauth/apps'
|
||||
OAuthApp *mux.Router // 'v1/team/oauth/apps/{app_id:[A-Za-z0-9]+}'
|
||||
|
||||
SAML *mux.Router // 'api/v4/saml'
|
||||
Compliance *mux.Router // 'api/v4/compliance'
|
||||
Cluster *mux.Router // 'api/v4/cluster'
|
||||
SAML *mux.Router // 'v1/team/saml'
|
||||
Compliance *mux.Router // 'v1/team/compliance'
|
||||
Cluster *mux.Router // 'v1/team/cluster'
|
||||
|
||||
Image *mux.Router // 'api/v4/image'
|
||||
Image *mux.Router // 'v1/team/image'
|
||||
|
||||
LDAP *mux.Router // 'api/v4/ldap'
|
||||
LDAP *mux.Router // 'v1/team/ldap'
|
||||
|
||||
Elasticsearch *mux.Router // 'api/v4/elasticsearch'
|
||||
Elasticsearch *mux.Router // 'v1/team/elasticsearch'
|
||||
|
||||
DataRetention *mux.Router // 'api/v4/data_retention'
|
||||
DataRetention *mux.Router // 'v1/team/data_retention'
|
||||
|
||||
Brand *mux.Router // 'api/v4/brand'
|
||||
Brand *mux.Router // 'v1/team/brand'
|
||||
|
||||
System *mux.Router // 'api/v4/system'
|
||||
System *mux.Router // 'v1/team/system'
|
||||
|
||||
Jobs *mux.Router // 'api/v4/jobs'
|
||||
Jobs *mux.Router // 'v1/team/jobs'
|
||||
|
||||
Recaps *mux.Router // 'api/v4/recaps'
|
||||
Recaps *mux.Router // 'v1/team/recaps'
|
||||
|
||||
ScheduledRecaps *mux.Router // 'api/v4/scheduled_recaps'
|
||||
ScheduledRecap *mux.Router // 'api/v4/scheduled_recaps/{scheduled_recap_id:[A-Za-z0-9]+}'
|
||||
ScheduledRecaps *mux.Router // 'v1/team/scheduled_recaps'
|
||||
ScheduledRecap *mux.Router // 'v1/team/scheduled_recaps/{scheduled_recap_id:[A-Za-z0-9]+}'
|
||||
|
||||
Preferences *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/preferences'
|
||||
Preferences *mux.Router // 'v1/team/users/{user_id:[A-Za-z0-9]+}/preferences'
|
||||
|
||||
License *mux.Router // 'api/v4/license'
|
||||
License *mux.Router // 'v1/team/license'
|
||||
|
||||
Public *mux.Router // 'api/v4/public'
|
||||
Public *mux.Router // 'v1/team/public'
|
||||
|
||||
Reactions *mux.Router // 'api/v4/reactions'
|
||||
Reactions *mux.Router // 'v1/team/reactions'
|
||||
|
||||
Roles *mux.Router // 'api/v4/roles'
|
||||
Schemes *mux.Router // 'api/v4/schemes'
|
||||
Roles *mux.Router // 'v1/team/roles'
|
||||
Schemes *mux.Router // 'v1/team/schemes'
|
||||
|
||||
Emojis *mux.Router // 'api/v4/emoji'
|
||||
Emoji *mux.Router // 'api/v4/emoji/{emoji_id:[A-Za-z0-9]+}'
|
||||
EmojiByName *mux.Router // 'api/v4/emoji/name/{emoji_name:[A-Za-z0-9\\_\\-\\+]+}'
|
||||
Emojis *mux.Router // 'v1/team/emoji'
|
||||
Emoji *mux.Router // 'v1/team/emoji/{emoji_id:[A-Za-z0-9]+}'
|
||||
EmojiByName *mux.Router // 'v1/team/emoji/name/{emoji_name:[A-Za-z0-9\\_\\-\\+]+}'
|
||||
|
||||
ReactionByNameForPostForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/posts/{post_id:[A-Za-z0-9]+}/reactions/{emoji_name:[A-Za-z0-9\\_\\-\\+]+}'
|
||||
ReactionByNameForPostForUser *mux.Router // 'v1/team/users/{user_id:[A-Za-z0-9]+}/posts/{post_id:[A-Za-z0-9]+}/reactions/{emoji_name:[A-Za-z0-9\\_\\-\\+]+}'
|
||||
|
||||
TermsOfService *mux.Router // 'api/v4/terms_of_service'
|
||||
Groups *mux.Router // 'api/v4/groups'
|
||||
TermsOfService *mux.Router // 'v1/team/terms_of_service'
|
||||
Groups *mux.Router // 'v1/team/groups'
|
||||
|
||||
Imports *mux.Router // 'api/v4/imports'
|
||||
Import *mux.Router // 'api/v4/imports/{import_name:.+\\.zip}'
|
||||
Imports *mux.Router // 'v1/team/imports'
|
||||
Import *mux.Router // 'v1/team/imports/{import_name:.+\\.zip}'
|
||||
|
||||
Exports *mux.Router // 'api/v4/exports'
|
||||
Export *mux.Router // 'api/v4/exports/{export_name:.+\\.zip}'
|
||||
Exports *mux.Router // 'v1/team/exports'
|
||||
Export *mux.Router // 'v1/team/exports/{export_name:.+\\.zip}'
|
||||
|
||||
RemoteCluster *mux.Router // 'api/v4/remotecluster'
|
||||
SharedChannels *mux.Router // 'api/v4/sharedchannels'
|
||||
ChannelForRemote *mux.Router // 'api/v4/remotecluster/{remote_id:[A-Za-z0-9]+}/channels/{channel_id:[A-Za-z0-9]+}'
|
||||
SharedChannelRemotes *mux.Router // 'api/v4/remotecluster/{remote_id:[A-Za-z0-9]+}/sharedchannelremotes'
|
||||
RemoteCluster *mux.Router // 'v1/team/remotecluster'
|
||||
SharedChannels *mux.Router // 'v1/team/sharedchannels'
|
||||
ChannelForRemote *mux.Router // 'v1/team/remotecluster/{remote_id:[A-Za-z0-9]+}/channels/{channel_id:[A-Za-z0-9]+}'
|
||||
SharedChannelRemotes *mux.Router // 'v1/team/remotecluster/{remote_id:[A-Za-z0-9]+}/sharedchannelremotes'
|
||||
|
||||
Permissions *mux.Router // 'api/v4/permissions'
|
||||
Permissions *mux.Router // 'v1/team/permissions'
|
||||
|
||||
Usage *mux.Router // 'api/v4/usage'
|
||||
Usage *mux.Router // 'v1/team/usage'
|
||||
|
||||
HostedCustomer *mux.Router // 'api/v4/hosted_customer'
|
||||
HostedCustomer *mux.Router // 'v1/team/hosted_customer'
|
||||
|
||||
Drafts *mux.Router // 'api/v4/drafts'
|
||||
Drafts *mux.Router // 'v1/team/drafts'
|
||||
|
||||
Reports *mux.Router // 'api/v4/reports'
|
||||
Reports *mux.Router // 'v1/team/reports'
|
||||
|
||||
OutgoingOAuthConnections *mux.Router // 'api/v4/oauth/outgoing_connections'
|
||||
OutgoingOAuthConnection *mux.Router // 'api/v4/oauth/outgoing_connections/{outgoing_oauth_connection_id:[A-Za-z0-9]+}'
|
||||
OutgoingOAuthConnections *mux.Router // 'v1/team/oauth/outgoing_connections'
|
||||
OutgoingOAuthConnection *mux.Router // 'v1/team/oauth/outgoing_connections/{outgoing_oauth_connection_id:[A-Za-z0-9]+}'
|
||||
|
||||
CustomProfileAttributes *mux.Router // 'api/v4/custom_profile_attributes'
|
||||
CustomProfileAttributesFields *mux.Router // 'api/v4/custom_profile_attributes/fields'
|
||||
CustomProfileAttributesField *mux.Router // 'api/v4/custom_profile_attributes/fields/{field_id:[A-Za-z0-9]+}'
|
||||
CustomProfileAttributesValues *mux.Router // 'api/v4/custom_profile_attributes/values'
|
||||
CustomProfileAttributes *mux.Router // 'v1/team/custom_profile_attributes'
|
||||
CustomProfileAttributesFields *mux.Router // 'v1/team/custom_profile_attributes/fields'
|
||||
CustomProfileAttributesField *mux.Router // 'v1/team/custom_profile_attributes/fields/{field_id:[A-Za-z0-9]+}'
|
||||
CustomProfileAttributesValues *mux.Router // 'v1/team/custom_profile_attributes/values'
|
||||
|
||||
AuditLogs *mux.Router // 'api/v4/audit_logs'
|
||||
AuditLogs *mux.Router // 'v1/team/audit_logs'
|
||||
|
||||
AccessControlPolicies *mux.Router // 'api/v4/access_control_policies'
|
||||
AccessControlPolicy *mux.Router // 'api/v4/access_control_policies/{policy_id:[A-Za-z0-9]+}'
|
||||
AccessControlPolicies *mux.Router // 'v1/team/access_control_policies'
|
||||
AccessControlPolicy *mux.Router // 'v1/team/access_control_policies/{policy_id:[A-Za-z0-9]+}'
|
||||
|
||||
ContentFlagging *mux.Router // 'api/v4/content_flagging'
|
||||
ContentFlagging *mux.Router // 'v1/team/content_flagging'
|
||||
|
||||
Agents *mux.Router // 'api/v4/agents'
|
||||
LLMServices *mux.Router // 'api/v4/llmservices'
|
||||
Agents *mux.Router // 'v1/team/agents'
|
||||
LLMServices *mux.Router // 'v1/team/llmservices'
|
||||
|
||||
Boards *mux.Router // 'api/v4/boards'
|
||||
Boards *mux.Router // 'v1/team/boards'
|
||||
|
||||
Properties *mux.Router // 'api/v4/properties'
|
||||
PropertyFields *mux.Router // 'api/v4/properties/groups/{group_name:[a-z][a-z0-9_]*}/{object_type:[a-z]+}/fields'
|
||||
PropertyField *mux.Router // 'api/v4/properties/groups/{group_name:[a-z][a-z0-9_]*}/{object_type:[a-z]+}/fields/{field_id:[A-Za-z0-9]+}'
|
||||
PropertyFieldsSearch *mux.Router // 'api/v4/properties/groups/{group_name:[a-z][a-z0-9_]*}/fields/search'
|
||||
PropertyValues *mux.Router // 'api/v4/properties/groups/{group_name:[a-z][a-z0-9_]*}/{object_type:[a-z]+}/values/{target_id:[A-Za-z0-9]+}'
|
||||
PropertySystemValues *mux.Router // 'api/v4/properties/groups/{group_name:[a-z][a-z0-9_]*}/system/values'
|
||||
Properties *mux.Router // 'v1/team/properties'
|
||||
PropertyFields *mux.Router // 'v1/team/properties/groups/{group_name:[a-z][a-z0-9_]*}/{object_type:[a-z]+}/fields'
|
||||
PropertyField *mux.Router // 'v1/team/properties/groups/{group_name:[a-z][a-z0-9_]*}/{object_type:[a-z]+}/fields/{field_id:[A-Za-z0-9]+}'
|
||||
PropertyFieldsSearch *mux.Router // 'v1/team/properties/groups/{group_name:[a-z][a-z0-9_]*}/fields/search'
|
||||
PropertyValues *mux.Router // 'v1/team/properties/groups/{group_name:[a-z][a-z0-9_]*}/{object_type:[a-z]+}/values/{target_id:[A-Za-z0-9]+}'
|
||||
PropertySystemValues *mux.Router // 'v1/team/properties/groups/{group_name:[a-z][a-z0-9_]*}/system/values'
|
||||
}
|
||||
|
||||
type API struct {
|
||||
@@ -191,7 +190,6 @@ func Init(srv *app.Server) (*API, error) {
|
||||
|
||||
api.BaseRoutes.Root = srv.Router
|
||||
api.BaseRoutes.APIRoot = srv.Router.PathPrefix(model.APIURLSuffix).Subrouter()
|
||||
api.BaseRoutes.APIRoot5 = srv.Router.PathPrefix(model.APIURLSuffixV5).Subrouter()
|
||||
|
||||
api.BaseRoutes.Users = api.BaseRoutes.APIRoot.PathPrefix("/users").Subrouter()
|
||||
api.BaseRoutes.User = api.BaseRoutes.APIRoot.PathPrefix("/users/{user_id:[A-Za-z0-9]+}").Subrouter()
|
||||
@@ -400,7 +398,7 @@ func Init(srv *app.Server) (*API, error) {
|
||||
api.BaseRoutes.Root.Handle("/manualtest", api.APIHandler(manualtesting.ManualTest)).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
srv.Router.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404))
|
||||
srv.Router.Handle(model.APIURLSuffix+"/{anything:.*}", http.HandlerFunc(api.Handle404))
|
||||
|
||||
InitLocal(srv)
|
||||
|
||||
@@ -509,7 +507,7 @@ func InitLocal(srv *app.Server) *API {
|
||||
api.InitAccessControlPolicyLocal()
|
||||
api.InitStatusLocal()
|
||||
|
||||
srv.LocalRouter.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404))
|
||||
srv.LocalRouter.Handle(model.APIURLSuffix+"/{anything:.*}", http.HandlerFunc(api.Handle404))
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
@@ -8192,7 +8192,7 @@ func TestChannelEndpointsExcludeSpaces(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestLocalChannelEndpoints404Spaces verifies the local (admin socket) channel endpoints 404 a
|
||||
// space, the same as /api/v4: space channels are excluded from the generic GetChannel these
|
||||
// space, the same as /v1/team: space channels are excluded from the generic GetChannel these
|
||||
// handlers resolve through, so they never reach the mutation.
|
||||
func TestLocalChannelEndpoints404Spaces(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
|
||||
@@ -133,7 +133,7 @@ func TestCORSRequestHandling(t *testing.T) {
|
||||
|
||||
port := th.App.Srv().ListenAddr.Port
|
||||
host := fmt.Sprintf("http://localhost:%v", port)
|
||||
url := fmt.Sprintf("%v/api/v4/system/ping", host)
|
||||
url := fmt.Sprintf("%v/v1/team/system/ping", host)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -11,7 +11,7 @@ channels, posts, and other Hanzo Team resources.
|
||||
|
||||
# Architecture
|
||||
|
||||
The API is structured around resource-based endpoints under the /api/v4/ path.
|
||||
The API is structured around resource-based endpoints under the /v1/team/ path.
|
||||
Each endpoint is handled by specific handler functions that provide different
|
||||
levels of authentication and authorization:
|
||||
|
||||
|
||||
@@ -20,58 +20,58 @@ import (
|
||||
)
|
||||
|
||||
func (api *API) InitGroup() {
|
||||
// GET /api/v4/groups
|
||||
// GET /v1/team/groups
|
||||
api.BaseRoutes.Groups.Handle("", api.APISessionRequired(getGroups)).Methods(http.MethodGet)
|
||||
|
||||
// POST /api/v4/groups
|
||||
// POST /v1/team/groups
|
||||
api.BaseRoutes.Groups.Handle("", api.APISessionRequired(createGroup)).Methods(http.MethodPost)
|
||||
|
||||
// GET /api/v4/groups/:group_id
|
||||
// GET /v1/team/groups/:group_id
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}",
|
||||
api.APISessionRequired(getGroup)).Methods(http.MethodGet)
|
||||
|
||||
// PUT /api/v4/groups/:group_id/patch
|
||||
// PUT /v1/team/groups/:group_id/patch
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/patch",
|
||||
api.APISessionRequired(patchGroup)).Methods(http.MethodPut)
|
||||
|
||||
// POST /api/v4/groups/:group_id/teams/:team_id/link
|
||||
// POST /api/v4/groups/:group_id/channels/:channel_id/link
|
||||
// POST /v1/team/groups/:group_id/teams/:team_id/link
|
||||
// POST /v1/team/groups/:group_id/channels/:channel_id/link
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}/link",
|
||||
api.APISessionRequired(linkGroupSyncable)).Methods(http.MethodPost)
|
||||
|
||||
// DELETE /api/v4/groups/:group_id/teams/:team_id/link
|
||||
// DELETE /api/v4/groups/:group_id/channels/:channel_id/link
|
||||
// DELETE /v1/team/groups/:group_id/teams/:team_id/link
|
||||
// DELETE /v1/team/groups/:group_id/channels/:channel_id/link
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}/link",
|
||||
api.APISessionRequired(unlinkGroupSyncable)).Methods(http.MethodDelete)
|
||||
|
||||
// GET /api/v4/groups/:group_id/teams/:team_id
|
||||
// GET /api/v4/groups/:group_id/channels/:channel_id
|
||||
// GET /v1/team/groups/:group_id/teams/:team_id
|
||||
// GET /v1/team/groups/:group_id/channels/:channel_id
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}",
|
||||
api.APISessionRequired(getGroupSyncable)).Methods(http.MethodGet)
|
||||
|
||||
// GET /api/v4/groups/:group_id/teams
|
||||
// GET /api/v4/groups/:group_id/channels
|
||||
// GET /v1/team/groups/:group_id/teams
|
||||
// GET /v1/team/groups/:group_id/channels
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}",
|
||||
api.APISessionRequired(getGroupSyncables)).Methods(http.MethodGet)
|
||||
|
||||
// PUT /api/v4/groups/:group_id/teams/:team_id/patch
|
||||
// PUT /api/v4/groups/:group_id/channels/:channel_id/patch
|
||||
// PUT /v1/team/groups/:group_id/teams/:team_id/patch
|
||||
// PUT /v1/team/groups/:group_id/channels/:channel_id/patch
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/{syncable_type:teams|channels}/{syncable_id:[A-Za-z0-9]+}/patch",
|
||||
api.APISessionRequired(patchGroupSyncable)).Methods(http.MethodPut)
|
||||
|
||||
// GET /api/v4/groups/:group_id/stats
|
||||
// GET /v1/team/groups/:group_id/stats
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/stats",
|
||||
api.APISessionRequired(getGroupStats)).Methods(http.MethodGet)
|
||||
|
||||
// GET /api/v4/groups/:group_id/members
|
||||
// GET /v1/team/groups/:group_id/members
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/members",
|
||||
api.APISessionRequired(getGroupMembers)).Methods(http.MethodGet)
|
||||
|
||||
// GET /api/v4/users/:user_id/groups
|
||||
// GET /v1/team/users/:user_id/groups
|
||||
api.BaseRoutes.Users.Handle("/{user_id:[A-Za-z0-9]+}/groups",
|
||||
api.APISessionRequired(getGroupsByUserId)).Methods(http.MethodGet)
|
||||
|
||||
// GET /api/v4/channels/:channel_id/groups
|
||||
// GET /v1/team/channels/:channel_id/groups
|
||||
api.BaseRoutes.Channels.Handle("/{channel_id:[A-Za-z0-9]+}/groups",
|
||||
api.APISessionRequired(getGroupsByChannel)).Methods(http.MethodGet)
|
||||
|
||||
@@ -79,27 +79,27 @@ func (api *API) InitGroup() {
|
||||
api.BaseRoutes.Groups.Handle("/names",
|
||||
api.APISessionRequired(getGroupsByNames)).Methods(http.MethodPost)
|
||||
|
||||
// GET /api/v4/teams/:team_id/groups
|
||||
// GET /v1/team/teams/:team_id/groups
|
||||
api.BaseRoutes.Teams.Handle("/{team_id:[A-Za-z0-9]+}/groups",
|
||||
api.APISessionRequired(getGroupsByTeam)).Methods(http.MethodGet)
|
||||
|
||||
// GET /api/v4/teams/:team_id/groups_by_channels
|
||||
// GET /v1/team/teams/:team_id/groups_by_channels
|
||||
api.BaseRoutes.Teams.Handle("/{team_id:[A-Za-z0-9]+}/groups_by_channels",
|
||||
api.APISessionRequired(getGroupsAssociatedToChannelsByTeam)).Methods(http.MethodGet)
|
||||
|
||||
// DELETE /api/v4/groups/:group_id
|
||||
// DELETE /v1/team/groups/:group_id
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}",
|
||||
api.APISessionRequired(deleteGroup)).Methods(http.MethodDelete)
|
||||
|
||||
// POST /api/v4/groups/:group_id
|
||||
// POST /v1/team/groups/:group_id
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/restore",
|
||||
api.APISessionRequired(restoreGroup)).Methods(http.MethodPost)
|
||||
|
||||
// POST /api/v4/groups/:group_id/members
|
||||
// POST /v1/team/groups/:group_id/members
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/members",
|
||||
api.APISessionRequired(addGroupMembers)).Methods(http.MethodPost)
|
||||
|
||||
// DELETE /api/v4/groups/:group_id/members
|
||||
// DELETE /v1/team/groups/:group_id/members
|
||||
api.BaseRoutes.Groups.Handle("/{group_id:[A-Za-z0-9]+}/members",
|
||||
api.APISessionRequired(deleteGroupMembers)).Methods(http.MethodDelete)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func handlerForGzip(t *testing.T) func(*Context, http.ResponseWriter, *http.Requ
|
||||
func testAPIHandlerGzipMode(t *testing.T, name string, h http.Handler, token string) {
|
||||
t.Run("Handler: "+name+" No Accept-Encoding", func(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+token)
|
||||
h.ServeHTTP(resp, req)
|
||||
assert.Equal(t, http.StatusOK, resp.Code)
|
||||
@@ -35,7 +35,7 @@ func testAPIHandlerGzipMode(t *testing.T, name string, h http.Handler, token str
|
||||
|
||||
t.Run("Handler: "+name+" With Accept-Encoding", func(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+token)
|
||||
|
||||
@@ -48,7 +48,7 @@ func testAPIHandlerGzipMode(t *testing.T, name string, h http.Handler, token str
|
||||
func testAPIHandlerNoGzipMode(t *testing.T, name string, h http.Handler, token string) {
|
||||
t.Run("Handler: "+name+" No Accept-Encoding", func(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+token)
|
||||
|
||||
h.ServeHTTP(resp, req)
|
||||
@@ -58,7 +58,7 @@ func testAPIHandlerNoGzipMode(t *testing.T, name string, h http.Handler, token s
|
||||
|
||||
t.Run("Handler: "+name+" With Accept-Encoding", func(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
req.Header.Set("Accept-Encoding", "gzip")
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+token)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// APIs for self-hosted workspaces to communicate with the backing customer & payments system.
|
||||
// Endpoints for cloud installations should not go in this file.
|
||||
func (api *API) InitHostedCustomer() {
|
||||
// POST /api/v4/hosted_customer/available
|
||||
// POST /v1/team/hosted_customer/available
|
||||
api.BaseRoutes.HostedCustomer.Handle("/signup_available", api.APISessionRequired(handleSignupAvailable)).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
|
||||
@@ -27,13 +27,13 @@ func (api *API) InitLdap() {
|
||||
|
||||
api.BaseRoutes.LDAP.Handle("/migrateid", api.APISessionRequired(migrateIDLdap)).Methods(http.MethodPost)
|
||||
|
||||
// GET /api/v4/ldap/groups?page=0&per_page=1000
|
||||
// GET /v1/team/ldap/groups?page=0&per_page=1000
|
||||
api.BaseRoutes.LDAP.Handle("/groups", api.APISessionRequired(getLdapGroups)).Methods(http.MethodGet)
|
||||
|
||||
// POST /api/v4/ldap/groups/:remote_id/link
|
||||
// POST /v1/team/ldap/groups/:remote_id/link
|
||||
api.BaseRoutes.LDAP.Handle(`/groups/{remote_id}/link`, api.APISessionRequired(linkLdapGroup)).Methods(http.MethodPost)
|
||||
|
||||
// DELETE /api/v4/ldap/groups/:remote_id/link
|
||||
// DELETE /v1/team/ldap/groups/:remote_id/link
|
||||
api.BaseRoutes.LDAP.Handle(`/groups/{remote_id}/link`, api.APISessionRequired(unlinkLdapGroup)).Methods(http.MethodDelete)
|
||||
|
||||
api.BaseRoutes.LDAP.Handle("/certificate/public", api.APISessionRequired(addLdapPublicCertificate, handlerParamFileAPI)).Methods(http.MethodPost)
|
||||
|
||||
@@ -1560,7 +1560,7 @@ func TestCreatePostCheckOnlineStatus(t *testing.T) {
|
||||
|
||||
postJSON, jsonErr := json.Marshal(post)
|
||||
require.NoError(t, jsonErr)
|
||||
req := httptest.NewRequest("POST", "/api/v4/posts?set_online=false", bytes.NewReader(postJSON))
|
||||
req := httptest.NewRequest("POST", "/v1/team/posts?set_online=false", bytes.NewReader(postJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1573,7 +1573,7 @@ func TestCreatePostCheckOnlineStatus(t *testing.T) {
|
||||
|
||||
postJSON, jsonErr = json.Marshal(post)
|
||||
require.NoError(t, jsonErr)
|
||||
req = httptest.NewRequest("POST", "/api/v4/posts", bytes.NewReader(postJSON))
|
||||
req = httptest.NewRequest("POST", "/v1/team/posts", bytes.NewReader(postJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1603,7 +1603,7 @@ func TestCreatePostSilentRejectedForHumanUser(t *testing.T) {
|
||||
|
||||
postJSON, jsonErr := json.Marshal(post)
|
||||
require.NoError(t, jsonErr)
|
||||
req := httptest.NewRequest("POST", "/api/v4/posts?silent=true", bytes.NewReader(postJSON))
|
||||
req := httptest.NewRequest("POST", "/v1/team/posts?silent=true", bytes.NewReader(postJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1629,7 +1629,7 @@ func TestCreatePostSilentQueryParam(t *testing.T) {
|
||||
t.Run("invalid silent value returns 400", func(t *testing.T) {
|
||||
session, _ := th.App.GetSession(th.Client.AuthToken)
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/api/v4/posts?silent=invalid", bytes.NewReader(postJSON))
|
||||
req := httptest.NewRequest("POST", "/v1/team/posts?silent=invalid", bytes.NewReader(postJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1639,7 +1639,7 @@ func TestCreatePostSilentQueryParam(t *testing.T) {
|
||||
t.Run("silent=false does not set prop", func(t *testing.T) {
|
||||
session, _ := th.App.GetSession(th.Client.AuthToken)
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/api/v4/posts?silent=false", bytes.NewReader(postJSON))
|
||||
req := httptest.NewRequest("POST", "/v1/team/posts?silent=false", bytes.NewReader(postJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1653,7 +1653,7 @@ func TestCreatePostSilentQueryParam(t *testing.T) {
|
||||
t.Run("silent=0 does not set prop", func(t *testing.T) {
|
||||
session, _ := th.App.GetSession(th.Client.AuthToken)
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/api/v4/posts?silent=0", bytes.NewReader(postJSON))
|
||||
req := httptest.NewRequest("POST", "/v1/team/posts?silent=0", bytes.NewReader(postJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1667,7 +1667,7 @@ func TestCreatePostSilentQueryParam(t *testing.T) {
|
||||
t.Run("empty silent param is ignored", func(t *testing.T) {
|
||||
session, _ := th.App.GetSession(th.Client.AuthToken)
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/api/v4/posts?silent=", bytes.NewReader(postJSON))
|
||||
req := httptest.NewRequest("POST", "/v1/team/posts?silent=", bytes.NewReader(postJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1705,7 +1705,7 @@ func TestCreatePostSilentQueryParam(t *testing.T) {
|
||||
}
|
||||
botPostJSON, err := json.Marshal(botPost)
|
||||
require.NoError(t, err)
|
||||
req := httptest.NewRequest("POST", "/api/v4/posts?silent=true", bytes.NewReader(botPostJSON))
|
||||
req := httptest.NewRequest("POST", "/v1/team/posts?silent=true", bytes.NewReader(botPostJSON))
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+rtoken.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestRateLimitingMiddleware(t *testing.T) {
|
||||
th.App.Srv().Store().(*storemocks.Store).On("License").Return(&licenseStore)
|
||||
|
||||
port := th.App.Srv().ListenAddr.Port
|
||||
url := fmt.Sprintf("http://localhost:%v/api/v4/system/ping", port)
|
||||
url := fmt.Sprintf("http://localhost:%v/v1/team/system/ping", port)
|
||||
client := &http.Client{}
|
||||
|
||||
t.Run("requests within burst succeed", func(t *testing.T) {
|
||||
@@ -93,7 +93,7 @@ func TestRateLimitingVaryByHeader(t *testing.T) {
|
||||
th.App.Srv().Store().(*storemocks.Store).On("License").Return(&licenseStore)
|
||||
|
||||
port := th.App.Srv().ListenAddr.Port
|
||||
url := fmt.Sprintf("http://localhost:%v/api/v4/system/ping", port)
|
||||
url := fmt.Sprintf("http://localhost:%v/v1/team/system/ping", port)
|
||||
client := &http.Client{}
|
||||
|
||||
// 2 requests with client-A should succeed
|
||||
@@ -157,7 +157,7 @@ func TestRateLimitingVaryByUser(t *testing.T) {
|
||||
th.App.Srv().RateLimiter = rl
|
||||
|
||||
port := th.App.Srv().ListenAddr.Port
|
||||
url := fmt.Sprintf("http://localhost:%v/api/v4/system/ping", port)
|
||||
url := fmt.Sprintf("http://localhost:%v/v1/team/system/ping", port)
|
||||
client := &http.Client{}
|
||||
|
||||
userAToken := th.Client.AuthToken
|
||||
|
||||
@@ -160,7 +160,7 @@ func fillUserReportOptions(values url.Values) (*model.UserReportOptions, *model.
|
||||
|
||||
// getPostsForReporting retrieves posts for reporting purposes with cursor-based pagination.
|
||||
//
|
||||
// API Endpoint: POST /api/v4/reports/posts
|
||||
// API Endpoint: POST /v1/team/reports/posts
|
||||
//
|
||||
// Cursor Behavior:
|
||||
// - The cursor is opaque and self-contained (base64-encoded)
|
||||
|
||||
@@ -329,7 +329,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
})
|
||||
|
||||
@@ -351,7 +351,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
})
|
||||
|
||||
@@ -364,7 +364,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.Client, "/api/v4/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.Client, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||
})
|
||||
|
||||
@@ -377,7 +377,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var result model.ReportPostListResponse
|
||||
@@ -395,7 +395,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
})
|
||||
|
||||
@@ -411,7 +411,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusNotFound, resp.StatusCode, "should return 404 for non-existent channel")
|
||||
})
|
||||
|
||||
@@ -426,7 +426,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "empty cursor should be accepted")
|
||||
|
||||
var result model.ReportPostListResponse
|
||||
@@ -447,7 +447,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
// cursor omitted
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "omitted cursor should be accepted")
|
||||
|
||||
var result model.ReportPostListResponse
|
||||
@@ -466,7 +466,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode, "invalid cursor format should be rejected")
|
||||
})
|
||||
|
||||
@@ -480,7 +480,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 5,
|
||||
}
|
||||
|
||||
resp1, body1 := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody1)
|
||||
resp1, body1 := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody1)
|
||||
require.Equal(t, http.StatusOK, resp1.StatusCode)
|
||||
|
||||
var result1 model.ReportPostListResponse
|
||||
@@ -496,7 +496,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 5,
|
||||
}
|
||||
|
||||
resp2, body2 := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody2)
|
||||
resp2, body2 := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody2)
|
||||
require.Equal(t, http.StatusOK, resp2.StatusCode)
|
||||
|
||||
var result2 model.ReportPostListResponse
|
||||
@@ -524,7 +524,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 5,
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var result model.ReportPostListResponse
|
||||
@@ -567,7 +567,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 100,
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "cursor parameters should take precedence (self-contained cursor)")
|
||||
|
||||
var result model.ReportPostListResponse
|
||||
@@ -609,7 +609,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var result model.ReportPostListResponse
|
||||
@@ -634,7 +634,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 100,
|
||||
}
|
||||
|
||||
resp1, body1 := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody1)
|
||||
resp1, body1 := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody1)
|
||||
require.Equal(t, http.StatusOK, resp1.StatusCode)
|
||||
|
||||
var result1 model.ReportPostListResponse
|
||||
@@ -659,7 +659,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 100,
|
||||
}
|
||||
|
||||
resp2, body2 := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody2)
|
||||
resp2, body2 := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody2)
|
||||
require.Equal(t, http.StatusOK, resp2.StatusCode)
|
||||
|
||||
var result2 model.ReportPostListResponse
|
||||
@@ -710,7 +710,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 100,
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var result model.ReportPostListResponse
|
||||
@@ -732,7 +732,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 5000, // More than max
|
||||
}
|
||||
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, body := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "should cap per_page to MaxReportingPerPage instead of rejecting")
|
||||
|
||||
// Verify it was capped to max (we can't directly check the query but the request succeeds)
|
||||
@@ -752,7 +752,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode, "should reject invalid ID format")
|
||||
})
|
||||
|
||||
@@ -768,7 +768,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode, "should reject cursor with invalid time_field")
|
||||
})
|
||||
|
||||
@@ -784,7 +784,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode, "should reject cursor with invalid sort_direction")
|
||||
})
|
||||
|
||||
@@ -800,7 +800,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode, "should reject cursor with invalid channel_id")
|
||||
})
|
||||
|
||||
@@ -816,7 +816,7 @@ func TestGetPostsForReporting(t *testing.T) {
|
||||
"per_page": 10,
|
||||
}
|
||||
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/api/v4/reports/posts", requestBody)
|
||||
resp, _ := doPostJSON(t, th.SystemAdminClient, "/v1/team/reports/posts", requestBody)
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode, "should reject cursor with invalid cursor_id")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func NewSelfReferentialSyncHandler(t *testing.T, service *sharedchannel.Service,
|
||||
// HandleRequest processes incoming HTTP requests for the test server.
|
||||
func (h *SelfReferentialSyncHandler) HandleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/v4/remotecluster/msg":
|
||||
case "/v1/team/remotecluster/msg":
|
||||
atomic.AddInt32(h.syncMessageCount, 1)
|
||||
|
||||
// Read and process the sync message
|
||||
@@ -117,10 +117,10 @@ func (h *SelfReferentialSyncHandler) HandleRequest(w http.ResponseWriter, r *htt
|
||||
|
||||
writeOKResponse(w)
|
||||
|
||||
case "/api/v4/remotecluster/ping":
|
||||
case "/v1/team/remotecluster/ping":
|
||||
writeOKResponse(w)
|
||||
|
||||
case "/api/v4/remotecluster/confirm_invite":
|
||||
case "/v1/team/remotecluster/confirm_invite":
|
||||
writeOKResponse(w)
|
||||
|
||||
default:
|
||||
|
||||
@@ -323,7 +323,7 @@ func TestSiteURLTest(t *testing.T) {
|
||||
client := th.Client
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasSuffix(r.URL.Path, "/valid/api/v4/system/ping") {
|
||||
if strings.HasSuffix(r.URL.Path, "/valid/v1/team/system/ping") {
|
||||
w.WriteHeader(200)
|
||||
} else {
|
||||
w.WriteHeader(400)
|
||||
@@ -1051,7 +1051,7 @@ func TestPushNotificationAck(t *testing.T) {
|
||||
t.Run("should return error when the ack body is not passed", func(t *testing.T) {
|
||||
handler := api.APIHandler(pushNotificationAck)
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/api/v4/notifications/ack", nil)
|
||||
req := httptest.NewRequest("POST", "/v1/team/notifications/ack", nil)
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
|
||||
handler.ServeHTTP(resp, req)
|
||||
@@ -1065,7 +1065,7 @@ func TestPushNotificationAck(t *testing.T) {
|
||||
|
||||
handler := api.APIHandler(pushNotificationAck)
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/api/v4/notifications/ack", nil)
|
||||
req := httptest.NewRequest("POST", "/v1/team/notifications/ack", nil)
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
req.Body = io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"id":"123", "is_id_loaded":true, "post_id":"%s", "type": "%s"}`, privatePost.Id, model.PushTypeMessage)))
|
||||
|
||||
@@ -1121,7 +1121,7 @@ func TestPushNotificationAck(t *testing.T) {
|
||||
|
||||
handler := api.APIHandler(pushNotificationAck)
|
||||
resp := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("POST", "/api/v4/notifications/ack", nil)
|
||||
req := httptest.NewRequest("POST", "/v1/team/notifications/ack", nil)
|
||||
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
||||
req.Body = io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"id":"123", "is_id_loaded":true, "platform": "%s", "post_id":"%s", "type": "%s"}`, tc.platform, th.BasicPost.Id, model.PushTypeMessage)))
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@ import (
|
||||
)
|
||||
|
||||
func (api *API) InitUsage() {
|
||||
// GET /api/v4/usage/posts
|
||||
// GET /v1/team/usage/posts
|
||||
api.BaseRoutes.Usage.Handle("/posts", api.APISessionRequired(getPostsUsage)).Methods(http.MethodGet)
|
||||
// GET /api/v4/usage/storage
|
||||
// GET /v1/team/usage/storage
|
||||
api.BaseRoutes.Usage.Handle("/storage", api.APISessionRequired(getStorageUsage)).Methods(http.MethodGet)
|
||||
// GET /api/v4/usage/teams
|
||||
// GET /v1/team/usage/teams
|
||||
api.BaseRoutes.Usage.Handle("/teams", api.APISessionRequired(getTeamsUsage)).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ func (a *App) RecycleDatabaseConnection(rctx request.CTX) {
|
||||
}
|
||||
|
||||
func (a *App) TestSiteURL(rctx request.CTX, siteURL string) *model.AppError {
|
||||
url := fmt.Sprintf("%s/api/v4/system/ping", siteURL)
|
||||
url := fmt.Sprintf("%s%s/system/ping", siteURL, model.APIURLSuffix)
|
||||
res, err := http.Get(url)
|
||||
if err != nil || res.StatusCode != 200 {
|
||||
return model.NewAppError("testSiteURL", "app.admin.test_site_url.failure", nil, "", http.StatusBadRequest)
|
||||
|
||||
@@ -408,7 +408,7 @@ func (a *App) MFARequired(rctx request.CTX) *model.AppError {
|
||||
|
||||
// Special case to let user get themself
|
||||
subpath, _ := utils.GetSubpathFromConfig(a.Config())
|
||||
if rctx.Path() == path.Join(subpath, "/api/v4/users/me") {
|
||||
if rctx.Path() == path.Join(subpath, model.APIURLSuffix, "users/me") {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
)
|
||||
|
||||
// channelJoinRequestPaginationDefaultPerPage matches the public /api/v4 default
|
||||
// channelJoinRequestPaginationDefaultPerPage matches the public /v1/team default
|
||||
// for paginated endpoints.
|
||||
const channelJoinRequestPaginationDefaultPerPage = 60
|
||||
|
||||
|
||||
@@ -315,7 +315,7 @@ func (a *App) GetEmojiStaticURL(rctx request.CTX, emojiName string) (string, *mo
|
||||
|
||||
emoji, err := a.Srv().Store().Emoji().GetByName(rctx, emojiName, true)
|
||||
if err == nil {
|
||||
return path.Join(subPath, "/api/v4/emoji", emoji.Id, "image"), nil
|
||||
return path.Join(subPath, model.APIURLSuffix, "emoji", emoji.Id, "image"), nil
|
||||
}
|
||||
var nfErr *store.ErrNotFound
|
||||
switch {
|
||||
|
||||
@@ -1767,7 +1767,7 @@ func TestGetPostActionClient(t *testing.T) {
|
||||
{
|
||||
name: "same host with non-plugin path does not get auth",
|
||||
siteURL: "http://localhost:8065",
|
||||
requestURL: "http://localhost:8065/api/v4/posts",
|
||||
requestURL: "http://localhost:8065/v1/team/posts",
|
||||
expectAuth: false,
|
||||
},
|
||||
{
|
||||
@@ -1791,8 +1791,8 @@ func TestGetPostActionClient(t *testing.T) {
|
||||
{
|
||||
name: "path traversal escaping plugins does not get auth",
|
||||
siteURL: "http://localhost:8065",
|
||||
requestURL: "http://localhost:8065/plugins/../api/v4/posts",
|
||||
expectAuth: false, // path.Clean normalizes to /api/v4/posts
|
||||
requestURL: "http://localhost:8065/plugins/../v1/team/posts",
|
||||
expectAuth: false, // path.Clean normalizes to /v1/team/posts
|
||||
},
|
||||
{
|
||||
name: "subpath with plugin path gets auth",
|
||||
@@ -2395,7 +2395,7 @@ func TestDoPluginRequest(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "non-plugins path",
|
||||
rawURL: "/api/v4/users",
|
||||
rawURL: "/v1/team/users",
|
||||
expectErr: true,
|
||||
errDetail: "plugins not in path",
|
||||
},
|
||||
|
||||
@@ -542,7 +542,7 @@ func (a *App) rawSendToPushProxy(msg *model.PushNotification) (model.PushRespons
|
||||
return nil, fmt.Errorf("failed to encode to JSON: %w", err)
|
||||
}
|
||||
|
||||
url := strings.TrimRight(*a.Config().EmailSettings.PushNotificationServer, "/") + model.APIURLSuffixV1 + "/send_push"
|
||||
url := strings.TrimRight(*a.Config().EmailSettings.PushNotificationServer, "/") + model.PushURLSuffix + "/send_push"
|
||||
request, err := http.NewRequest("POST", url, bytes.NewReader(msgJSON))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -627,7 +627,7 @@ func (a *App) SendAckToPushProxy(rctx request.CTX, ack *model.PushNotificationAc
|
||||
|
||||
request, err := http.NewRequest(
|
||||
"POST",
|
||||
strings.TrimRight(*a.Config().EmailSettings.PushNotificationServer, "/")+model.APIURLSuffixV1+"/ack",
|
||||
strings.TrimRight(*a.Config().EmailSettings.PushNotificationServer, "/")+model.PushURLSuffix+"/ack",
|
||||
bytes.NewReader(ackJSON),
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -953,7 +953,7 @@ func TestGetAuthorizationServerMetadata_DCRConfig(t *testing.T) {
|
||||
require.NotNil(t, metadata)
|
||||
|
||||
// Should include registration endpoint when DCR is enabled
|
||||
assert.Equal(t, "https://example.com/api/v4/oauth/apps/register", metadata.RegistrationEndpoint)
|
||||
assert.Equal(t, "https://example.com/v1/team/oauth/apps/register", metadata.RegistrationEndpoint)
|
||||
|
||||
// Should include basic OAuth endpoints
|
||||
assert.Equal(t, "https://example.com", metadata.Issuer)
|
||||
|
||||
@@ -984,8 +984,8 @@ func (a *App) getLinkMetadataForURL(rctx request.CTX, requestURL string) (*openg
|
||||
var body io.ReadCloser
|
||||
var contentType string
|
||||
|
||||
if (request.URL.Scheme+"://"+request.URL.Host) == a.GetSiteURL() && request.URL.Path == "/api/v4/image" {
|
||||
// /api/v4/image requires authentication, so bypass the API by hitting the proxy directly
|
||||
if (request.URL.Scheme+"://"+request.URL.Host) == a.GetSiteURL() && request.URL.Path == model.APIURLSuffix+"/image" {
|
||||
// The image endpoint requires authentication, so bypass the API by hitting the proxy directly
|
||||
body, contentType, err = a.ImageProxy().GetImageDirect(a.ImageProxy().GetUnproxiedImageURL(request.URL.String()))
|
||||
} else {
|
||||
request.Header.Add("Accept", "image/*")
|
||||
|
||||
@@ -1061,7 +1061,7 @@ func TestPreparePostForClientWithImageProxy(t *testing.T) {
|
||||
func testProxyLinkedImage(t *testing.T, th *TestHelper, shouldProxy bool) {
|
||||
postTemplate := ""
|
||||
imageURL := "http://mydomain.com/myimage"
|
||||
proxiedImageURL := "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
proxiedImageURL := "http://mymattermost.com/v1/team/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
|
||||
post := &model.Post{
|
||||
UserId: th.BasicUser.Id,
|
||||
@@ -1136,7 +1136,7 @@ func testProxyOpenGraphImage(t *testing.T, th *TestHelper, shouldProxy bool) {
|
||||
image := og.Images[0]
|
||||
if shouldProxy {
|
||||
assert.Equal(t, "", image.URL, "image URL should not be set with proxy")
|
||||
assert.Equal(t, "http://mymattermost.com/api/v4/image?url="+url.QueryEscape(server.URL+"/test-image3.png"), image.SecureURL, "secure image URL should be sent through proxy")
|
||||
assert.Equal(t, "http://mymattermost.com/v1/team/image?url="+url.QueryEscape(server.URL+"/test-image3.png"), image.SecureURL, "secure image URL should be sent through proxy")
|
||||
} else {
|
||||
assert.Equal(t, server.URL+"/test-image3.png", image.URL, "image URL should be set")
|
||||
assert.Equal(t, "", image.SecureURL, "secure image URL should not be set")
|
||||
@@ -2776,7 +2776,7 @@ func TestGetLinkMetadata(t *testing.T) {
|
||||
assert.IsType(t, &url.Error{}, err)
|
||||
assert.ErrorContains(t, err, httpservice.ErrAddressForbidden.Error())
|
||||
|
||||
requestURL = th.App.GetSiteURL() + "/api/v4/image?url=" + url.QueryEscape(requestURL)
|
||||
requestURL = th.App.GetSiteURL() + "/v1/team/image?url=" + url.QueryEscape(requestURL)
|
||||
|
||||
// Note that this request still fails while testing because the request made by the image proxy is blocked
|
||||
og, img, _, err = th.App.getLinkMetadata(th.Context, requestURL, timestamp, false, "")
|
||||
|
||||
@@ -910,7 +910,7 @@ func TestImageProxy(t *testing.T) {
|
||||
ProxyOptions: testHMACKey,
|
||||
ImageURL: "http://mydomain.com/myimage",
|
||||
ProxiedRemovedImageURL: "http://mydomain.com/myimage",
|
||||
ProxiedImageURL: "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage",
|
||||
ProxiedImageURL: "http://mymattermost.com/v1/team/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage",
|
||||
},
|
||||
"atmos/camo_SameSite": {
|
||||
ProxyType: model.ImageProxyTypeAtmosCamo,
|
||||
@@ -940,7 +940,7 @@ func TestImageProxy(t *testing.T) {
|
||||
ProxyType: model.ImageProxyTypeLocal,
|
||||
ImageURL: "http://mydomain.com/myimage",
|
||||
ProxiedRemovedImageURL: "http://mydomain.com/myimage",
|
||||
ProxiedImageURL: "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage",
|
||||
ProxiedImageURL: "http://mymattermost.com/v1/team/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage",
|
||||
},
|
||||
"local_SameSite": {
|
||||
ProxyType: model.ImageProxyTypeLocal,
|
||||
@@ -1172,7 +1172,7 @@ func TestCreatePost(t *testing.T) {
|
||||
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
|
||||
|
||||
imageURL := "http://mydomain.com/myimage"
|
||||
proxiedImageURL := "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
proxiedImageURL := "http://mymattermost.com/v1/team/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
|
||||
post := &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
@@ -2037,7 +2037,7 @@ func TestPatchPost(t *testing.T) {
|
||||
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
|
||||
|
||||
imageURL := "http://mydomain.com/myimage"
|
||||
proxiedImageURL := "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
proxiedImageURL := "http://mymattermost.com/v1/team/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
|
||||
post := &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
@@ -2498,7 +2498,7 @@ func TestUpdatePost(t *testing.T) {
|
||||
th.App.ch.imageProxy = imageproxy.MakeImageProxy(th.Server.platform, th.Server.HTTPService(), th.Server.Log())
|
||||
|
||||
imageURL := "http://mydomain.com/myimage"
|
||||
proxiedImageURL := "http://mymattermost.com/api/v4/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
proxiedImageURL := "http://mymattermost.com/v1/team/image?url=http%3A%2F%2Fmydomain.com%2Fmyimage"
|
||||
|
||||
post := &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
|
||||
@@ -120,7 +120,7 @@ func sessionAttributeRawValues(t *testing.T, th *TestHelper, sessionID string) m
|
||||
|
||||
func newSessionAttributesRequest(t *testing.T, userAgent, remoteAddr string) *http.Request {
|
||||
t.Helper()
|
||||
r := httptest.NewRequest(http.MethodGet, "/api/v4/test", nil)
|
||||
r := httptest.NewRequest(http.MethodGet, "/v1/team/test", nil)
|
||||
if userAgent != "" {
|
||||
r.Header.Set("User-Agent", userAgent)
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
|
||||
idx := i // Capture index for closure
|
||||
testServers[i] = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v4/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
clusterName := fmt.Sprintf("cluster-%d", idx+1)
|
||||
atomic.AddInt32(syncMessagesPerCluster[clusterName], 1)
|
||||
|
||||
@@ -455,7 +455,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
// Create test HTTP server
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/v4/remotecluster/msg":
|
||||
case "/v1/team/remotecluster/msg":
|
||||
syncAttempts.Add(1)
|
||||
|
||||
if failureMode.Load() {
|
||||
@@ -470,7 +470,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
} else {
|
||||
writeOKResponse(w)
|
||||
}
|
||||
case "/api/v4/remotecluster/ping":
|
||||
case "/v1/team/remotecluster/ping":
|
||||
writeOKResponse(w)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
@@ -577,7 +577,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
|
||||
// Create test HTTP server
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v4/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
syncMessageCount.Add(1)
|
||||
}
|
||||
writeOKResponse(w)
|
||||
@@ -666,7 +666,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
|
||||
// Create test HTTP server
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v4/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
syncMessageCount.Add(1)
|
||||
|
||||
// Parse message to check if it's a user sync
|
||||
@@ -855,7 +855,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/api/v4/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
syncAttempts.Add(1)
|
||||
// On second attempt, go offline
|
||||
if syncAttempts.Load() >= 2 {
|
||||
@@ -1106,7 +1106,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
|
||||
// Create test HTTP servers for both "servers"
|
||||
serverAHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v4/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
// Parse message to track what gets synced back to A
|
||||
bodyBytes, _ := io.ReadAll(r.Body)
|
||||
var frame model.RemoteClusterFrame
|
||||
@@ -1125,7 +1125,7 @@ func TestSharedChannelGlobalUserSyncSelfReferential(t *testing.T) {
|
||||
})
|
||||
|
||||
serverBHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v4/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
// Parse message to track what gets synced to B
|
||||
bodyBytes, _ := io.ReadAll(r.Body)
|
||||
var frame model.RemoteClusterFrame
|
||||
|
||||
@@ -534,7 +534,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
|
||||
var syncHandler *SelfReferentialSyncHandler
|
||||
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v4/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
syncAttempts.Add(1)
|
||||
|
||||
if failureMode.Load() {
|
||||
@@ -668,7 +668,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
|
||||
|
||||
// Create test HTTP server
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v4/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
bodyBytes, _ := io.ReadAll(r.Body)
|
||||
|
||||
// Count add and remove operations
|
||||
@@ -866,7 +866,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
|
||||
// Create test server for this cluster
|
||||
idx := i // Capture index for closure
|
||||
testServers[i] = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v4/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
clusterName := fmt.Sprintf("cluster-%d", idx+1)
|
||||
atomic.AddInt32(syncMessagesPerCluster[clusterName], 1)
|
||||
|
||||
@@ -1202,7 +1202,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/api/v4/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
currentAttempt := syncAttempts.Add(1)
|
||||
// On second sync cycle, go offline (allow first full sync to complete)
|
||||
if currentAttempt > 2 {
|
||||
@@ -1387,7 +1387,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
|
||||
// Create a wrapper handler to intercept sync messages
|
||||
testServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Intercept and track channel syncs
|
||||
if r.URL.Path == "/api/v4/remotecluster/msg" {
|
||||
if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
bodyBytes, pErr := io.ReadAll(r.Body)
|
||||
if pErr == nil {
|
||||
// Restore body for actual handler
|
||||
@@ -1584,7 +1584,7 @@ func TestSharedChannelMembershipSyncSelfReferential(t *testing.T) {
|
||||
|
||||
// // Create test HTTP server that tracks sync messages
|
||||
// testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// if r.URL.Path == "/api/v4/remotecluster/msg" {
|
||||
// if r.URL.Path == "/v1/team/remotecluster/msg" {
|
||||
// syncMessageCount.Add(1)
|
||||
|
||||
// // Read body once
|
||||
|
||||
@@ -62,12 +62,12 @@ func NewSelfReferentialSyncHandler(t *testing.T, service *sharedchannel.Service,
|
||||
|
||||
// HandleRequest processes incoming HTTP requests for the test server.
|
||||
// This handler includes common remote cluster endpoints to simulate a real remote cluster:
|
||||
// - /api/v4/remotecluster/msg: Main sync message endpoint
|
||||
// - /api/v4/remotecluster/ping: Ping endpoint to maintain online status (prevents offline after 5 minutes)
|
||||
// - /api/v4/remotecluster/confirm_invite: Invitation confirmation endpoint
|
||||
// - /v1/team/remotecluster/msg: Main sync message endpoint
|
||||
// - /v1/team/remotecluster/ping: Ping endpoint to maintain online status (prevents offline after 5 minutes)
|
||||
// - /v1/team/remotecluster/confirm_invite: Invitation confirmation endpoint
|
||||
func (h *SelfReferentialSyncHandler) HandleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/v4/remotecluster/msg":
|
||||
case "/v1/team/remotecluster/msg":
|
||||
currentCall := h.syncMessageCount.Add(1)
|
||||
|
||||
// Read and process the sync message
|
||||
@@ -150,10 +150,10 @@ func (h *SelfReferentialSyncHandler) HandleRequest(w http.ResponseWriter, r *htt
|
||||
|
||||
writeOKResponse(w)
|
||||
|
||||
case "/api/v4/remotecluster/ping":
|
||||
case "/v1/team/remotecluster/ping":
|
||||
writeOKResponse(w)
|
||||
|
||||
case "/api/v4/remotecluster/confirm_invite":
|
||||
case "/v1/team/remotecluster/confirm_invite":
|
||||
writeOKResponse(w)
|
||||
|
||||
default:
|
||||
|
||||
@@ -680,7 +680,7 @@ func TestCreateWebhookPostWithOverriddenIcon(t *testing.T) {
|
||||
|
||||
clientPost := th.App.PreparePostForClient(th.Context, post, &model.PreparePostForClientOpts{IsNewPost: true})
|
||||
|
||||
assert.Equal(t, fmt.Sprintf("/api/v4/emoji/%s/image", emoji.Id), clientPost.GetProp(model.PostPropsOverrideIconURL))
|
||||
assert.Equal(t, fmt.Sprintf("/v1/team/emoji/%s/image", emoji.Id), clientPost.GetProp(model.PostPropsOverrideIconURL))
|
||||
})
|
||||
|
||||
t.Run("should set props based on icon_emoji (with colons around emoji name)", func(t *testing.T) {
|
||||
|
||||
@@ -38,9 +38,9 @@ func TestHandlerServeHTTPErrors(t *testing.T) {
|
||||
redirect bool
|
||||
}{
|
||||
{"redirect on desktop non-api endpoint", "/login/sso/saml", false, true},
|
||||
{"not redirect on desktop api endpoint", "/api/v4/test", false, false},
|
||||
{"not redirect on desktop api endpoint", "/v1/team/test", false, false},
|
||||
{"not redirect on mobile non-api endpoint", "/login/sso/saml", true, false},
|
||||
{"not redirect on mobile api endpoint", "/api/v4/test", true, false},
|
||||
{"not redirect on mobile api endpoint", "/v1/team/test", true, false},
|
||||
}
|
||||
|
||||
for _, tt := range flagtests {
|
||||
@@ -71,7 +71,7 @@ func TestHandlerServeDefaultSecurityHeaders(t *testing.T) {
|
||||
handler := web.NewHandler(handlerForServeDefaultSecurityHeaders)
|
||||
|
||||
paths := []string{
|
||||
"/api/v4/test", // API
|
||||
"/v1/team/test", // API
|
||||
"/static/manifest.json", // this should always exist. Static files have their own handler
|
||||
// Note that the plugin handler isn't tested, also plugins may support arbitrary functionality
|
||||
}
|
||||
@@ -127,7 +127,7 @@ func TestHandlerServeHTTPSecureTransport(t *testing.T) {
|
||||
web := New(th.Server)
|
||||
handler := web.NewHandler(handlerForHTTPSecureTransport)
|
||||
|
||||
request := httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||
request := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
@@ -145,7 +145,7 @@ func TestHandlerServeHTTPSecureTransport(t *testing.T) {
|
||||
*config.ServiceSettings.TLSStrictTransport = false
|
||||
})
|
||||
|
||||
request = httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||
request = httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
|
||||
response = httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
@@ -199,7 +199,7 @@ func TestHandlerServeCSRFToken(t *testing.T) {
|
||||
|
||||
// CSRF Token Used - Success Expected
|
||||
|
||||
request := httptest.NewRequest("POST", "/api/v4/test", nil)
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request.AddCookie(cookie)
|
||||
request.AddCookie(cookie2)
|
||||
request.AddCookie(cookie3)
|
||||
@@ -213,7 +213,7 @@ func TestHandlerServeCSRFToken(t *testing.T) {
|
||||
|
||||
// No CSRF Token Used - Failure Expected
|
||||
|
||||
request = httptest.NewRequest("POST", "/api/v4/test", nil)
|
||||
request = httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request.AddCookie(cookie)
|
||||
request.AddCookie(cookie2)
|
||||
request.AddCookie(cookie3)
|
||||
@@ -229,7 +229,7 @@ func TestHandlerServeCSRFToken(t *testing.T) {
|
||||
th.App.UpdateConfig(func(config *model.Config) {
|
||||
*config.ServiceSettings.ExperimentalStrictCSRFEnforcement = false
|
||||
})
|
||||
request = httptest.NewRequest("POST", "/api/v4/test", nil)
|
||||
request = httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request.AddCookie(cookie)
|
||||
request.AddCookie(cookie2)
|
||||
request.AddCookie(cookie3)
|
||||
@@ -266,7 +266,7 @@ func TestHandlerServeCSRFToken(t *testing.T) {
|
||||
|
||||
// CSRF Token Used - Success Expected
|
||||
|
||||
request = httptest.NewRequest("POST", "/api/v4/test", nil)
|
||||
request = httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request.AddCookie(cookie)
|
||||
request.AddCookie(cookie2)
|
||||
request.AddCookie(cookie3)
|
||||
@@ -280,7 +280,7 @@ func TestHandlerServeCSRFToken(t *testing.T) {
|
||||
|
||||
// No CSRF Token Used - Failure Expected
|
||||
|
||||
request = httptest.NewRequest("POST", "/api/v4/test", nil)
|
||||
request = httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request.AddCookie(cookie)
|
||||
request.AddCookie(cookie2)
|
||||
request.AddCookie(cookie3)
|
||||
@@ -310,7 +310,7 @@ func TestHandlerServeCSPHeader(t *testing.T) {
|
||||
IsStatic: false,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest("POST", "/api/v4/test", nil)
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
assert.Equal(t, 200, response.Code)
|
||||
@@ -609,7 +609,7 @@ func TestHandlerServeInvalidToken(t *testing.T) {
|
||||
Value: "invalid",
|
||||
}
|
||||
|
||||
request := httptest.NewRequest("POST", "/api/v4/test", nil)
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request.AddCookie(cookie)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
@@ -670,7 +670,7 @@ func TestHandlerServeCSRFFailureClearsAuthCookie(t *testing.T) {
|
||||
Value: session.Token,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest("POST", "/api/v4/test", nil)
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
request.AddCookie(cookie)
|
||||
request.Header.Add(model.HeaderRequestedWith, model.HeaderRequestedWithXML)
|
||||
response := httptest.NewRecorder()
|
||||
@@ -1022,7 +1022,7 @@ func TestHandlerServeHTTPBasicSecurityChecks(t *testing.T) {
|
||||
handler := web.NewHandler(noOpHandler)
|
||||
|
||||
// using the default URL length
|
||||
request := httptest.NewRequest("GET", "/api/v4/test?with=not&many=query_params", nil)
|
||||
request := httptest.NewRequest("GET", "/v1/team/test?with=not&many=query_params", nil)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
assert.Equal(t, http.StatusOK, response.Code)
|
||||
@@ -1053,7 +1053,7 @@ func TestHandlerServeHTTPBasicSecurityChecks(t *testing.T) {
|
||||
web := New(th.Server)
|
||||
handler := web.NewHandler(noOpHandler)
|
||||
|
||||
request := httptest.NewRequest("GET", "/api/v4/test?a_url_longer_than_10_characters_including_query_params", nil)
|
||||
request := httptest.NewRequest("GET", "/v1/team/test?a_url_longer_than_10_characters_including_query_params", nil)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
assert.Equal(t, http.StatusRequestURITooLong, response.Code)
|
||||
@@ -1086,7 +1086,7 @@ func TestHandlerServeHTTPBasicSecurityChecks(t *testing.T) {
|
||||
|
||||
// this URL is within the 20 characters limit excluding query params.
|
||||
// but this should still fail as URL length includes query params
|
||||
request := httptest.NewRequest("GET", "/api/v4/test?a_url_longer_than_10_characters_including_query_params", nil)
|
||||
request := httptest.NewRequest("GET", "/v1/team/test?a_url_longer_than_10_characters_including_query_params", nil)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
assert.Equal(t, http.StatusRequestURITooLong, response.Code)
|
||||
@@ -1114,7 +1114,7 @@ func TestHandlerServeHTTPRequestPayloadLimit(t *testing.T) {
|
||||
handler := web.NewHandler(jsonReaderHandler)
|
||||
|
||||
body := strings.NewReader("\"a very small request body\"")
|
||||
request := httptest.NewRequest("POST", "/api/v4/test", body)
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", body)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
|
||||
@@ -1150,7 +1150,7 @@ func TestHandlerServeHTTPRequestPayloadLimit(t *testing.T) {
|
||||
// Even though we have set the max payload size to be 1, we still need at least 513 bytes (1 byte configured + bytes.MinRead = 1 + 512 = 513) bytes.
|
||||
// This is because the buffer will always be at least bytes.MinRead bytes large, so the effective payload limit is bytes.MinRead + the configured value.
|
||||
body := strings.NewReader("\"weunfrwghyajuaqqkrecexurpmrmgpimjieymiwfhfrrrgiqpqrznkjtubwcuybyixyxwtwddpytukritccyugyeuvdtzjkkyiwhquzqkrqkhgyyfqnquzchjqkrkzfrxthduzizqtdxzqirxhzihbivmkdwpbeddepdanzuuzqxbdqfvgkwumervhghywexitbjdnvxcniuamwmqdecbbqbgnjjqwkdcucvnpwynuruztpdtmmvbpkevurpjdwdhpayaindzmnkmyybudfkjdkqwuiviriudtqytybuwfkkwpepwhpekfewnxgpkfctdqjmemngvntnizvfznaiqpbumgtcxidvawtgcdyqijbxzrgezvjmcwikiabbpqabrwfgncrmvqththepffatnhchhnmrhkuqvgrzfugzhuwicaemhcacrazmgzmrgrkuhwucfydhwxfhzfukzjhvdxkuhzjrxwippxadvwzigndxwdxvganxggjjxwdqtgqgnpqqygndviadvttwfntcreitijaqrpfygdehbcftyfcjvrfwvjmbtdptutjgtbyhbyddfecyyujgrxyujzmryymj\"")
|
||||
request := httptest.NewRequest("POST", "/api/v4/test", body)
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", body)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
|
||||
@@ -1172,7 +1172,7 @@ func TestHandlerConnectionIdHeader(t *testing.T) {
|
||||
web := New(th.Server)
|
||||
handler := web.NewHandler(handlerFunc)
|
||||
|
||||
request := httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||
request := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
request.Header.Set(model.ConnectionId, connectionId)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
@@ -1193,7 +1193,7 @@ func TestHandlerConnectionIdHeader(t *testing.T) {
|
||||
web := New(th.Server)
|
||||
handler := web.NewHandler(handlerFunc)
|
||||
|
||||
request := httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||
request := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
|
||||
@@ -1221,7 +1221,7 @@ func TestHandleContextErrorZeroStatusCode(t *testing.T) {
|
||||
Err: appErr,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest("POST", "/api/v4/test", nil)
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
h := Handler{
|
||||
@@ -1246,7 +1246,7 @@ func TestHandleContextErrorZeroStatusCode(t *testing.T) {
|
||||
Err: appErr,
|
||||
}
|
||||
|
||||
request := httptest.NewRequest("POST", "/api/v4/test", nil)
|
||||
request := httptest.NewRequest("POST", "/v1/team/test", nil)
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
h := Handler{
|
||||
|
||||
@@ -51,7 +51,7 @@ func (w *Web) InitOAuth() {
|
||||
w.MainRouter.Handle("/api/v3/oauth/{service:[A-Za-z0-9]+}/complete", w.APIHandler(completeOAuth)).Methods(http.MethodGet)
|
||||
w.MainRouter.Handle("/signup/{service:[A-Za-z0-9]+}/complete", w.APIHandler(completeOAuth)).Methods(http.MethodGet)
|
||||
w.MainRouter.Handle("/login/{service:[A-Za-z0-9]+}/complete", w.APIHandler(completeOAuth)).Methods(http.MethodGet)
|
||||
w.MainRouter.Handle("/api/v4/oauth_test", w.APISessionRequired(testHandler)).Methods(http.MethodGet)
|
||||
w.MainRouter.Handle(model.APIURLSuffix+"/oauth_test", w.APISessionRequired(testHandler)).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
func testHandler(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -135,7 +135,7 @@ type Params struct {
|
||||
RequestId string
|
||||
}
|
||||
|
||||
var getChannelMembersForUserRegex = regexp.MustCompile("/api/v4/users/[A-Za-z0-9]{26}/channel_members")
|
||||
var getChannelMembersForUserRegex = regexp.MustCompile(model.APIURLSuffix + "/users/[A-Za-z0-9]{26}/channel_members")
|
||||
|
||||
func ParamsFromRequest(r *http.Request) *Params {
|
||||
params := &Params{}
|
||||
|
||||
@@ -37,7 +37,7 @@ func newResponseWithHijack(original *httptest.ResponseRecorder) *responseRecorde
|
||||
|
||||
func TestStatusCodeIsAccessible(t *testing.T) {
|
||||
resp := newWrappedWriter(httptest.NewRecorder())
|
||||
req := httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
handler := TestHandler{func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}}
|
||||
@@ -47,7 +47,7 @@ func TestStatusCodeIsAccessible(t *testing.T) {
|
||||
|
||||
func TestStatusCodeShouldBe200IfNotHeaderWritten(t *testing.T) {
|
||||
resp := newWrappedWriter(httptest.NewRecorder())
|
||||
req := httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
handler := TestHandler{func(w http.ResponseWriter, r *http.Request) {
|
||||
n, err := w.Write([]byte{})
|
||||
require.NoError(t, err, "Failed to write response")
|
||||
@@ -59,7 +59,7 @@ func TestStatusCodeShouldBe200IfNotHeaderWritten(t *testing.T) {
|
||||
|
||||
func TestForUnsupportedHijack(t *testing.T) {
|
||||
resp := newWrappedWriter(httptest.NewRecorder())
|
||||
req := httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
handler := TestHandler{func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, rw, err := w.(*responseWriterWrapper).Hijack()
|
||||
assert.Error(t, err)
|
||||
@@ -72,7 +72,7 @@ func TestForUnsupportedHijack(t *testing.T) {
|
||||
|
||||
func TestForSupportedHijack(t *testing.T) {
|
||||
resp := newWrappedWriter(newResponseWithHijack(httptest.NewRecorder()))
|
||||
req := httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
handler := TestHandler{func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, rw, err := w.(*responseWriterWrapper).Hijack()
|
||||
require.NoError(t, err, "Hijack should succeed with supporting ResponseWriter")
|
||||
@@ -84,7 +84,7 @@ func TestForSupportedHijack(t *testing.T) {
|
||||
|
||||
func TestForSupportedFlush(t *testing.T) {
|
||||
resp := newWrappedWriter(httptest.NewRecorder())
|
||||
req := httptest.NewRequest("GET", "/api/v4/test", nil)
|
||||
req := httptest.NewRequest("GET", "/v1/team/test", nil)
|
||||
handler := TestHandler{func(w http.ResponseWriter, r *http.Request) {
|
||||
n, err := w.Write([]byte{})
|
||||
require.NoError(t, err, "Failed to write response")
|
||||
|
||||
@@ -183,7 +183,7 @@ func (s *MmctlE2ETestSuite) TestArchiveCommandCmdF() {
|
||||
|
||||
err := archiveCommandCmdF(c, &cobra.Command{}, []string{nonexistentCommandID})
|
||||
s.Require().NotNil(err)
|
||||
s.Require().Equal(fmt.Sprintf("Unable to archive command '%s' error: Sorry, we could not find the page., There doesn't appear to be an api call for the url='/api/v4/commands/nonexistent-command-id'. Typo? are you missing a team_id or user_id as part of the url?", nonexistentCommandID), err.Error())
|
||||
s.Require().Equal(fmt.Sprintf("Unable to archive command '%s' error: Sorry, we could not find the page., There doesn't appear to be an api call for the url='/v1/team/commands/nonexistent-command-id'. Typo? are you missing a team_id or user_id as part of the url?", nonexistentCommandID), err.Error())
|
||||
s.Require().Len(printer.GetLines(), 0)
|
||||
s.Require().Len(printer.GetErrorLines(), 0)
|
||||
})
|
||||
|
||||
@@ -168,7 +168,7 @@ func TestVerifyCertificates(t *testing.T) {
|
||||
func TestNewAPIv4Client(t *testing.T) {
|
||||
t.Run("should take http proxy into account", func(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
router.Handle("/api/v4/users/me", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
router.Handle("/v1/team/users/me", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user := &model.User{
|
||||
Id: model.NewId(),
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -219,7 +221,7 @@ func Flush() error {
|
||||
fallthrough
|
||||
case len(printer.Lines) > 0:
|
||||
entity := cmd.Parent().Name()
|
||||
container := strings.TrimSuffix(printer.serverAddr, "api/v4")
|
||||
container := strings.TrimSuffix(printer.serverAddr, model.APIURLSuffix)
|
||||
if container != "" {
|
||||
container = fmt.Sprintf(" on %s", container)
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ services:
|
||||
- './../../enterprise:/home/enterprise'
|
||||
restart: on-failure
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://leader:8065/api/v4/system/ping"]
|
||||
test: ["CMD", "curl", "-f", "http://leader:8065/v1/team/system/ping"]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
retries: 30
|
||||
@@ -165,7 +165,7 @@ services:
|
||||
- './../:/home/mattermost-server:Z'
|
||||
- './../../enterprise:/home/enterprise:Z'
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://follower:8065/api/v4/system/ping"]
|
||||
test: ["CMD", "curl", "-f", "http://follower:8065/v1/team/system/ping"]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
retries: 30
|
||||
@@ -199,7 +199,7 @@ services:
|
||||
- './../:/home/mattermost-server:Z'
|
||||
- './../../enterprise:/home/enterprise:Z'
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://follower2:8065/api/v4/system/ping"]
|
||||
test: ["CMD", "curl", "-f", "http://follower2:8065/v1/team/system/ping"]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
retries: 30
|
||||
|
||||
@@ -153,7 +153,7 @@ func (proxy *ImageProxy) GetProxiedImageURL(imageURL string) string {
|
||||
return parsedURL.String()
|
||||
}
|
||||
|
||||
return proxy.siteURL.String() + "/api/v4/image?url=" + url.QueryEscape(parsedURL.String())
|
||||
return proxy.siteURL.String() + model.APIURLSuffix + "/image?url=" + url.QueryEscape(parsedURL.String())
|
||||
}
|
||||
|
||||
// GetUnproxiedImageURL takes the URL of an image on the image proxy and returns the original URL of the image.
|
||||
@@ -162,7 +162,7 @@ func (proxy *ImageProxy) GetUnproxiedImageURL(proxiedURL string) string {
|
||||
}
|
||||
|
||||
func getUnproxiedImageURL(proxiedURL, siteURL string) string {
|
||||
if !strings.HasPrefix(proxiedURL, siteURL+"/api/v4/image?url=") {
|
||||
if !strings.HasPrefix(proxiedURL, siteURL+model.APIURLSuffix+"/image?url=") {
|
||||
return proxiedURL
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestGetProxiedImageURL(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
imageURL := "https://hanzo.ai/wp-content/uploads/2022/02/logoHorizontal.png"
|
||||
proxiedURL := "https://mattermost.example.com/api/v4/image?url=https%3A%2F%2Fmattermost.com%2Fwp-content%2Fuploads%2F2022%2F02%2FlogoHorizontal.png"
|
||||
proxiedURL := "https://mattermost.example.com/v1/team/image?url=https%3A%2F%2Fmattermost.com%2Fwp-content%2Fuploads%2F2022%2F02%2FlogoHorizontal.png"
|
||||
|
||||
proxy := ImageProxy{siteURL: parsedURL}
|
||||
|
||||
@@ -56,17 +56,17 @@ func TestGetProxiedImageURL(t *testing.T) {
|
||||
{
|
||||
Name: "should not bypass protocol relative URLs",
|
||||
Input: "//mattermost.com/static/logo.png",
|
||||
Expected: "https://mattermost.example.com/api/v4/image?url=https%3A%2F%2Fmattermost.com%2Fstatic%2Flogo.png",
|
||||
Expected: "https://mattermost.example.com/v1/team/image?url=https%3A%2F%2Fmattermost.com%2Fstatic%2Flogo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not bypass if the host prefix is same",
|
||||
Input: "https://mattermost.example.com.anothersite.com/static/logo.png",
|
||||
Expected: "https://mattermost.example.com/api/v4/image?url=https%3A%2F%2Fmattermost.example.com.anothersite.com%2Fstatic%2Flogo.png",
|
||||
Expected: "https://mattermost.example.com/v1/team/image?url=https%3A%2F%2Fmattermost.example.com.anothersite.com%2Fstatic%2Flogo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not bypass for user auth URLs",
|
||||
Input: "https://[email protected]/static/logo.png",
|
||||
Expected: "https://mattermost.example.com/api/v4/image?url=https%3A%2F%2Fmattermost.example.com%40anothersite.com%2Fstatic%2Flogo.png",
|
||||
Expected: "https://mattermost.example.com/v1/team/image?url=https%3A%2F%2Fmattermost.example.com%40anothersite.com%2Fstatic%2Flogo.png",
|
||||
},
|
||||
{
|
||||
Name: "should not proxy embedded image",
|
||||
@@ -84,7 +84,7 @@ func TestGetUnproxiedImageURL(t *testing.T) {
|
||||
siteURL := "https://mattermost.example.com"
|
||||
|
||||
imageURL := "https://hanzo.ai/wp-content/uploads/2022/02/logoHorizontal.png"
|
||||
proxiedURL := "https://mattermost.example.com/api/v4/image?url=https%3A%2F%2Fmattermost.com%2Fwp-content%2Fuploads%2F2022%2F02%2FlogoHorizontal.png"
|
||||
proxiedURL := "https://mattermost.example.com/v1/team/image?url=https%3A%2F%2Fmattermost.com%2Fwp-content%2Fuploads%2F2022%2F02%2FlogoHorizontal.png"
|
||||
|
||||
for _, test := range []struct {
|
||||
Name string
|
||||
|
||||
@@ -43,7 +43,7 @@ func (rcs *Service) AcceptInvitation(invite *model.RemoteClusterInvite, name str
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/%s", rcSaved.SiteURL, ConfirmInviteURL)
|
||||
url := fmt.Sprintf("%s%s", rcSaved.SiteURL, ConfirmInviteURL)
|
||||
|
||||
// for the invite confirm message, we need to use the token that
|
||||
// the originating server sent in the invite instead of the one
|
||||
|
||||
@@ -135,7 +135,7 @@ func (rcs *Service) pingRemote(rc *model.RemoteCluster) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url := fmt.Sprintf("%s/%s", rc.SiteURL, PingURL)
|
||||
url := fmt.Sprintf("%s%s", rc.SiteURL, PingURL)
|
||||
|
||||
resp, err := rcs.sendFrameToRemote(PingTimeout, rc, frame, url)
|
||||
if err != nil {
|
||||
|
||||
@@ -23,15 +23,15 @@ const (
|
||||
ResultsChanBuffer = 50
|
||||
ResultQueueDrainTimeoutMillis = 10000
|
||||
MaxConcurrentSends = 10
|
||||
SendMsgURL = "api/v4/remotecluster/msg"
|
||||
SendMsgURL = model.APIURLSuffix + "/remotecluster/msg"
|
||||
SendTimeout = time.Minute
|
||||
SendFileTimeout = time.Minute * 5
|
||||
PingURL = "api/v4/remotecluster/ping"
|
||||
PingURL = model.APIURLSuffix + "/remotecluster/ping"
|
||||
// Raising PingFreq also raises the transport's IdleConnTimeout (PingFreq / 2),
|
||||
// which must stay below peer idle timeouts (60s default). See MM-69982.
|
||||
PingFreq = time.Minute
|
||||
PingTimeout = time.Second * 15
|
||||
ConfirmInviteURL = "api/v4/remotecluster/confirm_invite"
|
||||
ConfirmInviteURL = model.APIURLSuffix + "/remotecluster/confirm_invite"
|
||||
InvitationTopic = "invitation"
|
||||
PingTopic = "ping"
|
||||
ResponseStatusOK = model.StatusOk
|
||||
|
||||
@@ -158,50 +158,50 @@ sequenceDiagram
|
||||
participant SB as Server B
|
||||
|
||||
Note over UA,SB: Phase 1: Generate Invitation
|
||||
UA->>SA: POST /api/v4/remotecluster<br/>{name, display_name, password}
|
||||
UA->>SA: POST /v1/team/remotecluster<br/>{name, display_name, password}
|
||||
SA->>SA: Generate RemoteId, Token
|
||||
SA->>SA: Encrypt invitation (PBKDF2 + AES-GCM)
|
||||
SA->>UA: Return encrypted invite code
|
||||
|
||||
Note over UA,SB: Phase 2: Accept Invitation
|
||||
UA->>UB: Share invite code + password<br/>(out of band)
|
||||
UB->>SB: POST /api/v4/remotecluster/accept_invite<br/>{invite, password}
|
||||
UB->>SB: POST /v1/team/remotecluster/accept_invite<br/>{invite, password}
|
||||
SB->>SB: Decrypt invitation
|
||||
SB->>SB: Create RemoteCluster record
|
||||
SB->>SA: POST /api/v4/remotecluster/confirm_invite<br/>X-MM-RemoteCluster-Token: [token]<br/>{remote_id, site_url, token}
|
||||
SB->>SA: POST /v1/team/remotecluster/confirm_invite<br/>X-MM-RemoteCluster-Token: [token]<br/>{remote_id, site_url, token}
|
||||
SA->>SA: Update RemoteCluster with SiteURL
|
||||
SA->>SB: 200 OK
|
||||
SB->>UB: Connection established
|
||||
|
||||
Note over UA,SB: Phase 3: Continuous Heartbeat
|
||||
loop Every 60 seconds
|
||||
SA->>SB: POST /api/v4/remotecluster/ping<br/>{sent_at}
|
||||
SA->>SB: POST /v1/team/remotecluster/ping<br/>{sent_at}
|
||||
SB->>SA: {sent_at, recv_at}
|
||||
SB->>SA: POST /api/v4/remotecluster/ping<br/>{sent_at}
|
||||
SB->>SA: POST /v1/team/remotecluster/ping<br/>{sent_at}
|
||||
SA->>SB: {sent_at, recv_at}
|
||||
end
|
||||
```
|
||||
|
||||
**Step 1: Create Invitation (Server A)**
|
||||
- API: `POST /api/v4/remotecluster`
|
||||
- API: `POST /v1/team/remotecluster`
|
||||
- Generates encrypted invitation with PBKDF2 encryption
|
||||
- Returns base64-encoded invite code
|
||||
- Creates pending RemoteCluster record
|
||||
|
||||
**Step 2: Accept Invitation (Server B)**
|
||||
- API: `POST /api/v4/remotecluster/accept_invite`
|
||||
- API: `POST /v1/team/remotecluster/accept_invite`
|
||||
- Decrypts invitation using password
|
||||
- Creates local RemoteCluster record
|
||||
- Sends confirmation to Server A
|
||||
|
||||
**Step 3: Confirm Connection (Server A)**
|
||||
- API: `POST /api/v4/remotecluster/confirm_invite`
|
||||
- API: `POST /v1/team/remotecluster/confirm_invite`
|
||||
- Receives confirmation from Server B
|
||||
- Updates RemoteCluster with actual SiteURL
|
||||
- Connection established
|
||||
|
||||
**Step 4: Continuous Heartbeat**
|
||||
- API: `POST /api/v4/remotecluster/ping`
|
||||
- API: `POST /v1/team/remotecluster/ping`
|
||||
- Every 60 seconds (default)
|
||||
- Updates `LastPingAt` timestamp
|
||||
- Remote considered online if pinged within 5 minutes
|
||||
@@ -219,7 +219,7 @@ sequenceDiagram
|
||||
UA->>SA: Invite Remote B to Channel
|
||||
SA->>SA: Create SharedChannel (Home=true)
|
||||
SA->>SA: Create SharedChannelRemote
|
||||
SA->>SB: POST /api/v4/remotecluster/msg<br/>Topic: sharedchannel_invite<br/>{channel_id, name, type, ...}
|
||||
SA->>SB: POST /v1/team/remotecluster/msg<br/>Topic: sharedchannel_invite<br/>{channel_id, name, type, ...}
|
||||
SB->>SB: Validate invitation
|
||||
SB->>SB: Create local channel
|
||||
SB->>SB: Create SharedChannel (Home=false)
|
||||
@@ -231,7 +231,7 @@ sequenceDiagram
|
||||
Note over UA,SB: Initial Sync
|
||||
SA->>SA: Queue sync task for channel
|
||||
SA->>SA: Collect users, posts, reactions
|
||||
SA->>SB: POST /api/v4/remotecluster/msg<br/>Topic: sharedchannel_sync<br/>{users, posts, reactions, ...}
|
||||
SA->>SB: POST /v1/team/remotecluster/msg<br/>Topic: sharedchannel_sync<br/>{users, posts, reactions, ...}
|
||||
SB->>SB: Process sync message
|
||||
SB->>SB: Create synthetic users
|
||||
SB->>SB: Create posts
|
||||
@@ -247,7 +247,7 @@ sequenceDiagram
|
||||
- Contains channel metadata (name, type, permissions)
|
||||
|
||||
**Step 2: Receive Invitation (Server B)**
|
||||
- API: `POST /api/v4/remotecluster/msg` (topic: `sharedchannel_invite`)
|
||||
- API: `POST /v1/team/remotecluster/msg` (topic: `sharedchannel_invite`)
|
||||
- Creates local channel (regular, DM, or GM)
|
||||
- Creates SharedChannel record (`Home=false`)
|
||||
- Creates SharedChannelRemote record
|
||||
@@ -278,13 +278,13 @@ sequenceDiagram
|
||||
SA->>SA: Filter & batch (100 posts max)
|
||||
|
||||
alt Has file attachments
|
||||
SA->>SB: POST /api/v4/remotecluster/msg<br/>Topic: sharedchannel_upload<br/>{upload_session}
|
||||
SA->>SB: POST /v1/team/remotecluster/msg<br/>Topic: sharedchannel_upload<br/>{upload_session}
|
||||
SB->>SA: 200 OK {session_id}
|
||||
SA->>SB: POST /api/v4/remotecluster/upload/{id}<br/>multipart file data
|
||||
SA->>SB: POST /v1/team/remotecluster/upload/{id}<br/>multipart file data
|
||||
SB->>SB: Save file to filestore
|
||||
end
|
||||
|
||||
SA->>SB: POST /api/v4/remotecluster/msg<br/>Topic: sharedchannel_sync<br/>Headers: X-MM-RemoteCluster-Id, Token<br/>{SyncMsg}
|
||||
SA->>SB: POST /v1/team/remotecluster/msg<br/>Topic: sharedchannel_sync<br/>Headers: X-MM-RemoteCluster-Id, Token<br/>{SyncMsg}
|
||||
|
||||
Note over SB: Process Sync Message
|
||||
SB->>SB: Validate auth token
|
||||
@@ -302,7 +302,7 @@ sequenceDiagram
|
||||
SB->>SB: Save post
|
||||
SB->>UB: WebSocket: posted event
|
||||
SB->>SB: Queue sync task
|
||||
SB->>SA: POST /api/v4/remotecluster/msg<br/>Topic: sharedchannel_sync
|
||||
SB->>SA: POST /v1/team/remotecluster/msg<br/>Topic: sharedchannel_sync
|
||||
SA->>SA: Process sync message
|
||||
SA->>UA: WebSocket: posted event
|
||||
SA->>SB: 200 OK {timestamps}
|
||||
@@ -346,7 +346,7 @@ graph LR
|
||||
- Attachments: All for synced posts
|
||||
|
||||
**Send Sync Message (Server A → Server B)**
|
||||
- API: `POST /api/v4/remotecluster/msg` (topic: `sharedchannel_sync`)
|
||||
- API: `POST /v1/team/remotecluster/msg` (topic: `sharedchannel_sync`)
|
||||
- Headers:
|
||||
- `X-MM-RemoteCluster-Id`: Remote cluster ID
|
||||
- `X-MM-RemoteCluster-Token`: Authentication token
|
||||
@@ -380,14 +380,14 @@ graph LR
|
||||
1. Server A creates upload session
|
||||
2. Sends upload creation message (topic: `sharedchannel_upload`)
|
||||
3. Server B creates matching session
|
||||
4. Server A streams file data: `POST /api/v4/remotecluster/upload/{upload_id}`
|
||||
4. Server A streams file data: `POST /v1/team/remotecluster/upload/{upload_id}`
|
||||
5. Server B saves file and creates FileInfo record
|
||||
|
||||
#### 5. Profile Image Synchronization
|
||||
|
||||
**Upload Flow:**
|
||||
1. Detect user image update (`LastPictureUpdate` changed)
|
||||
2. Server A uploads image: `POST /api/v4/remotecluster/{user_id}/image`
|
||||
2. Server A uploads image: `POST /v1/team/remotecluster/{user_id}/image`
|
||||
3. Server B validates user belongs to remote
|
||||
4. Saves image and invalidates cache
|
||||
|
||||
@@ -434,7 +434,7 @@ sequenceDiagram
|
||||
SB->>SB: Store RemoteToken = Token_A
|
||||
|
||||
Note over SA,SB: Server A sends message to Server B
|
||||
SA->>SB: POST /api/v4/remotecluster/msg<br/>X-MM-RemoteCluster-Id: RemoteId_B<br/>X-MM-RemoteCluster-Token: Token_B
|
||||
SA->>SB: POST /v1/team/remotecluster/msg<br/>X-MM-RemoteCluster-Id: RemoteId_B<br/>X-MM-RemoteCluster-Token: Token_B
|
||||
SB->>SB: Validate RemoteId_B exists
|
||||
SB->>SB: Validate Token matches stored Token_B
|
||||
alt Valid Token
|
||||
@@ -444,7 +444,7 @@ sequenceDiagram
|
||||
end
|
||||
|
||||
Note over SA,SB: Server B sends message to Server A
|
||||
SB->>SA: POST /api/v4/remotecluster/msg<br/>X-MM-RemoteCluster-Id: RemoteId_A<br/>X-MM-RemoteCluster-Token: Token_A
|
||||
SB->>SA: POST /v1/team/remotecluster/msg<br/>X-MM-RemoteCluster-Id: RemoteId_A<br/>X-MM-RemoteCluster-Token: Token_A
|
||||
SA->>SA: Validate RemoteId_A exists
|
||||
SA->>SA: Validate Token matches stored Token_A
|
||||
alt Valid Token
|
||||
@@ -473,20 +473,20 @@ sequenceDiagram
|
||||
### Key API Endpoints
|
||||
|
||||
**Remote Cluster Management:**
|
||||
- `POST /api/v4/remotecluster` - Create remote (generate invite)
|
||||
- `POST /api/v4/remotecluster/accept_invite` - Accept invitation
|
||||
- `POST /api/v4/remotecluster/confirm_invite` - Confirm connection
|
||||
- `POST /api/v4/remotecluster/ping` - Heartbeat
|
||||
- `POST /api/v4/remotecluster/msg` - Message delivery (all topics)
|
||||
- `POST /v1/team/remotecluster` - Create remote (generate invite)
|
||||
- `POST /v1/team/remotecluster/accept_invite` - Accept invitation
|
||||
- `POST /v1/team/remotecluster/confirm_invite` - Confirm connection
|
||||
- `POST /v1/team/remotecluster/ping` - Heartbeat
|
||||
- `POST /v1/team/remotecluster/msg` - Message delivery (all topics)
|
||||
|
||||
**Shared Channel Management:**
|
||||
- `GET /api/v4/sharedchannels/{team_id}` - List shared channels
|
||||
- `POST /api/v4/channels/{channel_id}/remotes/{remote_id}/invite` - Share channel
|
||||
- `POST /api/v4/channels/{channel_id}/remotes/{remote_id}/uninvite` - Unshare
|
||||
- `GET /v1/team/sharedchannels/{team_id}` - List shared channels
|
||||
- `POST /v1/team/channels/{channel_id}/remotes/{remote_id}/invite` - Share channel
|
||||
- `POST /v1/team/channels/{channel_id}/remotes/{remote_id}/uninvite` - Unshare
|
||||
|
||||
**File Operations:**
|
||||
- `POST /api/v4/remotecluster/upload/{upload_id}` - Upload file
|
||||
- `POST /api/v4/remotecluster/{user_id}/image` - Upload profile image
|
||||
- `POST /v1/team/remotecluster/upload/{upload_id}` - Upload file
|
||||
- `POST /v1/team/remotecluster/{user_id}/image` - Upload profile image
|
||||
|
||||
### Error Handling & Monitoring
|
||||
|
||||
|
||||
@@ -52,10 +52,13 @@ const (
|
||||
|
||||
ClientDir = "client"
|
||||
|
||||
APIURLSuffixV1 = "/api/v1"
|
||||
APIURLSuffixV4 = "/api/v4"
|
||||
APIURLSuffixV5 = "/api/v5"
|
||||
APIURLSuffix = APIURLSuffixV4
|
||||
// APIURLSuffix is the base path of this server's HTTP API.
|
||||
APIURLSuffix = "/v1/team"
|
||||
|
||||
// PushURLSuffix is the base path of the external push notification
|
||||
// service addressed by EmailSettings.PushNotificationServer. It is that
|
||||
// service's versioning, not ours, and moves only when it does.
|
||||
PushURLSuffix = "/api/v1"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
@@ -68,7 +71,7 @@ type Response struct {
|
||||
|
||||
type Client4 struct {
|
||||
URL string // The location of the server, for example "http://localhost:8065"
|
||||
APIURL string // The api location of the server, for example "http://localhost:8065/api/v4"
|
||||
APIURL string // The api location of the server, for example "http://localhost:8065/v1/team"
|
||||
HTTPClient *http.Client // The http client
|
||||
AuthToken string
|
||||
AuthType string
|
||||
@@ -5633,7 +5636,7 @@ func (c *Client4) AuthorizeOAuthApp(ctx context.Context, authRequest *AuthorizeR
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
// The request doesn't go to the /api/v4 subpath, so we can't use the usual helper methods
|
||||
// The request doesn't go to the /v1/team subpath, so we can't use the usual helper methods
|
||||
r, err := c.doAPIRequestBytes(ctx, http.MethodPost, c.URL+"/oauth/authorize", buf, "")
|
||||
if err != nil {
|
||||
return "", BuildResponse(r), err
|
||||
@@ -5654,7 +5657,7 @@ func (c *Client4) DeauthorizeOAuthApp(ctx context.Context, appId string) (*Respo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The request doesn't go to the /api/v4 subpath, so we can't use the usual helper methods
|
||||
// The request doesn't go to the /v1/team subpath, so we can't use the usual helper methods
|
||||
r, err := c.doAPIRequestBytes(ctx, http.MethodPost, c.URL+"/oauth/deauthorize", buf, "")
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
@@ -5665,7 +5668,7 @@ func (c *Client4) DeauthorizeOAuthApp(ctx context.Context, appId string) (*Respo
|
||||
|
||||
// GetOAuthAccessToken is a test helper function for the OAuth access token endpoint.
|
||||
func (c *Client4) GetOAuthAccessToken(ctx context.Context, data url.Values) (*AccessResponse, *Response, error) {
|
||||
// The request doesn't go to the /api/v4 subpath, so we can't use the usual helper methods
|
||||
// The request doesn't go to the /v1/team subpath, so we can't use the usual helper methods
|
||||
r, err := c.doAPIRequestReader(ctx, http.MethodPost, c.URL+"/oauth/access_token", "application/x-www-form-urlencoded", strings.NewReader(data.Encode()), nil)
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
|
||||
@@ -170,7 +170,7 @@ func TestClient4LookupInteractiveDialog(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify request method and endpoint
|
||||
assert.Equal(t, "POST", r.Method)
|
||||
assert.Equal(t, "/api/v4/actions/dialogs/lookup", r.URL.Path)
|
||||
assert.Equal(t, "/v1/team/actions/dialogs/lookup", r.URL.Path)
|
||||
|
||||
// Decode and verify request body
|
||||
var submission model.SubmitDialogRequest
|
||||
|
||||
@@ -33,7 +33,7 @@ const (
|
||||
OAuthAuthorizeEndpoint = "/oauth/authorize"
|
||||
OAuthAccessTokenEndpoint = "/oauth/access_token"
|
||||
OAuthDeauthorizeEndpoint = "/oauth/deauthorize"
|
||||
OAuthAppsRegisterEndpoint = "/api/v4/oauth/apps/register"
|
||||
OAuthAppsRegisterEndpoint = APIURLSuffix + "/oauth/apps/register"
|
||||
OAuthMetadataEndpoint = "/.well-known/oauth-authorization-server"
|
||||
)
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ var (
|
||||
// audit level and should be applied whenever a REST API endpoint is accessed. It provides
|
||||
// a record of API usage patterns and access.
|
||||
//
|
||||
// Example usage: Logging when a user accesses the /api/v4/posts endpoint.
|
||||
// Example usage: Logging when a user accesses the /v1/team/posts endpoint.
|
||||
LvlAuditAPI = Level{ID: 100, Name: "audit-api"}
|
||||
|
||||
// LvlAuditContent is used for auditing content-generating operations such as creating posts,
|
||||
|
||||
Reference in New Issue
Block a user