diff --git a/Makefile b/Makefile index 724497d918..589521c371 100644 --- a/Makefile +++ b/Makefile @@ -41,17 +41,23 @@ build: install: @go get $(GOFLAGS) github.com/tools/godep - @if [ $(shell docker ps | grep -ci mattermost-mysql) -eq 0 ]; then \ + @if [ $(shell docker ps -a | grep -ci mattermost-mysql) -eq 0 ]; then \ echo restoring go libs using godep; \ $(GOPATH)/bin/godep restore; \ echo starting mattermost-mysql; \ docker run --name mattermost-mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=mostest \ -e MYSQL_USER=mmuser -e MYSQL_PASSWORD=mostest -e MYSQL_DATABASE=mattermost_test -d mysql > /dev/null; \ + elif [ $(shell docker ps | grep -ci mattermost-mysql) -eq 0 ]; then \ + echo restarting mattermost-mysql; \ + docker start mattermost-mysql > /dev/null; \ fi - @if [ $(shell docker ps | grep -ci mattermost-redis) -eq 0 ]; then \ + @if [ $(shell docker ps -a | grep -ci mattermost-redis) -eq 0 ]; then \ echo starting mattermost-redis; \ docker run --name mattermost-redis -p 6379:6379 -d redis > /dev/null; \ + elif [ $(shell docker ps | grep -ci mattermost-redis) -eq 0 ]; then \ + echo restarting mattermost-redis; \ + docker start mattermost-redis > /dev/null; \ fi @cd web/react/ && npm install diff --git a/README.md b/README.md index 04cbf718de..65a3e7c663 100644 --- a/README.md +++ b/README.md @@ -108,26 +108,10 @@ AWS Elastic Beanstalk Setup (Docker) 11. You can set the configuration details as you please but they may be left at their defaults. When you are done press next. 12. Environment tags my be left blank. Press next. 13. You will be asked to review your information. Press Launch. - 14. Up near the top of the dashboard you will see a domain of the form \*.elasticbeanstalk.com copy this as you will need it later. - -2. Map a wildcard domain to the new elastic beanstalk application - 15. From the AWS console select route 53 - 16. From the sidebar select Hosted Zones - 17. Select the domain you want to use or create a new one. - 18. Modify an existing CNAME record set or create a new one with the name * and the value of the domain you copied in step 1.13. - 19. Save the record set - -3. Set the environment variable "MATTERMOST\_DOMAIN" to the domain you mapped above (example.com not www.example.com) - 20. Return the Elastic Beanstalk from the AWS console. - 21. Select the environment you created. - 22. Select configuration from the sidebar. - 23. Click the gear beside software configuration. - 24. Add an environment property with the name “MATTERMOST\_DOMAIN” and a value of the domain you mapped in route 53. For example if your domain is \*.example.com you would enter example.com not www.example.com. - 25. Select apply. 4. Try it out! - 26. Return to the dashboard on the sidebar and wait for beanstalk update the environment. - 27. Try it out by entering the domain you mapped into your browser. + 14. Wait for beanstalk to update the environment. + 15. Try it out by entering the domain of the form \*.elasticbeanstalk.com found at the top of the dashboard into your browser. You can also map your own domain if you wish. Contributing ------------ @@ -137,8 +121,5 @@ To contribute to this open source project please review the Mattermost Contribut License ------- -Most Mattermost source files are made available under the terms of the GNU Affero General Public License (AGPL). See individual files for details. - -As an exception, Admin Tools and Configuration Files are are made available under the terms of the Apache License, version 2.0. See LICENSE.txt for more information. - +Mattermost is licensed under an "Apache-wrapped AGPL" model, which means you can run and link to the system using Configuration Files and Admin Tools licensed under Apache, version 2.0, as described in the LICENSE file, as an explicit exception to the terms of the GNU Affero General Public License (AGPL) that applies to most of the remaining source files. See individual files for details. diff --git a/api/api.go b/api/api.go index 70e1b64aea..25f3376c6f 100644 --- a/api/api.go +++ b/api/api.go @@ -16,10 +16,10 @@ var ServerTemplates *template.Template type ServerTemplatePage Page -func NewServerTemplatePage(templateName, teamUrl string) *ServerTemplatePage { +func NewServerTemplatePage(templateName, teamURL string) *ServerTemplatePage { props := make(map[string]string) props["AnalyticsUrl"] = utils.Cfg.ServiceSettings.AnalyticsUrl - return &ServerTemplatePage{TemplateName: templateName, SiteName: utils.Cfg.ServiceSettings.SiteName, FeedbackEmail: utils.Cfg.EmailSettings.FeedbackEmail, TeamUrl: teamUrl, Props: props} + return &ServerTemplatePage{TemplateName: templateName, SiteName: utils.Cfg.ServiceSettings.SiteName, FeedbackEmail: utils.Cfg.EmailSettings.FeedbackEmail, TeamURL: teamURL, Props: props} } func (me *ServerTemplatePage) Render() string { diff --git a/api/api_test.go b/api/api_test.go index b407c2a5e2..0c2e57891b 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -24,12 +24,12 @@ func Setup() { func SetupBenchmark() (*model.Team, *model.User, *model.Channel) { Setup() - team := &model.Team{Name: "Benchmark Team", Domain: "z-z-" + model.NewId() + "a", Email: "benchmark@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Benchmark Team", Name: "z-z-" + model.NewId() + "a", Email: "benchmark@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: model.NewId() + "benchmark@test.com", FullName: "Mr. Benchmarker", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: model.NewId() + "benchmark@test.com", Nickname: "Mr. Benchmarker", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") channel := &model.Channel{DisplayName: "Benchmark Channel", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel = Client.Must(Client.CreateChannel(channel)).Data.(*model.Channel) diff --git a/api/auto_constants.go b/api/auto_constants.go index 3f88310552..f80f15f2d2 100644 --- a/api/auto_constants.go +++ b/api/auto_constants.go @@ -12,8 +12,8 @@ const ( USER_PASSWORD = "passwd" CHANNEL_TYPE = model.CHANNEL_OPEN FUZZ_USER_EMAIL_PREFIX_LEN = 10 - BTEST_TEAM_NAME = "TestTeam" - BTEST_TEAM_DOMAIN_NAME = "z-z-testdomaina" + BTEST_TEAM_DISPLAY_NAME = "TestTeam" + BTEST_TEAM_NAME = "z-z-testdomaina" BTEST_TEAM_EMAIL = "test@nowhere.com" BTEST_TEAM_TYPE = model.TEAM_OPEN BTEST_USER_NAME = "Mr. Testing Tester" diff --git a/api/auto_teams.go b/api/auto_teams.go index 2fe8267745..e5c772b4c8 100644 --- a/api/auto_teams.go +++ b/api/auto_teams.go @@ -39,22 +39,22 @@ func NewAutoTeamCreator(client *model.Client) *AutoTeamCreator { func (cfg *AutoTeamCreator) createRandomTeam() (*model.Team, bool) { var teamEmail string + var teamDisplayName string var teamName string - var teamDomain string if cfg.Fuzzy { teamEmail = utils.FuzzEmail() + teamDisplayName = utils.FuzzName() teamName = utils.FuzzName() - teamDomain = utils.FuzzName() } else { teamEmail = utils.RandomEmail(cfg.EmailLength, cfg.EmailCharset) - teamName = utils.RandomName(cfg.NameLength, cfg.NameCharset) - teamDomain = utils.RandomName(cfg.NameLength, cfg.NameCharset) + model.NewId() + teamDisplayName = utils.RandomName(cfg.NameLength, cfg.NameCharset) + teamName = utils.RandomName(cfg.NameLength, cfg.NameCharset) + model.NewId() } team := &model.Team{ - Name: teamName, - Domain: teamDomain, - Email: teamEmail, - Type: model.TEAM_OPEN, + DisplayName: teamDisplayName, + Name: teamName, + Email: teamEmail, + Type: model.TEAM_OPEN, } result, err := cfg.client.CreateTeam(team) diff --git a/api/auto_users.go b/api/auto_users.go index 493ec5c17d..39ed201b96 100644 --- a/api/auto_users.go +++ b/api/auto_users.go @@ -33,15 +33,15 @@ func NewAutoUserCreator(client *model.Client, teamID string) *AutoUserCreator { // Basic test team and user so you always know one func CreateBasicUser(client *model.Client) *model.AppError { - result, _ := client.FindTeamByDomain(BTEST_TEAM_DOMAIN_NAME, true) + result, _ := client.FindTeamByName(BTEST_TEAM_NAME, true) if result.Data.(bool) == false { - newteam := &model.Team{Name: BTEST_TEAM_NAME, Domain: BTEST_TEAM_DOMAIN_NAME, Email: BTEST_TEAM_EMAIL, Type: BTEST_TEAM_TYPE} + newteam := &model.Team{DisplayName: BTEST_TEAM_DISPLAY_NAME, Name: BTEST_TEAM_NAME, Email: BTEST_TEAM_EMAIL, Type: BTEST_TEAM_TYPE} result, err := client.CreateTeam(newteam) if err != nil { return err } basicteam := result.Data.(*model.Team) - newuser := &model.User{TeamId: basicteam.Id, Email: BTEST_USER_EMAIL, FullName: BTEST_USER_NAME, Password: BTEST_USER_PASSWORD} + newuser := &model.User{TeamId: basicteam.Id, Email: BTEST_USER_EMAIL, Nickname: BTEST_USER_NAME, Password: BTEST_USER_PASSWORD} result, err = client.CreateUser(newuser, "") if err != nil { return err @@ -65,7 +65,7 @@ func (cfg *AutoUserCreator) createRandomUser() (*model.User, bool) { user := &model.User{ TeamId: cfg.teamID, Email: userEmail, - FullName: userName, + Nickname: userName, Password: USER_PASSWORD} result, err := cfg.client.CreateUser(user, "") diff --git a/api/channel.go b/api/channel.go index 88db27def1..4d8dbad099 100644 --- a/api/channel.go +++ b/api/channel.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/gorilla/mux" "github.com/mattermost/platform/model" - "github.com/mattermost/platform/store" "net/http" "strings" ) @@ -542,7 +541,7 @@ func updateLastViewedAt(c *Context, w http.ResponseWriter, r *http.Request) { message := model.NewMessage(c.Session.TeamId, id, c.Session.UserId, model.ACTION_VIEWED) message.Add("channel_id", id) - store.PublishAndForget(message) + PublishAndForget(message) result := make(map[string]string) result["id"] = id @@ -657,7 +656,7 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { message := model.NewMessage(c.Session.TeamId, "", userId, model.ACTION_USER_ADDED) - store.PublishAndForget(message) + PublishAndForget(message) <-Srv.Store.Channel().UpdateLastViewedAt(id, oUser.Id) w.Write([]byte(cm.ToJson())) diff --git a/api/channel_benchmark_test.go b/api/channel_benchmark_test.go index 461a7ed3a0..8816381769 100644 --- a/api/channel_benchmark_test.go +++ b/api/channel_benchmark_test.go @@ -138,10 +138,10 @@ func BenchmarkJoinChannel(b *testing.B) { } // Secondary test user to join channels created by primary test user - user := &model.User{TeamId: team.Id, Email: model.NewId() + "random@test.com", FullName: "That Guy", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: model.NewId() + "random@test.com", Nickname: "That Guy", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") // Benchmark Start b.ResetTimer() diff --git a/api/channel_test.go b/api/channel_test.go index ed05546939..ae77813026 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -14,17 +14,17 @@ import ( func TestCreateChannel(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - team2 := &model.Team{Name: "Name Team 2", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team2 := &model.Team{DisplayName: "Name Team 2", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") channel := model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} rchannel, err := Client.CreateChannel(&channel) @@ -94,18 +94,18 @@ func TestCreateChannel(t *testing.T) { func TestCreateDirectChannel(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") data := make(map[string]string) data["user_id"] = user2.Id @@ -149,23 +149,23 @@ func TestCreateDirectChannel(t *testing.T) { func TestUpdateChannel(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - userTeamAdmin := &model.User{TeamId: team.Id, Email: team.Email, FullName: "Corey Hulen", Password: "pwd"} + userTeamAdmin := &model.User{TeamId: team.Id, Email: team.Email, Nickname: "Corey Hulen", Password: "pwd"} userTeamAdmin = Client.Must(Client.CreateUser(userTeamAdmin, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(userTeamAdmin.Id)) - userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} userChannelAdmin = Client.Must(Client.CreateUser(userChannelAdmin, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(userChannelAdmin.Id)) - userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} userStd = Client.Must(Client.CreateUser(userStd, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(userStd.Id)) userStd.Roles = "" - Client.LoginByEmail(team.Domain, userChannelAdmin.Email, "pwd") + Client.LoginByEmail(team.Name, userChannelAdmin.Email, "pwd") channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -184,7 +184,7 @@ func TestUpdateChannel(t *testing.T) { t.Fatal("Channel admin failed to skip displayName") } - Client.LoginByEmail(team.Domain, userTeamAdmin.Email, "pwd") + Client.LoginByEmail(team.Name, userTeamAdmin.Email, "pwd") desc = "b" + model.NewId() + "b" upChannel1 = &model.Channel{Id: channel1.Id, Description: desc} @@ -210,7 +210,7 @@ func TestUpdateChannel(t *testing.T) { } } - Client.LoginByEmail(team.Domain, userStd.Email, "pwd") + Client.LoginByEmail(team.Name, userStd.Email, "pwd") if _, err := Client.UpdateChannel(upChannel1); err == nil { t.Fatal("Standard User should have failed to update") @@ -220,14 +220,14 @@ func TestUpdateChannel(t *testing.T) { func TestUpdateChannelDesc(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -266,11 +266,11 @@ func TestUpdateChannelDesc(t *testing.T) { t.Fatal("should have errored on bad channel desc") } - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") data["channel_id"] = channel1.Id data["channel_description"] = "new desc" @@ -282,14 +282,14 @@ func TestUpdateChannelDesc(t *testing.T) { func TestGetChannel(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -324,14 +324,14 @@ func TestGetChannel(t *testing.T) { func TestGetMoreChannel(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -339,11 +339,11 @@ func TestGetMoreChannel(t *testing.T) { channel2 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") rget := Client.Must(Client.GetMoreChannels("")) data := rget.Data.(*model.ChannelList) @@ -368,14 +368,14 @@ func TestGetMoreChannel(t *testing.T) { func TestJoinChannel(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -383,11 +383,11 @@ func TestJoinChannel(t *testing.T) { channel3 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel) - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") Client.Must(Client.JoinChannel(channel1.Id)) @@ -399,10 +399,10 @@ func TestJoinChannel(t *testing.T) { data["user_id"] = user1.Id rchannel := Client.Must(Client.CreateDirectChannel(data)).Data.(*model.Channel) - user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user3 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User) - Client.LoginByEmail(team.Domain, user3.Email, "pwd") + Client.LoginByEmail(team.Name, user3.Email, "pwd") if _, err := Client.JoinChannel(rchannel.Id); err == nil { t.Fatal("shoudn't be able to join direct channel") @@ -412,14 +412,14 @@ func TestJoinChannel(t *testing.T) { func TestLeaveChannel(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -427,11 +427,11 @@ func TestLeaveChannel(t *testing.T) { channel3 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel) - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") Client.Must(Client.JoinChannel(channel1.Id)) @@ -461,25 +461,25 @@ func TestLeaveChannel(t *testing.T) { func TestDeleteChannel(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - userTeamAdmin := &model.User{TeamId: team.Id, Email: team.Email, FullName: "Corey Hulen", Password: "pwd"} + userTeamAdmin := &model.User{TeamId: team.Id, Email: team.Email, Nickname: "Corey Hulen", Password: "pwd"} userTeamAdmin = Client.Must(Client.CreateUser(userTeamAdmin, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(userTeamAdmin.Id)) - userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} userChannelAdmin = Client.Must(Client.CreateUser(userChannelAdmin, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(userChannelAdmin.Id)) - Client.LoginByEmail(team.Domain, userChannelAdmin.Email, "pwd") + Client.LoginByEmail(team.Name, userChannelAdmin.Email, "pwd") channelMadeByCA := &model.Channel{DisplayName: "C Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channelMadeByCA = Client.Must(Client.CreateChannel(channelMadeByCA)).Data.(*model.Channel) Client.AddChannelMember(channelMadeByCA.Id, userTeamAdmin.Id) - Client.LoginByEmail(team.Domain, userTeamAdmin.Email, "pwd") + Client.LoginByEmail(team.Name, userTeamAdmin.Email, "pwd") channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -500,11 +500,11 @@ func TestDeleteChannel(t *testing.T) { t.Fatal("should have failed to post to deleted channel") } - userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} userStd = Client.Must(Client.CreateUser(userStd, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(userStd.Id)) - Client.LoginByEmail(team.Domain, userStd.Email, "pwd") + Client.LoginByEmail(team.Name, userStd.Email, "pwd") if _, err := Client.JoinChannel(channel1.Id); err == nil { t.Fatal("should have failed to join deleted channel") @@ -531,14 +531,14 @@ func TestDeleteChannel(t *testing.T) { func TestGetChannelExtraInfo(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -552,19 +552,19 @@ func TestGetChannelExtraInfo(t *testing.T) { func TestAddChannelMember(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) @@ -591,13 +591,13 @@ func TestAddChannelMember(t *testing.T) { channel2 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") if _, err := Client.AddChannelMember(channel2.Id, user2.Id); err == nil { t.Fatal("Should have errored, user not in channel") } - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") Client.Must(Client.DeleteChannel(channel2.Id)) @@ -610,30 +610,30 @@ func TestAddChannelMember(t *testing.T) { func TestRemoveChannelMember(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - userTeamAdmin := &model.User{TeamId: team.Id, Email: team.Email, FullName: "Corey Hulen", Password: "pwd"} + userTeamAdmin := &model.User{TeamId: team.Id, Email: team.Email, Nickname: "Corey Hulen", Password: "pwd"} userTeamAdmin = Client.Must(Client.CreateUser(userTeamAdmin, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(userTeamAdmin.Id)) - userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + userChannelAdmin := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} userChannelAdmin = Client.Must(Client.CreateUser(userChannelAdmin, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(userChannelAdmin.Id)) - Client.LoginByEmail(team.Domain, userChannelAdmin.Email, "pwd") + Client.LoginByEmail(team.Name, userChannelAdmin.Email, "pwd") channelMadeByCA := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channelMadeByCA = Client.Must(Client.CreateChannel(channelMadeByCA)).Data.(*model.Channel) Client.Must(Client.AddChannelMember(channelMadeByCA.Id, userTeamAdmin.Id)) - Client.LoginByEmail(team.Domain, userTeamAdmin.Email, "pwd") + Client.LoginByEmail(team.Name, userTeamAdmin.Email, "pwd") channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) - userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + userStd := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} userStd = Client.Must(Client.CreateUser(userStd, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(userStd.Id)) @@ -660,13 +660,13 @@ func TestRemoveChannelMember(t *testing.T) { channel2 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) - Client.LoginByEmail(team.Domain, userStd.Email, "pwd") + Client.LoginByEmail(team.Name, userStd.Email, "pwd") if _, err := Client.RemoveChannelMember(channel2.Id, userStd.Id); err == nil { t.Fatal("Should have errored, user not channel admin") } - Client.LoginByEmail(team.Domain, userTeamAdmin.Email, "pwd") + Client.LoginByEmail(team.Name, userTeamAdmin.Email, "pwd") Client.Must(Client.AddChannelMember(channel2.Id, userStd.Id)) Client.Must(Client.DeleteChannel(channel2.Id)) @@ -680,14 +680,14 @@ func TestRemoveChannelMember(t *testing.T) { func TestUpdateNotifyLevel(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -746,10 +746,10 @@ func TestUpdateNotifyLevel(t *testing.T) { t.Fatal("Should have errored - bad notify level") } - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") data["channel_id"] = channel1.Id data["user_id"] = user2.Id @@ -762,14 +762,14 @@ func TestUpdateNotifyLevel(t *testing.T) { func TestFuzzyChannel(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") // Strings that should pass as acceptable channel names var fuzzyStringsPass = []string{ diff --git a/api/command.go b/api/command.go index 810a8a07ed..f051bd42e7 100644 --- a/api/command.go +++ b/api/command.go @@ -27,8 +27,6 @@ var commands = []commandHandler{ func InitCommand(r *mux.Router) { l4g.Debug("Initializing command api routes") r.Handle("/command", ApiUserRequired(command)).Methods("POST") - - hub.Start() } func command(c *Context, w http.ResponseWriter, r *http.Request) { @@ -323,14 +321,14 @@ func loadTestSetupCommand(c *Context, command *model.Command) bool { numPosts, _ = strconv.Atoi(tokens[numArgs+2]) } } - client := model.NewClient(c.TeamUrl + "/api/v1") + client := model.NewClient(c.GetSiteURL() + "/api/v1") if doTeams { if err := CreateBasicUser(client); err != nil { l4g.Error("Failed to create testing enviroment") return true } - client.LoginByEmail(BTEST_TEAM_DOMAIN_NAME, BTEST_USER_EMAIL, BTEST_USER_PASSWORD) + client.LoginByEmail(BTEST_TEAM_NAME, BTEST_USER_EMAIL, BTEST_USER_PASSWORD) enviroment, err := CreateTestEnviromentWithTeams( client, utils.Range{numTeams, numTeams}, @@ -344,7 +342,7 @@ func loadTestSetupCommand(c *Context, command *model.Command) bool { } else { l4g.Info("Testing enviroment created") for i := 0; i < len(enviroment.Teams); i++ { - l4g.Info("Team Created: " + enviroment.Teams[i].Domain) + l4g.Info("Team Created: " + enviroment.Teams[i].Name) l4g.Info("\t User to login: " + enviroment.Enviroments[i].Users[0].Email + ", " + USER_PASSWORD) } } @@ -383,7 +381,7 @@ func loadTestUsersCommand(c *Context, command *model.Command) bool { if err == false { usersr = utils.Range{10, 15} } - client := model.NewClient(c.TeamUrl + "/api/v1") + client := model.NewClient(c.GetSiteURL() + "/api/v1") userCreator := NewAutoUserCreator(client, c.Session.TeamId) userCreator.Fuzzy = doFuzz userCreator.CreateTestUsers(usersr) @@ -413,7 +411,7 @@ func loadTestChannelsCommand(c *Context, command *model.Command) bool { if err == false { channelsr = utils.Range{20, 30} } - client := model.NewClient(c.TeamUrl + "/api/v1") + client := model.NewClient(c.GetSiteURL() + "/api/v1") client.MockSession(c.Session.Id) channelCreator := NewAutoChannelCreator(client, c.Session.TeamId) channelCreator.Fuzzy = doFuzz @@ -465,7 +463,7 @@ func loadTestPostsCommand(c *Context, command *model.Command) bool { } } - client := model.NewClient(c.TeamUrl + "/api/v1") + client := model.NewClient(c.GetSiteURL() + "/api/v1") client.MockSession(c.Session.Id) testPoster := NewAutoPostCreator(client, command.ChannelId) testPoster.Fuzzy = doFuzz diff --git a/api/command_test.go b/api/command_test.go index 624c445e68..a58ef9be53 100644 --- a/api/command_test.go +++ b/api/command_test.go @@ -12,14 +12,14 @@ import ( func TestSuggestRootCommands(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") if _, err := Client.Command("", "", true); err == nil { t.Fatal("Should fail") @@ -55,14 +55,14 @@ func TestSuggestRootCommands(t *testing.T) { func TestLogoutCommands(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") rs1 := Client.Must(Client.Command("", "/logout", false)).Data.(*model.Command) if rs1.GotoLocation != "/logout" { @@ -73,14 +73,14 @@ func TestLogoutCommands(t *testing.T) { func TestJoinCommands(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -90,7 +90,7 @@ func TestJoinCommands(t *testing.T) { channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) Client.Must(Client.LeaveChannel(channel2.Id)) - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) diff --git a/api/context.go b/api/context.go index bea0fbeff4..ac9dffcbc9 100644 --- a/api/context.go +++ b/api/context.go @@ -17,12 +17,14 @@ import ( var sessionCache *utils.Cache = utils.NewLru(model.SESSION_CACHE_SIZE) type Context struct { - Session model.Session - RequestId string - IpAddress string - TeamUrl string - Path string - Err *model.AppError + Session model.Session + RequestId string + IpAddress string + Path string + Err *model.AppError + teamURLValid bool + teamURL string + siteURL string } type Page struct { @@ -30,32 +32,36 @@ type Page struct { Title string SiteName string FeedbackEmail string - TeamUrl string + TeamURL string Props map[string]string } func ApiAppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{h, false, false, true, false} + return &handler{h, false, false, true, false, false} } func AppHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{h, false, false, false, false} + return &handler{h, false, false, false, false, false} +} + +func AppHandlerIndependent(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { + return &handler{h, false, false, false, false, true} } func ApiUserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{h, true, false, true, true} + return &handler{h, true, false, true, true, false} } func ApiUserRequiredActivity(h func(*Context, http.ResponseWriter, *http.Request), isUserActivity bool) http.Handler { - return &handler{h, true, false, true, isUserActivity} + return &handler{h, true, false, true, isUserActivity, false} } func UserRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{h, true, false, false, false} + return &handler{h, true, false, false, false, false} } func ApiAdminSystemRequired(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { - return &handler{h, true, true, true, false} + return &handler{h, true, true, true, false, false} } type handler struct { @@ -64,6 +70,7 @@ type handler struct { requireSystemAdmin bool isApi bool isUserActivity bool + isTeamIndependent bool } func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -73,7 +80,6 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { c := &Context{} c.RequestId = model.NewId() c.IpAddress = GetIpAddress(r) - c.Path = r.URL.Path protocol := "http" @@ -90,7 +96,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } - c.TeamUrl = protocol + "://" + r.Host + c.setSiteURL(protocol + "://" + r.Host) w.Header().Set(model.HEADER_REQUEST_ID, c.RequestId) w.Header().Set(model.HEADER_VERSION_ID, utils.Cfg.ServiceSettings.Version) @@ -135,6 +141,15 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } + if h.isApi || h.isTeamIndependent { + c.setTeamURL(c.GetSiteURL(), false) + c.Path = r.URL.Path + } else { + splitURL := strings.Split(r.URL.Path, "/") + c.setTeamURL(protocol+"://"+r.Host+"/"+splitURL[1], true) + c.Path = "/" + strings.Join(splitURL[2:], "/") + } + if c.Err == nil && h.requireUser { c.UserRequired() } @@ -165,7 +180,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte(c.Err.ToJson())) } else { if c.Err.StatusCode == http.StatusUnauthorized { - http.Redirect(w, r, "/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect) + http.Redirect(w, r, c.GetTeamURL()+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect) } else { RenderWebError(c.Err, w, r) } @@ -265,6 +280,16 @@ func (c *Context) IsSystemAdmin() bool { return false } +func (c *Context) IsTeamAdmin(userId string) bool { + if uresult := <-Srv.Store.User().Get(userId); uresult.Err != nil { + c.Err = uresult.Err + return false + } else { + user := uresult.Data.(*model.User) + return strings.Contains(c.Session.Roles, model.ROLE_ADMIN) && user.TeamId == c.Session.TeamId + } +} + func (c *Context) RemoveSessionCookie(w http.ResponseWriter) { sessionCache.Remove(c.Session.Id) @@ -289,6 +314,39 @@ func (c *Context) SetUnknownError(where string, details string) { c.Err = model.NewAppError(where, "An unknown error has occured. Please contact support.", details) } +func (c *Context) setTeamURL(url string, valid bool) { + c.teamURL = url + c.teamURLValid = valid +} + +func (c *Context) setTeamURLFromSession() { + if result := <-Srv.Store.Team().Get(c.Session.TeamId); result.Err == nil { + c.setTeamURL(c.GetSiteURL()+"/"+result.Data.(*model.Team).Name, true) + } +} + +func (c *Context) setSiteURL(url string) { + c.siteURL = url +} + +func (c *Context) GetTeamURLFromTeam(team *model.Team) string { + return c.GetSiteURL() + "/" + team.Name +} + +func (c *Context) GetTeamURL() string { + if !c.teamURLValid { + c.setTeamURLFromSession() + if !c.teamURLValid { + l4g.Debug("TeamURL accessed when not valid. Team URL should not be used in api functions or those that are team independent") + } + } + return c.teamURL +} + +func (c *Context) GetSiteURL() string { + return c.siteURL +} + func GetIpAddress(r *http.Request) string { address := r.Header.Get(model.HEADER_FORWARDED) if len(address) == 0 { diff --git a/api/file.go b/api/file.go index 0e08567d65..362cdf896b 100644 --- a/api/file.go +++ b/api/file.go @@ -13,9 +13,9 @@ import ( "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" "github.com/nfnt/resize" + _ "golang.org/x/image/bmp" "image" _ "image/gif" - _ "golang.org/x/image/bmp" "image/jpeg" "io" "net/http" @@ -115,7 +115,7 @@ func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) { return } - fileUrl := c.TeamUrl + "/api/v1/files/get/" + channelId + "/" + c.Session.UserId + "/" + uid + "/" + url.QueryEscape(files[i].Filename) + fileUrl := c.GetSiteURL() + "/api/v1/files/get/" + channelId + "/" + c.Session.UserId + "/" + uid + "/" + url.QueryEscape(files[i].Filename) resStruct.Filenames = append(resStruct.Filenames, fileUrl) } @@ -363,7 +363,7 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) { data := model.MapToJson(newProps) hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.PublicLinkSalt)) - url := fmt.Sprintf("%s/api/v1/files/%s/%s/%s/%s?d=%s&h=%s&t=%s", c.TeamUrl, getType, channelId, userId, filename, url.QueryEscape(data), url.QueryEscape(hash), c.Session.TeamId) + url := fmt.Sprintf("%s/api/v1/files/%s/%s/%s/%s?d=%s&h=%s&t=%s", c.GetSiteURL(), getType, channelId, userId, filename, url.QueryEscape(data), url.QueryEscape(hash), c.Session.TeamId) if !c.HasPermissionsToChannel(cchan, "getPublicLink") { return diff --git a/api/file_test.go b/api/file_test.go index e334fd6e50..79ee03c778 100644 --- a/api/file_test.go +++ b/api/file_test.go @@ -24,14 +24,14 @@ import ( func TestUploadFile(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -111,14 +111,14 @@ func TestUploadFile(t *testing.T) { func TestGetFile(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -173,10 +173,10 @@ func TestGetFile(t *testing.T) { t.Fatal("file get failed") } - team2 := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team) - user2 := &model.User{TeamId: team2.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team2.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) @@ -187,7 +187,7 @@ func TestGetFile(t *testing.T) { data := model.MapToJson(newProps) hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.PublicLinkSalt)) - Client.LoginByEmail(team2.Domain, user2.Email, "pwd") + Client.LoginByEmail(team2.Name, user2.Email, "pwd") if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h="+url.QueryEscape(hash)+"&t="+team.Id, true); downErr != nil { t.Fatal(downErr) @@ -258,18 +258,18 @@ func TestGetFile(t *testing.T) { func TestGetPublicLink(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -344,7 +344,7 @@ func TestGetPublicLink(t *testing.T) { t.Fatal("Should have errored - bad file path") } - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") data["filename"] = filenames[0] if _, err := Client.GetPublicLink(data); err == nil { t.Fatal("should have errored, user not member of channel") diff --git a/api/post.go b/api/post.go index 650f47062d..fb9fdd1eff 100644 --- a/api/post.go +++ b/api/post.go @@ -11,6 +11,7 @@ import ( "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" "net/http" + "path/filepath" "strconv" "strings" "time" @@ -121,7 +122,7 @@ func CreateValetPost(c *Context, post *model.Post) (*model.Post, *model.AppError rpost = result.Data.(*model.Post) } - fireAndForgetNotifications(rpost, c.Session.TeamId, c.TeamUrl) + fireAndForgetNotifications(rpost, c.Session.TeamId, c.GetSiteURL()) return rpost, nil } @@ -200,14 +201,14 @@ func CreatePost(c *Context, post *model.Post, doUpdateLastViewed bool) (*model.P } else { rpost = result.Data.(*model.Post) - fireAndForgetNotifications(rpost, c.Session.TeamId, c.TeamUrl) + fireAndForgetNotifications(rpost, c.Session.TeamId, c.GetSiteURL()) } return rpost, nil } -func fireAndForgetNotifications(post *model.Post, teamId, teamUrl string) { +func fireAndForgetNotifications(post *model.Post, teamId, siteURL string) { go func() { // Get a list of user names (to be used as keywords) and ids for the given team @@ -297,14 +298,11 @@ func fireAndForgetNotifications(post *model.Post, teamId, teamUrl string) { for _, k := range splitKeys { keywordMap[k] = append(keywordMap[strings.ToLower(k)], profile.Id) } + } - // If turned on, add the user's case sensitive first name - if profile.NotifyProps["first_name"] == "true" { - splitName := strings.Split(profile.FullName, " ") - if len(splitName) > 0 && splitName[0] != "" { - keywordMap[splitName[0]] = append(keywordMap[splitName[0]], profile.Id) - } - } + // If turned on, add the user's case sensitive first name + if profile.NotifyProps["first_name"] == "true" { + keywordMap[profile.FirstName] = append(keywordMap[profile.FirstName], profile.Id) } // Add @all to keywords if user has them turned on @@ -364,20 +362,22 @@ func fireAndForgetNotifications(post *model.Post, teamId, teamUrl string) { mentionedUsers = append(mentionedUsers, k) } - var teamName string + var teamDisplayName string + var teamURL string if result := <-tchan; result.Err != nil { l4g.Error("Failed to retrieve team team_id=%v, err=%v", teamId, result.Err) return } else { - teamName = result.Data.(*model.Team).Name + teamDisplayName = result.Data.(*model.Team).DisplayName + teamURL = siteURL + "/" + result.Data.(*model.Team).Name } // Build and send the emails location, _ := time.LoadLocation("UTC") tm := time.Unix(post.CreateAt/1000, 0).In(location) - subjectPage := NewServerTemplatePage("post_subject", teamUrl) - subjectPage.Props["TeamName"] = teamName + subjectPage := NewServerTemplatePage("post_subject", teamURL) + subjectPage.Props["TeamDisplayName"] = teamDisplayName subjectPage.Props["SubjectText"] = subjectText subjectPage.Props["Month"] = tm.Month().String()[:3] subjectPage.Props["Day"] = fmt.Sprintf("%d", tm.Day()) @@ -394,11 +394,9 @@ func fireAndForgetNotifications(post *model.Post, teamId, teamUrl string) { continue } - firstName := strings.Split(profileMap[id].FullName, " ")[0] - - bodyPage := NewServerTemplatePage("post_body", teamUrl) - bodyPage.Props["FullName"] = firstName - bodyPage.Props["TeamName"] = teamName + bodyPage := NewServerTemplatePage("post_body", teamURL) + bodyPage.Props["Nickname"] = profileMap[id].FirstName + bodyPage.Props["TeamDisplayName"] = teamDisplayName bodyPage.Props["ChannelName"] = channelName bodyPage.Props["BodyText"] = bodyText bodyPage.Props["SenderName"] = senderName @@ -407,7 +405,7 @@ func fireAndForgetNotifications(post *model.Post, teamId, teamUrl string) { bodyPage.Props["Month"] = tm.Month().String()[:3] bodyPage.Props["Day"] = fmt.Sprintf("%d", tm.Day()) bodyPage.Props["PostMessage"] = model.ClearMentionTags(post.Message) - bodyPage.Props["TeamLink"] = teamUrl + "/channels/" + channel.Name + bodyPage.Props["TeamLink"] = teamURL + "/channels/" + channel.Name if err := utils.SendMail(profileMap[id].Email, subjectPage.Render(), bodyPage.Render()); err != nil { l4g.Error("Failed to send mention email successfully email=%v err=%v", profileMap[id].Email, err) @@ -437,11 +435,24 @@ func fireAndForgetNotifications(post *model.Post, teamId, teamUrl string) { message := model.NewMessage(teamId, post.ChannelId, post.UserId, model.ACTION_POSTED) message.Add("post", post.ToJson()) + + if len(post.Filenames) != 0 { + message.Add("otherFile", "true") + + for _, filename := range post.Filenames { + ext := filepath.Ext(filename) + if model.IsFileExtImage(ext) { + message.Add("image", "true") + break + } + } + } + if len(mentionedUsers) != 0 { message.Add("mentions", model.ArrayToJson(mentionedUsers)) } - store.PublishAndForget(message) + PublishAndForget(message) }() } @@ -507,7 +518,7 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) { message.Add("channel_id", rpost.ChannelId) message.Add("message", rpost.Message) - store.PublishAndForget(message) + PublishAndForget(message) w.Write([]byte(rpost.ToJson())) } @@ -620,16 +631,17 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) { cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId) pchan := Srv.Store.Post().Get(postId) - if !c.HasPermissionsToChannel(cchan, "deletePost") { - return - } - if result := <-pchan; result.Err != nil { c.Err = result.Err return } else { + post := result.Data.(*model.PostList).Posts[postId] + if !c.HasPermissionsToChannel(cchan, "deletePost") && !c.IsTeamAdmin(post.UserId) { + return + } + if post == nil { c.SetInvalidParam("deletePost", "postId") return @@ -641,7 +653,7 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) { return } - if post.UserId != c.Session.UserId { + if post.UserId != c.Session.UserId && !strings.Contains(c.Session.Roles, model.ROLE_ADMIN) { c.Err = model.NewAppError("deletePost", "You do not have the appropriate permissions", "") c.Err.StatusCode = http.StatusForbidden return @@ -656,7 +668,7 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) { message.Add("post_id", post.Id) message.Add("channel_id", post.ChannelId) - store.PublishAndForget(message) + PublishAndForget(message) result := make(map[string]string) result["id"] = postId diff --git a/api/post_test.go b/api/post_test.go index 3af1874c26..e245366cac 100644 --- a/api/post_test.go +++ b/api/post_test.go @@ -15,21 +15,21 @@ import ( func TestCreatePost(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - team2 := &model.Team{Name: "Name Team 2", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team2 := &model.Team{DisplayName: "Name Team 2", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -96,18 +96,18 @@ func TestCreatePost(t *testing.T) { t.Fatal("Should have been forbidden") } - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") post7 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"} _, err = Client.CreatePost(post7) if err.StatusCode != http.StatusForbidden { t.Fatal("Should have been forbidden") } - user3 := &model.User{TeamId: team2.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user3 := &model.User{TeamId: team2.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user3.Id)) - Client.LoginByEmail(team2.Domain, user3.Email, "pwd") + Client.LoginByEmail(team2.Name, user3.Email, "pwd") channel3 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team2.Id} channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel) @@ -126,21 +126,21 @@ func TestCreatePost(t *testing.T) { func TestCreateValetPost(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - team2 := &model.Team{Name: "Name Team 2", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team2 := &model.Team{DisplayName: "Name Team 2", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -181,18 +181,18 @@ func TestCreateValetPost(t *testing.T) { t.Fatal("Should have been forbidden") } - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") post5 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"} _, err = Client.CreateValetPost(post5) if err != nil { t.Fatal(err) } - user3 := &model.User{TeamId: team2.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user3 := &model.User{TeamId: team2.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user3.Id)) - Client.LoginByEmail(team2.Domain, user3.Email, "pwd") + Client.LoginByEmail(team2.Name, user3.Email, "pwd") channel3 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team2.Id} channel3 = Client.Must(Client.CreateChannel(channel3)).Data.(*model.Channel) @@ -218,21 +218,21 @@ func TestCreateValetPost(t *testing.T) { func TestUpdatePost(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - team2 := &model.Team{Name: "Name Team 2", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team2 := &model.Team{DisplayName: "Name Team 2", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -289,14 +289,14 @@ func TestUpdatePost(t *testing.T) { func TestGetPosts(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -354,14 +354,14 @@ func TestGetPosts(t *testing.T) { func TestSearchPosts(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -400,14 +400,14 @@ func TestSearchPosts(t *testing.T) { func TestSearchHashtagPosts(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -431,14 +431,14 @@ func TestSearchHashtagPosts(t *testing.T) { func TestGetPostsCache(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -480,14 +480,18 @@ func TestGetPostsCache(t *testing.T) { func TestDeletePosts(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + userAdmin := &model.User{TeamId: team.Id, Email: team.Email, Nickname: "Corey Hulen", Password: "pwd"} + userAdmin = Client.Must(Client.CreateUser(userAdmin, "")).Data.(*model.User) + store.Must(Srv.Store.User().VerifyEmail(userAdmin.Id)) + + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -522,21 +526,29 @@ func TestDeletePosts(t *testing.T) { r2 := Client.Must(Client.GetPosts(channel1.Id, 0, 10, "")).Data.(*model.PostList) if len(r2.Posts) != 4 { - t.Fatal("should have returned 5 items") + t.Fatal("should have returned 4 items") } + + time.Sleep(10 * time.Millisecond) + post4 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"} + post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post) + + Client.LoginByEmail(team.Name, userAdmin.Email, "pwd") + + Client.Must(Client.DeletePost(channel1.Id, post4.Id)) } func TestEmailMention(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: "corey@test.com", FullName: "Bob Bobby", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: "corey@test.com", Nickname: "Bob Bobby", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -551,14 +563,14 @@ func TestEmailMention(t *testing.T) { func TestFuzzyPosts(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) diff --git a/api/server.go b/api/server.go index 58986a8d41..3163f79f53 100644 --- a/api/server.go +++ b/api/server.go @@ -28,7 +28,6 @@ func NewServer() { Srv = &Server{} Srv.Server = manners.NewServer() Srv.Store = store.NewSqlStore() - store.RedisClient() Srv.Router = mux.NewRouter() Srv.Router.NotFoundHandler = http.HandlerFunc(Handle404) @@ -54,7 +53,7 @@ func StopServer() { Srv.Server.Shutdown <- true Srv.Store.Close() - store.RedisClose() + hub.Stop() l4g.Info("Server stopped") } diff --git a/api/sharding.go b/api/sharding.go new file mode 100644 index 0000000000..569d3a232e --- /dev/null +++ b/api/sharding.go @@ -0,0 +1,79 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +/* +func createSubDomain(subDomain string, target string) { + + if utils.Cfg.AWSSettings.Route53AccessKeyId == "" { + return + } + + creds := aws.Creds(utils.Cfg.AWSSettings.Route53AccessKeyId, utils.Cfg.AWSSettings.Route53SecretAccessKey, "") + r53 := route53.New(aws.DefaultConfig.Merge(&aws.Config{Credentials: creds, Region: utils.Cfg.AWSSettings.Route53Region})) + + rr := route53.ResourceRecord{ + Value: aws.String(target), + } + + rrs := make([]*route53.ResourceRecord, 1) + rrs[0] = &rr + + change := route53.Change{ + Action: aws.String("CREATE"), + ResourceRecordSet: &route53.ResourceRecordSet{ + Name: aws.String(fmt.Sprintf("%v.%v", subDomain, utils.Cfg.ServiceSettings.Domain)), + TTL: aws.Long(300), + Type: aws.String("CNAME"), + ResourceRecords: rrs, + }, + } + + changes := make([]*route53.Change, 1) + changes[0] = &change + + r53req := &route53.ChangeResourceRecordSetsInput{ + HostedZoneID: aws.String(utils.Cfg.AWSSettings.Route53ZoneId), + ChangeBatch: &route53.ChangeBatch{ + Changes: changes, + }, + } + + if _, err := r53.ChangeResourceRecordSets(r53req); err != nil { + l4g.Error("erro in createSubDomain domain=%v err=%v", subDomain, err) + return + } +} + +func doesSubDomainExist(subDomain string) bool { + + // if it's configured for testing then skip this step + if utils.Cfg.AWSSettings.Route53AccessKeyId == "" { + return false + } + + creds := aws.Creds(utils.Cfg.AWSSettings.Route53AccessKeyId, utils.Cfg.AWSSettings.Route53SecretAccessKey, "") + r53 := route53.New(aws.DefaultConfig.Merge(&aws.Config{Credentials: creds, Region: utils.Cfg.AWSSettings.Route53Region})) + + r53req := &route53.ListResourceRecordSetsInput{ + HostedZoneID: aws.String(utils.Cfg.AWSSettings.Route53ZoneId), + MaxItems: aws.String("1"), + StartRecordName: aws.String(fmt.Sprintf("%v.%v.", subDomain, utils.Cfg.ServiceSettings.Domain)), + } + + if result, err := r53.ListResourceRecordSets(r53req); err != nil { + l4g.Error("error in doesSubDomainExist domain=%v err=%v", subDomain, err) + return true + } else { + + for _, v := range result.ResourceRecordSets { + if v.Name != nil && *v.Name == fmt.Sprintf("%v.%v.", subDomain, utils.Cfg.ServiceSettings.Domain) { + return true + } + } + } + + return false +} +*/ diff --git a/api/team.go b/api/team.go index c4a0ca1811..1145e6e81c 100644 --- a/api/team.go +++ b/api/team.go @@ -6,8 +6,6 @@ package api import ( l4g "code.google.com/p/log4go" "fmt" - "github.com/awslabs/aws-sdk-go/aws" - "github.com/awslabs/aws-sdk-go/service/route53" "github.com/gorilla/mux" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" @@ -24,11 +22,11 @@ func InitTeam(r *mux.Router) { sr.Handle("/create", ApiAppHandler(createTeam)).Methods("POST") sr.Handle("/create_from_signup", ApiAppHandler(createTeamFromSignup)).Methods("POST") sr.Handle("/signup", ApiAppHandler(signupTeam)).Methods("POST") - sr.Handle("/find_team_by_domain", ApiAppHandler(findTeamByDomain)).Methods("POST") + sr.Handle("/find_team_by_name", ApiAppHandler(findTeamByName)).Methods("POST") sr.Handle("/find_teams", ApiAppHandler(findTeams)).Methods("POST") sr.Handle("/email_teams", ApiAppHandler(emailTeams)).Methods("POST") sr.Handle("/invite_members", ApiUserRequired(inviteMembers)).Methods("POST") - sr.Handle("/update_name", ApiUserRequired(updateTeamName)).Methods("POST") + sr.Handle("/update_name", ApiUserRequired(updateTeamDisplayName)).Methods("POST") sr.Handle("/update_valet_feature", ApiUserRequired(updateValetFeature)).Methods("POST") sr.Handle("/me", ApiUserRequired(getMyTeam)).Methods("GET") } @@ -37,38 +35,38 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) { m := model.MapFromJson(r.Body) email := strings.ToLower(strings.TrimSpace(m["email"])) - name := strings.TrimSpace(m["name"]) + displayName := strings.TrimSpace(m["display_name"]) if len(email) == 0 { c.SetInvalidParam("signupTeam", "email") return } - if len(name) == 0 { - c.SetInvalidParam("signupTeam", "name") + if len(displayName) == 0 { + c.SetInvalidParam("signupTeam", "display_name") return } - subjectPage := NewServerTemplatePage("signup_team_subject", c.TeamUrl) - bodyPage := NewServerTemplatePage("signup_team_body", c.TeamUrl) + subjectPage := NewServerTemplatePage("signup_team_subject", c.GetSiteURL()) + bodyPage := NewServerTemplatePage("signup_team_body", c.GetSiteURL()) bodyPage.Props["TourUrl"] = utils.Cfg.TeamSettings.TourLink props := make(map[string]string) props["email"] = email - props["name"] = name + props["display_name"] = displayName props["time"] = fmt.Sprintf("%v", model.GetMillis()) data := model.MapToJson(props) hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.InviteSalt)) - bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_team_complete/?d=%s&h=%s", c.TeamUrl, url.QueryEscape(data), url.QueryEscape(hash)) + bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_team_complete/?d=%s&h=%s", c.GetSiteURL(), url.QueryEscape(data), url.QueryEscape(hash)) if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil { c.Err = err return } - if utils.Cfg.ServiceSettings.Mode == utils.MODE_DEV { + if utils.Cfg.ServiceSettings.Mode == utils.MODE_DEV || utils.Cfg.EmailSettings.ByPassEmail { m["follow_link"] = bodyPage.Props["Link"] } @@ -119,25 +117,16 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) { return } - found := FindTeamByDomain(c, teamSignup.Team.Domain, "true") + found := FindTeamByName(c, teamSignup.Team.Name, "true") if c.Err != nil { return } if found { - c.Err = model.NewAppError("createTeamFromSignup", "This URL is unavailable. Please try another.", "d="+teamSignup.Team.Domain) + c.Err = model.NewAppError("createTeamFromSignup", "This URL is unavailable. Please try another.", "d="+teamSignup.Team.Name) return } - if IsBetaDomain(r) { - for key, value := range utils.Cfg.ServiceSettings.Shards { - if strings.Index(r.Host, key) == 0 { - createSubDomain(teamSignup.Team.Domain, value) - break - } - } - } - teamSignup.Team.AllowValet = utils.Cfg.TeamSettings.AllowValetDefault if result := <-Srv.Store.Team().Save(&teamSignup.Team); result.Err != nil { @@ -166,7 +155,7 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) { } } - InviteMembers(rteam, ruser, teamSignup.Invites) + InviteMembers(c, rteam, ruser, teamSignup.Invites) teamSignup.Team = *rteam teamSignup.User = *ruser @@ -211,87 +200,14 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) { } } -func doesSubDomainExist(subDomain string) bool { - - // if it's configured for testing then skip this step - if utils.Cfg.AWSSettings.Route53AccessKeyId == "" { - return false - } - - creds := aws.Creds(utils.Cfg.AWSSettings.Route53AccessKeyId, utils.Cfg.AWSSettings.Route53SecretAccessKey, "") - r53 := route53.New(aws.DefaultConfig.Merge(&aws.Config{Credentials: creds, Region: utils.Cfg.AWSSettings.Route53Region})) - - r53req := &route53.ListResourceRecordSetsInput{ - HostedZoneID: aws.String(utils.Cfg.AWSSettings.Route53ZoneId), - MaxItems: aws.String("1"), - StartRecordName: aws.String(fmt.Sprintf("%v.%v.", subDomain, utils.Cfg.ServiceSettings.Domain)), - } - - if result, err := r53.ListResourceRecordSets(r53req); err != nil { - l4g.Error("error in doesSubDomainExist domain=%v err=%v", subDomain, err) - return true - } else { - - for _, v := range result.ResourceRecordSets { - if v.Name != nil && *v.Name == fmt.Sprintf("%v.%v.", subDomain, utils.Cfg.ServiceSettings.Domain) { - return true - } - } - } - - return false -} - -func createSubDomain(subDomain string, target string) { - - if utils.Cfg.AWSSettings.Route53AccessKeyId == "" { - return - } - - creds := aws.Creds(utils.Cfg.AWSSettings.Route53AccessKeyId, utils.Cfg.AWSSettings.Route53SecretAccessKey, "") - r53 := route53.New(aws.DefaultConfig.Merge(&aws.Config{Credentials: creds, Region: utils.Cfg.AWSSettings.Route53Region})) - - rr := route53.ResourceRecord{ - Value: aws.String(target), - } - - rrs := make([]*route53.ResourceRecord, 1) - rrs[0] = &rr - - change := route53.Change{ - Action: aws.String("CREATE"), - ResourceRecordSet: &route53.ResourceRecordSet{ - Name: aws.String(fmt.Sprintf("%v.%v", subDomain, utils.Cfg.ServiceSettings.Domain)), - TTL: aws.Long(300), - Type: aws.String("CNAME"), - ResourceRecords: rrs, - }, - } - - changes := make([]*route53.Change, 1) - changes[0] = &change - - r53req := &route53.ChangeResourceRecordSetsInput{ - HostedZoneID: aws.String(utils.Cfg.AWSSettings.Route53ZoneId), - ChangeBatch: &route53.ChangeBatch{ - Changes: changes, - }, - } - - if _, err := r53.ChangeResourceRecordSets(r53req); err != nil { - l4g.Error("erro in createSubDomain domain=%v err=%v", subDomain, err) - return - } -} - -func findTeamByDomain(c *Context, w http.ResponseWriter, r *http.Request) { +func findTeamByName(c *Context, w http.ResponseWriter, r *http.Request) { m := model.MapFromJson(r.Body) - domain := strings.ToLower(strings.TrimSpace(m["domain"])) + name := strings.ToLower(strings.TrimSpace(m["name"])) all := strings.ToLower(strings.TrimSpace(m["all"])) - found := FindTeamByDomain(c, domain, all) + found := FindTeamByName(c, name, all) if c.Err != nil { return @@ -304,56 +220,25 @@ func findTeamByDomain(c *Context, w http.ResponseWriter, r *http.Request) { } } -func FindTeamByDomain(c *Context, domain string, all string) bool { +func FindTeamByName(c *Context, name string, all string) bool { - if domain == "" || len(domain) > 64 { - c.SetInvalidParam("findTeamByDomain", "domain") + if name == "" || len(name) > 64 { + c.SetInvalidParam("findTeamByName", "domain") return false } - if model.IsReservedDomain(domain) { - c.Err = model.NewAppError("findTeamByDomain", "This URL is unavailable. Please try another.", "d="+domain) + if model.IsReservedTeamName(name) { + c.Err = model.NewAppError("findTeamByName", "This URL is unavailable. Please try another.", "name="+name) return false } - if all == "false" { - if result := <-Srv.Store.Team().GetByDomain(domain); result.Err != nil { - return false - } else { - return true - } + if result := <-Srv.Store.Team().GetByName(name); result.Err != nil { + return false } else { - if doesSubDomainExist(domain) { - return true - } - - protocol := "http" - - if utils.Cfg.ServiceSettings.UseSSL { - protocol = "https" - } - - for key, _ := range utils.Cfg.ServiceSettings.Shards { - url := fmt.Sprintf("%v://%v.%v/api/v1", protocol, key, utils.Cfg.ServiceSettings.Domain) - - if strings.Index(utils.Cfg.ServiceSettings.Domain, "localhost") == 0 { - url = fmt.Sprintf("%v://%v/api/v1", protocol, utils.Cfg.ServiceSettings.Domain) - } - - client := model.NewClient(url) - - if result, err := client.FindTeamByDomain(domain, false); err != nil { - c.Err = err - return false - } else { - if result.Data.(bool) { - return true - } - } - } - - return false + return true } + + return false } func findTeams(c *Context, w http.ResponseWriter, r *http.Request) { @@ -376,7 +261,7 @@ func findTeams(c *Context, w http.ResponseWriter, r *http.Request) { s := make([]string, 0, len(teams)) for _, v := range teams { - s = append(s, v.Domain) + s = append(s, v.Name) } w.Write([]byte(model.ArrayToJson(s))) @@ -394,33 +279,8 @@ func emailTeams(c *Context, w http.ResponseWriter, r *http.Request) { return } - protocol := "http" - - if utils.Cfg.ServiceSettings.UseSSL { - protocol = "https" - } - - subjectPage := NewServerTemplatePage("find_teams_subject", c.TeamUrl) - bodyPage := NewServerTemplatePage("find_teams_body", c.TeamUrl) - - for key, _ := range utils.Cfg.ServiceSettings.Shards { - url := fmt.Sprintf("%v://%v.%v/api/v1", protocol, key, utils.Cfg.ServiceSettings.Domain) - - if strings.Index(utils.Cfg.ServiceSettings.Domain, "localhost") == 0 { - url = fmt.Sprintf("%v://%v/api/v1", protocol, utils.Cfg.ServiceSettings.Domain) - } - - client := model.NewClient(url) - - if result, err := client.FindTeams(email); err != nil { - l4g.Error("An error occured while finding teams at %v err=%v", key, err) - } else { - data := result.Data.([]string) - for _, domain := range data { - bodyPage.Props[fmt.Sprintf("%v://%v.%v", protocol, domain, utils.Cfg.ServiceSettings.Domain)] = "" - } - } - } + subjectPage := NewServerTemplatePage("find_teams_subject", c.GetSiteURL()) + bodyPage := NewServerTemplatePage("find_teams_body", c.GetSiteURL()) if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil { l4g.Error("An error occured while sending an email in emailTeams err=%v", err) @@ -470,29 +330,18 @@ func inviteMembers(c *Context, w http.ResponseWriter, r *http.Request) { ia = append(ia, invite["email"]) } - InviteMembers(team, user, ia) + InviteMembers(c, team, user, ia) w.Write([]byte(invites.ToJson())) } -func InviteMembers(team *model.Team, user *model.User, invites []string) { +func InviteMembers(c *Context, team *model.Team, user *model.User, invites []string) { for _, invite := range invites { if len(invite) > 0 { - teamUrl := "" - if utils.Cfg.ServiceSettings.Mode == utils.MODE_DEV { - teamUrl = "http://localhost:8065" - } else if utils.Cfg.ServiceSettings.UseSSL { - teamUrl = fmt.Sprintf("https://%v.%v", team.Domain, utils.Cfg.ServiceSettings.Domain) - } else { - teamUrl = fmt.Sprintf("http://%v.%v", team.Domain, utils.Cfg.ServiceSettings.Domain) - } + teamURL := "" + teamURL = c.GetTeamURLFromTeam(team) - sender := "" - if len(strings.TrimSpace(user.FullName)) == 0 { - sender = user.Username - } else { - sender = user.FullName - } + sender := user.GetDisplayName() senderRole := "" if strings.Contains(user.Roles, model.ROLE_ADMIN) || strings.Contains(user.Roles, model.ROLE_SYSTEM_ADMIN) { @@ -501,11 +350,11 @@ func InviteMembers(team *model.Team, user *model.User, invites []string) { senderRole = "member" } - subjectPage := NewServerTemplatePage("invite_subject", teamUrl) + subjectPage := NewServerTemplatePage("invite_subject", teamURL) subjectPage.Props["SenderName"] = sender - subjectPage.Props["TeamName"] = team.Name - bodyPage := NewServerTemplatePage("invite_body", teamUrl) - bodyPage.Props["TeamName"] = team.Name + subjectPage.Props["TeamDisplayName"] = team.DisplayName + bodyPage := NewServerTemplatePage("invite_body", teamURL) + bodyPage.Props["TeamDisplayName"] = team.DisplayName bodyPage.Props["SenderName"] = sender bodyPage.Props["SenderStatus"] = senderRole @@ -514,12 +363,12 @@ func InviteMembers(team *model.Team, user *model.User, invites []string) { props := make(map[string]string) props["email"] = invite props["id"] = team.Id + props["display_name"] = team.DisplayName props["name"] = team.Name - props["domain"] = team.Domain props["time"] = fmt.Sprintf("%v", model.GetMillis()) data := model.MapToJson(props) hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.InviteSalt)) - bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&h=%s", teamUrl, url.QueryEscape(data), url.QueryEscape(hash)) + bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&h=%s", c.GetSiteURL(), url.QueryEscape(data), url.QueryEscape(hash)) if utils.Cfg.ServiceSettings.Mode == utils.MODE_DEV { l4g.Info("sending invitation to %v %v", invite, bodyPage.Props["Link"]) @@ -532,35 +381,35 @@ func InviteMembers(team *model.Team, user *model.User, invites []string) { } } -func updateTeamName(c *Context, w http.ResponseWriter, r *http.Request) { +func updateTeamDisplayName(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) new_name := props["new_name"] if len(new_name) == 0 { - c.SetInvalidParam("updateTeamName", "new_name") + c.SetInvalidParam("updateTeamDisplayName", "new_name") return } teamId := props["team_id"] if len(teamId) > 0 && len(teamId) != 26 { - c.SetInvalidParam("updateTeamName", "team_id") + c.SetInvalidParam("updateTeamDisplayName", "team_id") return } else if len(teamId) == 0 { teamId = c.Session.TeamId } - if !c.HasPermissionsToTeam(teamId, "updateTeamName") { + if !c.HasPermissionsToTeam(teamId, "updateTeamDisplayName") { return } if !strings.Contains(c.Session.Roles, model.ROLE_ADMIN) { - c.Err = model.NewAppError("updateTeamName", "You do not have the appropriate permissions", "userId="+c.Session.UserId) + c.Err = model.NewAppError("updateTeamDisplayName", "You do not have the appropriate permissions", "userId="+c.Session.UserId) c.Err.StatusCode = http.StatusForbidden return } - if result := <-Srv.Store.Team().UpdateName(new_name, c.Session.TeamId); result.Err != nil { + if result := <-Srv.Store.Team().UpdateDisplayName(new_name, c.Session.TeamId); result.Err != nil { c.Err = result.Err return } diff --git a/api/team_test.go b/api/team_test.go index 2bf3219e4c..2723eff577 100644 --- a/api/team_test.go +++ b/api/team_test.go @@ -32,8 +32,8 @@ func TestCreateFromSignupTeam(t *testing.T) { data := model.MapToJson(props) hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.InviteSalt)) - team := model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} - user := model.User{Email: props["email"], FullName: "Corey Hulen", Password: "hello"} + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + user := model.User{Email: props["email"], Nickname: "Corey Hulen", Password: "hello"} ts := model.TeamSignup{Team: team, User: user, Invites: []string{"corey@test.com"}, Data: data, Hash: hash} @@ -42,7 +42,7 @@ func TestCreateFromSignupTeam(t *testing.T) { t.Fatal(err) } - if rts.Data.(*model.TeamSignup).Team.Name != team.Name { + if rts.Data.(*model.TeamSignup).Team.DisplayName != team.DisplayName { t.Fatal("full name didn't match") } @@ -71,24 +71,24 @@ func TestCreateFromSignupTeam(t *testing.T) { func TestCreateTeam(t *testing.T) { Setup() - team := model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} rteam, err := Client.CreateTeam(&team) if err != nil { t.Fatal(err) } - user := &model.User{TeamId: rteam.Data.(*model.Team).Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: rteam.Data.(*model.Team).Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") c1 := Client.Must(Client.GetChannels("")).Data.(*model.ChannelList) if len(c1.Channels) != 2 { t.Fatal("default channels not created") } - if rteam.Data.(*model.Team).Name != team.Name { + if rteam.Data.(*model.Team).DisplayName != team.DisplayName { t.Fatal("full name didn't match") } @@ -111,10 +111,10 @@ func TestCreateTeam(t *testing.T) { func TestFindTeamByEmail(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) @@ -122,7 +122,7 @@ func TestFindTeamByEmail(t *testing.T) { t.Fatal(err) } else { domains := r1.Data.([]string) - if domains[0] != team.Domain { + if domains[0] != team.Name { t.Fatal(domains) } } @@ -139,14 +139,14 @@ XXXXXX investigate and fix failing test func TestFindTeamByDomain(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - if r1, err := Client.FindTeamByDomain(team.Domain, false); err != nil { + if r1, err := Client.FindTeamByDomain(team.Name, false); err != nil { t.Fatal(err) } else { val := r1.Data.(bool) @@ -155,7 +155,7 @@ func TestFindTeamByDomain(t *testing.T) { } } - if r1, err := Client.FindTeamByDomain(team.Domain, true); err != nil { + if r1, err := Client.FindTeamByDomain(team.Name, true); err != nil { t.Fatal(err) } else { val := r1.Data.(bool) @@ -179,10 +179,10 @@ func TestFindTeamByDomain(t *testing.T) { func TestFindTeamByEmailSend(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) @@ -203,14 +203,14 @@ func TestFindTeamByEmailSend(t *testing.T) { func TestInviteMembers(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") invite := make(map[string]string) invite["email"] = model.NewId() + "corey@test.com" @@ -229,54 +229,54 @@ func TestInviteMembers(t *testing.T) { } } -func TestUpdateTeamName(t *testing.T) { +func TestUpdateTeamDisplayName(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") data := make(map[string]string) data["new_name"] = "NewName" - if _, err := Client.UpdateTeamName(data); err == nil { + if _, err := Client.UpdateTeamDisplayName(data); err == nil { t.Fatal("Should have errored, not admin") } - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") data["new_name"] = "" - if _, err := Client.UpdateTeamName(data); err == nil { + if _, err := Client.UpdateTeamDisplayName(data); err == nil { t.Fatal("Should have errored, empty name") } data["new_name"] = "NewName" - if _, err := Client.UpdateTeamName(data); err != nil { + if _, err := Client.UpdateTeamDisplayName(data); err != nil { t.Fatal(err) } // No GET team web service, so hard to confirm here that team name updated data["team_id"] = "junk" - if _, err := Client.UpdateTeamName(data); err == nil { + if _, err := Client.UpdateTeamDisplayName(data); err == nil { t.Fatal("Should have errored, junk team id") } data["team_id"] = "12345678901234567890123456" - if _, err := Client.UpdateTeamName(data); err == nil { + if _, err := Client.UpdateTeamDisplayName(data); err == nil { t.Fatal("Should have errored, bad team id") } data["team_id"] = team.Id data["new_name"] = "NewNameAgain" - if _, err := Client.UpdateTeamName(data); err != nil { + if _, err := Client.UpdateTeamDisplayName(data); err != nil { t.Fatal(err) } // No GET team web service, so hard to confirm here that team name updated @@ -285,17 +285,17 @@ func TestUpdateTeamName(t *testing.T) { func TestFuzzyTeamCreate(t *testing.T) { for i := 0; i < len(utils.FUZZY_STRINGS_NAMES) || i < len(utils.FUZZY_STRINGS_EMAILS); i++ { - testName := "Name" + testDisplayName := "Name" testEmail := "test@nowhere.com" if i < len(utils.FUZZY_STRINGS_NAMES) { - testName = utils.FUZZY_STRINGS_NAMES[i] + testDisplayName = utils.FUZZY_STRINGS_NAMES[i] } if i < len(utils.FUZZY_STRINGS_EMAILS) { testEmail = utils.FUZZY_STRINGS_EMAILS[i] } - team := model.Team{Name: testName, Domain: "z-z-" + model.NewId() + "a", Email: testEmail, Type: model.TEAM_OPEN} + team := model.Team{DisplayName: testDisplayName, Name: "z-z-" + model.NewId() + "a", Email: testEmail, Type: model.TEAM_OPEN} _, err := Client.CreateTeam(&team) if err != nil { @@ -307,22 +307,22 @@ func TestFuzzyTeamCreate(t *testing.T) { func TestGetMyTeam(t *testing.T) { Setup() - team := model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} rteam, _ := Client.CreateTeam(&team) - user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} ruser, _ := Client.CreateUser(&user, "") store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) - Client.LoginByEmail(team.Domain, user.Email, user.Password) + Client.LoginByEmail(team.Name, user.Email, user.Password) if result, err := Client.GetMyTeam(""); err != nil { t.Fatal("Failed to get user") } else { - if result.Data.(*model.Team).Name != team.Name { + if result.Data.(*model.Team).DisplayName != team.DisplayName { t.Fatal("team names did not match") } - if result.Data.(*model.Team).Domain != team.Domain { + if result.Data.(*model.Team).Name != team.Name { t.Fatal("team domains did not match") } if result.Data.(*model.Team).Type != team.Type { @@ -334,25 +334,25 @@ func TestGetMyTeam(t *testing.T) { func TestUpdateValetFeature(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - team2 := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team) - user3 := &model.User{TeamId: team2.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user3 := &model.User{TeamId: team2.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user3.Id)) - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") data := make(map[string]string) data["allow_valet"] = "true" @@ -360,7 +360,7 @@ func TestUpdateValetFeature(t *testing.T) { t.Fatal("Should have errored, not admin") } - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") data["allow_valet"] = "" if _, err := Client.UpdateValetFeature(data); err == nil { @@ -398,7 +398,7 @@ func TestUpdateValetFeature(t *testing.T) { t.Fatal("Should have errored - allow valet property not updated") } - Client.LoginByEmail(team2.Domain, user3.Email, "pwd") + Client.LoginByEmail(team2.Name, user3.Email, "pwd") data["team_id"] = team.Id data["allow_valet"] = "true" diff --git a/api/templates/email_change_body.html b/api/templates/email_change_body.html index dffe589cd2..f8f3845e78 100644 --- a/api/templates/email_change_body.html +++ b/api/templates/email_change_body.html @@ -9,7 +9,7 @@ @@ -18,7 +18,7 @@ @@ -34,7 +34,7 @@
- +

You updated your email

-

You updated your email for {{.Props.TeamName}} on {{ .TeamUrl }}
If this change wasn't initiated by you, please reply to this email and let us know.

+

You updated your email for {{.Props.TeamDisplayName}} on {{ .TeamURL }}
If this change wasn't initiated by you, please reply to this email and let us know.

- +

(c) 2015 SpinPunch, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.
diff --git a/api/templates/email_change_subject.html b/api/templates/email_change_subject.html index 612dfcbe7c..5690b148a4 100644 --- a/api/templates/email_change_subject.html +++ b/api/templates/email_change_subject.html @@ -1 +1 @@ -{{define "email_change_subject"}}You updated your email for {{.Props.TeamName}} on {{ .Props.Domain }}{{end}} +{{define "email_change_subject"}}You updated your email for {{.Props.TeamDisplayName}} on {{ .Props.Domain }}{{end}} diff --git a/api/templates/find_teams_body.html b/api/templates/find_teams_body.html index d8b582b8a2..6eaaf56e07 100644 --- a/api/templates/find_teams_body.html +++ b/api/templates/find_teams_body.html @@ -9,7 +9,7 @@ @@ -42,7 +42,7 @@
- +

- +

(c) 2015 SpinPunch, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.
diff --git a/api/templates/invite_body.html b/api/templates/invite_body.html index 8be2ac0df2..46189fae57 100644 --- a/api/templates/invite_body.html +++ b/api/templates/invite_body.html @@ -9,7 +9,7 @@ @@ -18,7 +18,7 @@
- +

You've been invited

-

{{.Props.TeamName}} started using {{.SiteName}}.
The team {{.Props.SenderStatus}} {{.Props.SenderName}}, has invited you to join {{.Props.TeamName}}.

+

{{.Props.TeamDisplayName}} started using {{.SiteName}}.
The team {{.Props.SenderStatus}} {{.Props.SenderName}}, has invited you to join {{.Props.TeamDisplayName}}.

Join Team

@@ -37,7 +37,7 @@

- +

(c) 2015 SpinPunch, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.
diff --git a/api/templates/invite_subject.html b/api/templates/invite_subject.html index 4be15e3436..6a1e57dcca 100644 --- a/api/templates/invite_subject.html +++ b/api/templates/invite_subject.html @@ -1 +1 @@ -{{define "invite_subject"}}{{ .Props.SenderName }} invited you to join {{ .Props.TeamName }} Team on {{.SiteName}}{{end}} +{{define "invite_subject"}}{{ .Props.SenderName }} invited you to join {{ .Props.TeamDisplayName }} Team on {{.SiteName}}{{end}} diff --git a/api/templates/password_change_body.html b/api/templates/password_change_body.html index f6499d46dc..515c0a7d9d 100644 --- a/api/templates/password_change_body.html +++ b/api/templates/password_change_body.html @@ -9,7 +9,7 @@ @@ -18,7 +18,7 @@ @@ -34,7 +34,7 @@
- +

You updated your password

-

You updated your password for {{.Props.TeamName}} on {{ .TeamUrl }} by {{.Props.Method}}.
If this change wasn't initiated by you, please reply to this email and let us know.

+

You updated your password for {{.Props.TeamDisplayName}} on {{ .TeamURL }} by {{.Props.Method}}.
If this change wasn't initiated by you, please reply to this email and let us know.

- +

(c) 2015 SpinPunch, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.
diff --git a/api/templates/password_change_subject.html b/api/templates/password_change_subject.html index 57422c6925..55daefdb1c 100644 --- a/api/templates/password_change_subject.html +++ b/api/templates/password_change_subject.html @@ -1 +1 @@ -{{define "password_change_subject"}}You updated your password for {{.Props.TeamName}} on {{ .SiteName }}{{end}} +{{define "password_change_subject"}}You updated your password for {{.Props.TeamDisplayName}} on {{ .SiteName }}{{end}} diff --git a/api/templates/post_body.html b/api/templates/post_body.html index 069cdf1fb8..41a29d020d 100644 --- a/api/templates/post_body.html +++ b/api/templates/post_body.html @@ -9,7 +9,7 @@ @@ -37,7 +37,7 @@
- +

- +

(c) 2015 SpinPunch, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.
diff --git a/api/templates/post_subject.html b/api/templates/post_subject.html index 32df31018c..8ebc9550b9 100644 --- a/api/templates/post_subject.html +++ b/api/templates/post_subject.html @@ -1 +1 @@ -{{define "post_subject"}}[{{.Props.TeamName}} {{.SiteName}}] {{.Props.SubjectText}} for {{.Props.Month}} {{.Props.Day}}, {{.Props.Year}}{{end}} \ No newline at end of file +{{define "post_subject"}}[{{.Props.TeamDisplayName}} {{.SiteName}}] {{.Props.SubjectText}} for {{.Props.Month}} {{.Props.Day}}, {{.Props.Year}}{{end}} diff --git a/api/templates/reset_body.html b/api/templates/reset_body.html index 3a5d62ab48..af9f6b4e8b 100644 --- a/api/templates/reset_body.html +++ b/api/templates/reset_body.html @@ -9,7 +9,7 @@ @@ -18,7 +18,7 @@
- +

You requested a password reset

-

To change your password, click below.
If you did not mean to reset your password, please ignore this email and your password will remain the same.

+

To change your password, click "Reset Password" below.
If you did not mean to reset your password, please ignore this email and your password will remain the same.

Reset Password

@@ -37,7 +37,7 @@

- +

(c) 2015 SpinPunch, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.
diff --git a/api/templates/signup_team_body.html b/api/templates/signup_team_body.html index 6f8bbd92d3..5a5ae4d477 100644 --- a/api/templates/signup_team_body.html +++ b/api/templates/signup_team_body.html @@ -9,7 +9,7 @@ @@ -40,7 +40,7 @@
- +

- +

(c) 2015 SpinPunch, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.
diff --git a/api/templates/verify_body.html b/api/templates/verify_body.html index 56d85572b1..67ded9c20e 100644 --- a/api/templates/verify_body.html +++ b/api/templates/verify_body.html @@ -9,7 +9,7 @@ @@ -37,7 +37,7 @@
- +

- +

(c) 2015 SpinPunch, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.
diff --git a/api/templates/verify_subject.html b/api/templates/verify_subject.html index de0ef1d7a5..a661507356 100644 --- a/api/templates/verify_subject.html +++ b/api/templates/verify_subject.html @@ -1 +1 @@ -{{define "verify_subject"}}[{{ .Props.TeamName }} {{ .SiteName }}] Email Verification{{end}} \ No newline at end of file +{{define "verify_subject"}}[{{ .Props.TeamDisplayName }} {{ .SiteName }}] Email Verification{{end}} diff --git a/api/templates/welcome_body.html b/api/templates/welcome_body.html index fbc2e5551e..7107bc2e06 100644 --- a/api/templates/welcome_body.html +++ b/api/templates/welcome_body.html @@ -9,7 +9,7 @@ @@ -17,8 +17,8 @@
- +
@@ -34,7 +34,7 @@ ; - var isDirect = false; + var isDirect = (this.state.channel.type === 'D'); - if (this.state.channel.type === 'O') { - channelTitle = this.state.channel.display_name; - } else if (this.state.channel.type === 'P') { - channelTitle = this.state.channel.display_name; - } else if (this.state.channel.type === 'D') { - isDirect = true; + if (isDirect) { if (this.state.users.length > 1) { - if (this.state.users[0].id === UserStore.getCurrentId()) { - channelTitle = ; - } else { - channelTitle = ; - } + var contact = this.state.users[((this.state.users[0].id === currentId) ? 1 : 0)]; + channelTitle = ; } } @@ -192,25 +169,28 @@ module.exports = React.createClass({ @@ -220,14 +200,13 @@ module.exports = React.createClass({ {channelTitle} } - - { searchForm } + + + ) })} @@ -100,15 +103,7 @@ module.exports = React.createClass({

No more channels to join

Click 'Create New Channel' to make a new one

) - :
-
-

Loading

-
-
-
-
-
- } + : } { server_error }
diff --git a/web/react/components/msg_typing.jsx b/web/react/components/msg_typing.jsx index a6953028f1..aacb315dd9 100644 --- a/web/react/components/msg_typing.jsx +++ b/web/react/components/msg_typing.jsx @@ -51,7 +51,7 @@ module.exports = React.createClass({ }, render: function() { return ( - { this.state.text } + { this.state.text } ); } }); diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx index 35f7d90445..34c65c34fd 100644 --- a/web/react/components/navbar.jsx +++ b/web/react/components/navbar.jsx @@ -2,21 +2,21 @@ // See License.txt for license information. -var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); -var Sidebar = require('./sidebar.jsx'); var UserStore = require('../stores/user_store.jsx'); -var SocketStore = require('../stores/socket_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); -var Constants = require('../utils/constants.jsx'); +var TeamStore = require('../stores/team_store.jsx'); + var UserProfile = require('./user_profile.jsx'); var MessageWrapper = require('./message_wrapper.jsx'); + +var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; +var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); function getCountsStateFromStores() { - var count = 0; var channels = ChannelStore.getAll(); var members = ChannelStore.getAllMembers(); @@ -34,7 +34,7 @@ function getCountsStateFromStores() { } }); - return { count: count } + return { count: count }; } var NotifyCounts = React.createClass({ @@ -54,99 +54,11 @@ var NotifyCounts = React.createClass({ return getCountsStateFromStores(); }, render: function() { - if (this.state.count == 0) { - return (); + if (this.state.count) { + return { this.state.count }; + } else { + return null; } - else { - return ({ this.state.count }); - } - } -}); - -var NavbarLoginForm = React.createClass({ - handleSubmit: function(e) { - e.preventDefault(); - var state = { } - - var domain = this.refs.domain.getDOMNode().value.trim(); - if (!domain) { - state.server_error = "A domain is required" - this.setState(state); - return; - } - - var email = this.refs.email.getDOMNode().value.trim(); - if (!email) { - state.server_error = "An email is required" - this.setState(state); - return; - } - - var password = this.refs.password.getDOMNode().value.trim(); - if (!password) { - state.server_error = "A password is required" - this.setState(state); - return; - } - - state.server_error = ""; - this.setState(state); - - client.loginByEmail(domain, email, password, - function(data) { - UserStore.setLastDomain(domain); - UserStore.setLastEmail(email); - UserStore.setCurrentUser(data); - - var redirect = utils.getUrlParameter("redirect"); - if (redirect) { - window.location.href = decodeURI(redirect); - } else { - window.location.href = '/channels/town-square'; - } - - }.bind(this), - function(err) { - if (err.message == "Login failed because email address has not been verified") { - window.location.href = '/verify?domain=' + encodeURIComponent(domain) + '&email=' + encodeURIComponent(email); - return; - } - state.server_error = err.message; - this.valid = false; - this.setState(state); - }.bind(this) - ); - }, - getInitialState: function() { - return { }; - }, - render: function() { - var server_error = this.state.server_error ? : null; - - var subDomain = utils.getSubDomain(); - var subDomainClass = "form-control hidden"; - - if (subDomain == "") { - subDomain = UserStore.getLastDomain(); - subDomainClass = "form-control"; - } - - return ( -
- Find your team -
- { server_error } - -
-
- -
-
- -
- - - ); } }); @@ -159,13 +71,14 @@ function getStateFromStores() { } module.exports = React.createClass({ + displayName: 'Navbar', + componentDidMount: function() { ChannelStore.addChangeListener(this._onChange); ChannelStore.addExtraInfoChangeListener(this._onChange); - var self = this; - $('.inner__wrap').click(self.hideSidebars); + $('.inner__wrap').click(this.hideSidebars); - $('body').on('click.infopopover', function(e){ + $('body').on('click.infopopover', function(e) { if ($(e.target).attr('data-toggle') !== 'popover' && $(e.target).parents('.popover.in').length === 0) { $('.info-popover').popover('hide'); @@ -181,13 +94,13 @@ module.exports = React.createClass({ }, handleLeave: function(e) { client.leaveChannel(this.state.channel.id, - function(data) { + function(data, text, req) { AsyncClient.getChannels(true); - window.location.href = '/channels/town-square'; + window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square'; }.bind(this), function(err) { AsyncClient.dispatchError(err, "handleLeave"); - }.bind(this) + } ); }, hideSidebars: function(e) { @@ -204,7 +117,7 @@ module.exports = React.createClass({ }); if (e.target.className != 'navbar-toggle' && e.target.className != 'icon-bar') { - $('.inner__wrap').removeClass('move--right').removeClass('move--left').removeClass('move--left-small'); + $('.inner__wrap').removeClass('move--right move--left move--left-small'); $('.sidebar--left').removeClass('move--right'); $('.sidebar--right').removeClass('move--left'); $('.sidebar--menu').removeClass('move--left'); @@ -229,24 +142,22 @@ module.exports = React.createClass({ render: function() { var currentId = UserStore.getCurrentId(); - var channelName = ""; var popoverContent = ""; - var channelTitle = this.props.teamName; + var channelTitle = this.props.teamDisplayName; var isAdmin = false; var isDirect = false; var description = "" + var channel = this.state.channel; - if (this.state.channel) { - var channel = this.state.channel; - description = utils.textToJsx(this.state.channel.description, {"singleline": true, "noMentionHighlight": true}); - popoverContent = React.renderToString(); - channelName = this.state.channel.name; + if (channel) { + description = utils.textToJsx(channel.description, {"singleline": true, "noMentionHighlight": true}); + popoverContent = React.renderToString(); isAdmin = this.state.member.roles.indexOf("admin") > -1; if (channel.type === 'O') { - channelTitle = this.state.channel.display_name; + channelTitle = channel.display_name; } else if (channel.type === 'P') { - channelTitle = this.state.channel.display_name; + channelTitle = channel.display_name; } else if (channel.type === 'D') { isDirect = true; if (this.state.users.length > 1) { @@ -258,12 +169,11 @@ module.exports = React.createClass({ } } - if(this.state.channel.description.length == 0){ - popoverContent = React.renderToString(
No channel description yet.
Click here to add one.
); + if (channel.description.length == 0) { + popoverContent = React.renderToString(
No channel description yet.
Click here to add one.
); } } - var loginForm = currentId == null ? : null; var navbar_collapse_button = currentId != null ? null :
- ); -} + } }); - - diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx index 160241c1c5..49e0884580 100644 --- a/web/react/components/new_channel.jsx +++ b/web/react/components/new_channel.jsx @@ -6,6 +6,8 @@ var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); var asyncClient = require('../utils/async_client.jsx'); var UserStore = require('../stores/user_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); +var Constants = require('../utils/constants.jsx'); module.exports = React.createClass({ handleSubmit: function(e) { @@ -60,13 +62,13 @@ module.exports = React.createClass({ var self = this; client.createChannel(channel, - function(data) { + function() { this.refs.display_name.getDOMNode().value = ""; this.refs.channel_name.getDOMNode().value = ""; this.refs.channel_desc.getDOMNode().value = ""; $(self.refs.modal.getDOMNode()).modal('hide'); - window.location.href = "/channels/" + channel.name; + window.location = TeamStore.getCurrentTeamUrl() + "/channels/" + channel.name; asyncClient.getChannels(true); }.bind(this), function(err) { @@ -122,7 +124,7 @@ module.exports = React.createClass({
- +
{ server_error } diff --git a/web/react/components/password_reset.jsx b/web/react/components/password_reset.jsx index 24566c7b19..b2edea6208 100644 --- a/web/react/components/password_reset.jsx +++ b/web/react/components/password_reset.jsx @@ -10,13 +10,6 @@ SendResetPasswordLink = React.createClass({ e.preventDefault(); var state = {}; - var domain = this.refs.domain.getDOMNode().value.trim(); - if (!domain) { - state.error = "A domain is required" - this.setState(state); - return; - } - var email = this.refs.email.getDOMNode().value.trim(); if (!email) { state.error = "Please enter a valid email address." @@ -29,17 +22,17 @@ SendResetPasswordLink = React.createClass({ data = {}; data['email'] = email; - data['domain'] = domain; + data['name'] = this.props.teamName; client.sendPasswordReset(data, - function(data) { - this.setState({ error: null, update_text:

A password reset link has been sent to {email} for your {this.props.teamName} team on {config.SiteName}.com.

, more_update_text: "Please check your inbox." }); - $(this.refs.reset_form.getDOMNode()).hide(); - }.bind(this), - function(err) { - this.setState({ error: err.message, update_text: null, more_update_text: null }); - }.bind(this) - ); + function(data) { + this.setState({ error: null, update_text:

A password reset link has been sent to {email} for your {this.props.teamDisplayName} team on {window.location.hostname}.

, more_update_text: "Please check your inbox." }); + $(this.refs.reset_form.getDOMNode()).hide(); + }.bind(this), + function(err) { + this.setState({ error: err.message, update_text: null, more_update_text: null }); + }.bind(this) + ); }, getInitialState: function() { return {}; @@ -48,24 +41,13 @@ SendResetPasswordLink = React.createClass({ var update_text = this.state.update_text ?
{this.state.update_text}{this.state.more_update_text}
: null; var error = this.state.error ?
: null; - var subDomain = utils.getSubDomain(); - var subDomainClass = "form-control hidden"; - - if (subDomain == "") { - subDomain = UserStore.getLastDomain(); - subDomainClass = "form-control"; - } - return (

Password Reset

{ update_text }
-

{"To reset your password, enter the email address you used to sign up for " + this.props.teamName + "."}

-
- -
+

{"To reset your password, enter the email address you used to sign up for " + this.props.teamDisplayName + "."}

@@ -83,13 +65,6 @@ ResetPassword = React.createClass({ e.preventDefault(); var state = {}; - var domain = this.refs.domain.getDOMNode().value.trim(); - if (!domain) { - state.error = "A domain is required" - this.setState(state); - return; - } - var password = this.refs.password.getDOMNode().value.trim(); if (!password || password.length < 5) { state.error = "Please enter at least 5 characters." @@ -104,41 +79,30 @@ ResetPassword = React.createClass({ data['new_password'] = password; data['hash'] = this.props.hash; data['data'] = this.props.data; - data['domain'] = domain; + data['name'] = this.props.teamName; client.resetPassword(data, - function(data) { - this.setState({ error: null, update_text: "Your password has been updated successfully." }); - }.bind(this), - function(err) { - this.setState({ error: err.message, update_text: null }); - }.bind(this) - ); + function(data) { + this.setState({ error: null, update_text: "Your password has been updated successfully." }); + }.bind(this), + function(err) { + this.setState({ error: err.message, update_text: null }); + }.bind(this) + ); }, getInitialState: function() { return {}; }, render: function() { - var update_text = this.state.update_text ?

: null; + var update_text = this.state.update_text ?

: null; var error = this.state.error ?
: null; - var subDomain = this.props.domain != "" ? this.props.domain : utils.getSubDomain(); - var subDomainClass = "form-control hidden"; - - if (subDomain == "") { - subDomain = UserStore.getLastDomain(); - subDomainClass = "form-control"; - } - return (

Password Reset

-

{"Enter a new password for your " + this.props.teamName + " " + config.SiteName + " account."}

-
- -
+

{"Enter a new password for your " + this.props.teamDisplayName + " " + config.SiteName + " account."}

@@ -161,14 +125,15 @@ module.exports = React.createClass({ if (this.props.isReset === "false") { return ( ); } else { return ( diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index 04b5ba082c..e72a2d001c 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -10,6 +10,7 @@ var UserStore = require('../stores/user_store.jsx'); var ActionTypes = Constants.ActionTypes; module.exports = React.createClass({ + displayName: "Post", componentDidMount: function() { $('.modal').on('show.bs.modal', function () { $('.modal-body').css('overflow-y', 'auto'); @@ -19,7 +20,7 @@ module.exports = React.createClass({ handleCommentClick: function(e) { e.preventDefault(); - data = {}; + var data = {}; data.order = [this.props.post.id]; data.posts = this.props.posts; @@ -33,6 +34,10 @@ module.exports = React.createClass({ results: null }); }, + forceUpdateInfo: function() { + this.refs.info.forceUpdate(); + this.refs.header.forceUpdate(); + }, getInitialState: function() { return { }; }, @@ -48,7 +53,6 @@ module.exports = React.createClass({ var commentCount = 0; var commentRootId = parentPost ? post.root_id : post.id; - var rootUser = ""; for (var postId in posts) { if (posts[postId].root_id == commentRootId) { commentCount += 1; @@ -57,12 +61,7 @@ module.exports = React.createClass({ var error = this.state.error ?
: null; - if (this.props.sameRoot){ - rootUser = "same--root"; - } - else { - rootUser = "other--root"; - } + var rootUser = this.props.sameRoot ? "same--root" : "other--root"; var postType = ""; if (type != "Post"){ @@ -74,18 +73,20 @@ module.exports = React.createClass({ currentUserCss = "current--user"; } + var timestamp = UserStore.getCurrentUser().update_at; + return (
{ !this.props.hideProfilePic ?
- +
- : "" } + : null }
- + - +
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index 7d5ef4d338..d9678df30a 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -71,11 +71,22 @@ module.exports = React.createClass({ name = {profile.username}; } - var message = parentPost.message; + var message = "" + if(parentPost.message) { + message = utils.replaceHtmlEntities(parentPost.message) + } else if (parentPost.filenames.length) { + message = parentPost.filenames[0].split('/').pop(); + + if (parentPost.filenames.length === 2) { + message += " plus 1 other file"; + } else if (parentPost.filenames.length > 2) { + message += " plus " + (parentPost.filenames.length - 1) + " other files"; + } + } comment = (

- Commented on {name}{apostrophe} message: {utils.replaceHtmlEntities(message)} + Commented on {name}{apostrophe} message: {message}

); @@ -120,7 +131,7 @@ module.exports = React.createClass({ return (
{ comment } -

{inner}

+

{inner}

{ filenames && filenames.length > 0 ?
{ postFiles } diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx index cf01747f00..d6422fe3a7 100644 --- a/web/react/components/post_info.jsx +++ b/web/react/components/post_info.jsx @@ -11,6 +11,7 @@ module.exports = React.createClass({ render: function() { var post = this.props.post; var isOwner = UserStore.getCurrentId() == post.user_id; + var isAdmin = UserStore.getCurrentUser().roles.indexOf("admin") > -1 var type = "Post" if (post.root_id.length > 0) { @@ -30,13 +31,11 @@ module.exports = React.createClass({
{ isOwner || (this.props.allowReply === "true" && type != "Comment") ?
- +
  • Edit
  • : "" } - { isOwner ?
  • Delete
  • + { isOwner || isAdmin ?
  • Delete
  • : "" } { this.props.allowReply === "true" ?
  • Reply
  • : "" } diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index d6dc9ce303..c058455bae 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -6,8 +6,8 @@ var ChannelStore = require('../stores/channel_store.jsx'); var UserStore = require('../stores/user_store.jsx'); var UserProfile = require( './user_profile.jsx' ); var AsyncClient = require('../utils/async_client.jsx'); -var CreatePost = require('./create_post.jsx'); var Post = require('./post.jsx'); +var LoadingScreen = require('./loading_screen.jsx'); var SocketStore = require('../stores/socket_store.jsx'); var utils = require('../utils/utils.jsx'); var Client = require('../utils/client.jsx'); @@ -26,37 +26,8 @@ function getStateFromStores() { }; } -function changeColor(col, amt) { - - var usePound = false; - - if (col[0] == "#") { - col = col.slice(1); - usePound = true; - } - - var num = parseInt(col,16); - - var r = (num >> 16) + amt; - - if (r > 255) r = 255; - else if (r < 0) r = 0; - - var b = ((num >> 8) & 0x00FF) + amt; - - if (b > 255) b = 255; - else if (b < 0) b = 0; - - var g = (num & 0x0000FF) + amt; - - if (g > 255) g = 255; - else if (g < 0) g = 0; - - return (usePound?"#":"") + String("000000" + (g | (b << 8) | (r << 16)).toString(16)).slice(-6); - -} - module.exports = React.createClass({ + displayName: "PostList", scrollPosition: 0, preventScrollTrigger: false, gotMorePosts: false, @@ -69,7 +40,7 @@ module.exports = React.createClass({ utils.changeCss('a.theme', 'color:'+user.props.theme+'; fill:'+user.props.theme+'!important;'); utils.changeCss('div.theme', 'background-color:'+user.props.theme+';'); utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme+';'); - utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + changeColor(user.props.theme, -10) +';'); + utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, -10) +';'); utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme+';'); utils.changeCss('.mention', 'background: ' + user.props.theme+';'); utils.changeCss('.mention-link', 'color: ' + user.props.theme+';'); @@ -78,6 +49,7 @@ module.exports = React.createClass({ PostStore.addChangeListener(this._onChange); ChannelStore.addChangeListener(this._onChange); + UserStore.addStatusesChangeListener(this._onTimeChange); SocketStore.addChangeListener(this._onSocketChange); $(".post-list-holder-by-time").perfectScrollbar(); @@ -157,28 +129,25 @@ module.exports = React.createClass({ componentWillUnmount: function() { PostStore.removeChangeListener(this._onChange); ChannelStore.removeChangeListener(this._onChange); + UserStore.removeStatusesChangeListener(this._onTimeChange); SocketStore.removeChangeListener(this._onSocketChange); $('body').off('click.userpopover'); }, resize: function() { + var post_holder = $(".post-list-holder-by-time")[0]; + this.preventScrollTrigger = true; if (this.gotMorePosts) { this.gotMorePosts = false; - var post_holder = $(".post-list-holder-by-time")[0]; - this.preventScrollTrigger = true; $(post_holder).scrollTop($(post_holder).scrollTop() + (post_holder.scrollHeight-this.oldScrollHeight) ); - $(post_holder).perfectScrollbar('update'); } else { - var post_holder = $(".post-list-holder-by-time")[0]; - this.preventScrollTrigger = true; if ($("#new_message")[0] && !this.scrolledToNew) { $(post_holder).scrollTop($(post_holder).scrollTop() + $("#new_message").offset().top - 63); - $(post_holder).perfectScrollbar('update'); this.scrolledToNew = true; } else { $(post_holder).scrollTop(post_holder.scrollHeight); - $(post_holder).perfectScrollbar('update'); } } + $(post_holder).perfectScrollbar('update'); }, _onChange: function() { var newState = getStateFromStores(); @@ -239,12 +208,8 @@ module.exports = React.createClass({ var index = post_list.order.indexOf(msg.props.post_id); if (index > -1) post_list.order.splice(index, 1); - var scrollSave = $(".post-list-holder-by-time").scrollTop(); - this.setState({ post_list: post_list }); - $(".post-list-holder-by-time").scrollTop(scrollSave) - PostStore.storePosts(msg.channel_id, post_list); } else { AsyncClient.getPosts(true, msg.channel_id); @@ -253,10 +218,16 @@ module.exports = React.createClass({ if (activeRootPostId === msg.props.post_id && UserStore.getCurrentId() != msg.user_id) { $('#post_deleted').modal('show'); } - } else if(msg.action == "new_user") { + } else if (msg.action == "new_user") { AsyncClient.getProfiles(); } }, + _onTimeChange: function() { + for (var id in this.state.post_list.posts) { + if (!this.refs[id]) continue; + this.refs[id].forceUpdateInfo(); + } + }, getMorePosts: function(e) { e.preventDefault(); @@ -297,7 +268,7 @@ module.exports = React.createClass({ }, function(err) { $(self.refs.loadmore.getDOMNode()).text("Load more messages"); - dispatchError(err, "getPosts"); + AsyncClient.dispatchError(err, "getPosts"); } ); }, @@ -338,16 +309,19 @@ module.exports = React.createClass({ var teammate = utils.getDirectTeammate(channel.id) if (teammate) { - var teammate_name = teammate.full_name.length > 0 ? teammate.full_name : teammate.username; + var teammate_name = teammate.nickname.length > 0 ? teammate.nickname : teammate.username; more_messages = (
    - +
    -

    {"This is the start of your private message history with " + teammate_name + "." }
    {"Private messages and files shared here are not shown to people outside this area."}

    +

    + {"This is the start of your private message history with " + teammate_name + "." }
    + {"Private messages and files shared here are not shown to people outside this area."} +

    ); } else { @@ -370,7 +344,7 @@ module.exports = React.createClass({ } } - if (channel.name === Constants.DEFAULT_CHANNEL) { + if (ChannelStore.isDefault(channel)) { more_messages = (

    Welcome

    @@ -410,7 +384,7 @@ module.exports = React.createClass({ { channel.type === 'P' ? " Only invited members can see this private group." : " Any member can join and read this channel." }
    Set a description - Invite others to this {ui_type} + Invite others to this {ui_type}

    ); @@ -420,37 +394,35 @@ module.exports = React.createClass({ var postCtls = []; - if (posts != undefined) { + if (posts) { var previousPostDay = posts[order[order.length-1]] ? utils.getDateForUnixTicks(posts[order[order.length-1]].create_at): new Date(); - var currentPostDay = new Date(); + var currentPostDay; for (var i = order.length-1; i >= 0; i--) { var post = posts[order[i]]; - var parentPost; + var parentPost = post.parent_id ? posts[post.parent_id] : null; - if (post.parent_id) { - parentPost = posts[post.parent_id]; - } else { - parentPost = null; + var sameUser = ''; + var sameRoot = false; + var hideProfilePic = false; + var prevPost = (i < order.length - 1) ? posts[order[i + 1]] : null; + + if (prevPost) { + sameUser = (prevPost.user_id === post.user_id) && (post.create_at - prevPost.create_at <= 1000*60*5) ? "same--user" : ""; + sameRoot = utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id); + + // we only hide the profile pic if the previous post is not a comment, the current post is not a comment, and the previous post was made by the same user as the current post + hideProfilePic = (prevPost.user_id === post.user_id) && !utils.isComment(prevPost) && !utils.isComment(post); } - var sameUser = i < order.length-1 && posts[order[i+1]].user_id === post.user_id && post.create_at - posts[order[i+1]].create_at <= 1000*60*5 ? "same--user" : ""; - var sameRoot = i < order.length-1 && post.root_id != "" && (posts[order[i+1]].id === post.root_id || posts[order[i+1]].root_id === post.root_id) ? true : false; - - // we only hide the profile pic if the previous post is not a comment, the current post is not a comment, and the previous post was made by the same user as the current post - var hideProfilePic = i < order.length-1 && posts[order[i+1]].user_id === post.user_id && posts[order[i+1]].root_id === '' && post.root_id === ''; - // check if it's the last comment in a consecutive string of comments on the same post - var isLastComment = false; - if (utils.isComment(post)) { - // it is the last comment if it is last post in the channel or the next post has a different root post - isLastComment = (i === 0 || posts[order[i-1]].root_id != post.root_id); - } + // it is the last comment if it is last post in the channel or the next post has a different root post + var isLastComment = utils.isComment(post) && (i === 0 || posts[order[i-1]].root_id != post.root_id); - var postCtl = ; + var postCtl = ; currentPostDay = utils.getDateForUnixTicks(post.create_at); - if(currentPostDay.getDate() !== previousPostDay.getDate() || currentPostDay.getMonth() !== previousPostDay.getMonth() || currentPostDay.getFullYear() !== previousPostDay.getFullYear()) { + if (currentPostDay.toDateString() != previousPostDay.toDateString()) { postCtls.push(

    @@ -469,20 +441,10 @@ module.exports = React.createClass({ ); } postCtls.push(postCtl); - previousPostDay = utils.getDateForUnixTicks(post.create_at); + previousPostDay = currentPostDay; } - } - else { - postCtls.push( -
    -
    -

    Loading

    -
    -
    -
    -
    -
    - ); + } else { + postCtls.push(); } return ( @@ -497,5 +459,3 @@ module.exports = React.createClass({ ); } }); - - diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx index 115ee87d4a..581a1abe9e 100644 --- a/web/react/components/post_right.jsx +++ b/web/react/components/post_right.jsx @@ -43,7 +43,7 @@ RhsHeaderPost = React.createClass({ }); }, render: function() { - var back = this.props.fromSearch ? {"< "} : ""; + var back = this.props.fromSearch ? : ""; return (
    @@ -67,6 +67,8 @@ RootPost = React.createClass({ var message = utils.textToJsx(this.props.post.message); var filenames = this.props.post.filenames; var isOwner = UserStore.getCurrentId() == this.props.post.user_id; + var timestamp = UserStore.getProfile(this.props.post.user_id).update_at; + var channel = ChannelStore.get(this.props.post.channel_id); var type = "Post"; if (this.props.post.root_id.length > 0) { @@ -78,6 +80,10 @@ RootPost = React.createClass({ currentUserCss = "current--user"; } + if (channel) { + channelName = (channel.type === 'D') ? "Private Message" : channel.display_name; + } + if (filenames) { var postFiles = []; var images = []; @@ -117,8 +123,9 @@ RootPost = React.createClass({ return (
    +
    { channelName }
    - +
      @@ -128,9 +135,7 @@ RootPost = React.createClass({
      { isOwner ?
      - +
      • Edit
      • Delete
      • @@ -227,11 +232,12 @@ CommentPost = React.createClass({ } var message = utils.textToJsx(this.props.post.message); + var timestamp = UserStore.getCurrentUser().update_at; return (
        - +
          @@ -240,9 +246,7 @@ CommentPost = React.createClass({
        • { isOwner ?
          - +
          • Edit
          • Delete
          • @@ -282,6 +286,7 @@ module.exports = React.createClass({ componentDidMount: function() { PostStore.addSelectedPostChangeListener(this._onChange); PostStore.addChangeListener(this._onChangeAll); + UserStore.addStatusesChangeListener(this._onTimeChange); this.resize(); var self = this; $(window).resize(function(){ @@ -294,6 +299,7 @@ module.exports = React.createClass({ componentWillUnmount: function() { PostStore.removeSelectedPostChangeListener(this._onChange); PostStore.removeChangeListener(this._onChangeAll); + UserStore.removeStatusesChangeListener(this._onTimeChange); }, _onChange: function() { if (this.isMounted()) { @@ -304,7 +310,6 @@ module.exports = React.createClass({ } }, _onChangeAll: function() { - if (this.isMounted()) { // if something was changed in the channel like adding a @@ -333,6 +338,12 @@ module.exports = React.createClass({ this.setState(getStateFromStores()); } }, + _onTimeChange: function() { + for (var id in this.state.post_list.posts) { + if (!this.refs[id]) continue; + this.refs[id].forceUpdate(); + } + }, getInitialState: function() { return getStateFromStores(); }, @@ -392,7 +403,7 @@ module.exports = React.createClass({
            { posts_array.map(function(cpost) { - return + return })}
            diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx index b4ccb2937a..2ae331626a 100644 --- a/web/react/components/rename_channel_modal.jsx +++ b/web/react/components/rename_channel_modal.jsx @@ -6,6 +6,8 @@ var utils = require('../utils/utils.jsx'); var Client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); +var Constants = require('../utils/constants.jsx'); module.exports = React.createClass({ handleSubmit: function(e) { @@ -60,12 +62,12 @@ module.exports = React.createClass({ return; Client.updateChannel(channel, - function(data) { + function(data, text, req) { this.refs.display_name.getDOMNode().value = ""; this.refs.channel_name.getDOMNode().value = ""; $('#' + this.props.modalId).modal('hide'); - window.location.href = '/channels/' + this.state.channel_name; + window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + this.state.channel_name; AsyncClient.getChannels(true); }.bind(this), function(err) { diff --git a/web/react/components/rename_team_modal.jsx b/web/react/components/rename_team_modal.jsx index 67a150b9d3..a6da57b67f 100644 --- a/web/react/components/rename_team_modal.jsx +++ b/web/react/components/rename_team_modal.jsx @@ -24,13 +24,13 @@ module.exports = React.createClass({ if (!valid) return; - if (this.props.teamName === name) + if (this.props.teamDisplayName === name) return; var data = {}; data["new_name"] = name; - Client.updateTeamName(data, + Client.updateTeamDisplayName(data, function(data) { $('#rename_team_link').modal('hide'); window.location.reload(); @@ -47,11 +47,11 @@ module.exports = React.createClass({ componentDidMount: function() { var self = this; $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) { - self.setState({ name: self.props.teamName }); + self.setState({ name: self.props.teamDisplayName }); }); }, getInitialState: function() { - return { name: this.props.teamName }; + return { name: this.props.teamDisplayName }; }, render: function() { diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx index cddb738f94..f21f0cd588 100644 --- a/web/react/components/search_bar.jsx +++ b/web/react/components/search_bar.jsx @@ -3,6 +3,7 @@ var client = require('../utils/client.jsx'); +var AsyncClient = require('../utils/async_client.jsx'); var PostStore = require('../stores/post_store.jsx'); var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var utils = require('../utils/utils.jsx'); @@ -10,14 +11,14 @@ var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; function getSearchTermStateFromStores() { - term = PostStore.getSearchTerm(); - if (!term) term = ""; + var term = PostStore.getSearchTerm() || ''; return { search_term: term }; } module.exports = React.createClass({ + displayName: 'SearchBar', componentDidMount: function() { PostStore.addSearchTermChangeListener(this._onChange); }, @@ -58,14 +59,14 @@ module.exports = React.createClass({ e.target.select(); }, performSearch: function(terms, isMentionSearch) { - if (terms.length > 0) { - $("#search-spinner").removeClass("hidden"); + if (terms.length) { + this.setState({isSearching: true}); client.search( terms, function(data) { - $("#search-spinner").addClass("hidden"); - if(utils.isMobile()) { - $('#search')[0].value = ""; + this.setState({isSearching: false}); + if (utils.isMobile()) { + React.findDOMNode(this.refs.search).value = ''; } AppDispatcher.handleServerAction({ @@ -73,18 +74,17 @@ module.exports = React.createClass({ results: data, is_mention_search: isMentionSearch }); - }, + }.bind(this), function(err) { - $("#search-spinner").addClass("hidden"); - dispatchError(err, "search"); - } + this.setState({isSearching: false}); + AsyncClient.dispatchError(err, "search"); + }.bind(this) ); } }, handleSubmit: function(e) { e.preventDefault(); - terms = this.state.search_term.trim(); - this.performSearch(terms); + this.performSearch(this.state.search_term.trim()); }, getInitialState: function() { return getSearchTermStateFromStores(); @@ -92,11 +92,18 @@ module.exports = React.createClass({ render: function() { return (
            -
            +
            Cancel
            - - + + {this.state.isSearching ? : null}
            ); diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx index 003a38b7ee..643ad112b2 100644 --- a/web/react/components/search_results.jsx +++ b/web/react/components/search_results.jsx @@ -8,12 +8,13 @@ var UserStore = require('../stores/user_store.jsx'); var UserProfile = require( './user_profile.jsx' ); var SearchBox =require('./search_bar.jsx'); var utils = require('../utils/utils.jsx'); -var client =require('../utils/client.jsx'); +var client = require('../utils/client.jsx'); +var AsyncClient = require('../utils/async_client.jsx'); var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -RhsHeaderSearch = React.createClass({ +var RhsHeaderSearch = React.createClass({ handleClose: function(e) { e.preventDefault(); @@ -32,13 +33,13 @@ RhsHeaderSearch = React.createClass({ return (
            {title} - +
            ); } }); -SearchItem = React.createClass({ +var SearchItem = React.createClass({ handleClick: function(e) { e.preventDefault(); @@ -62,39 +63,41 @@ SearchItem = React.createClass({ }); }, function(err) { - dispatchError(err, "getPost"); + AsyncClient.dispatchError(err, "getPost"); } ); - var postChannel = ChannelStore.get(this.props.post.channel_id); - var teammate = postChannel.type === 'D' ? utils.getDirectTeammate(this.props.post.channel_id).username : ""; + var postChannel = ChannelStore.get(this.props.post.channel_id); + var teammate = postChannel.type === 'D' ? utils.getDirectTeammate(this.props.post.channel_id).username : ""; - utils.switchChannel(postChannel,teammate); + utils.switchChannel(postChannel, teammate); }, + render: function() { var message = utils.textToJsx(this.props.post.message, {searchTerm: this.props.term, noMentionHighlight: !this.props.isMentionSearch}); var channelName = ""; - var channel = ChannelStore.get(this.props.post.channel_id) + var channel = ChannelStore.get(this.props.post.channel_id); + var timestamp = UserStore.getCurrentUser().update_at; if (channel) { - if (channel.type === 'D') { - channelName = "Private Message"; - } else { - channelName = channel.display_name; - } + channelName = (channel.type === 'D') ? "Private Message" : channel.display_name; } - return ( + return (
            { channelName }
            - +
            • -
            • +
            • + +
            {message}
            @@ -103,11 +106,13 @@ SearchItem = React.createClass({ } }); + function getStateFromStores() { return { results: PostStore.getSearchResults() }; } module.exports = React.createClass({ + displayName: 'SearchResults', componentDidMount: function() { PostStore.addSearchChangeListener(this._onChange); this.resize(); @@ -143,41 +148,24 @@ module.exports = React.createClass({ var results = this.state.results; var currentId = UserStore.getCurrentId(); - var searchForm = currentId == null ? null : ; + var searchForm = currentId ? : null; + var noResults = (!results || !results.order || !results.order.length); + var searchTerm = PostStore.getSearchTerm(); - if (results == null) { - return ( -
            -
            Search Results
            -
            - ); - } - - if (!results.order || results.order.length == 0) { - return ( -
            -
            {searchForm}
            -
            - -
            -
            No results
            -
            -
            -
            - ); - } - - var self = this; return (
            {searchForm}
            - {results.order.map(function(id) { - var post = results.posts[id]; - return - })} + + { noResults ?
            No results
            + : results.order.map(function(id) { + var post = results.posts[id]; + return + }, this) + } +
            diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 2095978e87..3cf67e410e 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -6,6 +6,7 @@ var ChannelStore = require('../stores/channel_store.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var SocketStore = require('../stores/socket_store.jsx'); var UserStore = require('../stores/user_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); var utils = require('../utils/utils.jsx'); var SidebarHeader = require('./sidebar_header.jsx'); var SearchBox = require('./search_bar.jsx'); @@ -13,93 +14,6 @@ var SearchBox = require('./search_bar.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -var SidebarLoginForm = React.createClass({ - handleSubmit: function(e) { - e.preventDefault(); - var state = { } - - var domain = this.refs.domain.getDOMNode().value.trim(); - if (!domain) { - state.server_error = "A domain is required" - this.setState(state); - return; - } - - var email = this.refs.email.getDOMNode().value.trim(); - if (!email) { - state.server_error = "An email is required" - this.setState(state); - return; - } - - var password = this.refs.password.getDOMNode().value.trim(); - if (!password) { - state.server_error = "A password is required" - this.setState(state); - return; - } - - state.server_error = ""; - this.setState(state); - - client.loginByEmail(domain, email, password, - function(data) { - UserStore.setLastDomain(domain); - UserStore.setLastEmail(email); - UserStore.setCurrentUser(data); - - var redirect = utils.getUrlParameter("redirect"); - if (redirect) { - window.location.href = decodeURI(redirect); - } else { - window.location.href = '/channels/town-square'; - } - - }.bind(this), - function(err) { - if (err.message == "Login failed because email address has not been verified") { - window.location.href = '/verify?domain=' + encodeURIComponent(domain) + '&email=' + encodeURIComponent(email); - return; - } - state.server_error = err.message; - this.valid = false; - this.setState(state); - }.bind(this) - ); - }, - getInitialState: function() { - return { }; - }, - render: function() { - var server_error = this.state.server_error ? : null; - - var subDomain = utils.getSubDomain(); - var subDomainClass = "form-control hidden"; - - if (subDomain == "") { - subDomain = UserStore.getLastDomain(); - subDomainClass = "form-control"; - } - - return ( -
            - {"Find your " + strings.Team} -
            - { server_error } - -
            -
            - -
            -
            - -
            - - - ); - } -}); - function getStateFromStores() { var members = ChannelStore.getAllMembers(); var team_member_map = UserStore.getActiveOnlyProfiles(); @@ -131,7 +45,7 @@ function getStateFromStores() { var channel = ChannelStore.getByName(channelName); if (channel != null) { - channel.display_name = teammate.full_name.trim() != "" ? teammate.full_name : teammate.username; + channel.display_name = utils.getDisplayName(teammate); channel.teammate_username = teammate.username; channel.status = UserStore.getStatus(teammate.id); @@ -150,7 +64,7 @@ function getStateFromStores() { var tempChannel = {}; tempChannel.fake = true; tempChannel.name = channelName; - tempChannel.display_name = teammate.full_name.trim() != "" ? teammate.full_name : teammate.username; + tempChannel.display_name = utils.getDisplayName(teammate); tempChannel.status = UserStore.getStatus(teammate.id); tempChannel.last_post_at = 0; readDirectChannels.push(tempChannel); @@ -192,7 +106,7 @@ function getStateFromStores() { }; } -var SidebarLoggedIn = React.createClass({ +module.exports = React.createClass({ componentDidMount: function() { ChannelStore.addChangeListener(this._onChange); UserStore.addChangeListener(this._onChange); @@ -249,11 +163,27 @@ var SidebarLoggedIn = React.createClass({ var repRegex = new RegExp("
            ", "g"); var post = JSON.parse(msg.props.post); - var msg = post.message.replace(repRegex, "\n").split("\n")[0].replace("", "").replace("", ""); + var msgProps = msg.props; + var msg = post.message.replace(repRegex, "\n").replace(/\n+/g, " ").replace("", "").replace("", ""); + if (msg.length > 50) { msg = msg.substring(0,49) + "..."; } - utils.notifyMe(title, username + " wrote: " + msg, channel); + + if (msg.length === 0) { + if (msgProps.image) { + utils.notifyMe(title, username + " uploaded an image", channel); + } + else if (msgProps.otherFile) { + utils.notifyMe(title, username + " uploaded a file", channel); + } + else { + utils.notifyMe(title, username + " did something new", channel); + } + } + else { + utils.notifyMe(title, username + " wrote: " + msg, channel); + } if (!user.notify_props || user.notify_props.desktop_sound === "true") { utils.ding(); } @@ -367,7 +297,7 @@ var SidebarLoggedIn = React.createClass({ ); } else { return ( -
          • {badge}{channel.display_name}
          • +
          • {badge}{channel.display_name}
          • ); } @@ -398,7 +328,7 @@ var SidebarLoggedIn = React.createClass({ } return (
            - +
            @@ -424,25 +354,3 @@ var SidebarLoggedIn = React.createClass({ ); } }); - -var SidebarLoggedOut = React.createClass({ - render: function() { - return ( -
            - - -
            - ); - } -}); - -module.exports = React.createClass({ - render: function() { - var currentId = UserStore.getCurrentId(); - if (currentId != null) { - return ; - } else { - return ; - } - } -}); diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index 0b59d20364..bab2897b6b 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -37,17 +37,13 @@ var NavbarDropdown = React.createClass({ var invite_link = ""; var manage_link = ""; var rename_link = ""; - var currentUser = UserStore.getCurrentUser() + var currentUser = UserStore.getCurrentUser(); var isAdmin = false; if (currentUser != null) { isAdmin = currentUser.roles.indexOf("admin") > -1; - invite_link = ( -
          • - Invite New Member -
          • - ); + invite_link = (
          • Invite New Member
          • ); if (this.props.teamType == "O") { team_link = ( @@ -59,43 +55,31 @@ var NavbarDropdown = React.createClass({ } if (isAdmin) { - manage_link = ( -
          • - Manage Team -
          • - ); - rename_link = ( -
          • - Rename -
          • - ); + manage_link = (
          • Manage Team
          • ); + rename_link = (
          • Rename
          • ); } var teams = []; + teams.push(
          • ); if (this.state.teams.length > 1) { for (var i = 0; i < this.state.teams.length; i++) { - var domain = this.state.teams[i]; + var teamName = this.state.teams[i]; - if (domain == utils.getSubDomain()) - continue; - - if (teams.length == 0) - teams.push(
          • ); - - teams.push(
          • Switch to { domain }
          • ); + teams.push(
          • Switch to { teamName }
          • ); } } + teams.push(
          • Create a New Team
          • ); return (
              • Account Settings
              • - { isAdmin ?
              • Team Settings
              • : "" } + { isAdmin ?
              • Team Settings
              • : null } { invite_link } { team_link } { manage_link } @@ -113,23 +97,32 @@ var NavbarDropdown = React.createClass({ }); module.exports = React.createClass({ - handleSubmit: function(e) { - e.preventDefault(); - }, - getInitialState: function() { - return { }; + displayName: 'SidebarHeader', + + getDefaultProps: function() { + return { + teamName: config.SiteName + }; }, + render: function() { - var teamName = this.props.teamName ? this.props.teamName : config.SiteName; + var teamDisplayName = this.props.teamDisplayName ? this.props.teamDisplayName : config.SiteName; + var me = UserStore.getCurrentUser() + if (!me) { + return null; + } return ( +
            ); } }); - - - diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx index 22d1d9ad29..15306a499c 100644 --- a/web/react/components/sidebar_right_menu.jsx +++ b/web/react/components/sidebar_right_menu.jsx @@ -49,12 +49,12 @@ module.exports = React.createClass({ } var siteName = config.SiteName != null ? config.SiteName : ""; - var teamName = this.props.teamName ? this.props.teamName : siteName; + var teamDisplayName = this.props.teamDisplayName ? this.props.teamDisplayName : siteName; return (
            diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx index 22086250c0..cf982cc1eb 100644 --- a/web/react/components/signup_team.jsx +++ b/web/react/components/signup_team.jsx @@ -20,8 +20,8 @@ module.exports = React.createClass({ state.email_error = ""; } - team.name = this.refs.name.getDOMNode().value.trim(); - if (!team.name) { + team.display_name = this.refs.name.getDOMNode().value.trim(); + if (!team.display_name) { state.name_error = "This field is required"; state.inValid = true; } @@ -34,7 +34,7 @@ module.exports = React.createClass({ return; } - client.signupTeam(team.email, team.name, + client.signupTeam(team.email, team.display_name, function(data) { if (data["follow_link"]) { window.location.href = data["follow_link"]; @@ -61,7 +61,7 @@ module.exports = React.createClass({ return (
            - + { email_error }
            @@ -70,6 +70,9 @@ module.exports = React.createClass({
            { server_error } + ); } diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx index 500ee231e1..9ceeb63240 100644 --- a/web/react/components/signup_team_complete.jsx +++ b/web/react/components/signup_team_complete.jsx @@ -15,7 +15,7 @@ WelcomePage = React.createClass({ return; } e.preventDefault(); - this.props.state.wizard = "team_name"; + this.props.state.wizard = "team_display_name"; this.props.updateParent(this.props.state); }, handleDiffEmail: function (e) { @@ -57,6 +57,17 @@ WelcomePage = React.createClass({ getInitialState: function() { return { use_diff: false }; }, + handleKeyPress: function(event) { + if (event.keyCode == 13) { + this.submitNext(event); + } + }, + componentWillMount: function() { + document.addEventListener("keyup", this.handleKeyPress, false); + }, + componentWillUnmount: function() { + document.removeEventListener("keyup", this.handleKeyPress, false); + }, render: function() { client.track('signup', 'signup_team_01_welcome'); @@ -77,7 +88,7 @@ WelcomePage = React.createClass({ { this.props.state.team.email }

            - + { storage_error }

            @@ -92,15 +103,15 @@ WelcomePage = React.createClass({ { email_error }
            { server_error } - +
            - +
            ); } }); -TeamNamePage = React.createClass({ +TeamDisplayNamePage = React.createClass({ submitBack: function (e) { e.preventDefault(); this.props.state.wizard = "welcome"; @@ -109,19 +120,24 @@ TeamNamePage = React.createClass({ submitNext: function (e) { e.preventDefault(); - var name = this.refs.name.getDOMNode().value.trim(); - if (!name) { + var display_name = this.refs.name.getDOMNode().value.trim(); + if (!display_name) { this.setState({name_error: "This field is required"}); return; } this.props.state.wizard = "team_url"; - this.props.state.team.name = name; + this.props.state.team.display_name = display_name; this.props.updateParent(this.props.state); }, getInitialState: function() { return { }; }, + handleFocus: function(e) { + e.preventDefault(); + + e.currentTarget.select(); + }, render: function() { client.track('signup', 'signup_team_02_name'); @@ -130,29 +146,31 @@ TeamNamePage = React.createClass({ return (
            +

            {utils.toTitleCase(strings.Team) + " Name"}

            - +
            { name_error }

            {"Your " + strings.Team + " name shows in menus and headings. It may include the name of your " + strings.Company + ", but it's not required."}

            -   - +   + +
            ); } }); -TeamUrlPage = React.createClass({ +TeamURLPage = React.createClass({ submitBack: function (e) { e.preventDefault(); - this.props.state.wizard = "team_name"; + this.props.state.wizard = "team_display_name"; this.props.updateParent(this.props.state); }, submitNext: function (e) { @@ -172,18 +190,18 @@ TeamUrlPage = React.createClass({ return; } else if (cleaned_name.length <= 3 || cleaned_name.length > 15) { - this.setState({name_error: "Domain must be 4 or more characters up to a maximum of 15"}) + this.setState({name_error: "Name must be 4 or more characters up to a maximum of 15"}) return; } - for (var index = 0; index < constants.RESERVED_DOMAINS.length; index++) { - if (cleaned_name.indexOf(constants.RESERVED_DOMAINS[index]) == 0) { - this.setState({name_error: "This Team URL name is unavailable"}) + for (var index = 0; index < constants.RESERVED_TEAM_NAMES.length; index++) { + if (cleaned_name.indexOf(constants.RESERVED_TEAM_NAMES[index]) == 0) { + this.setState({name_error: "This team name is unavailable"}) return; } } - client.findTeamByDomain(name, + client.findTeamByName(name, function(data) { if (!data) { if (config.AllowSignupDomainsWizard) { @@ -193,7 +211,7 @@ TeamUrlPage = React.createClass({ this.props.state.team.type = 'O'; } - this.props.state.team.domain = name; + this.props.state.team.name = name; this.props.updateParent(this.props.state); } else { @@ -210,6 +228,11 @@ TeamUrlPage = React.createClass({ getInitialState: function() { return { }; }, + handleFocus: function(e) { + e.preventDefault(); + + e.currentTarget.select(); + }, render: function() { client.track('signup', 'signup_team_03_url'); @@ -218,14 +241,15 @@ TeamUrlPage = React.createClass({ return (
            +

            {utils.toTitleCase(strings.Team) + " URL"}

            - - .{ utils.getDomainWithOutSub() } + { window.location.origin + "/" } +
            @@ -233,8 +257,9 @@ TeamUrlPage = React.createClass({

            {"Pick something short and memorable for your " + strings.Team + "'s web address."}

            {"Your " + strings.Team + " URL can only contain lowercase letters, numbers and dashes. Also, it needs to start with a letter and cannot end in a dash."}

            -   - +   + +
            ); } @@ -291,6 +316,7 @@ AllowedDomainsPage = React.createClass({ return (
            +

            Email Domain

            @@ -303,7 +329,7 @@ AllowedDomainsPage = React.createClass({

            @ - +
            @@ -313,8 +339,9 @@ AllowedDomainsPage = React.createClass({

            -   - +   + +
            ); } @@ -356,7 +383,7 @@ EmailItem = React.createClass({ return (
            - + { email_error }
            ); @@ -424,16 +451,22 @@ SendInivtesPage = React.createClass({ var emails = []; for (var i = 0; i < this.props.state.invites.length; i++) { - emails.push(); + if (i == 0) { + emails.push(); + } else { + emails.push(); + } } return (
            +

            Send Invitations

            { emails } -
            -
             
            +
            +
             
            +

            {"If you'd prefer, you can send invitations after you finish setting up the "+ strings.Team + "."}

            @@ -477,20 +510,22 @@ UsernamePage = React.createClass({ return (
            +

            Choose a username

            - +
            { name_error }

            {"Pick something " + strings.Team + "mates will recognize. Your username is how you will appear to others."}

            It can be made of lowercase letters and numbers.

            -   - +   + +
            ); } @@ -531,18 +566,11 @@ PasswordPage = React.createClass({ props.state.wizard = "finished"; props.updateParent(props.state, true); - if (utils.isTestDomain()) { - UserStore.setLastDomain(teamSignup.team.domain); - UserStore.setLastEmail(teamSignup.team.email); - window.location.href = window.location.protocol + '//' + utils.getDomainWithOutSub() + '/login?email=' + encodeURIComponent(teamSignup.team.email); - } - else { - window.location.href = window.location.protocol + '//' + teamSignup.team.domain + '.' + utils.getDomainWithOutSub() + '/login?email=' + encodeURIComponent(teamSignup.team.email); - } + window.location.href = window.location.origin + '/' + props.state.team.name + '/login?email=' + encodeURIComponent(teamSignup.team.email); // client.loginByEmail(teamSignup.team.domain, teamSignup.team.email, teamSignup.user.password, // function(data) { - // UserStore.setLastDomain(teamSignup.team.domain); + // TeamStore.setLastName(teamSignup.team.domain); // UserStore.setLastEmail(teamSignup.team.email); // UserStore.setCurrentUser(data); // window.location.href = '/channels/town-square'; @@ -570,13 +598,14 @@ PasswordPage = React.createClass({ return (
            +

            Choose a password

            You'll use your email address ({this.props.state.team.email}) and password to log into {config.SiteName}.

            - +
            { name_error } @@ -585,10 +614,11 @@ PasswordPage = React.createClass({
            -   - +   +

            By proceeding to create your account and use { config.SiteName }, you agree to our Terms of Service and Privacy Policy. If you do not agree, you cannot use {config.SiteName}.

            +
            ); } @@ -596,28 +626,23 @@ PasswordPage = React.createClass({ module.exports = React.createClass({ updateParent: function(state, skipSet) { - BrowserStore.setGlobalItem(this.props.hash, JSON.stringify(state)); + BrowserStore.setGlobalItem(this.props.hash, state); if (!skipSet) { this.setState(state); } }, getInitialState: function() { - var props = null; - try { - props = JSON.parse(BrowserStore.getGlobalItem(this.props.hash)); - } - catch(parse_error) { - } + var props = BrowserStore.getGlobalItem(this.props.hash); if (!props) { props = {}; props.wizard = "welcome"; props.team = {}; props.team.email = this.props.email; - props.team.name = this.props.name; + props.team.display_name = this.props.name; props.team.company_name = this.props.name; - props.team.domain = utils.cleanUpUrlable(this.props.name); + props.team.name = utils.cleanUpUrlable(this.props.name); props.team.allowed_domains = ""; props.invites = []; props.invites.push(""); @@ -628,19 +653,19 @@ module.exports = React.createClass({ props.data = this.props.data; } - return props ; + return props; }, render: function() { if (this.state.wizard == "welcome") { return } - if (this.state.wizard == "team_name") { - return + if (this.state.wizard == "team_display_name") { + return } if (this.state.wizard == "team_url") { - return + return } if (this.state.wizard == "allowed_domains") { diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index fb96cc99fd..eed323d1f5 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -17,7 +17,7 @@ module.exports = React.createClass({ return; } - var username_error = utils.isValidUsername(this.state.user.username) + var username_error = utils.isValidUsername(this.state.user.username); if (username_error === "Cannot use a reserved word as a username.") { this.setState({name_error: "This username is reserved, please choose a new one.", email_error: "", password_error: "", server_error: ""}); return; @@ -46,25 +46,25 @@ module.exports = React.createClass({ function(data) { client.track('signup', 'signup_user_02_complete'); - if (data.email_verified) { - client.loginByEmail(this.props.domain, this.state.user.email, this.state.user.password, - function(data) { - UserStore.setLastDomain(this.props.domain); - UserStore.setLastEmail(this.state.user.email); - UserStore.setCurrentUser(data); - if (this.props.hash > 0) - BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: "finished"})); - window.location.href = '/channels/town-square'; - }.bind(this), - function(err) { + client.loginByEmail(this.props.domain, this.state.user.email, this.state.user.password, + function(data) { + UserStore.setLastEmail(this.state.user.email); + UserStore.setCurrentUser(data); + if (this.props.hash > 0) + { + BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: "finished"})); + } + window.location.href = '/'; + }.bind(this), + function(err) { + if (err.message == "Login failed because email address has not been verified") { + window.location.href = "/verify_email?email="+ encodeURIComponent(this.state.user.email) + "&domain=" + encodeURIComponent(this.props.domain); + } else { this.state.server_error = err.message; this.setState(this.state); - }.bind(this) - ); - } - else { - window.location.href = "/verify?email="+ encodeURIComponent(this.state.user.email) + "&domain=" + encodeURIComponent(this.props.domain); - } + } + }.bind(this) + ); }.bind(this), function(err) { this.state.server_error = err.message; @@ -73,12 +73,7 @@ module.exports = React.createClass({ ); }, getInitialState: function() { - var props = null; - try { - props = JSON.parse(BrowserStore.getGlobalItem(this.props.hash)); - } - catch(parse_error) { - } + var props = BrowserStore.getGlobalItem(this.props.hash); if (!props) { props = {}; @@ -91,7 +86,7 @@ module.exports = React.createClass({ props.original_email = this.props.email; } - return props ; + return props; }, render: function() { diff --git a/web/react/components/team_members.jsx b/web/react/components/team_members.jsx index 6b978f88bc..616fd2c995 100644 --- a/web/react/components/team_members.jsx +++ b/web/react/components/team_members.jsx @@ -57,7 +57,7 @@ module.exports = React.createClass({
            -

            {this.props.teamName + " Members"}

            +

            {this.props.teamDisplayName + " Members"}

            diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx index 648960471a..65f0259197 100644 --- a/web/react/components/user_profile.jsx +++ b/web/react/components/user_profile.jsx @@ -53,7 +53,7 @@ module.exports = React.createClass({ var name = this.props.overwriteName ? this.props.overwriteName : this.state.profile.username; - var data_content = ""; + var data_content = ""; if (!config.ShowEmail) { data_content += "
            Email not shared
            "; } else { @@ -61,7 +61,7 @@ module.exports = React.createClass({ } return ( -
            +
            { name }
            ); diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx index b4c3747afc..59c97c3096 100644 --- a/web/react/components/user_settings.jsx +++ b/web/react/components/user_settings.jsx @@ -156,6 +156,8 @@ var NotificationsTab = React.createClass({ var self = this; + var user = this.props.user; + var desktopSection; if (this.props.activeSection === 'desktop') { var notifyActive = [false, false, false]; @@ -314,20 +316,14 @@ var NotificationsTab = React.createClass({ var keysSection; if (this.props.activeSection === 'keys') { - var user = this.props.user; - var first_name = ""; - if (user.full_name.length > 0) { - first_name = user.full_name.split(' ')[0]; - } - var inputs = []; - if (first_name != "") { + if (user.first_name) { inputs.push(
            @@ -396,14 +392,9 @@ var NotificationsTab = React.createClass({ ); } else { var keys = []; - if (this.state.first_name_key) { - var first_name = ""; - var user = this.props.user; - if (user.full_name.length > 0) first_name = user.full_name.split(' ')[0]; - if (first_name != "") keys.push(first_name); - } - if (this.state.username_key) keys.push(this.props.user.username); - if (this.state.mention_key) keys.push('@'+this.props.user.username); + if (this.state.first_name_key) keys.push(user.first_name); + if (this.state.username_key) keys.push(user.username); + if (this.state.mention_key) keys.push('@'+user.username); if (this.state.all_key) keys.push('@all'); if (this.state.channel_key) keys.push('@channel'); if (this.state.custom_keys.length > 0) keys = keys.concat(this.state.custom_keys.split(',')); @@ -560,7 +551,7 @@ var AuditTab = React.createClass({

            Activity Log

            -
            +
-

You joined the {{.Props.TeamName}} team at {{.SiteName}}!

-

Please let me know if you have any questions.
Enjoy your stay at {{.SiteName}}.

+

You joined the {{.Props.TeamDisplayName}} team at {{.SiteName}}!

+

Please let me know if you have any questions.
Enjoy your stay at {{.SiteName}}.

- +

(c) 2015 SpinPunch, Inc. 855 El Camino Real, 13A-168, Palo Alto, CA, 94301.
diff --git a/api/user.go b/api/user.go index 483ae67b52..18c5e863a2 100644 --- a/api/user.go +++ b/api/user.go @@ -181,14 +181,13 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User { l4g.Error("Encountered an issue joining default channels user_id=%s, team_id=%s, err=%v", ruser.Id, ruser.TeamId, err) } - //fireAndForgetWelcomeEmail(strings.Split(ruser.FullName, " ")[0], ruser.Email, team.Name, c.TeamUrl+"/channels/town-square") - + //fireAndForgetWelcomeEmail(ruser.FirstName, ruser.Email, team.Name, c.TeamURL+"/channels/town-square") if user.EmailVerified { if cresult := <-Srv.Store.User().VerifyEmail(ruser.Id); cresult.Err != nil { l4g.Error("Failed to set email verified err=%v", cresult.Err) } } else { - FireAndForgetVerifyEmail(result.Data.(*model.User).Id, strings.Split(ruser.FullName, " ")[0], ruser.Email, team.Name, c.TeamUrl) + FireAndForgetVerifyEmail(result.Data.(*model.User).Id, ruser.FirstName, ruser.Email, team.DisplayName, c.GetTeamURLFromTeam(team)) } ruser.Sanitize(map[string]bool{}) @@ -196,19 +195,19 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User { // This message goes to every channel, so the channelId is irrelevant message := model.NewMessage(team.Id, "", ruser.Id, model.ACTION_NEW_USER) - store.PublishAndForget(message) + PublishAndForget(message) return ruser } } -func fireAndForgetWelcomeEmail(name, email, teamName, link string) { +func fireAndForgetWelcomeEmail(name, email, teamDisplayName, link string) { go func() { subjectPage := NewServerTemplatePage("welcome_subject", link) bodyPage := NewServerTemplatePage("welcome_body", link) - bodyPage.Props["FullName"] = name - bodyPage.Props["TeamName"] = teamName + bodyPage.Props["Nickname"] = name + bodyPage.Props["TeamDisplayName"] = teamDisplayName bodyPage.Props["FeedbackName"] = utils.Cfg.EmailSettings.FeedbackName if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil { @@ -218,16 +217,16 @@ func fireAndForgetWelcomeEmail(name, email, teamName, link string) { }() } -func FireAndForgetVerifyEmail(userId, name, email, teamName, teamUrl string) { +func FireAndForgetVerifyEmail(userId, name, email, teamDisplayName, teamURL string) { go func() { - link := fmt.Sprintf("%s/verify?uid=%s&hid=%s", teamUrl, userId, model.HashPassword(userId)) + link := fmt.Sprintf("%s/verify_email?uid=%s&hid=%s", teamURL, userId, model.HashPassword(userId)) - subjectPage := NewServerTemplatePage("verify_subject", teamUrl) - subjectPage.Props["TeamName"] = teamName - bodyPage := NewServerTemplatePage("verify_body", teamUrl) - bodyPage.Props["FullName"] = name - bodyPage.Props["TeamName"] = teamName + subjectPage := NewServerTemplatePage("verify_subject", teamURL) + subjectPage.Props["TeamDisplayName"] = teamDisplayName + bodyPage := NewServerTemplatePage("verify_body", teamURL) + bodyPage.Props["Nickname"] = name + bodyPage.Props["TeamDisplayName"] = teamDisplayName bodyPage.Props["VerifyUrl"] = link if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil { @@ -251,10 +250,10 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) { } var team *model.Team - if result.Data == nil && len(props["email"]) != 0 && len(props["domain"]) != 0 { - extraInfo = props["email"] + " in " + props["domain"] + if result.Data == nil && len(props["email"]) != 0 && len(props["name"]) != 0 { + extraInfo = props["email"] + " in " + props["name"] - if nr := <-Srv.Store.Team().GetByDomain(props["domain"]); nr.Err != nil { + if nr := <-Srv.Store.Team().GetByName(props["name"]); nr.Err != nil { c.Err = nr.Err return } else { @@ -293,7 +292,7 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !user.EmailVerified { + if !user.EmailVerified && !utils.Cfg.EmailSettings.ByPassEmail { c.Err = model.NewAppError("login", "Login failed because email address has not been verified", extraInfo) c.Err.StatusCode = http.StatusForbidden return @@ -517,7 +516,6 @@ func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) { } w.Header().Set(model.HEADER_ETAG_SERVER, etag) - w.Header().Set("Cache-Control", "max-age=120, public") // 2 mins w.Write([]byte(model.UserMapToJson(profiles))) return } @@ -729,6 +727,8 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { return } + Srv.Store.User().UpdateLastPictureUpdate(c.Session.UserId) + c.LogAudit("") } @@ -756,7 +756,8 @@ func updateUser(c *Context, w http.ResponseWriter, r *http.Request) { if tresult := <-Srv.Store.Team().Get(rusers[1].TeamId); tresult.Err != nil { l4g.Error(tresult.Err.Message) } else { - fireAndForgetEmailChangeEmail(rusers[1].Email, tresult.Data.(*model.Team).Name, c.TeamUrl) + team := tresult.Data.(*model.Team) + fireAndForgetEmailChangeEmail(rusers[1].Email, team.DisplayName, c.GetTeamURLFromTeam(team)) } } @@ -827,7 +828,8 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) { if tresult := <-tchan; tresult.Err != nil { l4g.Error(tresult.Err.Message) } else { - fireAndForgetPasswordChangeEmail(user.Email, tresult.Data.(*model.Team).Name, c.TeamUrl, "using the settings menu") + team := tresult.Data.(*model.Team) + fireAndForgetPasswordChangeEmail(user.Email, team.DisplayName, c.GetTeamURLFromTeam(team), "using the settings menu") } data := make(map[string]string) @@ -986,14 +988,14 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) { return } - domain := props["domain"] - if len(domain) == 0 { - c.SetInvalidParam("sendPasswordReset", "domain") + name := props["name"] + if len(name) == 0 { + c.SetInvalidParam("sendPasswordReset", "name") return } var team *model.Team - if result := <-Srv.Store.Team().GetByDomain(domain); result.Err != nil { + if result := <-Srv.Store.Team().GetByName(name); result.Err != nil { c.Err = result.Err return } else { @@ -1015,10 +1017,10 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) { data := model.MapToJson(newProps) hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.ResetSalt)) - link := fmt.Sprintf("%s/reset_password?d=%s&h=%s", c.TeamUrl, url.QueryEscape(data), url.QueryEscape(hash)) + link := fmt.Sprintf("%s/reset_password?d=%s&h=%s", c.GetTeamURLFromTeam(team), url.QueryEscape(data), url.QueryEscape(hash)) - subjectPage := NewServerTemplatePage("reset_subject", c.TeamUrl) - bodyPage := NewServerTemplatePage("reset_body", c.TeamUrl) + subjectPage := NewServerTemplatePage("reset_subject", c.GetTeamURLFromTeam(team)) + bodyPage := NewServerTemplatePage("reset_body", c.GetTeamURLFromTeam(team)) bodyPage.Props["ResetUrl"] = link if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil { @@ -1060,16 +1062,16 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) { return } - domain := props["domain"] - if len(domain) == 0 { - c.SetInvalidParam("resetPassword", "domain") + name := props["name"] + if len(name) == 0 { + c.SetInvalidParam("resetPassword", "name") return } c.LogAuditWithUserId(userId, "attempt") var team *model.Team - if result := <-Srv.Store.Team().GetByDomain(domain); result.Err != nil { + if result := <-Srv.Store.Team().GetByName(name); result.Err != nil { c.Err = result.Err return } else { @@ -1108,19 +1110,19 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAuditWithUserId(userId, "success") } - fireAndForgetPasswordChangeEmail(user.Email, team.Name, c.TeamUrl, "using a reset password link") + fireAndForgetPasswordChangeEmail(user.Email, team.DisplayName, c.GetTeamURLFromTeam(team), "using a reset password link") props["new_password"] = "" w.Write([]byte(model.MapToJson(props))) } -func fireAndForgetPasswordChangeEmail(email, teamName, teamUrl, method string) { +func fireAndForgetPasswordChangeEmail(email, teamDisplayName, teamURL, method string) { go func() { - subjectPage := NewServerTemplatePage("password_change_subject", teamUrl) - subjectPage.Props["TeamName"] = teamName - bodyPage := NewServerTemplatePage("password_change_body", teamUrl) - bodyPage.Props["TeamName"] = teamName + subjectPage := NewServerTemplatePage("password_change_subject", teamURL) + subjectPage.Props["TeamDisplayName"] = teamDisplayName + bodyPage := NewServerTemplatePage("password_change_body", teamURL) + bodyPage.Props["TeamDisplayName"] = teamDisplayName bodyPage.Props["Method"] = method if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil { @@ -1130,13 +1132,13 @@ func fireAndForgetPasswordChangeEmail(email, teamName, teamUrl, method string) { }() } -func fireAndForgetEmailChangeEmail(email, teamName, teamUrl string) { +func fireAndForgetEmailChangeEmail(email, teamDisplayName, teamURL string) { go func() { - subjectPage := NewServerTemplatePage("email_change_subject", teamUrl) - subjectPage.Props["TeamName"] = teamName - bodyPage := NewServerTemplatePage("email_change_body", teamUrl) - bodyPage.Props["TeamName"] = teamName + subjectPage := NewServerTemplatePage("email_change_subject", teamURL) + subjectPage.Props["TeamDisplayName"] = teamDisplayName + bodyPage := NewServerTemplatePage("email_change_body", teamURL) + bodyPage.Props["TeamDisplayName"] = teamDisplayName if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil { l4g.Error("Failed to send update password email successfully err=%v", err) diff --git a/api/user_test.go b/api/user_test.go index f2b69aba4b..e7a3f153a2 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -25,18 +25,18 @@ import ( func TestCreateUser(t *testing.T) { Setup() - team := model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} rteam, _ := Client.CreateTeam(&team) - user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "hello"} + user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "hello"} ruser, err := Client.CreateUser(&user, "") if err != nil { t.Fatal(err) } - if ruser.Data.(*model.User).FullName != user.FullName { - t.Fatal("full name didn't match") + if ruser.Data.(*model.User).Nickname != user.Nickname { + t.Fatal("nickname didn't match") } if ruser.Data.(*model.User).Password != "" { @@ -68,7 +68,7 @@ func TestCreateUser(t *testing.T) { } } - user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "hello", Username: model.BOT_USERNAME} + user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "hello", Username: model.BOT_USERNAME} if _, err := Client.CreateUser(&user2, ""); err == nil { t.Fatal("Should have failed using reserved bot name") @@ -82,10 +82,10 @@ func TestCreateUser(t *testing.T) { func TestCreateUserAllowedDomains(t *testing.T) { Setup() - team := model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_INVITE, AllowedDomains: "spinpunch.com, @nowh.com,@hello.com"} + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_INVITE, AllowedDomains: "spinpunch.com, @nowh.com,@hello.com"} rteam, _ := Client.CreateTeam(&team) - user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "hello"} + user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "hello"} _, err := Client.CreateUser(&user, "") if err == nil { @@ -102,10 +102,10 @@ func TestCreateUserAllowedDomains(t *testing.T) { func TestLogin(t *testing.T) { Setup() - team := model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} rteam, _ := Client.CreateTeam(&team) - user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} ruser, _ := Client.CreateUser(&user, "") store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) @@ -117,7 +117,7 @@ func TestLogin(t *testing.T) { } } - if result, err := Client.LoginByEmail(team.Domain, user.Email, user.Password); err != nil { + if result, err := Client.LoginByEmail(team.Name, user.Email, user.Password); err != nil { t.Fatal(err) } else { if result.Data.(*model.User).Email != user.Email { @@ -125,11 +125,11 @@ func TestLogin(t *testing.T) { } } - if _, err := Client.LoginByEmail(team.Domain, user.Email, user.Password+"invalid"); err == nil { + if _, err := Client.LoginByEmail(team.Name, user.Email, user.Password+"invalid"); err == nil { t.Fatal("Invalid Password") } - if _, err := Client.LoginByEmail(team.Domain, "", user.Password); err == nil { + if _, err := Client.LoginByEmail(team.Name, "", user.Password); err == nil { t.Fatal("should have failed") } @@ -142,10 +142,10 @@ func TestLogin(t *testing.T) { Client.AuthToken = "" - team2 := model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_INVITE} + team2 := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_INVITE} rteam2 := Client.Must(Client.CreateTeam(&team2)) - user2 := model.User{TeamId: rteam2.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := model.User{TeamId: rteam2.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} if _, err := Client.CreateUserFromSignup(&user2, "junk", "1231312"); err == nil { t.Fatal("Should have errored, signed up without hashed email") @@ -154,14 +154,14 @@ func TestLogin(t *testing.T) { props := make(map[string]string) props["email"] = user2.Email props["id"] = rteam2.Data.(*model.Team).Id - props["name"] = rteam2.Data.(*model.Team).Name + props["display_name"] = rteam2.Data.(*model.Team).DisplayName props["time"] = fmt.Sprintf("%v", model.GetMillis()) data := model.MapToJson(props) hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.InviteSalt)) ruser2, _ := Client.CreateUserFromSignup(&user2, data, hash) - if _, err := Client.LoginByEmail(team2.Domain, ruser2.Data.(*model.User).Email, user2.Password); err != nil { + if _, err := Client.LoginByEmail(team2.Name, ruser2.Data.(*model.User).Email, user2.Password); err != nil { t.Fatal("From verfied hash") } @@ -171,16 +171,16 @@ func TestLogin(t *testing.T) { func TestSessions(t *testing.T) { Setup() - team := model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} rteam, _ := Client.CreateTeam(&team) - user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(ruser.Id)) deviceId := model.NewId() - Client.LoginByEmailWithDevice(team.Domain, user.Email, user.Password, deviceId) - Client.LoginByEmail(team.Domain, user.Email, user.Password) + Client.LoginByEmailWithDevice(team.Name, user.Email, user.Password, deviceId) + Client.LoginByEmail(team.Name, user.Email, user.Password) r1, err := Client.GetSessions(ruser.Id) if err != nil { @@ -228,18 +228,18 @@ func TestSessions(t *testing.T) { func TestGetUser(t *testing.T) { Setup() - team := model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} rteam, _ := Client.CreateTeam(&team) - user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} ruser, _ := Client.CreateUser(&user, "") store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) - user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} ruser2, _ := Client.CreateUser(&user2, "") store.Must(Srv.Store.User().VerifyEmail(ruser2.Data.(*model.User).Id)) - Client.LoginByEmail(team.Domain, user.Email, user.Password) + Client.LoginByEmail(team.Name, user.Email, user.Password) rId := ruser.Data.(*model.User).Id if result, err := Client.GetUser(rId, ""); err != nil { @@ -299,16 +299,16 @@ func TestGetUser(t *testing.T) { func TestGetAudits(t *testing.T) { Setup() - team := model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} rteam, _ := Client.CreateTeam(&team) - user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} ruser, _ := Client.CreateUser(&user, "") store.Must(Srv.Store.User().VerifyEmail(ruser.Data.(*model.User).Id)) time.Sleep(100 * time.Millisecond) - Client.LoginByEmail(team.Domain, user.Email, user.Password) + Client.LoginByEmail(team.Name, user.Email, user.Password) time.Sleep(100 * time.Millisecond) @@ -352,14 +352,14 @@ func TestUserCreateImage(t *testing.T) { t.Fatal("Failed to create correct color") } - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") Client.DoGet("/users/"+user.Id+"/image", "", "") @@ -368,10 +368,10 @@ func TestUserCreateImage(t *testing.T) { func TestUserUploadProfileImage(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) @@ -384,7 +384,7 @@ func TestUserUploadProfileImage(t *testing.T) { t.Fatal("Should have errored") } - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") if _, upErr := Client.UploadFile("/users/newimage", body.Bytes(), writer.FormDataContentType()); upErr == nil { t.Fatal("Should have errored") @@ -465,12 +465,12 @@ func TestUserUploadProfileImage(t *testing.T) { func TestUserUpdate(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) time1 := model.GetMillis() - user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd", LastActivityAt: time1, LastPingAt: time1, Roles: ""} + user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd", LastActivityAt: time1, LastPingAt: time1, Roles: ""} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) @@ -478,7 +478,7 @@ func TestUserUpdate(t *testing.T) { t.Fatal("Should have errored") } - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") time.Sleep(100 * time.Millisecond) @@ -486,7 +486,7 @@ func TestUserUpdate(t *testing.T) { time.Sleep(100 * time.Millisecond) - user.FullName = "Jim Jimmy" + user.Nickname = "Jim Jimmy" user.TeamId = "12345678901234567890123456" user.LastActivityAt = time2 user.LastPingAt = time2 @@ -496,8 +496,8 @@ func TestUserUpdate(t *testing.T) { if result, err := Client.UpdateUser(user); err != nil { t.Fatal(err) } else { - if result.Data.(*model.User).FullName != "Jim Jimmy" { - t.Fatal("FullName did not update properly") + if result.Data.(*model.User).Nickname != "Jim Jimmy" { + t.Fatal("Nickname did not update properly") } if result.Data.(*model.User).TeamId != team.Id { t.Fatal("TeamId should not have updated") @@ -526,13 +526,13 @@ func TestUserUpdate(t *testing.T) { t.Fatal("Should have errored") } - user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") - user.FullName = "Tim Timmy" + user.Nickname = "Tim Timmy" if _, err := Client.UpdateUser(user); err == nil { t.Fatal("Should have errored") @@ -542,10 +542,10 @@ func TestUserUpdate(t *testing.T) { func TestUserUpdatePassword(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) @@ -553,7 +553,7 @@ func TestUserUpdatePassword(t *testing.T) { t.Fatal("Should have errored") } - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") if _, err := Client.UpdateUserPassword("123", "pwd", "newpwd"); err == nil { t.Fatal("Should have errored") @@ -584,14 +584,14 @@ func TestUserUpdatePassword(t *testing.T) { t.Fatal("LastPasswordUpdate should have changed") } - if _, err := Client.LoginByEmail(team.Domain, user.Email, "newpwd"); err != nil { + if _, err := Client.LoginByEmail(team.Name, user.Email, "newpwd"); err != nil { t.Fatal(err) } - user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") if _, err := Client.UpdateUserPassword(user.Id, "pwd", "newpwd"); err == nil { t.Fatal("Should have errored") @@ -601,14 +601,14 @@ func TestUserUpdatePassword(t *testing.T) { func TestUserUpdateRoles(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) @@ -620,20 +620,20 @@ func TestUserUpdateRoles(t *testing.T) { t.Fatal("Should have errored, not logged in") } - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") if _, err := Client.UpdateUserRoles(data); err == nil { t.Fatal("Should have errored, not admin") } - team2 := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team) - user3 := &model.User{TeamId: team2.Id, Email: "test@nowhere.com", FullName: "Corey Hulen", Password: "pwd"} + user3 := &model.User{TeamId: team2.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"} user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user3.Id)) - Client.LoginByEmail(team2.Domain, user3.Email, "pwd") + Client.LoginByEmail(team2.Name, user3.Email, "pwd") data["user_id"] = user2.Id @@ -641,7 +641,7 @@ func TestUserUpdateRoles(t *testing.T) { t.Fatal("Should have errored, wrong team") } - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") data["user_id"] = "junk" data["new_roles"] = "admin" @@ -670,14 +670,14 @@ func TestUserUpdateRoles(t *testing.T) { func TestUserUpdateActive(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) - user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) @@ -685,26 +685,26 @@ func TestUserUpdateActive(t *testing.T) { t.Fatal("Should have errored, not logged in") } - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") if _, err := Client.UpdateActive(user.Id, false); err == nil { t.Fatal("Should have errored, not admin") } - team2 := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team) - user3 := &model.User{TeamId: team2.Id, Email: "test@nowhere.com", FullName: "Corey Hulen", Password: "pwd"} + user3 := &model.User{TeamId: team2.Id, Email: "test@nowhere.com", Nickname: "Corey Hulen", Password: "pwd"} user3 = Client.Must(Client.CreateUser(user3, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user3.Id)) - Client.LoginByEmail(team2.Domain, user3.Email, "pwd") + Client.LoginByEmail(team2.Name, user3.Email, "pwd") if _, err := Client.UpdateActive(user.Id, false); err == nil { t.Fatal("Should have errored, wrong team") } - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") if _, err := Client.UpdateActive("junk", false); err == nil { t.Fatal("Should have errored, bad id") @@ -734,16 +734,16 @@ func TestUserUpdateActive(t *testing.T) { func TestSendPasswordReset(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) data := make(map[string]string) data["email"] = user.Email - data["domain"] = team.Domain + data["name"] = team.Name if _, err := Client.SendPasswordReset(data); err != nil { t.Fatal(err) @@ -760,24 +760,24 @@ func TestSendPasswordReset(t *testing.T) { } data["email"] = user.Email - data["domain"] = "" + data["name"] = "" if _, err := Client.SendPasswordReset(data); err == nil { - t.Fatal("Should have errored - no domain") + t.Fatal("Should have errored - no name") } - data["domain"] = "junk" + data["name"] = "junk" if _, err := Client.SendPasswordReset(data); err == nil { - t.Fatal("Should have errored - bad domain") + t.Fatal("Should have errored - bad name") } } func TestResetPassword(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) @@ -788,7 +788,7 @@ func TestResetPassword(t *testing.T) { props["time"] = fmt.Sprintf("%v", model.GetMillis()) data["data"] = model.MapToJson(props) data["hash"] = model.HashPassword(fmt.Sprintf("%v:%v", data["data"], utils.Cfg.ServiceSettings.ResetSalt)) - data["domain"] = team.Domain + data["name"] = team.Name if _, err := Client.ResetPassword(data); err != nil { t.Fatal(err) @@ -846,10 +846,10 @@ func TestResetPassword(t *testing.T) { t.Fatal("Should have errored - bad domain") } - team2 := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test2@nowhere.com", Type: model.TEAM_OPEN} + team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test2@nowhere.com", Type: model.TEAM_OPEN} team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team) - data["domain"] = team2.Domain + data["domain"] = team2.Name if _, err := Client.ResetPassword(data); err == nil { t.Fatal("Should have errored - domain team doesn't match user team") } @@ -858,10 +858,10 @@ func TestResetPassword(t *testing.T) { func TestUserUpdateNotify(t *testing.T) { Setup() - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd", Roles: ""} + user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd", Roles: ""} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) @@ -875,7 +875,7 @@ func TestUserUpdateNotify(t *testing.T) { t.Fatal("Should have errored - not logged in") } - Client.LoginByEmail(team.Domain, user.Email, "pwd") + Client.LoginByEmail(team.Name, user.Email, "pwd") if result, err := Client.UpdateUserNotify(data); err != nil { t.Fatal(err) @@ -926,7 +926,7 @@ func TestUserUpdateNotify(t *testing.T) { func TestFuzzyUserCreate(t *testing.T) { - team := model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} rteam, _ := Client.CreateTeam(&team) for i := 0; i < len(utils.FUZZY_STRINGS_NAMES) || i < len(utils.FUZZY_STRINGS_EMAILS); i++ { @@ -940,7 +940,7 @@ func TestFuzzyUserCreate(t *testing.T) { testEmail = utils.FUZZY_STRINGS_EMAILS[i] } - user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + testEmail, FullName: testName, Password: "hello"} + user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + testEmail, Nickname: testName, Password: "hello"} _, err := Client.CreateUser(&user, "") if err != nil { @@ -952,14 +952,14 @@ func TestFuzzyUserCreate(t *testing.T) { func TestStatuses(t *testing.T) { Setup() - team := model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} rteam, _ := Client.CreateTeam(&team) - user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(ruser.Id)) - Client.LoginByEmail(team.Domain, user.Email, user.Password) + Client.LoginByEmail(team.Name, user.Email, user.Password) r1, err := Client.GetStatuses() if err != nil { diff --git a/api/web_conn.go b/api/web_conn.go index 751f6f407f..0990de8ef2 100644 --- a/api/web_conn.go +++ b/api/web_conn.go @@ -70,7 +70,7 @@ func (c *WebConn) readPump() { } else { msg.TeamId = c.TeamId msg.UserId = c.UserId - store.PublishAndForget(&msg) + PublishAndForget(&msg) } } } diff --git a/api/web_hub.go b/api/web_hub.go index bf5fbb3216..c7be19cacb 100644 --- a/api/web_hub.go +++ b/api/web_hub.go @@ -5,12 +5,14 @@ package api import ( l4g "code.google.com/p/log4go" + "github.com/mattermost/platform/model" ) type Hub struct { teamHubs map[string]*TeamHub register chan *WebConn unregister chan *WebConn + broadcast chan *model.Message stop chan string } @@ -18,9 +20,16 @@ var hub = &Hub{ register: make(chan *WebConn), unregister: make(chan *WebConn), teamHubs: make(map[string]*TeamHub), + broadcast: make(chan *model.Message), stop: make(chan string), } +func PublishAndForget(message *model.Message) { + go func() { + hub.Broadcast(message) + }() +} + func (h *Hub) Register(webConn *WebConn) { h.register <- webConn } @@ -29,8 +38,14 @@ func (h *Hub) Unregister(webConn *WebConn) { h.unregister <- webConn } -func (h *Hub) Stop(teamId string) { - h.stop <- teamId +func (h *Hub) Broadcast(message *model.Message) { + if message != nil { + h.broadcast <- message + } +} + +func (h *Hub) Stop() { + h.stop <- "all" } func (h *Hub) Start() { @@ -53,18 +68,17 @@ func (h *Hub) Start() { if nh, ok := h.teamHubs[c.TeamId]; ok { nh.Unregister(c) } - - case s := <-h.stop: - if len(s) == 0 { - l4g.Debug("stopping all connections") - for _, v := range h.teamHubs { - v.Stop() - } - return - } else if nh, ok := h.teamHubs[s]; ok { - delete(h.teamHubs, s) - nh.Stop() + case msg := <-h.broadcast: + nh := h.teamHubs[msg.TeamId] + if nh != nil { + nh.broadcast <- msg } + case s := <-h.stop: + l4g.Debug("stopping %v connections", s) + for _, v := range h.teamHubs { + v.Stop() + } + return } } }() diff --git a/api/web_socket_test.go b/api/web_socket_test.go index 4cb49220f9..161274ff79 100644 --- a/api/web_socket_test.go +++ b/api/web_socket_test.go @@ -17,13 +17,13 @@ func TestSocket(t *testing.T) { Setup() url := "ws://localhost:" + utils.Cfg.ServiceSettings.Port + "/api/v1/websocket" - team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} + team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) - user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user1.Id)) - Client.LoginByEmail(team.Domain, user1.Email, "pwd") + Client.LoginByEmail(team.Name, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test Web Scoket 1", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) @@ -39,10 +39,10 @@ func TestSocket(t *testing.T) { t.Fatal(err) } - user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"} + user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user2.Id)) - Client.LoginByEmail(team.Domain, user2.Email, "pwd") + Client.LoginByEmail(team.Name, user2.Email, "pwd") header2 := http.Header{} header2.Set(model.HEADER_AUTH, "BEARER "+Client.AuthToken) @@ -115,9 +115,6 @@ func TestSocket(t *testing.T) { }() time.Sleep(2 * time.Second) - - hub.Stop(team.Id) - } func TestZZWebSocketTearDown(t *testing.T) { diff --git a/api/web_team_hub.go b/api/web_team_hub.go index 7c7981e76f..7a63b84d10 100644 --- a/api/web_team_hub.go +++ b/api/web_team_hub.go @@ -6,8 +6,6 @@ package api import ( l4g "code.google.com/p/log4go" "github.com/mattermost/platform/model" - "github.com/mattermost/platform/store" - "strings" ) type TeamHub struct { @@ -43,43 +41,6 @@ func (h *TeamHub) Stop() { } func (h *TeamHub) Start() { - - pubsub := store.RedisClient().PubSub() - - go func() { - defer func() { - l4g.Debug("redis reader finished for teamId=%v", h.teamId) - hub.Stop(h.teamId) - }() - - l4g.Debug("redis reader starting for teamId=%v", h.teamId) - - err := pubsub.Subscribe(h.teamId) - if err != nil { - l4g.Error("Error while subscribing to redis %v %v", h.teamId, err) - return - } - - for { - if payload, err := pubsub.ReceiveTimeout(REDIS_WAIT); err != nil { - if strings.Contains(err.Error(), "i/o timeout") { - if len(h.connections) == 0 { - l4g.Debug("No active connections so sending stop %v", h.teamId) - return - } - } else { - return - } - } else { - msg := store.GetMessageFromPayload(payload) - if msg != nil { - h.broadcast <- msg - } - } - } - - }() - go func() { for { select { @@ -110,7 +71,6 @@ func (h *TeamHub) Start() { webCon.WebSocket.Close() } - pubsub.Close() return } } diff --git a/config/config.json b/config/config.json index b0a019e8de..e6025ef517 100644 --- a/config/config.json +++ b/config/config.json @@ -9,7 +9,6 @@ }, "ServiceSettings": { "SiteName": "Mattermost", - "Domain": "xxxxxxmustbefilledin.com", "Mode" : "dev", "AllowTesting" : false, "UseSSL": false, @@ -31,19 +30,11 @@ "Trace": false, "AtRestEncryptKey": "Ya0xMrybACJ3sZZVWQC7e31h5nSDWZFS" }, - "RedisSettings": { - "DataSource": "dockerhost:6379", - "MaxOpenConns": 1000 - }, "AWSSettings": { "S3AccessKeyId": "", "S3SecretAccessKey": "", "S3Bucket": "", - "S3Region": "", - "Route53AccessKeyId": "", - "Route53SecretAccessKey": "", - "Route53ZoneId": "", - "Route53Region": "" + "S3Region": "" }, "ImageSettings": { "ThumbnailWidth": 200, @@ -54,11 +45,12 @@ "ProfileHeight": 128 }, "EmailSettings": { + "ByPassEmail" : true, "SMTPUsername": "", "SMTPPassword": "", "SMTPServer": "", "UseTLS": false, - "FeedbackEmail": "feedback@xxxxxxmustbefilledin.com", + "FeedbackEmail": "", "FeedbackName": "", "ApplePushServer": "", "ApplePushCertPublic": "", diff --git a/config/config_docker.json b/config/config_docker.json index 85f0d9c73b..9be8370724 100644 --- a/config/config_docker.json +++ b/config/config_docker.json @@ -9,7 +9,6 @@ }, "ServiceSettings": { "SiteName": "Mattermost", - "Domain": "", "Mode" : "dev", "AllowTesting" : false, "UseSSL": false, @@ -31,19 +30,11 @@ "Trace": false, "AtRestEncryptKey": "Ya0xMrybACJ3sZZVWQC7e31h5nSDWZFS" }, - "RedisSettings": { - "DataSource": "localhost:6379", - "MaxOpenConns": 1000 - }, "AWSSettings": { "S3AccessKeyId": "", "S3SecretAccessKey": "", "S3Bucket": "", - "S3Region": "", - "Route53AccessKeyId": "", - "Route53SecretAccessKey": "", - "Route53ZoneId": "", - "Route53Region": "" + "S3Region": "" }, "ImageSettings": { "ThumbnailWidth": 200, @@ -54,9 +45,10 @@ "ProfileHeight": 128 }, "EmailSettings": { + "ByPassEmail" : true, "SMTPUsername": "", "SMTPPassword": "", - "SMTPServer": "localhost:25", + "SMTPServer": "", "UseTLS": false, "FeedbackEmail": "", "FeedbackName": "", diff --git a/manualtesting/manual_testing.go b/manualtesting/manual_testing.go index 929f7ab5d8..f7408b814a 100644 --- a/manualtesting/manual_testing.go +++ b/manualtesting/manual_testing.go @@ -57,17 +57,17 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) { // Check for username parameter and create a user if present username, ok1 := params["username"] - teamname, ok2 := params["teamname"] + teamDisplayName, ok2 := params["teamname"] var teamID string var userID string if ok1 && ok2 { l4g.Info("Creating user and team") // Create team for testing team := &model.Team{ - Name: teamname[0], - Domain: utils.RandomName(utils.Range{20, 20}, utils.LOWERCASE), - Email: utils.RandomEmail(utils.Range{20, 20}, utils.LOWERCASE), - Type: model.TEAM_OPEN, + DisplayName: teamDisplayName[0], + Name: utils.RandomName(utils.Range{20, 20}, utils.LOWERCASE), + Email: utils.RandomEmail(utils.Range{20, 20}, utils.LOWERCASE), + Type: model.TEAM_OPEN, } if result := <-api.Srv.Store.Team().Save(team); result.Err != nil { @@ -90,7 +90,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) { user := &model.User{ TeamId: teamID, Email: utils.RandomEmail(utils.Range{20, 20}, utils.LOWERCASE), - FullName: username[0], + Nickname: username[0], Password: api.USER_PASSWORD} result, err := client.CreateUser(user, "") diff --git a/model/channel_extra.go b/model/channel_extra.go index a5c9acf714..3a918b5244 100644 --- a/model/channel_extra.go +++ b/model/channel_extra.go @@ -10,7 +10,7 @@ import ( type ExtraMember struct { Id string `json:"id"` - FullName string `json:"full_name"` + Nickname string `json:"nickname"` Email string `json:"email"` Roles string `json:"roles"` Username string `json:"username"` @@ -20,9 +20,6 @@ func (o *ExtraMember) Sanitize(options map[string]bool) { if len(options) == 0 || !options["email"] { o.Email = "" } - if len(options) == 0 || !options["fullname"] { - o.FullName = "" - } } type ChannelExtra struct { diff --git a/model/client.go b/model/client.go index e22f2308ed..ad1e4b5f1f 100644 --- a/model/client.go +++ b/model/client.go @@ -100,10 +100,10 @@ func (c *Client) Must(result *Result, err *AppError) *Result { return result } -func (c *Client) SignupTeam(email string, name string) (*Result, *AppError) { +func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppError) { m := make(map[string]string) m["email"] = email - m["name"] = name + m["display_name"] = displayName if r, err := c.DoPost("/teams/signup", MapToJson(m)); err != nil { return nil, err } else { @@ -130,11 +130,11 @@ func (c *Client) CreateTeam(team *Team) (*Result, *AppError) { } } -func (c *Client) FindTeamByDomain(domain string, allServers bool) (*Result, *AppError) { +func (c *Client) FindTeamByName(name string, allServers bool) (*Result, *AppError) { m := make(map[string]string) - m["domain"] = domain + m["name"] = name m["all"] = fmt.Sprintf("%v", allServers) - if r, err := c.DoPost("/teams/find_team_by_domain", MapToJson(m)); err != nil { + if r, err := c.DoPost("/teams/find_team_by_name", MapToJson(m)); err != nil { return nil, err } else { val := false @@ -179,7 +179,7 @@ func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) { } } -func (c *Client) UpdateTeamName(data map[string]string) (*Result, *AppError) { +func (c *Client) UpdateTeamDisplayName(data map[string]string) (*Result, *AppError) { if r, err := c.DoPost("/teams/update_name", MapToJson(data)); err != nil { return nil, err } else { @@ -249,17 +249,17 @@ func (c *Client) LoginById(id string, password string) (*Result, *AppError) { return c.login(m) } -func (c *Client) LoginByEmail(domain string, email string, password string) (*Result, *AppError) { +func (c *Client) LoginByEmail(name string, email string, password string) (*Result, *AppError) { m := make(map[string]string) - m["domain"] = domain + m["name"] = name m["email"] = email m["password"] = password return c.login(m) } -func (c *Client) LoginByEmailWithDevice(domain string, email string, password string, deviceId string) (*Result, *AppError) { +func (c *Client) LoginByEmailWithDevice(name string, email string, password string, deviceId string) (*Result, *AppError) { m := make(map[string]string) - m["domain"] = domain + m["name"] = name m["email"] = email m["password"] = password m["device_id"] = deviceId diff --git a/model/team.go b/model/team.go index 5c66f3b1fb..e7005625b1 100644 --- a/model/team.go +++ b/model/team.go @@ -18,8 +18,8 @@ type Team struct { CreateAt int64 `json:"create_at"` UpdateAt int64 `json:"update_at"` DeleteAt int64 `json:"delete_at"` + DisplayName string `json:"display_name"` Name string `json:"name"` - Domain string `json:"domain"` Email string `json:"email"` Type string `json:"type"` CompanyName string `json:"company_name"` @@ -97,20 +97,20 @@ func (o *Team) IsValid() *AppError { return NewAppError("Team.IsValid", "Invalid email", "id="+o.Id) } - if len(o.Name) > 64 { + if len(o.DisplayName) > 64 { return NewAppError("Team.IsValid", "Invalid name", "id="+o.Id) } - if len(o.Domain) > 64 { - return NewAppError("Team.IsValid", "Invalid domain", "id="+o.Id) + if len(o.Name) > 64 { + return NewAppError("Team.IsValid", "Invalid URL Identifier", "id="+o.Id) } - if IsReservedDomain(o.Domain) { + if IsReservedTeamName(o.Name) { return NewAppError("Team.IsValid", "This URL is unavailable. Please try another.", "id="+o.Id) } - if !IsValidDomain(o.Domain) { - return NewAppError("Team.IsValid", "Domain must be 4 or more lowercase alphanumeric characters", "id="+o.Id) + if !IsValidTeamName(o.Name) { + return NewAppError("Team.IsValid", "Name must be 4 or more lowercase alphanumeric characters", "id="+o.Id) } if !(o.Type == TEAM_OPEN || o.Type == TEAM_INVITE) { diff --git a/model/team_signup_test.go b/model/team_signup_test.go index f3f74470b2..eb2fbc69ff 100644 --- a/model/team_signup_test.go +++ b/model/team_signup_test.go @@ -9,7 +9,7 @@ import ( ) func TestTeamSignupJson(t *testing.T) { - team := Team{Id: NewId(), Name: NewId()} + team := Team{Id: NewId(), DisplayName: NewId()} o := TeamSignup{Team: team, Data: "data"} json := o.ToJson() ro := TeamSignupFromJson(strings.NewReader(json)) diff --git a/model/team_test.go b/model/team_test.go index 6261ed6bf7..071b1a2e9b 100644 --- a/model/team_test.go +++ b/model/team_test.go @@ -9,7 +9,7 @@ import ( ) func TestTeamJson(t *testing.T) { - o := Team{Id: NewId(), Name: NewId()} + o := Team{Id: NewId(), DisplayName: NewId()} json := o.ToJson() ro := TeamFromJson(strings.NewReader(json)) @@ -46,18 +46,18 @@ func TestTeamIsValid(t *testing.T) { } o.Email = "corey@hulen.com" - o.Name = strings.Repeat("01234567890", 20) + o.DisplayName = strings.Repeat("01234567890", 20) if err := o.IsValid(); err == nil { t.Fatal("should be invalid") } - o.Name = "1234" - o.Domain = "ZZZZZZZ" + o.DisplayName = "1234" + o.Name = "ZZZZZZZ" if err := o.IsValid(); err == nil { t.Fatal("should be invalid") } - o.Domain = "zzzzz" + o.Name = "zzzzz" o.Type = TEAM_OPEN if err := o.IsValid(); err != nil { t.Fatal(err) @@ -65,12 +65,12 @@ func TestTeamIsValid(t *testing.T) { } func TestTeamPreSave(t *testing.T) { - o := Team{Name: "test"} + o := Team{DisplayName: "test"} o.PreSave() o.Etag() } func TestTeamPreUpdate(t *testing.T) { - o := Team{Name: "test"} + o := Team{DisplayName: "test"} o.PreUpdate() } diff --git a/model/user.go b/model/user.go index b94ceb899a..727165b8c2 100644 --- a/model/user.go +++ b/model/user.go @@ -37,7 +37,9 @@ type User struct { AuthData string `json:"auth_data"` Email string `json:"email"` EmailVerified bool `json:"email_verified"` - FullName string `json:"full_name"` + Nickname string `json:"nickname"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` Roles string `json:"roles"` LastActivityAt int64 `json:"last_activity_at"` LastPingAt int64 `json:"last_ping_at"` @@ -45,6 +47,7 @@ type User struct { Props StringMap `json:"props"` NotifyProps StringMap `json:"notify_props"` LastPasswordUpdate int64 `json:"last_password_update"` + LastPictureUpdate int64 `json:"last_picture_update"` } // IsValid validates the user and returns an error if it isn't configured @@ -81,8 +84,16 @@ func (u *User) IsValid() *AppError { return NewAppError("User.IsValid", "Invalid email", "user_id="+u.Id) } - if len(u.FullName) > 64 { - return NewAppError("User.IsValid", "Invalid full name", "user_id="+u.Id) + if len(u.Nickname) > 64 { + return NewAppError("User.IsValid", "Invalid nickname", "user_id="+u.Id) + } + + if len(u.FirstName) > 64 { + return NewAppError("User.IsValid", "Invalid first name", "user_id="+u.Id) + } + + if len(u.LastName) > 64 { + return NewAppError("User.IsValid", "Invalid last name", "user_id="+u.Id) } return nil @@ -151,7 +162,7 @@ func (u *User) SetDefaultNotifications() { u.NotifyProps["first_name"] = "false" u.NotifyProps["all"] = "true" u.NotifyProps["channel"] = "true" - splitName := strings.Split(u.FullName, " ") + splitName := strings.Split(u.Nickname, " ") if len(splitName) > 0 && splitName[0] != "" { u.NotifyProps["first_name"] = "true" u.NotifyProps["mention_keys"] += "," + splitName[0] @@ -190,7 +201,8 @@ func (u *User) Sanitize(options map[string]bool) { u.Email = "" } if len(options) != 0 && !options["fullname"] { - u.FullName = "" + u.FirstName = "" + u.LastName = "" } if len(options) != 0 && !options["skypeid"] { // TODO - fill in when SkypeId is added to user model @@ -225,6 +237,28 @@ func (u *User) AddNotifyProp(key string, value string) { u.NotifyProps[key] = value } +func (u *User) GetFullName() string { + if u.FirstName != "" && u.LastName != "" { + return u.FirstName + " " + u.LastName + } else if u.FirstName != "" { + return u.FirstName + } else if u.LastName != "" { + return u.LastName + } else { + return "" + } +} + +func (u *User) GetDisplayName() string { + if u.Nickname != "" { + return u.Nickname + } else if fullName := u.GetFullName(); fullName != "" { + return fullName + } else { + return u.Username + } +} + // UserFromJson will decode the input and return a User func UserFromJson(data io.Reader) *User { decoder := json.NewDecoder(data) diff --git a/model/user_test.go b/model/user_test.go index df9ac19c22..a48c3f2e78 100644 --- a/model/user_test.go +++ b/model/user_test.go @@ -80,13 +80,73 @@ func TestUserIsValid(t *testing.T) { } user.Email = "test@nowhere.com" - user.FullName = strings.Repeat("01234567890", 20) + user.Nickname = strings.Repeat("01234567890", 20) if err := user.IsValid(); err == nil { t.Fatal() } - user.FullName = "" + user.Nickname = "" if err := user.IsValid(); err != nil { t.Fatal(err) } + + user.FirstName = "" + user.LastName = "" + if err := user.IsValid(); err != nil { + t.Fatal(err) + } + + user.FirstName = strings.Repeat("01234567890", 20) + if err := user.IsValid(); err == nil { + t.Fatal(err) + } + + user.FirstName = "" + user.LastName = strings.Repeat("01234567890", 20) + if err := user.IsValid(); err == nil { + t.Fatal(err) + } +} + +func TestUserGetFullName(t *testing.T) { + user := User{} + + if fullName := user.GetFullName(); fullName != "" { + t.Fatal("Full name should be blank") + } + + user.FirstName = "first" + if fullName := user.GetFullName(); fullName != "first" { + t.Fatal("Full name should be first name") + } + + user.FirstName = "" + user.LastName = "last" + if fullName := user.GetFullName(); fullName != "last" { + t.Fatal("Full name should be last name") + } + + user.FirstName = "first" + if fullName := user.GetFullName(); fullName != "first last" { + t.Fatal("Full name should be first name and last name") + } +} + +func TestUserGetDisplayName(t *testing.T) { + user := User{Username: "user"} + + if displayName := user.GetDisplayName(); displayName != "user" { + t.Fatal("Display name should be username") + } + + user.FirstName = "first" + user.LastName = "last" + if displayName := user.GetDisplayName(); displayName != "first last" { + t.Fatal("Display name should be full name") + } + + user.Nickname = "nickname" + if displayName := user.GetDisplayName(); displayName != "nickname" { + t.Fatal("Display name should be nickname") + } } diff --git a/model/utils.go b/model/utils.go index 465901a095..38592b984b 100644 --- a/model/utils.go +++ b/model/utils.go @@ -148,7 +148,7 @@ func IsValidEmail(email string) bool { return false } -var reservedDomains = []string{ +var reservedName = []string{ "www", "web", "admin", @@ -168,10 +168,10 @@ var reservedDomains = []string{ "api", } -func IsReservedDomain(s string) bool { +func IsReservedTeamName(s string) bool { s = strings.ToLower(s) - for _, value := range reservedDomains { + for _, value := range reservedName { if strings.Index(s, value) == 0 { return true } @@ -180,7 +180,7 @@ func IsReservedDomain(s string) bool { return false } -func IsValidDomain(s string) bool { +func IsValidTeamName(s string) bool { if !IsValidAlphaNum(s) { return false diff --git a/model/utils_test.go b/model/utils_test.go index a9721042d8..dbb4488823 100644 --- a/model/utils_test.go +++ b/model/utils_test.go @@ -83,9 +83,9 @@ var domains = []struct { {"test", true}, } -func TestValidDomain(t *testing.T) { +func TestValidTeamName(t *testing.T) { for _, v := range domains { - if IsValidDomain(v.value) != v.expected { + if IsValidTeamName(v.value) != v.expected { t.Errorf("expect %v as %v", v.value, v.expected) } } @@ -102,9 +102,9 @@ var tReservedDomains = []struct { {"spin-punch-admin", false}, } -func TestReservedDomain(t *testing.T) { +func TestReservedTeamName(t *testing.T) { for _, v := range tReservedDomains { - if IsReservedDomain(v.value) != v.expected { + if IsReservedTeamName(v.value) != v.expected { t.Errorf("expect %v as %v", v.value, v.expected) } } diff --git a/scripts/README_DEV.md b/scripts/README_DEV.md index 6a2dfc54da..be24daad90 100644 --- a/scripts/README_DEV.md +++ b/scripts/README_DEV.md @@ -10,7 +10,7 @@ DOCKER SETUP 3. Add a line to your /etc/hosts that goes ` dockerhost` 4. Run `boot2docker shellinit` and copy the export statements to your ~/.bash_profile -Any issues? Please let us know on our forums at: http://bit.ly/1MY1kul +Any issues? Please let us know on our forums at: http://forum.mattermost.org GO SETUP @@ -39,4 +39,4 @@ MATTERMOST SETUP 6. Then do `cd platform` and `make test`. Provided the test runs fine, you now have a complete build environment. 7. Use `make run` to run your code -Any issues? Please let us know on our forums at: http://bit.ly/1MY1kul \ No newline at end of file +Any issues? Please let us know on our forums at: http://forum.mattermost.org diff --git a/store/redis.go b/store/redis.go deleted file mode 100644 index 262040d437..0000000000 --- a/store/redis.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. -// See License.txt for license information. - -package store - -import ( - l4g "code.google.com/p/log4go" - "github.com/mattermost/platform/model" - "github.com/mattermost/platform/utils" - "gopkg.in/redis.v2" - "strings" - "time" -) - -var client *redis.Client - -func RedisClient() *redis.Client { - - if client == nil { - - addr := utils.Cfg.RedisSettings.DataSource - - client = redis.NewTCPClient(&redis.Options{ - Addr: addr, - Password: "", - DB: 0, - PoolSize: utils.Cfg.RedisSettings.MaxOpenConns, - }) - - l4g.Info("Pinging redis at '%v'", addr) - pong, err := client.Ping().Result() - - if err != nil { - l4g.Critical("Failed to open redis connection to '%v' err:%v", addr, err) - time.Sleep(time.Second) - panic("Failed to open redis connection " + err.Error()) - } - - if pong != "PONG" { - l4g.Critical("Failed to ping redis connection to '%v' err:%v", addr, err) - time.Sleep(time.Second) - panic("Failed to open ping connection " + err.Error()) - } - } - - return client -} - -func RedisClose() { - l4g.Info("Closing redis") - - if client != nil { - client.Close() - client = nil - } -} - -func PublishAndForget(message *model.Message) { - - go func() { - c := RedisClient() - result := c.Publish(message.TeamId, message.ToJson()) - if result.Err() != nil { - l4g.Error("Failed to publish message err=%v, payload=%v", result.Err(), message.ToJson()) - } - }() -} - -func GetMessageFromPayload(m interface{}) *model.Message { - if msg, found := m.(*redis.Message); found { - return model.MessageFromJson(strings.NewReader(msg.Payload)) - } else { - return nil - } -} diff --git a/store/redis_test.go b/store/redis_test.go deleted file mode 100644 index 11bd9ca6a0..0000000000 --- a/store/redis_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. -// See License.txt for license information. - -package store - -import ( - "fmt" - "github.com/mattermost/platform/model" - "github.com/mattermost/platform/utils" - "testing" -) - -func TestRedis(t *testing.T) { - utils.LoadConfig("config.json") - - c := RedisClient() - - if c == nil { - t.Fatal("should have a valid redis connection") - } - - pubsub := c.PubSub() - defer pubsub.Close() - - m := model.NewMessage(model.NewId(), model.NewId(), model.NewId(), model.ACTION_TYPING) - m.Add("RootId", model.NewId()) - - err := pubsub.Subscribe(m.TeamId) - if err != nil { - t.Fatal(err) - } - - // should be the subscribe success message - // lets gobble that up - if _, err := pubsub.Receive(); err != nil { - t.Fatal(err) - } - - PublishAndForget(m) - - fmt.Println("here1") - - if msg, err := pubsub.Receive(); err != nil { - t.Fatal(err) - } else { - - rmsg := GetMessageFromPayload(msg) - - if m.TeamId != rmsg.TeamId { - t.Fatal("Ids do not match") - } - - if m.Props["RootId"] != rmsg.Props["RootId"] { - t.Fatal("Ids do not match") - } - } - - RedisClose() -} diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index 5aa7f21f9f..dbdfc16b12 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -360,7 +360,7 @@ func (s SqlChannelStore) GetExtraMembers(channelId string, limit int) StoreChann result := StoreResult{} var members []model.ExtraMember - _, err := s.GetReplica().Select(&members, "SELECT Id, FullName, Email, ChannelMembers.Roles, Username FROM ChannelMembers, Users WHERE ChannelMembers.UserId = Users.Id AND ChannelId = :ChannelId LIMIT :Limit", map[string]interface{}{"ChannelId": channelId, "Limit": limit}) + _, err := s.GetReplica().Select(&members, "SELECT Id, Nickname, Email, ChannelMembers.Roles, Username FROM ChannelMembers, Users WHERE ChannelMembers.UserId = Users.Id AND ChannelId = :ChannelId LIMIT :Limit", map[string]interface{}{"ChannelId": channelId, "Limit": limit}) if err != nil { result.Err = model.NewAppError("SqlChannelStore.GetExtraMembers", "We couldn't get the extra info for channel members", "channel_id="+channelId+", "+err.Error()) } else { diff --git a/store/sql_channel_store_test.go b/store/sql_channel_store_test.go index 3cb249b398..ae29bc2b33 100644 --- a/store/sql_channel_store_test.go +++ b/store/sql_channel_store_test.go @@ -200,13 +200,13 @@ func TestChannelMemberStore(t *testing.T) { u1 := model.User{} u1.TeamId = model.NewId() u1.Email = model.NewId() - u1.FullName = model.NewId() + u1.Nickname = model.NewId() Must(store.User().Save(&u1)) u2 := model.User{} u2.TeamId = model.NewId() u2.Email = model.NewId() - u2.FullName = model.NewId() + u2.Nickname = model.NewId() Must(store.User().Save(&u2)) o1 := model.ChannelMember{} diff --git a/store/sql_post_store.go b/store/sql_post_store.go index 13e7b891d6..56c174e4c2 100644 --- a/store/sql_post_store.go +++ b/store/sql_post_store.go @@ -36,6 +36,11 @@ func NewSqlPostStore(sqlStore *SqlStore) PostStore { } func (s SqlPostStore) UpgradeSchemaIfNeeded() { + + // These execs are for upgrading currently created databases to full utf8mb4 compliance + // Will be removed as seen fit for upgrading + s.GetMaster().Exec("ALTER TABLE Posts charset=utf8mb4") + s.GetMaster().Exec("ALTER TABLE Posts MODIFY COLUMN Message varchar(4000) CHARACTER SET utf8mb4") } func (s SqlPostStore) CreateIndexesIfNotExists() { diff --git a/store/sql_store.go b/store/sql_store.go index 5439456059..216060dba2 100644 --- a/store/sql_store.go +++ b/store/sql_store.go @@ -83,7 +83,14 @@ func NewSqlStore() Store { func setupConnection(con_type string, driver string, dataSource string, maxIdle int, maxOpen int, trace bool) *gorp.DbMap { - db, err := dbsql.Open(driver, dataSource) + charset := "" + if strings.Index(dataSource, "?") > -1 { + charset = "&charset=utf8mb4,utf8" + } else { + charset = "?charset=utf8mb4,utf8" + } + + db, err := dbsql.Open(driver, dataSource+charset) if err != nil { l4g.Critical("Failed to open sql connection to '%v' err:%v", dataSource, err) time.Sleep(time.Second) @@ -106,7 +113,7 @@ func setupConnection(con_type string, driver string, dataSource string, maxIdle if driver == "sqlite3" { dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.SqliteDialect{}} } else if driver == "mysql" { - dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"}} + dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8MB4"}} } else if driver == "postgres" { dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.PostgresDialect{}} } else { @@ -122,62 +129,9 @@ func setupConnection(con_type string, driver string, dataSource string, maxIdle return dbmap } -func (ss SqlStore) CreateColumnIfNotExists(tableName string, columnName string, colType string, defaultValue string) bool { - - var count int64 - var err error - - if utils.Cfg.SqlSettings.DriverName == "postgres" { - - count, err = ss.GetMaster().SelectInt( - `SELECT - COUNT(0) AS column_exists - FROM - information_schema.COLUMNS - WHERE - TABLE_NAME = $1 - AND COLUMN_NAME = $2`, - tableName, - columnName, - ) - } else if utils.Cfg.SqlSettings.DriverName == "mysql" { - count, err = ss.GetMaster().SelectInt( - `SELECT - COUNT(0) AS column_exists - FROM - information_schema.COLUMNS - WHERE - TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = ? - AND COLUMN_NAME = ?`, - tableName, - columnName, - ) - } - - if err != nil { - l4g.Critical("Failed to check if column exists %v", err) - time.Sleep(time.Second) - panic("Failed to check if column exists " + err.Error()) - } - - if count > 0 { - return false - } - - _, err = ss.GetMaster().Exec("ALTER TABLE " + tableName + " ADD " + columnName + " " + colType + " DEFAULT '" + defaultValue + "'") - if err != nil { - l4g.Critical("Failed to create column %v", err) - time.Sleep(time.Second) - panic("Failed to create column " + err.Error()) - } - - return true -} - -func (ss SqlStore) RemoveColumnIfExists(tableName string, columnName string) bool { +func (ss SqlStore) DoesColumnExist(tableName string, columnName string) bool { count, err := ss.GetMaster().SelectInt( - `SELECT + `SELECT COUNT(0) AS column_exists FROM information_schema.COLUMNS @@ -194,11 +148,30 @@ func (ss SqlStore) RemoveColumnIfExists(tableName string, columnName string) boo panic("Failed to check if column exists " + err.Error()) } - if count == 0 { + return count > 0 +} + +func (ss SqlStore) CreateColumnIfNotExists(tableName string, columnName string, afterName string, colType string, defaultValue string) bool { + if ss.DoesColumnExist(tableName, columnName) { return false } - _, err = ss.GetMaster().Exec("ALTER TABLE " + tableName + " DROP COLUMN " + columnName) + _, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " ADD " + columnName + " " + colType + " DEFAULT '" + defaultValue + "'" + " AFTER " + afterName) + if err != nil { + l4g.Critical("Failed to create column %v", err) + time.Sleep(time.Second) + panic("Failed to create column " + err.Error()) + } + + return true +} + +func (ss SqlStore) RemoveColumnIfExists(tableName string, columnName string) bool { + if !ss.DoesColumnExist(tableName, columnName) { + return false + } + + _, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " DROP COLUMN " + columnName) if err != nil { l4g.Critical("Failed to drop column %v", err) time.Sleep(time.Second) @@ -208,6 +181,25 @@ func (ss SqlStore) RemoveColumnIfExists(tableName string, columnName string) boo return true } +func (ss SqlStore) RenameColumnIfExists(tableName string, oldColumnName string, newColumnName string, colType string) bool { + if !ss.DoesColumnExist(tableName, oldColumnName) { + return false + } + + _, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " CHANGE " + oldColumnName + " " + newColumnName + " " + colType) + + // when we eventually support PostgreSQL, we can use the following instead + //_, err := ss.GetMaster().Exec("ALTER TABLE " + tableName + " RENAME COLUMN " + oldColumnName + " TO " + newColumnName) + + if err != nil { + l4g.Critical("Failed to rename column %v", err) + time.Sleep(time.Second) + panic("Failed to drop column " + err.Error()) + } + + return true +} + func (ss SqlStore) CreateIndexIfNotExists(indexName string, tableName string, columnName string) { ss.createIndexIfNotExists(indexName, tableName, columnName, false) } @@ -217,55 +209,27 @@ func (ss SqlStore) CreateFullTextIndexIfNotExists(indexName string, tableName st } func (ss SqlStore) createIndexIfNotExists(indexName string, tableName string, columnName string, fullText bool) { - - if utils.Cfg.SqlSettings.DriverName == "postgres" { - _, err := ss.GetMaster().SelectStr("SELECT to_regclass($1)", indexName) - // It should fail if the index does not exist - if err == nil { - return - } - - query := "" - if fullText { - query = "CREATE INDEX " + indexName + " ON " + tableName + " USING gin(to_tsvector('english', " + columnName + "))" - } else { - query = "CREATE INDEX " + indexName + " ON " + tableName + " (" + columnName + ")" - } - - _, err = ss.GetMaster().Exec(query) - if err != nil { - l4g.Critical("Failed to create index %v", err) - time.Sleep(time.Second) - panic("Failed to create index " + err.Error()) - } - } else if utils.Cfg.SqlSettings.DriverName == "mysql" { - - count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName) - if err != nil { - l4g.Critical("Failed to check index %v", err) - time.Sleep(time.Second) - panic("Failed to check index " + err.Error()) - } - - if count > 0 { - return - } - - fullTextIndex := "" - if fullText { - fullTextIndex = " FULLTEXT " - } - - _, err = ss.GetMaster().Exec("CREATE " + fullTextIndex + " INDEX " + indexName + " ON " + tableName + " (" + columnName + ")") - if err != nil { - l4g.Critical("Failed to create index %v", err) - time.Sleep(time.Second) - panic("Failed to create index " + err.Error()) - } - } else { - l4g.Critical("Failed to create index because of missing driver") + count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName) + if err != nil { + l4g.Critical("Failed to check index", err) time.Sleep(time.Second) - panic("Failed to create index because of missing driver") + panic("Failed to check index" + err.Error()) + } + + if count > 0 { + return + } + + fullTextIndex := "" + if fullText { + fullTextIndex = " FULLTEXT " + } + + _, err = ss.GetMaster().Exec("CREATE " + fullTextIndex + " INDEX " + indexName + " ON " + tableName + " (" + columnName + ")") + if err != nil { + l4g.Critical("Failed to create index", err) + time.Sleep(time.Second) + panic("Failed to create index " + err.Error()) } } diff --git a/store/sql_team_store.go b/store/sql_team_store.go index 193d0a62c7..73d6031519 100644 --- a/store/sql_team_store.go +++ b/store/sql_team_store.go @@ -17,8 +17,8 @@ func NewSqlTeamStore(sqlStore *SqlStore) TeamStore { for _, db := range sqlStore.GetAllConns() { table := db.AddTableWithName(model.Team{}, "Teams").SetKeys(false, "Id") table.ColMap("Id").SetMaxSize(26) - table.ColMap("Name").SetMaxSize(64) - table.ColMap("Domain").SetMaxSize(64).SetUnique(true) + table.ColMap("DisplayName").SetMaxSize(64) + table.ColMap("Name").SetMaxSize(64).SetUnique(true) table.ColMap("Email").SetMaxSize(128) table.ColMap("CompanyName").SetMaxSize(64) table.ColMap("AllowedDomains").SetMaxSize(500) @@ -28,6 +28,15 @@ func NewSqlTeamStore(sqlStore *SqlStore) TeamStore { } func (s SqlTeamStore) UpgradeSchemaIfNeeded() { + defaultValue := "0" + if utils.Cfg.TeamSettings.AllowValetDefault { + defaultValue = "1" + } + s.CreateColumnIfNotExists("Teams", "AllowValet", "AllowedDomains", "tinyint(1)", defaultValue) + if !s.DoesColumnExist("Teams", "DisplayName") { + s.RenameColumnIfExists("Teams", "Name", "DisplayName", "varchar(64)") + s.RenameColumnIfExists("Teams", "Domain", "Name", "varchar(64)") + } } func (s SqlTeamStore) CreateIndexesIfNotExists() { @@ -56,7 +65,7 @@ func (s SqlTeamStore) Save(team *model.Team) StoreChannel { } if err := s.GetMaster().Insert(team); err != nil { - if IsUniqueConstraintError(err.Error(), "Domain", "teams_domain_key") { + if IsUniqueConstraintError(err.Error(), "Name", "teams_domain_key") { result.Err = model.NewAppError("SqlTeamStore.Save", "A team with that domain already exists", "id="+team.Id+", "+err.Error()) } else { result.Err = model.NewAppError("SqlTeamStore.Save", "We couldn't save the team", "id="+team.Id+", "+err.Error()) @@ -94,7 +103,7 @@ func (s SqlTeamStore) Update(team *model.Team) StoreChannel { } else { oldTeam := oldResult.(*model.Team) team.CreateAt = oldTeam.CreateAt - team.Domain = oldTeam.Domain + team.Name = oldTeam.Name if count, err := s.GetMaster().Update(team); err != nil { result.Err = model.NewAppError("SqlTeamStore.Update", "We encounted an error updating the team", "id="+team.Id+", "+err.Error()) @@ -112,14 +121,14 @@ func (s SqlTeamStore) Update(team *model.Team) StoreChannel { return storeChannel } -func (s SqlTeamStore) UpdateName(name string, teamId string) StoreChannel { +func (s SqlTeamStore) UpdateDisplayName(name string, teamId string) StoreChannel { storeChannel := make(StoreChannel) go func() { result := StoreResult{} - if _, err := s.GetMaster().Exec("UPDATE Teams SET Name = :Name WHERE Id = :Id", map[string]interface{}{"Name": name, "Id": teamId}); err != nil { + if _, err := s.GetMaster().Exec("UPDATE Teams SET DisplayName = :Name WHERE Id = :Id", map[string]interface{}{"Name": name, "Id": teamId}); err != nil { result.Err = model.NewAppError("SqlTeamStore.UpdateName", "We couldn't update the team name", "team_id="+teamId) } else { result.Data = teamId @@ -153,7 +162,7 @@ func (s SqlTeamStore) Get(id string) StoreChannel { return storeChannel } -func (s SqlTeamStore) GetByDomain(domain string) StoreChannel { +func (s SqlTeamStore) GetByName(name string) StoreChannel { storeChannel := make(StoreChannel) go func() { @@ -161,8 +170,8 @@ func (s SqlTeamStore) GetByDomain(domain string) StoreChannel { team := model.Team{} - if err := s.GetReplica().SelectOne(&team, "SELECT * FROM Teams WHERE Domain = :Domain", map[string]interface{}{"Domain": domain}); err != nil { - result.Err = model.NewAppError("SqlTeamStore.GetByDomain", "We couldn't find the existing team", "domain="+domain+", "+err.Error()) + if err := s.GetReplica().SelectOne(&team, "SELECT * FROM Teams WHERE Name = :Name", map[string]interface{}{"Name": name}); err != nil { + result.Err = model.NewAppError("SqlTeamStore.GetByName", "We couldn't find the existing team", "name="+name+", "+err.Error()) } result.Data = &team diff --git a/store/sql_team_store_test.go b/store/sql_team_store_test.go index bd1a7de2ea..1f13e466cb 100644 --- a/store/sql_team_store_test.go +++ b/store/sql_team_store_test.go @@ -13,8 +13,8 @@ func TestTeamStoreSave(t *testing.T) { Setup() o1 := model.Team{} - o1.Name = "Name" - o1.Domain = "a" + model.NewId() + "b" + o1.DisplayName = "DisplayName" + o1.Name = "a" + model.NewId() + "b" o1.Email = model.NewId() + "@nowhere.com" o1.Type = model.TEAM_OPEN @@ -36,8 +36,8 @@ func TestTeamStoreUpdate(t *testing.T) { Setup() o1 := model.Team{} - o1.Name = "Name" - o1.Domain = "a" + model.NewId() + "b" + o1.DisplayName = "DisplayName" + o1.Name = "a" + model.NewId() + "b" o1.Email = model.NewId() + "@nowhere.com" o1.Type = model.TEAM_OPEN if err := (<-store.Team().Save(&o1)).Err; err != nil { @@ -61,25 +61,25 @@ func TestTeamStoreUpdate(t *testing.T) { } } -func TestTeamStoreUpdateName(t *testing.T) { +func TestTeamStoreUpdateDisplayName(t *testing.T) { Setup() o1 := &model.Team{} - o1.Name = "Name" - o1.Domain = "a" + model.NewId() + "b" + o1.DisplayName = "Display Name" + o1.Name = "a" + model.NewId() + "b" o1.Email = model.NewId() + "@nowhere.com" o1.Type = model.TEAM_OPEN o1 = (<-store.Team().Save(o1)).Data.(*model.Team) - newName := "NewName" + newDisplayName := "NewDisplayName" - if err := (<-store.Team().UpdateName(newName, o1.Id)).Err; err != nil { + if err := (<-store.Team().UpdateDisplayName(newDisplayName, o1.Id)).Err; err != nil { t.Fatal(err) } ro1 := (<-store.Team().Get(o1.Id)).Data.(*model.Team) - if ro1.Name != newName { - t.Fatal("Name not updated") + if ro1.DisplayName != newDisplayName { + t.Fatal("DisplayName not updated") } } @@ -87,8 +87,8 @@ func TestTeamStoreGet(t *testing.T) { Setup() o1 := model.Team{} - o1.Name = "Name" - o1.Domain = "a" + model.NewId() + "b" + o1.DisplayName = "DisplayName" + o1.Name = "a" + model.NewId() + "b" o1.Email = model.NewId() + "@nowhere.com" o1.Type = model.TEAM_OPEN Must(store.Team().Save(&o1)) @@ -106,12 +106,12 @@ func TestTeamStoreGet(t *testing.T) { } } -func TestTeamStoreGetByDomain(t *testing.T) { +func TestTeamStoreGetByName(t *testing.T) { Setup() o1 := model.Team{} - o1.Name = "Name" - o1.Domain = "a" + model.NewId() + "b" + o1.DisplayName = "DisplayName" + o1.Name = "a" + model.NewId() + "b" o1.Email = model.NewId() + "@nowhere.com" o1.Type = model.TEAM_OPEN @@ -119,7 +119,7 @@ func TestTeamStoreGetByDomain(t *testing.T) { t.Fatal(err) } - if r1 := <-store.Team().GetByDomain(o1.Domain); r1.Err != nil { + if r1 := <-store.Team().GetByName(o1.Name); r1.Err != nil { t.Fatal(r1.Err) } else { if r1.Data.(*model.Team).ToJson() != o1.ToJson() { @@ -127,7 +127,7 @@ func TestTeamStoreGetByDomain(t *testing.T) { } } - if err := (<-store.Team().GetByDomain("")).Err; err == nil { + if err := (<-store.Team().GetByName("")).Err; err == nil { t.Fatal("Missing id should have failed") } } @@ -136,8 +136,8 @@ func TestTeamStoreGetForEmail(t *testing.T) { Setup() o1 := model.Team{} - o1.Name = "Name" - o1.Domain = "a" + model.NewId() + "b" + o1.DisplayName = "DisplayName" + o1.Name = "a" + model.NewId() + "b" o1.Email = model.NewId() + "@nowhere.com" o1.Type = model.TEAM_OPEN Must(store.Team().Save(&o1)) diff --git a/store/sql_user_store.go b/store/sql_user_store.go index 1eb20734b9..5feef5e69f 100644 --- a/store/sql_user_store.go +++ b/store/sql_user_store.go @@ -24,7 +24,9 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore { table.ColMap("Password").SetMaxSize(128) table.ColMap("AuthData").SetMaxSize(128) table.ColMap("Email").SetMaxSize(128) - table.ColMap("FullName").SetMaxSize(64) + table.ColMap("Nickname").SetMaxSize(64) + table.ColMap("FirstName").SetMaxSize(64) + table.ColMap("LastName").SetMaxSize(64) table.ColMap("Roles").SetMaxSize(64) table.ColMap("Props").SetMaxSize(4000) table.ColMap("NotifyProps").SetMaxSize(2000) @@ -35,9 +37,29 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore { return us } -func (s SqlUserStore) UpgradeSchemaIfNeeded() { +func (us SqlUserStore) UpgradeSchemaIfNeeded() { + us.CreateColumnIfNotExists("Users", "LastPictureUpdate", "LastPasswordUpdate", "bigint(20)", "0") + + // migrating the FullName column to Nickname and adding the FirstName and LastName columns for MM-825 + if us.RenameColumnIfExists("Users", "FullName", "Nickname", "varchar(64)") { + us.CreateColumnIfNotExists("Users", "FirstName", "Nickname", "varchar(64)", "") + us.CreateColumnIfNotExists("Users", "LastName", "FirstName", "varchar(64)", "") + + // infer values of first and last name by splitting the previous full name + if _, err := us.GetMaster().Exec("UPDATE Users SET FirstName = SUBSTRING_INDEX(SUBSTRING_INDEX(Nickname, ' ', 1), ' ', -1)"); err != nil { + panic("Failed to set first name from nickname " + err.Error()) + } + + // only set the last name from full names that are comprised of multiple words (ie that have at least one space in them) + if _, err := us.GetMaster().Exec("Update Users SET LastName = SUBSTRING(Nickname, INSTR(Nickname, ' ') + 1) " + + "WHERE CHAR_LENGTH(REPLACE(Nickname, ' ', '')) < CHAR_LENGTH(Nickname)"); err != nil { + panic("Failed to set last name from nickname " + err.Error()) + } + } } +//func (ss SqlStore) CreateColumnIfNotExists(tableName string, columnName string, afterName string, colType string, defaultValue string) bool { + func (us SqlUserStore) CreateIndexesIfNotExists() { us.CreateIndexIfNotExists("idx_users_team_id", "Users", "TeamId") us.CreateIndexIfNotExists("idx_users_email", "Users", "Email") @@ -120,6 +142,7 @@ func (us SqlUserStore) Update(user *model.User, allowActiveUpdate bool) StoreCha user.AuthData = oldUser.AuthData user.Password = oldUser.Password user.LastPasswordUpdate = oldUser.LastPasswordUpdate + user.LastPictureUpdate = oldUser.LastPictureUpdate user.TeamId = oldUser.TeamId user.LastActivityAt = oldUser.LastActivityAt user.LastPingAt = oldUser.LastPingAt @@ -150,6 +173,27 @@ func (us SqlUserStore) Update(user *model.User, allowActiveUpdate bool) StoreCha return storeChannel } +func (us SqlUserStore) UpdateLastPictureUpdate(userId string) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + curTime := model.GetMillis() + + if _, err := us.GetMaster().Exec("UPDATE Users SET LastPictureUpdate = ?, UpdateAt = ? WHERE Id = ?", curTime, curTime, userId); err != nil { + result.Err = model.NewAppError("SqlUserStore.UpdateUpdateAt", "We couldn't update the update_at", "user_id="+userId) + } else { + result.Data = userId + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} + func (us SqlUserStore) UpdateLastPingAt(userId string, time int64) StoreChannel { storeChannel := make(StoreChannel) diff --git a/store/store.go b/store/store.go index 070ee05622..5b0e13fce6 100644 --- a/store/store.go +++ b/store/store.go @@ -36,9 +36,9 @@ type Store interface { type TeamStore interface { Save(team *model.Team) StoreChannel Update(team *model.Team) StoreChannel - UpdateName(name string, teamId string) StoreChannel + UpdateDisplayName(name string, teamId string) StoreChannel Get(id string) StoreChannel - GetByDomain(domain string) StoreChannel + GetByName(name string) StoreChannel GetTeamsForEmail(domain string) StoreChannel } @@ -77,6 +77,7 @@ type PostStore interface { type UserStore interface { Save(user *model.User) StoreChannel Update(user *model.User, allowRoleUpdate bool) StoreChannel + UpdateLastPictureUpdate(userId string) StoreChannel UpdateLastPingAt(userId string, time int64) StoreChannel UpdateLastActivityAt(userId string, time int64) StoreChannel UpdateUserAndSessionActivity(userId string, sessionId string, time int64) StoreChannel diff --git a/utils/config.go b/utils/config.go index eb2ae30505..efa4b263a9 100644 --- a/utils/config.go +++ b/utils/config.go @@ -19,13 +19,11 @@ const ( type ServiceSettings struct { SiteName string - Domain string Mode string AllowTesting bool UseSSL bool Port string Version string - Shards map[string]string InviteSalt string PublicLinkSalt string ResetSalt string @@ -42,11 +40,6 @@ type SqlSettings struct { AtRestEncryptKey string } -type RedisSettings struct { - DataSource string - MaxOpenConns int -} - type LogSettings struct { ConsoleEnable bool ConsoleLevel string @@ -57,14 +50,10 @@ type LogSettings struct { } type AWSSettings struct { - S3AccessKeyId string - S3SecretAccessKey string - S3Bucket string - S3Region string - Route53AccessKeyId string - Route53SecretAccessKey string - Route53ZoneId string - Route53Region string + S3AccessKeyId string + S3SecretAccessKey string + S3Bucket string + S3Region string } type ImageSettings struct { @@ -77,6 +66,7 @@ type ImageSettings struct { } type EmailSettings struct { + ByPassEmail bool SMTPUsername string SMTPPassword string SMTPServer string @@ -112,7 +102,6 @@ type Config struct { LogSettings LogSettings ServiceSettings ServiceSettings SqlSettings SqlSettings - RedisSettings RedisSettings AWSSettings AWSSettings ImageSettings ImageSettings EmailSettings EmailSettings @@ -218,18 +207,10 @@ func LoadConfig(fileName string) { panic("Error decoding configuration " + err.Error()) } - // Grabs the domain from enviroment variable if not in configuration - if config.ServiceSettings.Domain == "" { - config.ServiceSettings.Domain = os.Getenv("MATTERMOST_DOMAIN") - // If the enviroment variable is not set, use a default - if config.ServiceSettings.Domain == "" { - config.ServiceSettings.Domain = "localhost" - } - } - // Check for a valid email for feedback, if not then do feedback@domain if _, err := mail.ParseAddress(config.EmailSettings.FeedbackEmail); err != nil { - config.EmailSettings.FeedbackEmail = "feedback@" + config.ServiceSettings.Domain + config.EmailSettings.FeedbackEmail = "feedback@localhost" + l4g.Error("Misconfigured feedback email setting: %s", config.EmailSettings.FeedbackEmail) } configureLog(config.LogSettings) diff --git a/utils/config_test.go b/utils/config_test.go index 9067dc6478..4d37b4e88f 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -4,24 +4,9 @@ package utils import ( - "os" "testing" ) func TestConfig(t *testing.T) { LoadConfig("config.json") } - -func TestEnvOverride(t *testing.T) { - os.Setenv("MATTERMOST_DOMAIN", "testdomain.com") - - LoadConfig("config_docker.json") - if Cfg.ServiceSettings.Domain != "testdomain.com" { - t.Fail() - } - - LoadConfig("config.json") - if Cfg.ServiceSettings.Domain == "testdomain.com" { - t.Fail() - } -} diff --git a/utils/mail.go b/utils/mail.go index 3cd37ffef8..0fe7042b76 100644 --- a/utils/mail.go +++ b/utils/mail.go @@ -8,14 +8,14 @@ import ( "crypto/tls" "fmt" "github.com/mattermost/platform/model" + "html" "net" "net/mail" "net/smtp" - "html" ) func CheckMailSettings() *model.AppError { - if len(Cfg.EmailSettings.SMTPServer) == 0 { + if len(Cfg.EmailSettings.SMTPServer) == 0 || Cfg.EmailSettings.ByPassEmail { return model.NewAppError("CheckMailSettings", "No email settings present, mail will not be sent", "") } conn, err := connectToSMTPServer() @@ -79,6 +79,10 @@ func newSMTPClient(conn net.Conn) (*smtp.Client, *model.AppError) { func SendMail(to, subject, body string) *model.AppError { + if len(Cfg.EmailSettings.SMTPServer) == 0 || Cfg.EmailSettings.ByPassEmail { + return nil + } + fromMail := mail.Address{"", Cfg.EmailSettings.FeedbackEmail} toMail := mail.Address{"", to} @@ -95,11 +99,6 @@ func SendMail(to, subject, body string) *model.AppError { } message += "\r\n" + body + "" - if len(Cfg.EmailSettings.SMTPServer) == 0 { - l4g.Warn("Skipping sending of email because EmailSettings are not configured") - return nil - } - conn, err1 := connectToSMTPServer() if err1 != nil { return err1 diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index 68de80228a..30435dc08c 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. + var ChannelStore = require('../stores/channel_store.jsx'); var UserStore = require('../stores/user_store.jsx'); var PostStore = require('../stores/post_store.jsx'); @@ -16,7 +17,7 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -var ExtraMembers = React.createClass({ +var PopoverListMembers = React.createClass({ componentDidMount: function() { var originalLeave = $.fn.popover.Constructor.prototype.leave; $.fn.popover.Constructor.prototype.leave = function(obj) { @@ -35,30 +36,29 @@ var ExtraMembers = React.createClass({ $("#member_popover").popover({placement : 'bottom', trigger: 'click', html: true}); $('body').on('click', function (e) { - if ($(e.target.parentNode.parentNode)[0] !== $("#member_popover")[0] && $(e.target).parents('.popover.in').length === 0) { + if ($(e.target.parentNode.parentNode)[0] !== $("#member_popover")[0] && $(e.target).parents('.popover.in').length === 0) { $("#member_popover").popover('hide'); } }); - }, + render: function() { - var count = this.props.members.length == 0 ? "-" : this.props.members.length; - count = this.props.members.length > 19 ? "20+" : count; - var data_content = ""; - var sortedMembers = this.props.members; + var popoverHtml = ''; + var members = this.props.members; + var count = (members.length > 20) ? "20+" : (members.length || '-'); - if(sortedMembers) { - sortedMembers.sort(function(a,b) { + if (members) { + members.sort(function(a,b) { return a.username.localeCompare(b.username); - }) + }); - sortedMembers.forEach(function(m) { - data_content += "

" + m.username + "
"; + members.forEach(function(m) { + popoverHtml += "
" + m.username + "
"; }); } return ( -
+
{count}
@@ -78,6 +78,7 @@ function getStateFromStores() { } module.exports = React.createClass({ + displayName: 'ChannelHeader', componentDidMount: function() { ChannelStore.addChangeListener(this._onChange); ChannelStore.addExtraInfoChangeListener(this._onChange); @@ -99,7 +100,7 @@ module.exports = React.createClass({ $(".channel-header__info .description").popover({placement : 'bottom', trigger: 'hover', html: true, delay: {show: 500, hide: 500}}); }, _onSocketChange: function(msg) { - if(msg.action === "new_user") { + if (msg.action === "new_user") { AsyncClient.getChannelExtraInfo(true); } }, @@ -107,15 +108,14 @@ module.exports = React.createClass({ return getStateFromStores(); }, handleLeave: function(e) { - var self = this; Client.leaveChannel(this.state.channel.id, function(data) { var townsquare = ChannelStore.getByName('town-square'); utils.switchChannel(townsquare); - }.bind(this), + }, function(err) { AsyncClient.dispatchError(err, "handleLeave"); - }.bind(this) + } ); }, searchMentions: function(e) { @@ -131,52 +131,29 @@ module.exports = React.createClass({ AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_SEARCH_TERM, term: terms, - do_search: false + do_search: true, + is_mention_search: true }); - - Client.search( - terms, - function(data) { - AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_SEARCH, - results: data, - is_mention_search: true - }); - }, - function(err) { - dispatchError(err, "search"); - } - ); }, + render: function() { if (this.state.channel == null) { - return ( -
- ); + return null; } - var description = utils.textToJsx(this.state.channel.description, {"singleline": true, "noMentionHighlight": true}); - var popoverContent = React.renderToString(); - var channelTitle = ""; - var channelName = this.state.channel.name; + var channel = this.state.channel; + var description = utils.textToJsx(channel.description, {"singleline": true, "noMentionHighlight": true}); + var popoverContent = React.renderToString(); + var channelTitle = channel.display_name; var currentId = UserStore.getCurrentId(); var isAdmin = this.state.memberChannel.roles.indexOf("admin") > -1 || this.state.memberTeam.roles.indexOf("admin") > -1; - var searchForm =
-
+
- - -
@@ -237,5 +216,3 @@ module.exports = React.createClass({ ); } }); - - diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 9e3feb25c2..6ed0f0b349 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -43,7 +43,7 @@ module.exports = React.createClass({ client.createPost(post, ChannelStore.getCurrent(), function(data) { - this.setState({ messageText: '', submitting: false, post_error: null }); + this.setState({ messageText: '', submitting: false, post_error: null, server_error: null }); this.clearPreviews(); AsyncClient.getPosts(true, this.props.channelId); @@ -57,6 +57,7 @@ module.exports = React.createClass({ function(err) { var state = {}; state.server_error = err.message; + state.submitting = false; if (err.message === "Invalid RootId parameter") { if ($('#post_deleted').length > 0) $('#post_deleted').modal('show'); diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 0c23dcfac0..d38a6798f9 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -234,7 +234,7 @@ module.exports = React.createClass({ }, render: function() { - var server_error = this.state.server_error ?
: null; + var server_error = this.state.server_error ?
: null; var post_error = this.state.post_error ? : null; var limit_error = this.state.limit_error ?
: null; diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx index a8c6907893..e23a377402 100644 --- a/web/react/components/delete_channel_modal.jsx +++ b/web/react/components/delete_channel_modal.jsx @@ -12,7 +12,7 @@ module.exports = React.createClass({ Client.deleteChannel(this.state.channel_id, function(data) { AsyncClient.getChannels(true); - window.location.href = '/channels/town-square'; + window.location.href = '/'; }.bind(this), function(err) { AsyncClient.dispatchError(err, "handleDelete"); diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx index fefac12d72..11970bc2b3 100644 --- a/web/react/components/delete_post_modal.jsx +++ b/web/react/components/delete_post_modal.jsx @@ -56,13 +56,13 @@ module.exports = React.createClass({ $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { var newState = {}; if(BrowserStore.getItem('edit_state_transfer')) { - newState = JSON.parse(BrowserStore.getItem('edit_state_transfer')); + newState = BrowserStore.getItem('edit_state_transfer'); BrowserStore.removeItem('edit_state_transfer'); } else { var button = e.relatedTarget; newState = { title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments') }; } - self.setState(newState) + self.setState(newState); }); PostStore.addSelectedPostChangeListener(this._onChange); }, diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx index 255654fd52..c0818959ad 100644 --- a/web/react/components/edit_channel_modal.jsx +++ b/web/react/components/edit_channel_modal.jsx @@ -43,7 +43,7 @@ module.exports = React.createClass({

Edit {this.state.title} Description

- +
diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx index d741e189bb..21b75bb6ee 100644 --- a/web/react/components/edit_post_modal.jsx +++ b/web/react/components/edit_post_modal.jsx @@ -4,6 +4,7 @@ var Client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var Textbox = require('./textbox.jsx'); +var BrowserStore = require('../stores/browser_store.jsx'); module.exports = React.createClass({ handleEdit: function(e) { @@ -13,14 +14,14 @@ module.exports = React.createClass({ if (updatedPost.message.length === 0) { var tempState = this.state; delete tempState.editText; - BrowserStore.setItem('edit_state_transfer', JSON.stringify(tempState)); + BrowserStore.setItem('edit_state_transfer', tempState); $("#edit_post").modal('hide'); $("#delete_post").modal('show'); return; } - updatedPost.id = this.state.post_id - updatedPost.channel_id = this.state.channel_id + updatedPost.id = this.state.post_id; + updatedPost.channel_id = this.state.channel_id; Client.updatePost(updatedPost, function(data) { diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx index bbfdce63a6..af5314e648 100644 --- a/web/react/components/get_link_modal.jsx +++ b/web/react/components/get_link_modal.jsx @@ -38,7 +38,7 @@ module.exports = React.createClass({

{"The link below is used for open " + strings.TeamPlural + " or if you allowed your " + strings.Team + " members to sign up using their " + strings.Company + " email addresses."}

- +
diff --git a/web/react/components/loading_screen.jsx b/web/react/components/loading_screen.jsx new file mode 100644 index 0000000000..5905e519b8 --- /dev/null +++ b/web/react/components/loading_screen.jsx @@ -0,0 +1,24 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +module.exports = React.createClass({ + displayName: "LoadingScreen", + propTypes: { + position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit']) + }, + getDefaultProps: function() { + return { position: 'relative' }; + }, + render: function() { + return ( +
+
+

Loading

+
+
+
+
+
+ ); + } +}); diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index 74c7d40659..71fefff5b7 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -1,102 +1,21 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. - - - var utils = require('../utils/utils.jsx'); var client = require('../utils/client.jsx'); var UserStore = require('../stores/user_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); - - -var FindTeamDomain = React.createClass({ - handleSubmit: function(e) { - e.preventDefault(); - var state = { } - - var domain = this.refs.domain.getDOMNode().value.trim(); - if (!domain) { - state.server_error = "A domain is required" - this.setState(state); - return; - } - - if (!BrowserStore.isLocalStorageSupported()) { - state.server_error = "This service requires local storage to be enabled. Please enable it or exit private browsing."; - this.setState(state); - return; - } - - state.server_error = ""; - this.setState(state); - - client.findTeamByDomain(domain, - function(data) { - console.log(data); - if (data) { - window.location.href = window.location.protocol + "//" + domain + "." + utils.getDomainWithOutSub(); - } - else { - this.state.server_error = "We couldn't find your " + strings.Team + "."; - this.setState(this.state); - } - }.bind(this), - function(err) { - this.state.server_error = err.message; - this.setState(this.state); - }.bind(this) - ); - }, - getInitialState: function() { - return { }; - }, - render: function() { - var server_error = this.state.server_error ? : null; - - return ( -
-
- { config.SiteName } -
- Enter your {strings.Team}'s domain. -
-
-
-
-
- { server_error } - -
-
- -
-
- Don't remember your {strings.Team}'s domain? Find it here -
-
-
-
-
-
-
-
- {"Want to create your own " + strings.Team + "?"} Sign up now -
-
-
- ); - } -}); +var Constants = require('../utils/constants.jsx'); module.exports = React.createClass({ handleSubmit: function(e) { e.preventDefault(); var state = { } - var domain = this.refs.domain.getDOMNode().value.trim(); - if (!domain) { - state.server_error = "A domain is required" + var name = this.props.teamName + if (!name) { + state.server_error = "Bad team name" this.setState(state); return; } @@ -124,23 +43,22 @@ module.exports = React.createClass({ state.server_error = ""; this.setState(state); - client.loginByEmail(domain, email, password, + client.loginByEmail(name, email, password, function(data) { - UserStore.setLastDomain(domain); - UserStore.setLastEmail(email); UserStore.setCurrentUser(data); + UserStore.setLastEmail(email); var redirect = utils.getUrlParameter("redirect"); if (redirect) { - window.location.href = decodeURI(redirect); + window.location.pathname = decodeURI(redirect); } else { - window.location.href = '/channels/town-square'; + window.location.pathname = '/' + name + '/channels/town-square'; } }.bind(this), function(err) { if (err.message == "Login failed because email address has not been verified") { - window.location.href = '/verify?domain=' + encodeURIComponent(domain) + '&email=' + encodeURIComponent(email); + window.location.href = '/verify_email?name=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email); return; } state.server_error = err.message; @@ -161,35 +79,35 @@ module.exports = React.createClass({ priorEmail = decodeURIComponent(emailParam); } - var subDomainClass = "form-control hidden"; - var subDomain = utils.getSubDomain(); + var teamDisplayName = this.props.teamDisplayName; + var teamName = this.props.teamName; - if (utils.isTestDomain()) { - subDomainClass = "form-control"; - subDomain = UserStore.getLastDomain(); - } else if (subDomain == "") { - return (); + var focusEmail = false; + var focusPassword = false; + if (priorEmail != "") { + focusPassword = true; + } else { + focusEmail = true; } return (
- { subDomain } + { teamDisplayName }
- { utils.getDomainWithOutSub() } + /{ teamName }/

{ server_error } -
- +
- +
@@ -198,10 +116,10 @@ module.exports = React.createClass({ {"Find other " + strings.TeamPlural}
- {"Want to create your own " + strings.Team + "?"} Sign up now + {"Want to create your own " + strings.Team + "?"} Sign up now
diff --git a/web/react/components/member_list_item.jsx b/web/react/components/member_list_item.jsx index 357fd49a87..a5279909b1 100644 --- a/web/react/components/member_list_item.jsx +++ b/web/react/components/member_list_item.jsx @@ -23,6 +23,7 @@ module.exports = React.createClass({ var member = this.props.member; var isAdmin = this.props.isAdmin; var isMemberAdmin = member.roles.indexOf("admin") > -1; + var timestamp = UserStore.getCurrentUser().update_at; var invite; if (member.invited && this.props.handleInvite) { @@ -48,12 +49,12 @@ module.exports = React.createClass({
); } else { - invite =
{member.roles || 'Member'}
; + invite =
{member.roles || 'Member'}
; } return (
- + {member.username} {member.email} { invite } diff --git a/web/react/components/member_list_team.jsx b/web/react/components/member_list_team.jsx index cfb473e5ee..cb48e5cc59 100644 --- a/web/react/components/member_list_team.jsx +++ b/web/react/components/member_list_team.jsx @@ -5,6 +5,7 @@ var ChannelStore = require('../stores/channel_store.jsx'); var UserStore = require('../stores/user_store.jsx'); var Client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); +var utils = require('../utils/utils.jsx'); var MemberListTeamItem = React.createClass({ handleMakeMember: function() { @@ -59,9 +60,10 @@ var MemberListTeamItem = React.createClass({ return {}; }, render: function() { - var server_error = this.state.server_error ?
: null; + var server_error = this.state.server_error ?
: null; var user = this.props.user; - var currentRoles = "Member" + var currentRoles = "Member"; + var timestamp = UserStore.getCurrentUser().update_at; if (user.roles.length > 0) { currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1); @@ -83,9 +85,9 @@ var MemberListTeamItem = React.createClass({ return (
- - {user.full_name.trim() ? user.full_name : user.username} - {user.full_name.trim() ? user.username : email} + + {utils.getDisplayName(user)} + {email}
{currentRoles} diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx index 3c33ddf493..114dc183f7 100644 --- a/web/react/components/mention.jsx +++ b/web/react/components/mention.jsx @@ -1,19 +1,27 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. +var UserStore = require("../stores/user_store.jsx"); module.exports = React.createClass({ handleClick: function() { this.props.handleClick(this.props.username); }, + getInitialState: function() { + return null; + }, render: function() { + var self = this; var icon; - if (this.props.id != null) { - icon = ; + var timestamp = UserStore.getCurrentUser().update_at; + if (this.props.id === "allmention" || this.props.id === "channelmention") { + icon = ; + } else if (this.props.id != null) { + icon = ; } return ( -
+
{icon}
@{this.props.username}{this.props.secondary_text}
diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx index ba2c53612d..71a6083d23 100644 --- a/web/react/components/mention_list.jsx +++ b/web/react/components/mention_list.jsx @@ -7,19 +7,48 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var Mention = require('./mention.jsx'); var Constants = require('../utils/constants.jsx'); +var Utils = require('../utils/utils.jsx'); var ActionTypes = Constants.ActionTypes; +var MAX_HEIGHT_LIST = 292; +var MAX_ITEMS_IN_LIST = 25; +var ITEM_HEIGHT = 36; + module.exports = React.createClass({ + displayName: "MentionList", componentDidMount: function() { PostStore.addMentionDataChangeListener(this._onChange); - var self = this; - $('body').on('keypress.mentionlist', '#'+this.props.id, + + $('body').on('keydown.mentionlist', '#'+this.props.id, function(e) { - if (!self.isEmpty() && self.state.mentionText != '-1' && e.which === 13) { + if (!self.isEmpty() && self.state.mentionText != '-1' && (e.which === 13 || e.which === 9)) { e.stopPropagation(); e.preventDefault(); - self.addFirstMention(); + self.addCurrentMention(); + } + else if (!self.isEmpty() && self.state.mentionText != '-1' && (e.which === 38 || e.which === 40)) { + e.stopPropagation(); + e.preventDefault(); + + var tempSelectedMention = -1; + if (e.which === 38) { + if (self.getSelection(self.state.selectedMention - 1)) + self.setState({ selectedMention: self.state.selectedMention - 1, selectedUsername: self.refs['mention' + (self.state.selectedMention - 1)].props.username }); + else { + while (self.getSelection(++tempSelectedMention)) + ; //Need to find the top of the list + self.setState({ selectedMention: tempSelectedMention - 1, selectedUsername: self.refs['mention' + (tempSelectedMention - 1)].props.username }); + } + } + else if (e.which === 40) { + if (self.getSelection(self.state.selectedMention + 1)) + self.setState({ selectedMention: self.state.selectedMention + 1, selectedUsername: self.refs['mention' + (self.state.selectedMention + 1)].props.username }); + else + self.setState({ selectedMention: 0, selectedUsername: self.refs.mention0.props.username }); + } + + self.scrollToMention(e.which, tempSelectedMention); } } ); @@ -32,7 +61,28 @@ module.exports = React.createClass({ }, componentWillUnmount: function() { PostStore.removeMentionDataChangeListener(this._onChange); - $('body').off('keypress.mentionlist', '#'+this.props.id); + $('body').off('keydown.mentionlist', '#'+this.props.id); + }, + componentDidUpdate: function() { + if (this.state.mentionText != "-1") { + if (this.state.selectedUsername !== "" && (!this.getSelection(this.state.selectedMention) || this.state.selectedUsername !== this.refs['mention' + this.state.selectedMention].props.username)) { + var tempSelectedMention = -1; + var foundMatch = false; + while (tempSelectedMention < this.state.selectedMention && this.getSelection(++tempSelectedMention)) { + if (this.state.selectedUsername === this.refs['mention' + tempSelectedMention].props.username) { + this.setState({ selectedMention: tempSelectedMention }); + foundMatch = true; + break; + } + } + if (this.getSelection(0) && !foundMatch) { + this.setState({ selectedMention: 0, selectedUsername: this.refs.mention0.props.username }); + } + } + } + else if (this.state.selectedMention !== 0) { + this.setState({ selectedMention: 0, selectedUsername: "" }); + } }, _onChange: function(id, mentionText, excludeList) { if (id !== this.props.id) return; @@ -40,6 +90,7 @@ module.exports = React.createClass({ var newState = this.state; if (mentionText != null) newState.mentionText = mentionText; if (excludeList != null) newState.excludeUsers = excludeList; + this.setState(newState); }, handleClick: function(name) { @@ -51,6 +102,21 @@ module.exports = React.createClass({ this.setState({ mentionText: '-1' }); }, + handleMouseEnter: function(listId) { + this.setState({ selectedMention: listId, selectedUsername: this.refs['mention' + listId].props.username }); + }, + getSelection: function(listId) { + if (!this.refs['mention' + listId]) + return false; + else + return true; + }, + addCurrentMention: function() { + if (!this.getSelection(this.state.selectedMention)) + this.addFirstMention(); + else + this.refs['mention' + this.state.selectedMention].handleClick(); + }, addFirstMention: function() { if (!this.refs.mention0) return; this.refs.mention0.handleClick(); @@ -58,6 +124,23 @@ module.exports = React.createClass({ isEmpty: function() { return (!this.refs.mention0); }, + scrollToMention: function(keyPressed, ifLoopUp) { + var direction = keyPressed === 38 ? "up" : "down"; + var scrollAmount = 0; + + if (direction === "up" && ifLoopUp !== -1) + scrollAmount = $("#mentionsbox").height() * 100; //Makes sure that it scrolls all the way to the bottom + else if (direction === "down" && this.state.selectedMention === 0) + scrollAmount = 0; + else if (direction === "up") + scrollAmount = "-=" + ($('#'+this.refs['mention' + this.state.selectedMention].props.id +"_mentions").innerHeight() - 5); + else if (direction === "down") + scrollAmount = "+=" + ($('#'+this.refs['mention' + this.state.selectedMention].props.id +"_mentions").innerHeight() - 5); + + $("#mentionsbox").animate({ + scrollTop: scrollAmount + }, 75); + }, alreadyMentioned: function(username) { var excludeUsers = this.state.excludeUsers; for (var i = 0; i < excludeUsers.length; i++) { @@ -68,11 +151,12 @@ module.exports = React.createClass({ return false; }, getInitialState: function() { - return { excludeUsers: [], mentionText: "-1" }; + return { excludeUsers: [], mentionText: "-1", selectedMention: 0, selectedUsername: "" }; }, render: function() { + var self = this; var mentionText = this.state.mentionText; - if (mentionText === '-1') return (
); + if (mentionText === '-1') return null; var profiles = UserStore.getActiveOnlyProfiles(); var users = []; @@ -82,14 +166,16 @@ module.exports = React.createClass({ var all = {}; all.username = "all"; - all.full_name = ""; + all.nickname = ""; all.secondary_text = "Notifies everyone in the team"; + all.id = "allmention"; users.push(all); var channel = {}; channel.username = "channel"; - channel.full_name = ""; + channel.nickname = ""; channel.secondary_text = "Notifies everyone in the channel"; + channel.id = "channelmention"; users.push(channel); users.sort(function(a,b) { @@ -100,44 +186,42 @@ module.exports = React.createClass({ var mentions = {}; var index = 0; - for (var i = 0; i < users.length; i++) { - if (Object.keys(mentions).length >= 25) break; + for (var i = 0; i < users.length && index < MAX_ITEMS_IN_LIST; i++) { if (this.alreadyMentioned(users[i].username)) continue; - var firstName = "", lastName = ""; - if (users[i].full_name.length > 0) { - var splitName = users[i].full_name.split(' '); - firstName = splitName[0].toLowerCase(); - lastName = splitName.length > 1 ? splitName[splitName.length-1].toLowerCase() : ""; - users[i].secondary_text = users[i].full_name; - } - - if (firstName.lastIndexOf(mentionText,0) === 0 - || lastName.lastIndexOf(mentionText,0) === 0 || users[i].username.lastIndexOf(mentionText,0) === 0) { - mentions[i+1] = ( + if ((users[i].first_name && users[i].first_name.lastIndexOf(mentionText,0) === 0) + || (users[i].last_name && users[i].last_name.lastIndexOf(mentionText,0) === 0) || users[i].username.lastIndexOf(mentionText,0) === 0) { + mentions[index] = ( ); index++; } } + var numMentions = Object.keys(mentions).length; - if (numMentions < 1) return (
); + if (numMentions < 1) return null; - var height = (numMentions*36) + 4; - var width = $('#'+this.props.id).parent().width(); - var bottom = $(window).height() - $('#'+this.props.id).offset().top; - var left = $('#'+this.props.id).offset().left; - var max_height = $('#'+this.props.id).offset().top - 10; + var $mention_tab = $('#'+this.props.id); + var maxHeight = Math.min(MAX_HEIGHT_LIST, $mention_tab.offset().top - 10); + var style = { + height: Math.min(maxHeight, (numMentions*ITEM_HEIGHT) + 4), + width: $mention_tab.parent().width(), + bottom: $(window).height() - $mention_tab.offset().top, + left: $mention_tab.offset().left + }; return ( -
-
+
+
{ mentions }
diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx index c3ddc76f35..007476f9b9 100644 --- a/web/react/components/more_channels.jsx +++ b/web/react/components/more_channels.jsx @@ -7,6 +7,7 @@ var client = require('../utils/client.jsx'); var asyncClient = require('../utils/async_client.jsx'); var UserStore = require('../stores/user_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); +var LoadingScreen = require('./loading_screen.jsx'); function getStateFromStores() { return { @@ -16,6 +17,8 @@ function getStateFromStores() { } module.exports = React.createClass({ + displayName: "MoreChannelsModal", + componentDidMount: function() { ChannelStore.addMoreChangeListener(this._onChange); $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) { @@ -90,7 +93,7 @@ module.exports = React.createClass({

{channel.display_name}

{channel.description}

-
@@ -576,11 +567,11 @@ var AuditTab = React.createClass({ this.state.audits.map(function(value, index) { return ( - - - - - + + + + + ); }, this) @@ -626,7 +617,7 @@ var SecurityTab = React.createClass({ client.updatePassword(data, function(data) { - this.updateSection(""); + this.props.updateSection(""); AsyncClient.getMe(); this.setState({ current_password: '', new_password: '', confirm_password: '' }); }.bind(this), @@ -752,6 +743,21 @@ var GeneralTab = React.createClass({ this.submitUser(user); }, + submitNickname: function(e) { + e.preventDefault(); + + var user = UserStore.getCurrentUser(); + var nickname = this.state.nickname.trim(); + + if (user.nickname === nickname) { + this.setState({client_error: "You must submit a new nickname"}) + return; + } + + user.nickname = nickname; + + this.submitUser(user); + }, submitName: function(e) { e.preventDefault(); @@ -759,14 +765,13 @@ var GeneralTab = React.createClass({ var firstName = this.state.first_name.trim(); var lastName = this.state.last_name.trim(); - var fullName = firstName + ' ' + lastName; - - if (user.full_name === fullName) { - this.setState({client_error: "You must submit a new name"}) + if (user.first_name === firstName && user.last_name === lastName) { + this.setState({client_error: "You must submit a new first or last name"}) return; } - user.full_name = fullName; + user.first_name = firstName; + user.last_name = lastName; this.submitUser(user); }, @@ -820,6 +825,7 @@ var GeneralTab = React.createClass({ client.uploadProfileImage(formData, function(data) { this.submitActive = false; + AsyncClient.getMe(); window.location.reload(); }.bind(this), function(err) { @@ -838,6 +844,9 @@ var GeneralTab = React.createClass({ updateLastName: function(e) { this.setState({ last_name: e.target.value}); }, + updateNickname: function(e) { + this.setState({nickname: e.target.value}); + }, updateEmail: function(e) { this.setState({ email: e.target.value}); }, @@ -860,11 +869,7 @@ var GeneralTab = React.createClass({ getInitialState: function() { var user = this.props.user; - var splitStr = user.full_name.split(' '); - var firstName = splitStr.shift(); - var lastName = splitStr.join(' '); - - return { username: user.username, first_name: firstName, last_name: lastName, + return { username: user.username, first_name: user.first_name, last_name: user.last_name, nickname: user.nickname, email: user.email, picture: null }; }, render: function() { @@ -900,7 +905,7 @@ var GeneralTab = React.createClass({ nameSection = ( ); } else { + var full_name = ""; + + if (user.first_name && user.last_name) { + full_name = user.first_name + " " + user.last_name; + } else if (user.first_name) { + full_name = user.first_name; + } else if (user.last_name) { + full_name = user.last_name; + } + nameSection = ( ); } + var nicknameSection; + if (this.props.activeSection === 'nickname') { + var inputs = []; + + inputs.push( +
+ +
+ +
+
+ ); + + nicknameSection = ( + + ); + } else { + nicknameSection = ( + + ); + } + var usernameSection; if (this.props.activeSection === 'username') { var inputs = []; @@ -989,7 +1037,7 @@ var GeneralTab = React.createClass({ ); @@ -1021,6 +1073,8 @@ var GeneralTab = React.createClass({
{usernameSection}
+ {nicknameSection} +
{emailSection}
{pictureSection} diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index 38f439946b..2274f3f2e6 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -25,7 +25,11 @@ module.exports = React.createClass({ this.setState({ imgId: nextProps.startId }); }, loadImage: function(id) { - if (this.state.loaded[id] || this.state.images[id]) return; + var imgHeight = $(window).height()-100; + if (this.state.loaded[id] || this.state.images[id]){ + $('.modal .modal-image .image-wrapper img').css("max-height",imgHeight); + return; + }; var src = ""; if (this.props.imgCount > 0) { @@ -48,6 +52,7 @@ module.exports = React.createClass({ var loaded = self.state.loaded; loaded[imgid] = true; self.setState({ loaded: loaded }); + $(self.refs.image.getDOMNode()).css("max-height",imgHeight); }; }(id); var images = this.state.images; @@ -56,10 +61,8 @@ module.exports = React.createClass({ }, componentDidUpdate: function() { if (this.refs.image) { - var height = $(window).height()-100; if (this.state.loaded[this.state.imgId]) { $(this.refs.imageWrap.getDOMNode()).removeClass("default"); - $(this.refs.image.getDOMNode()).css("max-height",height); } } }, diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index 3aa9858637..f70d60e3af 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -61,17 +61,17 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann ); React.render( - , + , document.getElementById('navbar') ); React.render( - , + , document.getElementById('sidebar-left') ); React.render( - , + , document.getElementById('rename_team_modal') ); @@ -91,7 +91,7 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann ); React.render( - , + , document.getElementById('team_members_modal') ); @@ -186,7 +186,7 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann ); React.render( - , + , document.getElementById('sidebar-menu') ); diff --git a/web/react/pages/home.jsx b/web/react/pages/home.jsx index 08dd32f73a..b12fa49491 100644 --- a/web/react/pages/home.jsx +++ b/web/react/pages/home.jsx @@ -2,13 +2,14 @@ // See License.txt for license information. var ChannelStore = require('../stores/channel_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); var Constants = require('../utils/constants.jsx'); -global.window.setup_home_page = function() { +global.window.setup_home_page = function(teamURL) { var last = ChannelStore.getLastVisitedName(); if (last == null || last.length === 0) { - window.location.replace("/channels/" + Constants.DEFAULT_CHANNEL); + window.location = teamURL + "/channels/" + Constants.DEFAULT_CHANNEL; } else { - window.location.replace("/channels/" + last); + window.location = teamURL + "/channels/" + last; } } diff --git a/web/react/pages/login.jsx b/web/react/pages/login.jsx index a4e6b438eb..8348f0b5d3 100644 --- a/web/react/pages/login.jsx +++ b/web/react/pages/login.jsx @@ -3,9 +3,9 @@ var Login = require('../components/login.jsx'); -global.window.setup_login_page = function() { +global.window.setup_login_page = function(teamDisplayName, teamName) { React.render( - , + , document.getElementById('login') ); }; diff --git a/web/react/pages/password_reset.jsx b/web/react/pages/password_reset.jsx index 6d0d88a10e..c7a208973a 100644 --- a/web/react/pages/password_reset.jsx +++ b/web/react/pages/password_reset.jsx @@ -3,13 +3,13 @@ var PasswordReset = require('../components/password_reset.jsx'); -global.window.setup_password_reset_page = function(is_reset, team_name, domain, hash, data) { +global.window.setup_password_reset_page = function(is_reset, team_display_name, team_name, hash, data) { React.render( , diff --git a/web/react/pages/signup_team_complete.jsx b/web/react/pages/signup_team_complete.jsx index c17cbdfacc..346f2ab5ab 100644 --- a/web/react/pages/signup_team_complete.jsx +++ b/web/react/pages/signup_team_complete.jsx @@ -5,7 +5,7 @@ var SignupTeamComplete =require('../components/signup_team_complete.jsx'); global.window.setup_signup_team_complete_page = function(email, name, data, hash) { React.render( - , + , document.getElementById('signup-team-complete') ); -}; \ No newline at end of file +}; diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx index 82cf9a9423..4eed754cc9 100644 --- a/web/react/stores/browser_store.jsx +++ b/web/react/stores/browser_store.jsx @@ -1,85 +1,104 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var UserStore = require('../stores/user_store.jsx'); -// Also change model/utils.go ETAG_ROOT_VERSION -var BROWSER_STORE_VERSION = '.1'; - -var _initialized = false; - -function _initialize() { - var currentVersion = localStorage.getItem("local_storage_version"); - if (currentVersion !== BROWSER_STORE_VERSION) { - localStorage.clear(); - sessionStorage.clear(); - localStorage.setItem("local_storage_version", BROWSER_STORE_VERSION); - } - _initialized = true; +var UserStore; +function getPrefix() { + if (!UserStore) UserStore = require('./user_store.jsx'); + return UserStore.getCurrentId() + '_'; } -module.exports.setItem = function(name, value) { - if (!_initialized) _initialize(); - var user_id = UserStore.getCurrentId(); - localStorage.setItem(user_id + "_" + name, value); -}; +// Also change model/utils.go ETAG_ROOT_VERSION +var BROWSER_STORE_VERSION = '.4'; -module.exports.getItem = function(name) { - if (!_initialized) _initialize(); - var user_id = UserStore.getCurrentId(); - return localStorage.getItem(user_id + "_" + name); -}; +module.exports = { + _initialized: false, -module.exports.removeItem = function(name) { - if (!_initialized) _initialize(); - var user_id = UserStore.getCurrentId(); - localStorage.removeItem(user_id + "_" + name); -}; + _initialize: function() { + var currentVersion = localStorage.getItem("local_storage_version"); + if (currentVersion !== BROWSER_STORE_VERSION) { + this.clear(); + localStorage.setItem("local_storage_version", BROWSER_STORE_VERSION); + } + this._initialized = true; + }, -module.exports.setGlobalItem = function(name, value) { - if (!_initialized) _initialize(); - localStorage.setItem(name, value); -}; + getItem: function(name, defaultValue) { + return this.getGlobalItem(getPrefix() + name, defaultValue); + }, -module.exports.getGlobalItem = function(name) { - if (!_initialized) _initialize(); - return localStorage.getItem(name); -}; + setItem: function(name, value) { + this.setGlobalItem(getPrefix() + name, value); + }, -module.exports.removeGlobalItem = function(name) { - if (!_initialized) _initialize(); - localStorage.removeItem(name); -}; + removeItem: function(name) { + if (!this._initialized) this._initialize(); -module.exports.clear = function() { - localStorage.clear(); - sessionStorage.clear(); -}; + localStorage.removeItem(getPrefix() + name); + }, -// Preforms the given action on each item that has the given prefix -// Signiture for action is action(key, value) -module.exports.actionOnItemsWithPrefix = function (prefix, action) { - var user_id = UserStore.getCurrentId(); - var id_len = user_id.length; - var prefix_len = prefix.length; - for (var key in localStorage) { - if (key.substring(id_len, id_len + prefix_len) === prefix) { - var userkey = key.substring(id_len); - action(userkey, BrowserStore.getItem(key)); + setGlobalItem: function(name, value) { + if (!this._initialized) this._initialize(); + + localStorage.setItem(name, JSON.stringify(value)); + }, + + getGlobalItem: function(name, defaultValue) { + if (!this._initialized) this._initialize(); + + var result = null; + try { + result = JSON.parse(localStorage.getItem(name)); + } catch (err) {} + + if (result === null && typeof defaultValue !== 'undefined') { + result = defaultValue; + } + + return result; + }, + + removeGlobalItem: function(name) { + if (!this._initialized) this._initialize(); + + localStorage.removeItem(name); + }, + + clear: function() { + localStorage.clear(); + sessionStorage.clear(); + }, + + /** + * Preforms the given action on each item that has the given prefix + * Signiture for action is action(key, value) + */ + actionOnItemsWithPrefix: function (prefix, action) { + if (!this._initialized) this._initialize(); + + var globalPrefix = getPrefix(); + var globalPrefixiLen = globalPrefix.length; + for (var key in localStorage) { + if (key.lastIndexOf(globalPrefix + prefix, 0) === 0) { + var userkey = key.substring(globalPrefixiLen); + action(userkey, this.getGlobalItem(key)); + } + } + }, + + isLocalStorageSupported: function() { + try { + sessionStorage.setItem("testSession", '1'); + sessionStorage.removeItem("testSession"); + + localStorage.setItem("testLocal", '1'); + if (localStorage.getItem("testLocal") != '1') { + return false; + } + localStorage.removeItem("testLocal", '1'); + + return true; + } catch (e) { + return false; } } }; - -module.exports.isLocalStorageSupported = function() { - try { - sessionStorage.setItem("testSession", '1'); - sessionStorage.removeItem("testSession"); - - localStorage.setItem("testLocal", '1'); - localStorage.removeItem("testLocal", '1'); - - return true; - } - catch (e) { - return false; - } -}; diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx index 4429a5312c..a97f133912 100644 --- a/web/react/stores/channel_store.jsx +++ b/web/react/stores/channel_store.jsx @@ -16,6 +16,7 @@ var MORE_CHANGE_EVENT = 'change'; var EXTRA_INFO_EVENT = 'extra_info'; var ChannelStore = assign({}, EventEmitter.prototype, { + _current_id: null, emitChange: function() { this.emit(CHANGE_EVENT); }, @@ -43,40 +44,24 @@ var ChannelStore = assign({}, EventEmitter.prototype, { removeExtraInfoChangeListener: function(callback) { this.removeListener(EXTRA_INFO_EVENT, callback); }, - get: function(id) { - var current = null; - var c = this._getChannels(); - - c.some(function(channel) { - if (channel.id == id) { - current = channel; - return true; + findFirstBy: function(field, value) { + var channels = this._getChannels(); + for (var i = 0; i < channels.length; i++) { + if (channels[i][field] == value) { + return channels[i]; } - return false; - }); + } - return current; + return null; + }, + get: function(id) { + return this.findFirstBy('id', id); }, getMember: function(id) { - var current = null; return this.getAllMembers()[id]; }, getByName: function(name) { - var current = null; - var c = this._getChannels(); - - c.some(function(channel) { - if (channel.name == name) { - current = channel; - return true; - } - - return false; - - }); - - return current; - + return this.findFirstBy('name', name); }, getAll: function() { return this._getChannels(); @@ -88,10 +73,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, { return this._getMoreChannels(); }, setCurrentId: function(id) { - if (id == null) - BrowserStore.removeItem("current_channel_id"); - else - BrowserStore.setItem("current_channel_id", id); + this._current_id = id; }, setLastVisitedName: function(name) { if (name == null) @@ -117,12 +99,12 @@ var ChannelStore = assign({}, EventEmitter.prototype, { this._storeChannelMembers(cm); }, getCurrentId: function() { - return BrowserStore.getItem("current_channel_id"); + return this._current_id; }, getCurrent: function() { - var currentId = ChannelStore.getCurrentId(); + var currentId = this.getCurrentId(); - if (currentId != null) + if (currentId) return this.get(currentId); else return null; @@ -130,7 +112,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, { getCurrentMember: function() { var currentId = ChannelStore.getCurrentId(); - if (currentId != null) + if (currentId) return this.getAllMembers()[currentId]; else return null; @@ -145,7 +127,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, { var currentId = ChannelStore.getCurrentId(); var extra = null; - if (currentId != null) + if (currentId) extra = this._getExtraInfos()[currentId]; if (extra == null) @@ -156,7 +138,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, { getExtraInfo: function(channel_id) { var extra = null; - if (channel_id != null) + if (channel_id) extra = this._getExtraInfos()[channel_id]; if (extra == null) @@ -165,49 +147,22 @@ var ChannelStore = assign({}, EventEmitter.prototype, { return extra; }, _storeChannels: function(channels) { - BrowserStore.setItem("channels", JSON.stringify(channels)); + BrowserStore.setItem("channels", channels); }, _getChannels: function() { - var channels = []; - try { - channels = JSON.parse(BrowserStore.getItem("channels")); - } - catch (err) { - } - - if (channels == null) { - channels = []; - } - - return channels; + return BrowserStore.getItem("channels", []); }, _storeChannelMembers: function(channelMembers) { - BrowserStore.setItem("channel_members", JSON.stringify(channelMembers)); + BrowserStore.setItem("channel_members", channelMembers); }, _getChannelMembers: function() { - var members = {}; - try { - members = JSON.parse(BrowserStore.getItem("channel_members")); - } - catch (err) { - } - - if (members == null) { - members = {}; - } - - return members; + return BrowserStore.getItem("channel_members", {}); }, _storeMoreChannels: function(channels) { - BrowserStore.setItem("more_channels", JSON.stringify(channels)); + BrowserStore.setItem("more_channels", channels); }, _getMoreChannels: function() { - var channels = null; - try { - channels = JSON.parse(BrowserStore.getItem("more_channels")); - } - catch (err) { - } + var channels = BrowserStore.getItem("more_channels"); if (channels == null) { channels = {}; @@ -217,22 +172,14 @@ var ChannelStore = assign({}, EventEmitter.prototype, { return channels; }, _storeExtraInfos: function(extraInfos) { - BrowserStore.setItem("extra_infos", JSON.stringify(extraInfos)); + BrowserStore.setItem("extra_infos", extraInfos); }, _getExtraInfos: function() { - var members = {}; - try { - members = JSON.parse(BrowserStore.getItem("extra_infos")); - } - catch (err) { - } - - if (members == null) { - members = {}; - } - - return members; - } + return BrowserStore.getItem("extra_infos", {}); + }, + isDefault: function(channel) { + return channel.name == Constants.DEFAULT_CHANNEL; + } }); ChannelStore.dispatchToken = AppDispatcher.register(function(payload) { @@ -271,4 +218,4 @@ ChannelStore.dispatchToken = AppDispatcher.register(function(payload) { } }); -module.exports = ChannelStore; +module.exports = ChannelStore; \ No newline at end of file diff --git a/web/react/stores/error_store.jsx b/web/react/stores/error_store.jsx index 3aed6aef23..203b692ec4 100644 --- a/web/react/stores/error_store.jsx +++ b/web/react/stores/error_store.jsx @@ -29,18 +29,11 @@ var ErrorStore = assign({}, EventEmitter.prototype, { BrowserStore.removeItem("last_error"); }, getLastError: function() { - var error = null; - try { - error = JSON.parse(BrowserStore.getItem("last_error")); - } - catch (err) { - } - - return error; + return BrowserStore.getItem('last_error'); }, _storeLastError: function(error) { - BrowserStore.setItem("last_error", JSON.stringify(error)); + BrowserStore.setItem("last_error", error); }, }); diff --git a/web/react/stores/post_store.jsx b/web/react/stores/post_store.jsx index 8bf3fdcb20..5280bfe08c 100644 --- a/web/react/stores/post_store.jsx +++ b/web/react/stores/post_store.jsx @@ -106,55 +106,27 @@ var PostStore = assign({}, EventEmitter.prototype, { this.emitChange(); }, _storePosts: function(channelId, posts) { - BrowserStore.setItem("posts_" + channelId, JSON.stringify(posts)); + BrowserStore.setItem("posts_" + channelId, posts); }, getPosts: function(channelId) { - var posts = null; - try { - posts = JSON.parse(BrowserStore.getItem("posts_" + channelId)); - } - catch (err) { - } - - return posts; + return BrowserStore.getItem("posts_" + channelId); }, storeSearchResults: function(results, is_mention_search) { - BrowserStore.setItem("search_results", JSON.stringify(results)); + BrowserStore.setItem("search_results", results); is_mention_search = is_mention_search ? true : false; // force to bool - BrowserStore.setItem("is_mention_search", JSON.stringify(is_mention_search)); + BrowserStore.setItem("is_mention_search", is_mention_search); }, getSearchResults: function() { - var results = null; - try { - results = JSON.parse(BrowserStore.getItem("search_results")); - } - catch (err) { - } - - return results; + return BrowserStore.getItem("search_results"); }, getIsMentionSearch: function() { - var result = false; - try { - result = JSON.parse(BrowserStore.getItem("is_mention_search")); - } - catch (err) { - } - - return result; + return BrowserStore.getItem("is_mention_search"); }, storeSelectedPost: function(post_list) { - BrowserStore.setItem("select_post", JSON.stringify(post_list)); + BrowserStore.setItem("select_post", post_list); }, getSelectedPost: function() { - var post_list = null; - try { - post_list = JSON.parse(BrowserStore.getItem("select_post")); - } - catch (err) { - } - - return post_list; + return BrowserStore.getItem("select_post"); }, storeSearchTerm: function(term) { BrowserStore.setItem("search_term", term); @@ -165,27 +137,26 @@ var PostStore = assign({}, EventEmitter.prototype, { storeCurrentDraft: function(draft) { var channel_id = ChannelStore.getCurrentId(); var user_id = UserStore.getCurrentId(); - BrowserStore.setItem("draft_" + channel_id + "_" + user_id, JSON.stringify(draft)); + BrowserStore.setItem("draft_" + channel_id + "_" + user_id, draft); }, getCurrentDraft: function() { var channel_id = ChannelStore.getCurrentId(); var user_id = UserStore.getCurrentId(); - return JSON.parse(BrowserStore.getItem("draft_" + channel_id + "_" + user_id)); + return BrowserStore.getItem("draft_" + channel_id + "_" + user_id); }, storeDraft: function(channel_id, user_id, draft) { - BrowserStore.setItem("draft_" + channel_id + "_" + user_id, JSON.stringify(draft)); + BrowserStore.setItem("draft_" + channel_id + "_" + user_id, draft); }, getDraft: function(channel_id, user_id) { - return JSON.parse(BrowserStore.getItem("draft_" + channel_id + "_" + user_id)); + return BrowserStore.getItem("draft_" + channel_id + "_" + user_id); }, clearDraftUploads: function() { - BrowserStore.actionOnItemsWithPrefix("draft_", function (key, value) { - var d = JSON.parse(value); - if (d) { - d['uploadsInProgress'] = 0; - BrowserStore.setItem(key, JSON.stringify(d)); - } - }); + BrowserStore.actionOnItemsWithPrefix("draft_", function (key, value) { + if (value) { + value.uploadsInProgress = 0; + BrowserStore.setItem(key, value); + } + }); } }); diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx index 39800ead5f..8ebb854c94 100644 --- a/web/react/stores/socket_store.jsx +++ b/web/react/stores/socket_store.jsx @@ -10,8 +10,6 @@ var client = require('../utils/client.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -var BrowserStore = require('../stores/browser_store.jsx'); - var CHANGE_EVENT = 'change'; var conn; diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx index c494cb5b54..3f12725f81 100644 --- a/web/react/stores/team_store.jsx +++ b/web/react/stores/team_store.jsx @@ -57,28 +57,19 @@ var TeamStore = assign({}, EventEmitter.prototype, { else return null; }, + getCurrentTeamUrl: function() { + return window.location.origin + "/" + this.getCurrent().name; + }, storeTeam: function(team) { - var teams = this._getTeams(); - teams[team.id] = team; - this._storeTeams(teams); + var teams = this._getTeams(); + teams[team.id] = team; + this._storeTeams(teams); }, _storeTeams: function(teams) { - BrowserStore.setItem("user_teams", JSON.stringify(teams)); + BrowserStore.setItem("user_teams", teams); }, _getTeams: function() { - var teams = {}; - - try { - teams = JSON.parse(BrowserStore.getItem("user_teams")); - } - catch (err) { - } - - if (teams == null) { - teams = {}; - } - - return teams; + return BrowserStore.getItem("user_teams", {}); } }); diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx index e832b34c7f..d03016c5d6 100644 --- a/web/react/stores/user_store.jsx +++ b/web/react/stores/user_store.jsx @@ -8,7 +8,7 @@ var client = require('../utils/client.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -var BrowserStore = require('../stores/browser_store.jsx'); +var BrowserStore = require('./browser_store.jsx'); var CHANGE_EVENT = 'change'; var CHANGE_EVENT_SESSIONS = 'change_sessions'; @@ -18,6 +18,8 @@ var CHANGE_EVENT_STATUSES = 'change_statuses'; var UserStore = assign({}, EventEmitter.prototype, { + _current_id: null, + emitChange: function(userId) { this.emit(CHANGE_EVENT, userId); }, @@ -64,13 +66,19 @@ var UserStore = assign({}, EventEmitter.prototype, { this.removeListener(CHANGE_EVENT_STATUSES, callback); }, setCurrentId: function(id) { - if (id == null) - BrowserStore.removeGlobalItem("current_user_id"); - else - BrowserStore.setGlobalItem("current_user_id", id); + this._current_id = id; + if (id == null) { + BrowserStore.removeGlobalItem("current_user_id"); + } else { + BrowserStore.setGlobalItem("current_user_id", id); + } }, getCurrentId: function(skipFetch) { - var current_id = BrowserStore.getGlobalItem("current_user_id"); + var current_id = this._current_id; + + if (current_id == null) { + current_id = BrowserStore.getGlobalItem("current_user_id"); + } // this is a speical case to force fetch the // current user if it's missing @@ -93,25 +101,11 @@ var UserStore = assign({}, EventEmitter.prototype, { return this._getProfiles()[this.getCurrentId()]; }, setCurrentUser: function(user) { - this.saveProfile(user); this.setCurrentId(user.id); - }, - getLastDomain: function() { - var last_domain = BrowserStore.getItem("last_domain"); - if (last_domain == null) { - last_domain = ""; - } - return last_domain; - }, - setLastDomain: function(domain) { - BrowserStore.setItem("last_domain", domain); + this.saveProfile(user); }, getLastEmail: function() { - var last_email = BrowserStore.getItem("last_email"); - if (last_email == null) { - last_email = ""; - } - return last_email; + return BrowserStore.getItem("last_email", ''); }, setLastEmail: function(email) { BrowserStore.setItem("last_email", email); @@ -153,116 +147,51 @@ var UserStore = assign({}, EventEmitter.prototype, { this._storeProfiles(ps); }, _storeProfiles: function(profiles) { - BrowserStore.setGlobalItem("profiles", JSON.stringify(profiles)); + BrowserStore.setItem("profiles", profiles); var profileUsernameMap = {}; for (var id in profiles) { profileUsernameMap[profiles[id].username] = profiles[id]; } - BrowserStore.setGlobalItem("profileUsernameMap", JSON.stringify(profileUsernameMap)); + BrowserStore.setItem("profileUsernameMap", profileUsernameMap); }, _getProfiles: function() { - var profiles = {}; - try { - profiles = JSON.parse(BrowserStore.getGlobalItem("profiles")); - } - catch (err) { - } - - if (profiles == null) { - profiles = {}; - } - - return profiles; + return BrowserStore.getItem("profiles", {}); }, _getProfilesUsernameMap: function() { - var profileUsernameMap = {}; - try { - profileUsernameMap = JSON.parse(BrowserStore.getGlobalItem("profileUsernameMap")); - } - catch (err) { - } - - if (profileUsernameMap == null) { - profileUsernameMap = {}; - } - - return profileUsernameMap; + return BrowserStore.getItem("profileUsernameMap", {}); }, setSessions: function(sessions) { - BrowserStore.setItem("sessions", JSON.stringify(sessions)); + BrowserStore.setItem("sessions", sessions); }, getSessions: function() { - var sessions = []; - try { - sessions = JSON.parse(BrowserStore.getItem("sessions")); - } - catch (err) { - } - if (sessions == null) { - sessions = []; - } - - return sessions; + return BrowserStore.getItem("sessions", []); }, setAudits: function(audits) { - BrowserStore.setItem("audits", JSON.stringify(audits)); + BrowserStore.setItem("audits", audits); }, getAudits: function() { - var audits = []; - try { - audits = JSON.parse(BrowserStore.getItem("audits")); - } - catch (err) { - } - - if (audits == null) { - audits = []; - } - - return audits; + return BrowserStore.getItem("audits", []); }, setTeams: function(teams) { - BrowserStore.setItem("teams", JSON.stringify(teams)); + BrowserStore.setItem("teams", teams); }, getTeams: function() { - var teams = []; - try { - teams = JSON.parse(BrowserStore.getItem("teams")); - - } - catch (err) { - } - - if (teams == null) { - teams = []; - } - - return teams; + return BrowserStore.getItem("teams", []); }, getCurrentMentionKeys: function() { var user = this.getCurrentUser(); - if (user.notify_props && user.notify_props.mention_keys) { - var keys = user.notify_props.mention_keys.split(','); - if (user.full_name.length > 0 && user.notify_props.first_name === "true") { - var first = user.full_name.split(' ')[0]; - if (first.length > 0) keys.push(first); - } + var keys = []; - if (user.notify_props.all === "true") keys.push('@all'); - if (user.notify_props.channel === "true") keys.push('@channel'); + if (user.notify_props && user.notify_props.mention_keys) keys = keys.concat(user.notify_props.mention_keys.split(',')); + if (user.first_name && user.notify_props.first_name === "true") keys.push(user.first_name); + if (user.notify_props.all === "true") keys.push('@all'); + if (user.notify_props.channel === "true") keys.push('@channel'); - return keys; - } else { - return []; - } + return keys; }, getLastVersion: function() { - var last_version = BrowserStore.getItem("last_version"); - if (last_version == null) { - last_version = ""; - } - return last_version; + return BrowserStore.getItem("last_version", ''); }, setLastVersion: function(version) { BrowserStore.setItem("last_version", version); @@ -272,7 +201,7 @@ var UserStore = assign({}, EventEmitter.prototype, { this.emitStatusesChange(); }, _setStatuses: function(statuses) { - BrowserStore.setItem("statuses", JSON.stringify(statuses)); + BrowserStore.setItem("statuses", statuses); }, setStatus: function(user_id, status) { var statuses = this.getStatuses(); @@ -281,18 +210,7 @@ var UserStore = assign({}, EventEmitter.prototype, { this.emitStatusesChange(); }, getStatuses: function() { - var statuses = {}; - try { - statuses = JSON.parse(BrowserStore.getItem("statuses")); - } - catch (err) { - } - - if (statuses == null) { - statuses = {}; - } - - return statuses; + return BrowserStore.getItem("statuses", {}); }, getStatus: function(id) { return this.getStatuses()[id]; @@ -341,4 +259,3 @@ UserStore.setMaxListeners(0); global.window.UserStore = UserStore; module.exports = UserStore; - diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 11d4c26013..1c31dc5ed7 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. var BrowserStore = require('../stores/browser_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); module.exports.track = function(category, action, label, prop, val) { global.window.snowplow('trackStructEvent', category, action, label, prop, val); @@ -44,7 +45,12 @@ function handleError(method_name, xhr, status, err) { module.exports.track('api', 'api_weberror', method_name, 'message', msg); if (xhr.status == 401) { - window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname+window.location.search); + if (window.location.href.indexOf("/channels") === 0) { + window.location.pathname = '/login?redirect=' + encodeURIComponent(window.location.pathname+window.location.search); + } else { + var teamURL = window.location.href.split('/channels')[0]; + window.location.href = teamURL + '/login?redirect=' + encodeURIComponent(window.location.pathname+window.location.search); + } } return e; @@ -205,17 +211,18 @@ module.exports.resetPassword = function(data, success, error) { module.exports.logout = function() { module.exports.track('api', 'api_users_logout'); - BrowserStore.clear(); - window.location.href = "/logout"; + var currentTeamUrl = TeamStore.getCurrentTeamUrl(); + BrowserStore.clear(); + window.location.href = currentTeamUrl + "/logout"; }; -module.exports.loginByEmail = function(domain, email, password, success, error) { +module.exports.loginByEmail = function(name, email, password, success, error) { $.ajax({ url: "/api/v1/users/login", dataType: 'json', contentType: 'application/json', type: 'POST', - data: JSON.stringify({domain: domain, email: email, password: password}), + data: JSON.stringify({name: name, email: email, password: password}), success: function(data, textStatus, xhr) { module.exports.track('api', 'api_users_login_success', data.team_id, 'email', data.email); success(data, textStatus, xhr); @@ -317,7 +324,7 @@ module.exports.inviteMembers = function(data, success, error) { module.exports.track('api', 'api_teams_invite_members'); }; -module.exports.updateTeamName = function(data, success, error) { +module.exports.updateTeamDisplayName = function(data, success, error) { $.ajax({ url: "/api/v1/teams/update_name", dataType: 'json', @@ -326,7 +333,7 @@ module.exports.updateTeamName = function(data, success, error) { data: JSON.stringify(data), success: success, error: function(xhr, status, err) { - e = handleError("updateTeamName", xhr, status, err); + e = handleError("updateTeamDisplayName", xhr, status, err); error(e); } }); @@ -334,13 +341,13 @@ module.exports.updateTeamName = function(data, success, error) { module.exports.track('api', 'api_teams_update_name'); }; -module.exports.signupTeam = function(email, name, success, error) { +module.exports.signupTeam = function(email, display_name, success, error) { $.ajax({ url: "/api/v1/teams/signup", dataType: 'json', contentType: 'application/json', type: 'POST', - data: JSON.stringify({email: email, name: name}), + data: JSON.stringify({email: email, display_name: display_name}), success: success, error: function(xhr, status, err) { e = handleError("singupTeam", xhr, status, err); @@ -366,16 +373,16 @@ module.exports.createTeam = function(team, success, error) { }); }; -module.exports.findTeamByDomain = function(domain, success, error) { +module.exports.findTeamByName = function(teamName, success, error) { $.ajax({ - url: "/api/v1/teams/find_team_by_domain", + url: "/api/v1/teams/find_team_by_name", dataType: 'json', contentType: 'application/json', type: 'POST', - data: JSON.stringify({domain: domain}), + data: JSON.stringify({name: teamName}), success: success, error: function(xhr, status, err) { - e = handleError("findTeamByDomain", xhr, status, err); + e = handleError("findTeamByName", xhr, status, err); error(e); } }); diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 3aadfb4b0d..187e3c4a39 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -54,7 +54,7 @@ module.exports = { DEFAULT_CHANNEL: 'town-square', OFFTOPIC_CHANNEL: 'off-topic', POST_CHUNK_SIZE: 60, - RESERVED_DOMAINS: [ + RESERVED_TEAM_NAMES: [ "www", "web", "admin", diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 5ded0e76f5..00580af6e3 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -16,10 +16,10 @@ module.exports.isEmail = function(email) { }; module.exports.cleanUpUrlable = function(input) { - var cleaned = input.trim().replace(/-/g, ' ').replace(/[^\w\s]/gi, '').toLowerCase().replace(/\s/g, '-'); - cleaned = cleaned.replace(/^\-+/, ''); - cleaned = cleaned.replace(/\-+$/, ''); - return cleaned; + var cleaned = input.trim().replace(/-/g, ' ').replace(/[^\w\s]/gi, '').toLowerCase().replace(/\s/g, '-'); + cleaned = cleaned.replace(/^\-+/, ''); + cleaned = cleaned.replace(/\-+$/, ''); + return cleaned; }; @@ -114,7 +114,7 @@ module.exports.notifyMe = function(title, body, channel) { if (channel) { module.exports.switchChannel(channel); } else { - window.location.href = "/channels/town-square"; + window.location.href = "/"; } }; setTimeout(function(){ @@ -198,7 +198,13 @@ module.exports.getTimestamp = function() { } var testUrlMatch = function(text) { - var urlMatcher = new Autolinker.matchParser.MatchParser; + var urlMatcher = new Autolinker.matchParser.MatchParser({ + urls: true, + emails: false, + twitter: false, + phone: false, + hashtag: false, + }); var result = []; var replaceFn = function(match) { var linkData = {}; @@ -225,7 +231,7 @@ module.exports.extractLinks = function(text) { } return { "links": links, "text": text }; -} +} module.exports.escapeRegExp = function(string) { return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); @@ -303,18 +309,25 @@ var getYoutubeEmbed = function(link) { }; var success = function(data) { - $('.video-uploader.'+youtubeId).html(data.data.uploader); - $('.video-title.'+youtubeId).find('a').html(data.data.title); + if(!data.items.length || !data.items[0].snippet) { + return; + } + var metadata = data.items[0].snippet; + $('.video-uploader.'+youtubeId).html(metadata.channelTitle); + $('.video-title.'+youtubeId).find('a').html(metadata.title); $(".post-list-holder-by-time").scrollTop($(".post-list-holder-by-time")[0].scrollHeight); $(".post-list-holder-by-time").perfectScrollbar('update'); }; - $.ajax({ - async: true, - url: 'https://gdata.youtube.com/feeds/api/videos/'+youtubeId+'?v=2&alt=jsonc', - type: 'GET', - success: success - }); + if(config.GoogleDeveloperKey) { + $.ajax({ + async: true, + url: "https://www.googleapis.com/youtube/v3/videos", + type: 'GET', + data: {part:"snippet", id:youtubeId, key:config.GoogleDeveloperKey}, + success: success + }); + } return (
@@ -671,13 +684,13 @@ module.exports.isValidUsername = function (name) { error = "First character must be a letter."; } - else + else { var lowerName = name.toLowerCase().trim(); - for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++) + for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++) { - if (lowerName === Constants.RESERVED_USERNAMES[i]) + if (lowerName === Constants.RESERVED_USERNAMES[i]) { error = "Cannot use a reserved word as a username."; break; @@ -695,8 +708,8 @@ module.exports.switchChannel = function(channel, teammate_name) { id: channel.id }); - var domain = window.location.href.split('/channels')[0]; - history.replaceState('data', '', domain + '/channels/' + channel.name); + var teamURL = window.location.href.split('/channels')[0]; + history.replaceState('data', '', teamURL + '/channels/' + channel.name); if (channel.type === 'D' && teammate_name) { document.title = teammate_name + " " + document.title.substring(document.title.lastIndexOf("-")); @@ -771,14 +784,57 @@ Image.prototype.load = function(url, progressCallback) { Image.prototype.completedPercentage = 0; -module.exports.getHomeLink = function() { - if (config.HomeLink != "") { - return config.HomeLink; - } - var parts = window.location.host.split("."); - if (parts.length <= 1) { - return window.location.protocol + "//" + window.location.host; - } - parts[0] = "www"; - return window.location.protocol + "//" + parts.join("."); -} +module.exports.changeColor =function(col, amt) { + + var usePound = false; + + if (col[0] == "#") { + col = col.slice(1); + usePound = true; + } + + var num = parseInt(col,16); + + var r = (num >> 16) + amt; + + if (r > 255) r = 255; + else if (r < 0) r = 0; + + var b = ((num >> 8) & 0x00FF) + amt; + + if (b > 255) b = 255; + else if (b < 0) b = 0; + + var g = (num & 0x0000FF) + amt; + + if (g > 255) g = 255; + else if (g < 0) g = 0; + + return (usePound?"#":"") + String("000000" + (g | (b << 8) | (r << 16)).toString(16)).slice(-6); +}; + +module.exports.getFullName = function(user) { + if (user.first_name && user.last_name) { + return user.first_name + " " + user.last_name; + } else if (user.first_name) { + return user.first_name; + } else if (user.last_name) { + return user.last_name; + } else { + return ""; + } +}; + +module.exports.getDisplayName = function(user) { + if (user.nickname && user.nickname.trim().length > 0) { + return user.nickname; + } else { + var fullName = module.exports.getFullName(user); + + if (fullName) { + return fullName; + } else { + return user.username; + } + } +}; diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss index fd6225bddc..1fb9700753 100644 --- a/web/sass-files/sass/partials/_base.scss +++ b/web/sass-files/sass/partials/_base.scss @@ -10,6 +10,9 @@ body { height: 100%; &.white { background: #fff; + > .container-fluid { + overflow: auto; + } .inner__wrap { > .row.content { min-height: 100%; @@ -53,6 +56,9 @@ div.theme { .form-control { @include border-radius(2px); + &.no-resize { + resize: none; + } } .form-group { @@ -126,6 +132,10 @@ div.theme { to { transform: scale(1) rotate(360deg);} } +.glyphicon-refresh-animate { + @include animation(spin .7s infinite linear); +} + .black-bg { background-color: black !important; } diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss index 79142176ec..56d03e1719 100644 --- a/web/sass-files/sass/partials/_files.scss +++ b/web/sass-files/sass/partials/_files.scss @@ -32,6 +32,7 @@ } } .preview-img { + display: block; height: auto; max-width: 100%; } @@ -137,7 +138,7 @@ border: 1px solid #E2E2E2; background-color: #FFF; background-repeat: no-repeat; - background-position: left center; + background-position: top left; } a { text-decoration: none; diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss index 1ec1109a59..adeaa70d71 100644 --- a/web/sass-files/sass/partials/_headers.scss +++ b/web/sass-files/sass/partials/_headers.scss @@ -75,14 +75,16 @@ // Team Header in Sidebar .sidebar--left, .sidebar--menu { .team__header { - padding: 0 15px 0 15px; + padding: 10px; @include legacy-pie-clearfix; a { color: #fff; } .navbar-right { font-size: 0.85em; - margin: 16px -5px 0; + position: absolute; + top: 20px; + right: 22px; .dropdown-toggle { padding: 0 10px; } @@ -93,24 +95,38 @@ } } .dropdown__icon { - background: url("../images/dropdown-icon.png"); - width: 4px; - height: 16px; - @include background-size(100% 100%); - display: inline-block; + fill: #fff; } } - .team__name { + .settings__link a:hover, a:visited, a:link, a:active { + text-decoration: none; + } + .user__picture { + width: 36px; + height: 36px; float: left; - line-height: 50px; + @include border-radius(36px); + } + .header__info { + padding-left: 42px; + color: #fff; + } + .team__name, .user__name { + display: block; + line-height: 18px; font-weight: 600; - font-size: 1.2em; + font-size: 16px; max-width: 80%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; text-decoration: none; } + .user__name { + font-size: 14px; + font-weight: 400; + color: #eee; + } > .nav { > li { > a { @@ -144,9 +160,10 @@ font-size: 14px; line-height: 50px; #member_popover { + margin-right: 5px; width: 45px; color: #999; - + cursor: pointer; } &.alt { margin: 0; @@ -213,3 +230,25 @@ top: 1px; } } + +.channel-header__links { + height: 32px; + vertical-align: top; + display: inline-block; + width: 15px; + margin: 9px 4px 3px 0; + &:hover { + svg { + fill: #888; + } + } + a { + height: 100%; + display: block; + } + svg { + vertical-align: top; + margin-top: 8px; + fill: #AAA; + } +} diff --git a/web/sass-files/sass/partials/_loading.scss b/web/sass-files/sass/partials/_loading.scss index bc819e8f5d..d710557224 100644 --- a/web/sass-files/sass/partials/_loading.scss +++ b/web/sass-files/sass/partials/_loading.scss @@ -2,13 +2,8 @@ display: table; width: 100%; height: 100%; - position: absolute; - @include box-sizing(border-box); + padding: 60px; text-align: center; - &.loading-screen--channel { - position: relative; - padding: 4em 0 3.5em; - } .loading__content { display: table-cell; vertical-align: middle; @@ -19,11 +14,7 @@ margin: 0 0.2em 0; display: inline-block; } - } -} -.loading-screen { - .loading__content { .round { background-color: #444; width: 4px; @@ -32,43 +23,18 @@ margin: 0 2px; opacity: 0.1; @include border-radius(10px); - -moz-animation: move 0.75s infinite linear; - -webkit-animation: move 0.75s infinite linear; + @include animation(move 0.75s infinite linear); } - #round_1 { - -moz-animation-delay: .2s; - -webkit-animation-delay: .2s; - } - - #round_2 { - -moz-animation-delay: .4s; - -webkit-animation-delay: .4s; - } - - #round_3 { - -moz-animation-delay: .6s; - -webkit-animation-delay: .6s; - } - - @-moz-keyframes move { - 0% { - opacity: 1; + @for $i from 1 through 3 { + .round-#{$i} { + @include animation-delay(.2s*$i); } - - 100% { - opacity: 0.1; - }; } - @-webkit-keyframes move { - 0% { - opacity: 1; - } - - 100% { - opacity: 0.1; - }; + @include keyframes(move) { + from { opacity: 1; } + to { opacity: 0.1; } } } } diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss index da46866c82..1396f21a11 100644 --- a/web/sass-files/sass/partials/_mentions.scss +++ b/web/sass-files/sass/partials/_mentions.scss @@ -11,13 +11,14 @@ position: absolute; z-index: 1060; .mentions-box { - max-height: 303px; - position:absolute; - background-color:#fff; + width: 100%; + height: 100%; + position: absolute; + background-color: #fff; border: $border-gray; overflow-x: hidden; overflow-y: scroll; - bottom:0; + bottom: 0; } } @@ -36,6 +37,10 @@ } } +.mentions-focus { + background-color: #E6F2FA; +} + .mentions-text { font-color:black; } diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss index 707e71cf0a..f359037c51 100644 --- a/web/sass-files/sass/partials/_modal.scss +++ b/web/sass-files/sass/partials/_modal.scss @@ -154,7 +154,8 @@ background: #FFF; position: relative; max-width: 90%; - min-width: 280px; + min-height: 50px; + min-width: 320px; @include border-radius(3px); display: table; margin: 0 auto; diff --git a/web/sass-files/sass/partials/_navbar.scss b/web/sass-files/sass/partials/_navbar.scss index 62864afb7b..6d8f11ce38 100644 --- a/web/sass-files/sass/partials/_navbar.scss +++ b/web/sass-files/sass/partials/_navbar.scss @@ -43,6 +43,7 @@ font-size: 16px; .heading { margin-right: 3px; + font-weight: 600; color: #fff; } .header-dropdown__icon { diff --git a/web/sass-files/sass/partials/_popover.scss b/web/sass-files/sass/partials/_popover.scss new file mode 100644 index 0000000000..fa1b448419 --- /dev/null +++ b/web/sass-files/sass/partials/_popover.scss @@ -0,0 +1,9 @@ +.user-popover { + cursor: pointer; + display: inline-block; +} + +.user-popover__image { + margin: 0 0 10px; + @include border-radius(128px); +} \ No newline at end of file diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index d0c5363635..481ed63a52 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -119,6 +119,7 @@ body.ios { table-layout: fixed; width: 100%; min-height: 100%; + height: 100%; .post-list__content { display: table-cell; vertical-align: bottom; @@ -186,6 +187,12 @@ body.ios { .post-create-footer { @include clearfix; padding: 0; + .has-error { + .control-label { + font-weight: normal; + margin-bottom: 0; + } + } .msg-typing { min-height: 20px; line-height: 18px; @@ -207,6 +214,12 @@ body.ios { .dropdown, .comment-icon__container { @include opacity(1); } + .dropdown-toggle:after { + content: '...'; + } + .dropdown-toggle:hover:after { + content: '[...]'; + } } background: #f5f5f5; } @@ -424,4 +437,4 @@ body.ios { width: 40px; } } -} +} \ No newline at end of file diff --git a/web/sass-files/sass/partials/_post_right.scss b/web/sass-files/sass/partials/_post_right.scss index 8816393c8f..4cf3e32a17 100644 --- a/web/sass-files/sass/partials/_post_right.scss +++ b/web/sass-files/sass/partials/_post_right.scss @@ -11,7 +11,7 @@ .post { &.post--root { - padding: 1em 1em 0; + padding: 0 1em 0; margin: 1em 0; hr { border-color: #DDD; @@ -62,6 +62,11 @@ } } +.post-right-channel__name { + font-weight: 600; + margin: 0 0 10px 0; +} + .post-right-root-container li { display: inline; list-style-type: none; diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index 9c0c09ee3b..a33d693784 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -413,7 +413,7 @@ } } .footer, .footer-pane, .footer-push { - height: 185px; + height: auto; } .footer-pane { .footer-link { @@ -432,14 +432,16 @@ color: #fff; .search__form { border: none; - padding: 0 10px 0 30px; + padding: 0 60px 0 25px; .form-control { + line-height: 31px; background: none; color: #fff; - border-bottom: 1px solid #fff; - border-bottom: 1px solid rgba(#fff, 0.7); border-radius: 0; - padding: 0 0 0 23px; + padding: 0 10px 0; + @include input-placeholder { + color: rgba(#fff, 0.6); + } } ::-webkit-input-placeholder { color: #fff; @@ -534,6 +536,11 @@ .sidebar--right__close { display: none; } + .search__form { + .glyphicon { + color: #fff; + } + } } .inner__wrap { &.move--right { @@ -570,6 +577,8 @@ .modal { .modal-image { .image-wrapper { + font-size: 12px; + max-width: 280px; .modal-close { @include opacity(1); } diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss index 8d51d00c01..d4a4da2433 100644 --- a/web/sass-files/sass/partials/_search.scss +++ b/web/sass-files/sass/partials/_search.scss @@ -2,21 +2,20 @@ padding: 8px 8px 8px 0; } .sidebar__collapse { - width: 20px; - height: 30px; + width: auto; + height: auto; position: absolute; - top: 10px; - left: 6px; + top: 17px; + right: 15px; cursor: pointer; - background: url("../images/arrow-left.png") center no-repeat; - @include background-size(10px 15px); z-index: 5; display: none; } .sidebar__search-icon { position: absolute; - left: 40px; + left: 15px; top: 18px; + font-size: 16px; @include opacity(0.8); display: none; } diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss index af759c650b..e60bc290ec 100644 --- a/web/sass-files/sass/partials/_settings.scss +++ b/web/sass-files/sass/partials/_settings.scss @@ -1,6 +1,10 @@ .user-settings { background: #fff; min-height:300px; + .table-responsive { + max-width: 560px; + max-height: 300px; + } } .settings-modal { diff --git a/web/sass-files/sass/partials/_sidebar--right.scss b/web/sass-files/sass/partials/_sidebar--right.scss index a0e82fd2f4..d02a924489 100644 --- a/web/sass-files/sass/partials/_sidebar--right.scss +++ b/web/sass-files/sass/partials/_sidebar--right.scss @@ -10,6 +10,14 @@ &.move--left { right: 0; } + .sidebar--right__back { + color: #666; + width: 20px; + text-align: center; + margin: 0 0 0 -6px; + font-size: 12px; + display: inline-block; + } .sidebar-right__body { border-left: $border-gray; border-top: $border-gray; diff --git a/web/sass-files/sass/partials/_signup.scss b/web/sass-files/sass/partials/_signup.scss index a714aa44f6..98931279b8 100644 --- a/web/sass-files/sass/partials/_signup.scss +++ b/web/sass-files/sass/partials/_signup.scss @@ -6,7 +6,7 @@ } .signup-team__container { padding: 100px 0px 50px 0px; - max-width: 340px; + max-width: 600px; margin: 0 auto; font-size: 1.1em; position: relative; @@ -118,4 +118,4 @@ .signup-team-login { padding-bottom: 10px; font-weight: 700; -} \ No newline at end of file +} diff --git a/web/sass-files/sass/partials/_variables.scss b/web/sass-files/sass/partials/_variables.scss index eb1f3eef31..5d883ab448 100644 --- a/web/sass-files/sass/partials/_variables.scss +++ b/web/sass-files/sass/partials/_variables.scss @@ -7,10 +7,4 @@ $primary-color: #2389D7; $primary-color--hover: darken(#2389D7, 5%); $body-bg: #e9e9e9; $header-bg: #f9f9f9; -$border-gray: 1px solid #ddd; - -// Animation -.glyphicon-refresh-animate { - -animation: spin .7s infinite linear; - -webkit-animation: spin2 .7s infinite linear; -} \ No newline at end of file +$border-gray: 1px solid #ddd; \ No newline at end of file diff --git a/web/sass-files/sass/styles.scss b/web/sass-files/sass/styles.scss index 9cc26320c2..294f6122af 100644 --- a/web/sass-files/sass/styles.scss +++ b/web/sass-files/sass/styles.scss @@ -15,6 +15,7 @@ @import "partials/headers"; @import "partials/footer"; @import "partials/content"; +@import "partials/popover"; @import "partials/post"; @import "partials/post_right"; @import "partials/navbar"; @@ -29,7 +30,7 @@ @import "partials/modal"; @import "partials/mentions"; @import "partials/error"; -@import "partials/loading"; +@import "partials/loading"; // Responsive Css @import "partials/responsive"; diff --git a/web/static/config/config.js b/web/static/config/config.js index 45c713da2e..0d564b77ed 100644 --- a/web/static/config/config.js +++ b/web/static/config/config.js @@ -16,6 +16,10 @@ var config = { RequireInviteNames: false, AllowSignupDomainsWizard: false, + // Google Developer Key (for Youtube API links) + // Leave blank to disable + GoogleDeveloperKey: "", + // Privacy switches ShowEmail: true, diff --git a/web/static/images/dropdown-icon.png b/web/static/images/dropdown-icon.png deleted file mode 100644 index 5c271cfc78..0000000000 Binary files a/web/static/images/dropdown-icon.png and /dev/null differ diff --git a/web/templates/channel.html b/web/templates/channel.html index d96aee3d45..eaf0f2563b 100644 --- a/web/templates/channel.html +++ b/web/templates/channel.html @@ -46,7 +46,7 @@
diff --git a/web/templates/head.html b/web/templates/head.html index ead6485713..d143409986 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -31,7 +31,6 @@ - @@ -43,6 +42,9 @@ } config.SiteName = '{{ .SiteName }}'; + + + diff --git a/web/templates/login.html b/web/templates/login.html index c107e1ad5c..24cebec8f2 100644 --- a/web/templates/login.html +++ b/web/templates/login.html @@ -7,7 +7,7 @@
@@ -20,7 +20,7 @@
diff --git a/web/templates/password_reset.html b/web/templates/password_reset.html index 8b63556b1f..6244f6418f 100644 --- a/web/templates/password_reset.html +++ b/web/templates/password_reset.html @@ -9,7 +9,7 @@
diff --git a/web/templates/signup_team.html b/web/templates/signup_team.html index fad332beee..b865905891 100644 --- a/web/templates/signup_team.html +++ b/web/templates/signup_team.html @@ -12,7 +12,6 @@

All team communication in one place, searchable and accessible anywhere

{{ .SiteName }} is free for an unlimited time, for unlimited users

-
@@ -23,7 +22,7 @@
diff --git a/web/templates/signup_team_complete.html b/web/templates/signup_team_complete.html index 59f49cdbda..674e54ee2f 100644 --- a/web/templates/signup_team_complete.html +++ b/web/templates/signup_team_complete.html @@ -19,7 +19,7 @@ diff --git a/web/templates/signup_team_confirm.html b/web/templates/signup_team_confirm.html index 9e21126da8..3a6134af3b 100644 --- a/web/templates/signup_team_confirm.html +++ b/web/templates/signup_team_confirm.html @@ -8,7 +8,6 @@
diff --git a/web/web.go b/web/web.go index e771157d8b..3e4bc2d537 100644 --- a/web/web.go +++ b/web/web.go @@ -42,25 +42,28 @@ func (me *HtmlTemplatePage) Render(c *api.Context, w http.ResponseWriter) { func InitWeb() { l4g.Debug("Initializing web routes") + mainrouter := api.Srv.Router + staticDir := utils.FindDir("web/static") l4g.Debug("Using static directory at %v", staticDir) - api.Srv.Router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", - http.FileServer(http.Dir(staticDir)))) + mainrouter.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))) - api.Srv.Router.Handle("/", api.AppHandler(root)).Methods("GET") - api.Srv.Router.Handle("/login", api.AppHandler(login)).Methods("GET") - api.Srv.Router.Handle("/signup_team_confirm/", api.AppHandler(signupTeamConfirm)).Methods("GET") - api.Srv.Router.Handle("/signup_team_complete/", api.AppHandler(signupTeamComplete)).Methods("GET") - api.Srv.Router.Handle("/signup_user_complete/", api.AppHandler(signupUserComplete)).Methods("GET") + mainrouter.Handle("/", api.AppHandlerIndependent(root)).Methods("GET") + mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}", api.AppHandler(login)).Methods("GET") + mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/", api.AppHandler(login)).Methods("GET") + mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/login", api.AppHandler(login)).Methods("GET") + mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/logout", api.AppHandler(logout)).Methods("GET") + mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}/reset_password", api.AppHandler(resetPassword)).Methods("GET") + // Bug in gorilla.mux pervents us from using regex here. + mainrouter.Handle("/{team}/channels/{channelname}", api.UserRequired(getChannel)).Methods("GET") - api.Srv.Router.Handle("/logout", api.AppHandler(logout)).Methods("GET") - - api.Srv.Router.Handle("/verify", api.AppHandler(verifyEmail)).Methods("GET") - api.Srv.Router.Handle("/find_team", api.AppHandler(findTeam)).Methods("GET") - api.Srv.Router.Handle("/reset_password", api.AppHandler(resetPassword)).Methods("GET") - - csr := api.Srv.Router.PathPrefix("/channels").Subrouter() - csr.Handle("/{name:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}", api.UserRequired(getChannel)).Methods("GET") + // Anything added here must have an _ in it so it does not conflict with team names + mainrouter.Handle("/signup_team_complete/", api.AppHandlerIndependent(signupTeamComplete)).Methods("GET") + mainrouter.Handle("/signup_user_complete/", api.AppHandlerIndependent(signupUserComplete)).Methods("GET") + mainrouter.Handle("/signup_team_confirm/", api.AppHandlerIndependent(signupTeamConfirm)).Methods("GET") + mainrouter.Handle("/verify_email", api.AppHandlerIndependent(verifyEmail)).Methods("GET") + mainrouter.Handle("/find_team", api.AppHandlerIndependent(findTeam)).Methods("GET") + mainrouter.Handle("/signup_team", api.AppHandlerIndependent(signup)).Methods("GET") watchAndParseTemplates() } @@ -128,46 +131,53 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) { } if len(c.Session.UserId) == 0 { - if api.IsTestDomain(r) || strings.Index(r.Host, "www") == 0 || strings.Index(r.Host, "beta") == 0 || strings.Index(r.Host, "ci") == 0 { - page := NewHtmlTemplatePage("signup_team", "Signup") - page.Render(c, w) - } else { - login(c, w, r) - } + page := NewHtmlTemplatePage("signup_team", "Signup") + page.Render(c, w) } else { page := NewHtmlTemplatePage("home", "Home") + page.Props["TeamURL"] = c.GetTeamURL() page.Render(c, w) } } +func signup(c *api.Context, w http.ResponseWriter, r *http.Request) { + + if !CheckBrowserCompatability(c, r) { + return + } + + page := NewHtmlTemplatePage("signup_team", "Signup") + page.Render(c, w) +} + func login(c *api.Context, w http.ResponseWriter, r *http.Request) { if !CheckBrowserCompatability(c, r) { return } + params := mux.Vars(r) + teamName := params["team"] - teamName := "Beta" - teamDomain := "" - siteDomain := "." + utils.Cfg.ServiceSettings.Domain - - if utils.Cfg.ServiceSettings.Mode == utils.MODE_DEV { - teamDomain = "developer" - } else if utils.Cfg.ServiceSettings.Mode == utils.MODE_BETA { - teamDomain = "beta" + var team *model.Team + if tResult := <-api.Srv.Store.Team().GetByName(teamName); tResult.Err != nil { + l4g.Error("Couldn't find team name=%v, teamURL=%v, err=%v", teamName, c.GetTeamURL(), tResult.Err.Message) + // This should probably do somthing nicer + http.Redirect(w, r, "http://"+r.Host, http.StatusTemporaryRedirect) + return } else { - teamDomain, siteDomain = model.GetSubDomain(c.TeamUrl) - siteDomain = "." + siteDomain + ".com" + team = tResult.Data.(*model.Team) + } - if tResult := <-api.Srv.Store.Team().GetByDomain(teamDomain); tResult.Err != nil { - l4g.Error("Couldn't find team teamDomain=%v, siteDomain=%v, teamUrl=%v, err=%v", teamDomain, siteDomain, c.TeamUrl, tResult.Err.Message) - } else { - teamName = tResult.Data.(*model.Team).Name - } + // If we are already logged into this team then go to home + if len(c.Session.UserId) != 0 && c.Session.TeamId == team.Id { + page := NewHtmlTemplatePage("home", "Home") + page.Props["TeamURL"] = c.GetTeamURL() + page.Render(c, w) + return } page := NewHtmlTemplatePage("login", "Login") + page.Props["TeamDisplayName"] = team.DisplayName page.Props["TeamName"] = teamName - page.Props["TeamDomain"] = teamDomain - page.Props["SiteDomain"] = siteDomain page.Render(c, w) } @@ -198,7 +208,7 @@ func signupTeamComplete(c *api.Context, w http.ResponseWriter, r *http.Request) page := NewHtmlTemplatePage("signup_team_complete", "Complete Team Sign Up") page.Props["Email"] = props["email"] - page.Props["Name"] = props["name"] + page.Props["DisplayName"] = props["display_name"] page.Props["Data"] = data page.Props["Hash"] = hash page.Render(c, w) @@ -225,8 +235,8 @@ func signupUserComplete(c *api.Context, w http.ResponseWriter, r *http.Request) } props["email"] = "" + props["display_name"] = team.DisplayName props["name"] = team.Name - props["domain"] = team.Domain props["id"] = team.Id data = model.MapToJson(props) hash = "" @@ -249,8 +259,8 @@ func signupUserComplete(c *api.Context, w http.ResponseWriter, r *http.Request) page := NewHtmlTemplatePage("signup_user_complete", "Complete User Sign Up") page.Props["Email"] = props["email"] + page.Props["TeamDisplayName"] = props["display_name"] page.Props["TeamName"] = props["name"] - page.Props["TeamDomain"] = props["domain"] page.Props["TeamId"] = props["id"] page.Props["Data"] = data page.Props["Hash"] = hash @@ -259,12 +269,12 @@ func signupUserComplete(c *api.Context, w http.ResponseWriter, r *http.Request) func logout(c *api.Context, w http.ResponseWriter, r *http.Request) { api.Logout(c, w, r) - http.Redirect(w, r, "/", http.StatusFound) + http.Redirect(w, r, c.GetTeamURL(), http.StatusFound) } func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) - name := params["name"] + name := params["channelname"] var channelId string if result := <-api.Srv.Store.Channel().CheckPermissionsToByName(c.Session.TeamId, name, c.Session.UserId); result.Err != nil { @@ -303,8 +313,8 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { //api.Handle404(w, r) //Bad channel urls just redirect to the town-square for now - - http.Redirect(w,r,"/channels/town-square", http.StatusFound) + + http.Redirect(w, r, c.GetTeamURL()+"/channels/town-square", http.StatusFound) return } } @@ -319,8 +329,8 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { } page := NewHtmlTemplatePage("channel", "") - page.Title = name + " - " + team.Name + " " + page.SiteName - page.Props["TeamName"] = team.Name + page.Title = name + " - " + team.DisplayName + " " + page.SiteName + page.Props["TeamDisplayName"] = team.DisplayName page.Props["TeamType"] = team.Type page.Props["TeamId"] = team.Id page.Props["ChannelName"] = name @@ -331,7 +341,7 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) { resend := r.URL.Query().Get("resend") - domain := r.URL.Query().Get("domain") + name := r.URL.Query().Get("name") email := r.URL.Query().Get("email") hashedId := r.URL.Query().Get("hid") userId := r.URL.Query().Get("uid") @@ -339,7 +349,7 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) { if resend == "true" { teamId := "" - if result := <-api.Srv.Store.Team().GetByDomain(domain); result.Err != nil { + if result := <-api.Srv.Store.Team().GetByName(name); result.Err != nil { c.Err = result.Err return } else { @@ -351,7 +361,7 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) { return } else { user := result.Data.(*model.User) - api.FireAndForgetVerifyEmail(user.Id, strings.Split(user.FullName, " ")[0], user.Email, domain, c.TeamUrl) + api.FireAndForgetVerifyEmail(user.Id, strings.Split(user.Nickname, " ")[0], user.Email, name, c.GetTeamURL()) http.Redirect(w, r, "/", http.StatusFound) return } @@ -387,6 +397,8 @@ func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) { isResetLink := true hash := r.URL.Query().Get("h") data := r.URL.Query().Get("d") + params := mux.Vars(r) + teamName := params["team"] if len(hash) == 0 || len(data) == 0 { isResetLink = false @@ -405,30 +417,25 @@ func resetPassword(c *api.Context, w http.ResponseWriter, r *http.Request) { } } - teamName := "Developer/Beta" - domain := "" - if utils.Cfg.ServiceSettings.Mode != utils.MODE_DEV { - domain, _ = model.GetSubDomain(c.TeamUrl) + teamDisplayName := "Developer/Beta" + var team *model.Team + if tResult := <-api.Srv.Store.Team().GetByName(teamName); tResult.Err != nil { + c.Err = tResult.Err + return + } else { + team = tResult.Data.(*model.Team) + } - var team *model.Team - if tResult := <-api.Srv.Store.Team().GetByDomain(domain); tResult.Err != nil { - c.Err = tResult.Err - return - } else { - team = tResult.Data.(*model.Team) - } - - if team != nil { - teamName = team.Name - } + if team != nil { + teamDisplayName = team.DisplayName } page := NewHtmlTemplatePage("password_reset", "") page.Title = "Reset Password - " + page.SiteName - page.Props["TeamName"] = teamName + page.Props["TeamDisplayName"] = teamDisplayName page.Props["Hash"] = hash page.Props["Data"] = data - page.Props["Domain"] = domain + page.Props["TeamName"] = teamName page.Props["IsReset"] = strconv.FormatBool(isResetLink) page.Render(c, w) }
{ new Date(value.create_at).toLocaleString() }{ value.action.replace("/api/v1", "") }{ value.ip_address }{ value.session_id }{ value.extra_info }{ new Date(value.create_at).toLocaleString() }{ value.action.replace("/api/v1", "") }{ value.ip_address }{ value.session_id }{ value.extra_info }