Этот коммит содержится в:
=Corey Hulen
2015-06-14 23:53:32 -08:00
Коммит cf7a05f80f
665 изменённых файлов: 129987 добавлений и 0 удалений

39
model/audit.go Обычный файл
Просмотреть файл

@@ -0,0 +1,39 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
type Audit struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UserId string `json:"user_id"`
Action string `json:"action"`
ExtraInfo string `json:"extra_info"`
IpAddress string `json:"ip_address"`
SessionId string `json:"session_id"`
}
func (o *Audit) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func AuditFromJson(data io.Reader) *Audit {
decoder := json.NewDecoder(data)
var o Audit
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}

19
model/audit_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,19 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestAuditJson(t *testing.T) {
audit := Audit{Id: NewId(), UserId: NewId(), CreateAt: GetMillis()}
json := audit.ToJson()
result := AuditFromJson(strings.NewReader(json))
if audit.Id != result.Id {
t.Fatal("Ids do not match")
}
}

39
model/audits.go Обычный файл
Просмотреть файл

@@ -0,0 +1,39 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
type Audits []Audit
func (o Audits) Etag() string {
if len(o) > 0 {
// the first in the list is always the most current
return Etag(o[0].CreateAt)
} else {
return ""
}
}
func (o Audits) ToJson() string {
if b, err := json.Marshal(o); err != nil {
return "[]"
} else {
return string(b)
}
}
func AuditsFromJson(data io.Reader) Audits {
decoder := json.NewDecoder(data)
var o Audits
err := decoder.Decode(&o)
if err == nil {
return o
} else {
return nil
}
}

29
model/audits_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,29 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestAuditsJson(t *testing.T) {
audit := Audit{Id: NewId(), UserId: NewId(), CreateAt: GetMillis()}
json := audit.ToJson()
result := AuditFromJson(strings.NewReader(json))
if audit.Id != result.Id {
t.Fatal("Ids do not match")
}
var audits Audits = make([]Audit, 1)
audits[0] = audit
ljson := audits.ToJson()
results := AuditsFromJson(strings.NewReader(ljson))
if audits[0].Id != results[0].Id {
t.Fatal("Ids do not match")
}
}

104
model/channel.go Обычный файл
Просмотреть файл

@@ -0,0 +1,104 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
CHANNEL_OPEN = "O"
CHANNEL_PRIVATE = "P"
CHANNEL_DIRECT = "D"
DEFAULT_CHANNEL = "town-square"
)
type Channel struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
TeamId string `json:"team_id"`
Type string `json:"type"`
DisplayName string `json:"display_name"`
Name string `json:"name"`
Description string `json:"description"`
LastPostAt int64 `json:"last_post_at"`
TotalMsgCount int64 `json:"total_msg_count"`
}
func (o *Channel) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func ChannelFromJson(data io.Reader) *Channel {
decoder := json.NewDecoder(data)
var o Channel
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}
func (o *Channel) Etag() string {
return Etag(o.Id, o.LastPostAt)
}
func (o *Channel) IsValid() *AppError {
if len(o.Id) != 26 {
return NewAppError("Channel.IsValid", "Invalid Id", "")
}
if o.CreateAt == 0 {
return NewAppError("Channel.IsValid", "Create at must be a valid time", "id="+o.Id)
}
if o.UpdateAt == 0 {
return NewAppError("Channel.IsValid", "Update at must be a valid time", "id="+o.Id)
}
if len(o.DisplayName) > 64 {
return NewAppError("Channel.IsValid", "Invalid display name", "id="+o.Id)
}
if len(o.Name) > 64 {
return NewAppError("Channel.IsValid", "Invalid name", "id="+o.Id)
}
if !IsValidChannelIdentifier(o.Name) {
return NewAppError("Channel.IsValid", "Name must be 2 or more lowercase alphanumeric characters", "id="+o.Id)
}
if !(o.Type == CHANNEL_OPEN || o.Type == CHANNEL_PRIVATE || o.Type == CHANNEL_DIRECT) {
return NewAppError("Channel.IsValid", "Invalid type", "id="+o.Id)
}
if len(o.Description) > 1024 {
return NewAppError("Channel.IsValid", "Invalid description", "id="+o.Id)
}
return nil
}
func (o *Channel) PreSave() {
if o.Id == "" {
o.Id = NewId()
}
o.CreateAt = GetMillis()
o.UpdateAt = o.CreateAt
}
func (o *Channel) PreUpdate() {
o.UpdateAt = GetMillis()
}

51
model/channel_extra.go Обычный файл
Просмотреть файл

@@ -0,0 +1,51 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
type ExtraMember struct {
Id string `json:"id"`
FullName string `json:"full_name"`
Email string `json:"email"`
Roles string `json:"roles"`
Username string `json:"username"`
}
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 {
Id string `json:"id"`
Members []ExtraMember `json:"members"`
}
func (o *ChannelExtra) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func ChannelExtraFromJson(data io.Reader) *ChannelExtra {
decoder := json.NewDecoder(data)
var o ChannelExtra
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}

71
model/channel_list.go Обычный файл
Просмотреть файл

@@ -0,0 +1,71 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
type ChannelList struct {
Channels []*Channel `json:"channels"`
Members map[string]*ChannelMember `json:"members"`
}
func (o *ChannelList) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func (o *ChannelList) Etag() string {
id := "0"
var t int64 = 0
var delta int64 = 0
for _, v := range o.Channels {
if v.LastPostAt > t {
t = v.LastPostAt
id = v.Id
}
if v.UpdateAt > t {
t = v.UpdateAt
id = v.Id
}
member := o.Members[v.Id]
if member != nil {
max := v.LastPostAt
if v.UpdateAt > max {
max = v.UpdateAt
}
delta += max - member.LastViewedAt
if member.LastViewedAt > t {
t = member.LastViewedAt
id = v.Id
}
}
}
return Etag(id, t, delta, len(o.Channels))
}
func ChannelListFromJson(data io.Reader) *ChannelList {
decoder := json.NewDecoder(data)
var o ChannelList
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}

75
model/channel_member.go Обычный файл
Просмотреть файл

@@ -0,0 +1,75 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
"strings"
)
const (
CHANNEL_ROLE_ADMIN = "admin"
CHANNEL_NOTIFY_ALL = "all"
CHANNEL_NOTIFY_MENTION = "mention"
CHANNEL_NOTIFY_NONE = "none"
CHANNEL_NOTIFY_QUIET = "quiet"
)
type ChannelMember struct {
ChannelId string `json:"channel_id"`
UserId string `json:"user_id"`
Roles string `json:"roles"`
LastViewedAt int64 `json:"last_viewed_at"`
MsgCount int64 `json:"msg_count"`
MentionCount int64 `json:"mention_count"`
NotifyLevel string `json:"notify_level"`
}
func (o *ChannelMember) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func ChannelMemberFromJson(data io.Reader) *ChannelMember {
decoder := json.NewDecoder(data)
var o ChannelMember
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}
func (o *ChannelMember) IsValid() *AppError {
if len(o.ChannelId) != 26 {
return NewAppError("ChannelMember.IsValid", "Invalid channel id", "")
}
if len(o.UserId) != 26 {
return NewAppError("ChannelMember.IsValid", "Invalid user id", "")
}
for _, role := range strings.Split(o.Roles, " ") {
if !(role == "" || role == CHANNEL_ROLE_ADMIN) {
return NewAppError("ChannelMember.IsValid", "Invalid role", "role="+role)
}
}
if len(o.NotifyLevel) > 20 || !IsChannelNotifyLevelValid(o.NotifyLevel) {
return NewAppError("ChannelMember.IsValid", "Invalid notify level", "notify_level="+o.NotifyLevel)
}
return nil
}
func IsChannelNotifyLevelValid(notifyLevel string) bool {
return notifyLevel == CHANNEL_NOTIFY_ALL || notifyLevel == CHANNEL_NOTIFY_MENTION || notifyLevel == CHANNEL_NOTIFY_NONE || notifyLevel == CHANNEL_NOTIFY_QUIET
}

60
model/channel_member_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,60 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestChannelMemberJson(t *testing.T) {
o := ChannelMember{ChannelId: NewId(), UserId: NewId()}
json := o.ToJson()
ro := ChannelMemberFromJson(strings.NewReader(json))
if o.ChannelId != ro.ChannelId {
t.Fatal("Ids do not match")
}
}
func TestChannelMemberIsValid(t *testing.T) {
o := ChannelMember{}
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.ChannelId = NewId()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Roles = "missing"
o.NotifyLevel = CHANNEL_NOTIFY_ALL
o.UserId = NewId()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Roles = CHANNEL_ROLE_ADMIN
o.NotifyLevel = "junk"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.NotifyLevel = "123456789012345678901"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.NotifyLevel = CHANNEL_NOTIFY_ALL
if err := o.IsValid(); err != nil {
t.Fatal(err)
}
o.Roles = ""
if err := o.IsValid(); err != nil {
t.Fatal(err)
}
}

81
model/channel_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,81 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestChannelJson(t *testing.T) {
o := Channel{Id: NewId(), Name: NewId()}
json := o.ToJson()
ro := ChannelFromJson(strings.NewReader(json))
if o.Id != ro.Id {
t.Fatal("Ids do not match")
}
}
func TestChannelIsValid(t *testing.T) {
o := Channel{}
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Id = NewId()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.CreateAt = GetMillis()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.UpdateAt = GetMillis()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.DisplayName = strings.Repeat("01234567890", 20)
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.DisplayName = "1234"
o.Name = "ZZZZZZZ"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Name = "zzzzz"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Type = "U"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Type = "P"
if err := o.IsValid(); err != nil {
t.Fatal(err)
}
}
func TestChannelPreSave(t *testing.T) {
o := Channel{Name: "test"}
o.PreSave()
o.Etag()
}
func TestChannelPreUpdate(t *testing.T) {
o := Channel{Name: "test"}
o.PreUpdate()
}

652
model/client.go Обычный файл
Просмотреть файл

@@ -0,0 +1,652 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
)
const (
HEADER_REQUEST_ID = "X-Request-ID"
HEADER_VERSION_ID = "X-Version-ID"
HEADER_ETAG_SERVER = "ETag"
HEADER_ETAG_CLIENT = "If-None-Match"
HEADER_FORWARDED = "X-Forwarded-For"
HEADER_FORWARDED_PROTO = "X-Forwarded-Proto"
HEADER_TOKEN = "token"
HEADER_AUTH = "Authorization"
)
type Result struct {
RequestId string
Etag string
Data interface{}
}
type Client struct {
Url string // The location of the server like "http://localhost/api/v1"
HttpClient *http.Client // The http client
AuthToken string
}
// NewClient constructs a new client with convienence methods for talking to
// the server.
func NewClient(url string) *Client {
return &Client{url, &http.Client{}, ""}
}
func (c *Client) DoPost(url string, data string) (*http.Response, *AppError) {
rq, _ := http.NewRequest("POST", c.Url+url, strings.NewReader(data))
if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
}
if rp, err := c.HttpClient.Do(rq); err != nil {
return nil, NewAppError(url, "We encountered an error while connecting to the server", err.Error())
} else if rp.StatusCode >= 300 {
return nil, AppErrorFromJson(rp.Body)
} else {
return rp, nil
}
}
func (c *Client) DoGet(url string, data string, etag string) (*http.Response, *AppError) {
rq, _ := http.NewRequest("GET", c.Url+url, strings.NewReader(data))
if len(etag) > 0 {
rq.Header.Set(HEADER_ETAG_CLIENT, etag)
}
if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
}
if rp, err := c.HttpClient.Do(rq); err != nil {
return nil, NewAppError(url, "We encountered an error while connecting to the server", err.Error())
} else if rp.StatusCode == 304 {
return rp, nil
} else if rp.StatusCode >= 300 {
return rp, AppErrorFromJson(rp.Body)
} else {
return rp, nil
}
}
func getCookie(name string, resp *http.Response) *http.Cookie {
for _, cookie := range resp.Cookies() {
if cookie.Name == name {
return cookie
}
}
return nil
}
func (c *Client) Must(result *Result, err *AppError) *Result {
if err != nil {
panic(err)
}
return result
}
func (c *Client) SignupTeam(email string, name string) (*Result, *AppError) {
m := make(map[string]string)
m["email"] = email
m["name"] = name
if r, err := c.DoPost("/teams/signup", MapToJson(m)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppError) {
if r, err := c.DoPost("/teams/create_from_signup", teamSignup.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), TeamSignupFromJson(r.Body)}, nil
}
}
func (c *Client) CreateTeam(team *Team) (*Result, *AppError) {
if r, err := c.DoPost("/teams/create", team.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil
}
}
func (c *Client) FindTeamByDomain(domain string, allServers bool) (*Result, *AppError) {
m := make(map[string]string)
m["domain"] = domain
m["all"] = fmt.Sprintf("%v", allServers)
if r, err := c.DoPost("/teams/find_team_by_domain", MapToJson(m)); err != nil {
return nil, err
} else {
val := false
if body, _ := ioutil.ReadAll(r.Body); string(body) == "true" {
val = true
}
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), val}, nil
}
}
func (c *Client) FindTeams(email string) (*Result, *AppError) {
m := make(map[string]string)
m["email"] = email
if r, err := c.DoPost("/teams/find_teams", MapToJson(m)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), ArrayFromJson(r.Body)}, nil
}
}
func (c *Client) FindTeamsSendEmail(email string) (*Result, *AppError) {
m := make(map[string]string)
m["email"] = email
if r, err := c.DoPost("/teams/email_teams", MapToJson(m)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), ArrayFromJson(r.Body)}, nil
}
}
func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) {
if r, err := c.DoPost("/teams/invite_members", invites.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), InvitesFromJson(r.Body)}, nil
}
}
func (c *Client) UpdateTeamName(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/teams/update_name", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) {
if r, err := c.DoPost("/users/create", user.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
}
}
func (c *Client) CreateUserFromSignup(user *User, data string, hash string) (*Result, *AppError) {
if r, err := c.DoPost("/users/create?d="+data+"&h="+hash, user.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
}
}
func (c *Client) GetUser(id string, etag string) (*Result, *AppError) {
if r, err := c.DoGet("/users/"+id, "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
}
}
func (c *Client) GetMe(etag string) (*Result, *AppError) {
if r, err := c.DoGet("/users/me", "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
}
}
func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) {
if r, err := c.DoGet("/users/profiles", "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), UserMapFromJson(r.Body)}, nil
}
}
func (c *Client) LoginById(id string, password string) (*Result, *AppError) {
m := make(map[string]string)
m["id"] = id
m["password"] = password
return c.login(m)
}
func (c *Client) LoginByEmail(domain string, email string, password string) (*Result, *AppError) {
m := make(map[string]string)
m["domain"] = domain
m["email"] = email
m["password"] = password
return c.login(m)
}
func (c *Client) LoginByEmailWithDevice(domain string, email string, password string, deviceId string) (*Result, *AppError) {
m := make(map[string]string)
m["domain"] = domain
m["email"] = email
m["password"] = password
m["device_id"] = deviceId
return c.login(m)
}
func (c *Client) login(m map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/users/login", MapToJson(m)); err != nil {
return nil, err
} else {
c.AuthToken = r.Header.Get(HEADER_TOKEN)
sessionId := getCookie(SESSION_TOKEN, r)
if c.AuthToken != sessionId.Value {
NewAppError("/users/login", "Authentication tokens didn't match", "")
}
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
}
}
func (c *Client) Logout() (*Result, *AppError) {
if r, err := c.DoPost("/users/logout", ""); err != nil {
return nil, err
} else {
c.AuthToken = ""
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) RevokeSession(sessionAltId string) (*Result, *AppError) {
m := make(map[string]string)
m["id"] = sessionAltId
if r, err := c.DoPost("/users/revoke_session", MapToJson(m)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) GetSessions(id string) (*Result, *AppError) {
if r, err := c.DoGet("/users/"+id+"/sessions", "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), SessionsFromJson(r.Body)}, nil
}
}
func (c *Client) Command(channelId string, command string, suggest bool) (*Result, *AppError) {
m := make(map[string]string)
m["command"] = command
m["channelId"] = channelId
m["suggest"] = strconv.FormatBool(suggest)
if r, err := c.DoPost("/command", MapToJson(m)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), CommandFromJson(r.Body)}, nil
}
}
func (c *Client) GetAudits(id string, etag string) (*Result, *AppError) {
if r, err := c.DoGet("/users/"+id+"/audits", "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), AuditsFromJson(r.Body)}, nil
}
}
func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) {
if r, err := c.DoPost("/channels/create", channel.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil
}
}
func (c *Client) CreateDirectChannel(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/create_direct", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil
}
}
func (c *Client) UpdateChannel(channel *Channel) (*Result, *AppError) {
if r, err := c.DoPost("/channels/update", channel.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil
}
}
func (c *Client) UpdateChannelDesc(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/update_desc", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), ChannelFromJson(r.Body)}, nil
}
}
func (c *Client) UpdateNotifyLevel(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/update_notify_level", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) GetChannels(etag string) (*Result, *AppError) {
if r, err := c.DoGet("/channels/", "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil
}
}
func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) {
if r, err := c.DoGet("/channels/more", "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), ChannelListFromJson(r.Body)}, nil
}
}
func (c *Client) JoinChannel(id string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+id+"/join", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
}
}
func (c *Client) LeaveChannel(id string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+id+"/leave", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
}
}
func (c *Client) DeleteChannel(id string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+id+"/delete", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
}
}
func (c *Client) AddChannelMember(id, user_id string) (*Result, *AppError) {
data := make(map[string]string)
data["user_id"] = user_id
if r, err := c.DoPost("/channels/"+id+"/add", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
}
}
func (c *Client) RemoveChannelMember(id, user_id string) (*Result, *AppError) {
data := make(map[string]string)
data["user_id"] = user_id
if r, err := c.DoPost("/channels/"+id+"/remove", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
}
}
func (c *Client) UpdateLastViewedAt(channelId string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+channelId+"/update_last_viewed_at", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), nil}, nil
}
}
func (c *Client) GetChannelExtraInfo(id string) (*Result, *AppError) {
if r, err := c.DoGet("/channels/"+id+"/extra_info", "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), ChannelExtraFromJson(r.Body)}, nil
}
}
func (c *Client) CreatePost(post *Post) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+post.ChannelId+"/create", post.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil
}
}
func (c *Client) CreateValetPost(post *Post) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+post.ChannelId+"/valet_create", post.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil
}
}
func (c *Client) UpdatePost(post *Post) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+post.ChannelId+"/update", post.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), PostFromJson(r.Body)}, nil
}
}
func (c *Client) GetPosts(channelId string, offset int, limit int, etag string) (*Result, *AppError) {
if r, err := c.DoGet(fmt.Sprintf("/channels/%v/posts/%v/%v", channelId, offset, limit), "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
}
}
func (c *Client) GetPost(channelId string, postId string, etag string) (*Result, *AppError) {
if r, err := c.DoGet(fmt.Sprintf("/channels/%v/post/%v", channelId, postId), "", etag); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
}
}
func (c *Client) DeletePost(channelId string, postId string) (*Result, *AppError) {
if r, err := c.DoPost(fmt.Sprintf("/channels/%v/post/%v/delete", channelId, postId), ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) SearchPosts(terms string) (*Result, *AppError) {
if r, err := c.DoGet("/posts/search?terms="+url.QueryEscape(terms), "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), PostListFromJson(r.Body)}, nil
}
}
func (c *Client) UploadFile(url string, data []byte, contentType string) (*Result, *AppError) {
rq, _ := http.NewRequest("POST", c.Url+url, bytes.NewReader(data))
rq.Header.Set("Content-Type", contentType)
if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
}
if rp, err := c.HttpClient.Do(rq); err != nil {
return nil, NewAppError(url, "We encountered an error while connecting to the server", err.Error())
} else if rp.StatusCode >= 300 {
return nil, AppErrorFromJson(rp.Body)
} else {
return &Result{rp.Header.Get(HEADER_REQUEST_ID),
rp.Header.Get(HEADER_ETAG_SERVER), FileUploadResponseFromJson(rp.Body)}, nil
}
}
func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) {
var rq *http.Request
if isFullUrl {
rq, _ = http.NewRequest("GET", url, nil)
} else {
rq, _ = http.NewRequest("GET", c.Url+url, nil)
}
if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
}
if rp, err := c.HttpClient.Do(rq); err != nil {
return nil, NewAppError(url, "We encountered an error while connecting to the server", err.Error())
} else if rp.StatusCode >= 300 {
return nil, AppErrorFromJson(rp.Body)
} else {
return &Result{rp.Header.Get(HEADER_REQUEST_ID),
rp.Header.Get(HEADER_ETAG_SERVER), rp.Body}, nil
}
}
func (c *Client) GetPublicLink(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/files/get_public_link", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) UpdateUser(user *User) (*Result, *AppError) {
if r, err := c.DoPost("/users/update", user.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
}
}
func (c *Client) UpdateUserRoles(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/users/update_roles", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
}
}
func (c *Client) UpdateActive(userId string, active bool) (*Result, *AppError) {
data := make(map[string]string)
data["user_id"] = userId
data["active"] = strconv.FormatBool(active)
if r, err := c.DoPost("/users/update_active", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
}
}
func (c *Client) UpdateUserNotify(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/users/update_notify", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
}
}
func (c *Client) UpdateUserPassword(userId, currentPassword, newPassword string) (*Result, *AppError) {
data := make(map[string]string)
data["current_password"] = currentPassword
data["new_password"] = newPassword
data["user_id"] = userId
if r, err := c.DoPost("/users/newpassword", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), UserFromJson(r.Body)}, nil
}
}
func (c *Client) SendPasswordReset(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/users/send_password_reset", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) ResetPassword(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/users/reset_password", MapToJson(data)); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) GetStatuses() (*Result, *AppError) {
if r, err := c.DoGet("/users/status", "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) MockSession(sessionToken string) {
c.AuthToken = sessionToken
}

53
model/command.go Обычный файл
Просмотреть файл

@@ -0,0 +1,53 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
RESP_EXECUTED = "executed"
)
type Command struct {
Command string `json:"command"`
Response string `json:"reponse"`
GotoLocation string `json:"goto_location"`
ChannelId string `json:"channel_id"`
Suggest bool `json:"-"`
Suggestions []*SuggestCommand `json:"suggestions"`
}
func (o *Command) AddSuggestion(suggest *SuggestCommand) {
if o.Suggest {
if o.Suggestions == nil {
o.Suggestions = make([]*SuggestCommand, 0, 128)
}
o.Suggestions = append(o.Suggestions, suggest)
}
}
func (o *Command) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func CommandFromJson(data io.Reader) *Command {
decoder := json.NewDecoder(data)
var o Command
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}

25
model/command_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,25 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestCommandJson(t *testing.T) {
command := &Command{Command: NewId(), Suggest: true}
command.AddSuggestion(&SuggestCommand{Suggestion: NewId()})
json := command.ToJson()
result := CommandFromJson(strings.NewReader(json))
if command.Command != result.Command {
t.Fatal("Ids do not match")
}
if command.Suggestions[0].Suggestion != result.Suggestions[0].Suggestion {
t.Fatal("Ids do not match")
}
}

42
model/file.go Обычный файл
Просмотреть файл

@@ -0,0 +1,42 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
MAX_FILE_SIZE = 50000000 // 50 MB
)
var (
IMAGE_EXTENSIONS = [4]string{".jpg", ".gif", ".bmp", ".png"}
IMAGE_MIME_TYPES = map[string]string{".jpg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".png": "image/png", ".tiff": "image/tiff"}
)
type FileUploadResponse struct {
Filenames []string `json:"filenames"`
}
func FileUploadResponseFromJson(data io.Reader) *FileUploadResponse {
decoder := json.NewDecoder(data)
var o FileUploadResponse
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}
func (o *FileUploadResponse) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}

54
model/message.go Обычный файл
Просмотреть файл

@@ -0,0 +1,54 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
ACTION_TYPING = "typing"
ACTION_POSTED = "posted"
ACTION_POST_EDITED = "post_edited"
ACTION_POST_DELETED = "post_deleted"
ACTION_VIEWED = "viewed"
ACTION_NEW_USER = "new_user"
)
type Message struct {
TeamId string `json:"team_id"`
ChannelId string `json:"channel_id"`
UserId string `json:"user_id"`
Action string `json:"action"`
Props map[string]string `json:"props"`
}
func (m *Message) Add(key string, value string) {
m.Props[key] = value
}
func NewMessage(teamId string, channekId string, userId string, action string) *Message {
return &Message{TeamId: teamId, ChannelId: channekId, UserId: userId, Action: action, Props: make(map[string]string)}
}
func (o *Message) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func MessageFromJson(data io.Reader) *Message {
decoder := json.NewDecoder(data)
var o Message
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}

24
model/message_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,24 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestMessgaeJson(t *testing.T) {
m := NewMessage(NewId(), NewId(), NewId(), ACTION_TYPING)
m.Add("RootId", NewId())
json := m.ToJson()
result := MessageFromJson(strings.NewReader(json))
if m.TeamId != result.TeamId {
t.Fatal("Ids do not match")
}
if m.Props["RootId"] != result.Props["RootId"] {
t.Fatal("Ids do not match")
}
}

147
model/post.go Обычный файл
Просмотреть файл

@@ -0,0 +1,147 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
POST_DEFAULT = ""
)
type Post struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
RootId string `json:"root_id"`
ParentId string `json:"parent_id"`
OriginalId string `json:"original_id"`
Message string `json:"message"`
ImgCount int64 `json:"img_count"`
Type string `json:"type"`
Props StringMap `json:"props"`
Hashtags string `json:"hashtags"`
Filenames StringArray `json:"filenames"`
}
func (o *Post) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func PostFromJson(data io.Reader) *Post {
decoder := json.NewDecoder(data)
var o Post
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}
func (o *Post) Etag() string {
return Etag(o.Id, o.UpdateAt)
}
func (o *Post) IsValid() *AppError {
if len(o.Id) != 26 {
return NewAppError("Post.IsValid", "Invalid Id", "")
}
if o.CreateAt == 0 {
return NewAppError("Post.IsValid", "Create at must be a valid time", "id="+o.Id)
}
if o.UpdateAt == 0 {
return NewAppError("Post.IsValid", "Update at must be a valid time", "id="+o.Id)
}
if len(o.UserId) != 26 {
return NewAppError("Post.IsValid", "Invalid user id", "")
}
if len(o.ChannelId) != 26 {
return NewAppError("Post.IsValid", "Invalid channel id", "")
}
if !(len(o.RootId) == 26 || len(o.RootId) == 0) {
return NewAppError("Post.IsValid", "Invalid root id", "")
}
if !(len(o.ParentId) == 26 || len(o.ParentId) == 0) {
return NewAppError("Post.IsValid", "Invalid parent id", "")
}
if len(o.ParentId) == 26 && len(o.RootId) == 0 {
return NewAppError("Post.IsValid", "Invalid root id must be set if parent id set", "")
}
if !(len(o.OriginalId) == 26 || len(o.OriginalId) == 0) {
return NewAppError("Post.IsValid", "Invalid original id", "")
}
if len(o.Message) > 4000 {
return NewAppError("Post.IsValid", "Invalid message", "id="+o.Id)
}
if len(o.Hashtags) > 1000 {
return NewAppError("Post.IsValid", "Invalid hashtags", "id="+o.Id)
}
if !(o.Type == POST_DEFAULT) {
return NewAppError("Post.IsValid", "Invalid type", "id="+o.Type)
}
if len(ArrayToJson(o.Filenames)) > 4000 {
return NewAppError("Post.IsValid", "Invalid filenames", "id="+o.Id)
}
return nil
}
func (o *Post) PreSave() {
if o.Id == "" {
o.Id = NewId()
}
o.OriginalId = ""
o.CreateAt = GetMillis()
o.UpdateAt = o.CreateAt
if o.Props == nil {
o.Props = make(map[string]string)
}
if o.Filenames == nil {
o.Filenames = []string{}
}
}
func (o *Post) MakeNonNil() {
if o.Props == nil {
o.Props = make(map[string]string)
}
if o.Filenames == nil {
o.Filenames = []string{}
}
}
func (o *Post) AddProp(key string, value string) {
o.MakeNonNil()
o.Props[key] = value
}

91
model/post_list.go Обычный файл
Просмотреть файл

@@ -0,0 +1,91 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
type PostList struct {
Order []string `json:"order"`
Posts map[string]*Post `json:"posts"`
}
func (o *PostList) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func (o *PostList) MakeNonNil() {
if o.Order == nil {
o.Order = make([]string, 0)
}
if o.Posts == nil {
o.Posts = make(map[string]*Post)
}
for _, v := range o.Posts {
v.MakeNonNil()
}
}
func (o *PostList) AddOrder(id string) {
if o.Order == nil {
o.Order = make([]string, 0, 128)
}
o.Order = append(o.Order, id)
}
func (o *PostList) AddPost(post *Post) {
if o.Posts == nil {
o.Posts = make(map[string]*Post)
}
o.Posts[post.Id] = post
}
func (o *PostList) Etag() string {
id := "0"
var t int64 = 0
for _, v := range o.Posts {
if v.UpdateAt > t {
t = v.UpdateAt
id = v.Id
}
}
return Etag(id, t)
}
func (o *PostList) IsChannelId(channelId string) bool {
for _, v := range o.Posts {
if v.ChannelId != channelId {
return false
}
}
return true
}
func PostListFromJson(data io.Reader) *PostList {
decoder := json.NewDecoder(data)
var o PostList
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}

36
model/post_list_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,36 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestPostListJson(t *testing.T) {
pl := PostList{}
p1 := &Post{Id: NewId(), Message: NewId()}
pl.AddPost(p1)
p2 := &Post{Id: NewId(), Message: NewId()}
pl.AddPost(p2)
pl.AddOrder(p1.Id)
pl.AddOrder(p2.Id)
json := pl.ToJson()
rpl := PostListFromJson(strings.NewReader(json))
if rpl.Posts[p1.Id].Message != p1.Message {
t.Fatal("failed to serialize")
}
if rpl.Posts[p2.Id].Message != p2.Message {
t.Fatal("failed to serialize")
}
if rpl.Order[1] != p2.Id {
t.Fatal("failed to serialize")
}
}

87
model/post_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,87 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestPostJson(t *testing.T) {
o := Post{Id: NewId(), Message: NewId()}
json := o.ToJson()
ro := PostFromJson(strings.NewReader(json))
if o.Id != ro.Id {
t.Fatal("Ids do not match")
}
}
func TestPostIsValid(t *testing.T) {
o := Post{}
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Id = NewId()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.CreateAt = GetMillis()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.UpdateAt = GetMillis()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.UserId = NewId()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.ChannelId = NewId()
o.RootId = "123"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.RootId = ""
o.ParentId = "123"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.ParentId = NewId()
o.RootId = ""
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.ParentId = ""
o.Message = strings.Repeat("0", 4001)
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Message = strings.Repeat("0", 4000)
if err := o.IsValid(); err != nil {
t.Fatal(err)
}
o.Message = "test"
if err := o.IsValid(); err != nil {
t.Fatal(err)
}
}
func TestPostPreSave(t *testing.T) {
o := Post{Message: "test"}
o.PreSave()
o.Etag()
}

119
model/session.go Обычный файл
Просмотреть файл

@@ -0,0 +1,119 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
SESSION_TOKEN = "MMSID"
SESSION_TIME_WEB_IN_DAYS = 365
SESSION_TIME_WEB_IN_SECS = 60 * 60 * 24 * SESSION_TIME_WEB_IN_DAYS
SESSION_TIME_MOBILE_IN_DAYS = 365
SESSION_TIME_MOBILE_IN_SECS = 60 * 60 * 24 * SESSION_TIME_MOBILE_IN_DAYS
SESSION_CACHE_IN_SECS = 60 * 10
SESSION_CACHE_SIZE = 10000
SESSION_PROP_PLATFORM = "platform"
SESSION_PROP_OS = "os"
SESSION_PROP_BROWSER = "browser"
)
type Session struct {
Id string `json:"id"`
AltId string `json:"alt_id"`
CreateAt int64 `json:"create_at"`
ExpiresAt int64 `json:"expires_at"`
LastActivityAt int64 `json:"last_activity_at"`
UserId string `json:"user_id"`
TeamId string `json:"team_id"`
DeviceId string `json:"device_id"`
Roles string `json:"roles"`
Props StringMap `json:"props"`
}
func (me *Session) ToJson() string {
b, err := json.Marshal(me)
if err != nil {
return ""
} else {
return string(b)
}
}
func SessionFromJson(data io.Reader) *Session {
decoder := json.NewDecoder(data)
var me Session
err := decoder.Decode(&me)
if err == nil {
return &me
} else {
return nil
}
}
func (me *Session) PreSave() {
if me.Id == "" {
me.Id = NewId()
}
me.AltId = NewId()
me.CreateAt = GetMillis()
me.LastActivityAt = me.CreateAt
if me.Props == nil {
me.Props = make(map[string]string)
}
}
func (me *Session) Sanitize() {
me.Id = ""
}
func (me *Session) IsExpired() bool {
if me.ExpiresAt <= 0 {
return false
}
if GetMillis() > me.ExpiresAt {
return true
}
return false
}
func (me *Session) SetExpireInDays(days int64) {
me.ExpiresAt = GetMillis() + (1000 * 60 * 60 * 24 * days)
}
func (me *Session) AddProp(key string, value string) {
if me.Props == nil {
me.Props = make(map[string]string)
}
me.Props[key] = value
}
func SessionsToJson(o []*Session) string {
if b, err := json.Marshal(o); err != nil {
return "[]"
} else {
return string(b)
}
}
func SessionsFromJson(data io.Reader) []*Session {
decoder := json.NewDecoder(data)
var o []*Session
err := decoder.Decode(&o)
if err == nil {
return o
} else {
return nil
}
}

35
model/session_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,35 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
"time"
)
func TestSessionJson(t *testing.T) {
session := Session{}
session.PreSave()
json := session.ToJson()
rsession := SessionFromJson(strings.NewReader(json))
if rsession.Id != session.Id {
t.Fatal("Ids do not match")
}
session.Sanitize()
if session.IsExpired() {
t.Fatal("Shouldn't expire")
}
session.ExpiresAt = GetMillis()
time.Sleep(10 * time.Millisecond)
if !session.IsExpired() {
t.Fatal("Should expire")
}
session.SetExpireInDays(10)
}

34
model/suggest_command.go Обычный файл
Просмотреть файл

@@ -0,0 +1,34 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
type SuggestCommand struct {
Suggestion string `json:"suggestion"`
Description string `json:"description"`
}
func (o *SuggestCommand) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func SuggestCommandFromJson(data io.Reader) *SuggestCommand {
decoder := json.NewDecoder(data)
var o SuggestCommand
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}

19
model/suggest_command_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,19 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestSuggestCommandJson(t *testing.T) {
command := &SuggestCommand{Suggestion: NewId()}
json := command.ToJson()
result := SuggestCommandFromJson(strings.NewReader(json))
if command.Suggestion != result.Suggestion {
t.Fatal("Ids do not match")
}
}

141
model/team.go Обычный файл
Просмотреть файл

@@ -0,0 +1,141 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
TEAM_OPEN = "O"
TEAM_INVITE = "I"
)
type Team struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
Name string `json:"name"`
Domain string `json:"domain"`
Email string `json:"email"`
Type string `json:"type"`
CompanyName string `json:"company_name"`
AllowedDomains string `json:"allowed_domains"`
}
type Invites struct {
Invites []map[string]string `json:"invites"`
}
func InvitesFromJson(data io.Reader) *Invites {
decoder := json.NewDecoder(data)
var o Invites
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}
func (o *Invites) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func (o *Team) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}
func TeamFromJson(data io.Reader) *Team {
decoder := json.NewDecoder(data)
var o Team
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
return nil
}
}
func (o *Team) Etag() string {
return Etag(o.Id, o.UpdateAt)
}
func (o *Team) IsValid() *AppError {
if len(o.Id) != 26 {
return NewAppError("Team.IsValid", "Invalid Id", "")
}
if o.CreateAt == 0 {
return NewAppError("Team.IsValid", "Create at must be a valid time", "id="+o.Id)
}
if o.UpdateAt == 0 {
return NewAppError("Team.IsValid", "Update at must be a valid time", "id="+o.Id)
}
if len(o.Email) > 128 {
return NewAppError("Team.IsValid", "Invalid email", "id="+o.Id)
}
if !IsValidEmail(o.Email) {
return NewAppError("Team.IsValid", "Invalid email", "id="+o.Id)
}
if len(o.Name) > 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 IsReservedDomain(o.Domain) {
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 !(o.Type == TEAM_OPEN || o.Type == TEAM_INVITE) {
return NewAppError("Team.IsValid", "Invalid type", "id="+o.Id)
}
if len(o.CompanyName) > 64 {
return NewAppError("Team.IsValid", "Invalid company name", "id="+o.Id)
}
if len(o.AllowedDomains) > 500 {
return NewAppError("Team.IsValid", "Invalid allowed domains", "id="+o.Id)
}
return nil
}
func (o *Team) PreSave() {
if o.Id == "" {
o.Id = NewId()
}
o.CreateAt = GetMillis()
o.UpdateAt = o.CreateAt
}
func (o *Team) PreUpdate() {
o.UpdateAt = GetMillis()
}

40
model/team_signup.go Обычный файл
Просмотреть файл

@@ -0,0 +1,40 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
)
type TeamSignup struct {
Team Team `json:"team"`
User User `json:"user"`
Invites []string `json:"invites"`
Data string `json:"data"`
Hash string `json:"hash"`
}
func TeamSignupFromJson(data io.Reader) *TeamSignup {
decoder := json.NewDecoder(data)
var o TeamSignup
err := decoder.Decode(&o)
if err == nil {
return &o
} else {
fmt.Println(err)
return nil
}
}
func (o *TeamSignup) ToJson() string {
b, err := json.Marshal(o)
if err != nil {
return ""
} else {
return string(b)
}
}

20
model/team_signup_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,20 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestTeamSignupJson(t *testing.T) {
team := Team{Id: NewId(), Name: NewId()}
o := TeamSignup{Team: team, Data: "data"}
json := o.ToJson()
ro := TeamSignupFromJson(strings.NewReader(json))
if o.Team.Id != ro.Team.Id {
t.Fatal("Ids do not match")
}
}

76
model/team_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,76 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestTeamJson(t *testing.T) {
o := Team{Id: NewId(), Name: NewId()}
json := o.ToJson()
ro := TeamFromJson(strings.NewReader(json))
if o.Id != ro.Id {
t.Fatal("Ids do not match")
}
}
func TestTeamIsValid(t *testing.T) {
o := Team{}
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Id = NewId()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.CreateAt = GetMillis()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.UpdateAt = GetMillis()
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Email = strings.Repeat("01234567890", 20)
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Email = "corey@hulen.com"
o.Name = strings.Repeat("01234567890", 20)
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Name = "1234"
o.Domain = "ZZZZZZZ"
if err := o.IsValid(); err == nil {
t.Fatal("should be invalid")
}
o.Domain = "zzzzz"
o.Type = TEAM_OPEN
if err := o.IsValid(); err != nil {
t.Fatal(err)
}
}
func TestTeamPreSave(t *testing.T) {
o := Team{Name: "test"}
o.PreSave()
o.Etag()
}
func TestTeamPreUpdate(t *testing.T) {
o := Team{Name: "test"}
o.PreUpdate()
}

293
model/user.go Обычный файл
Просмотреть файл

@@ -0,0 +1,293 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"code.google.com/p/go.crypto/bcrypt"
"encoding/json"
"io"
"regexp"
"strings"
)
const (
ROLE_ADMIN = "admin"
ROLE_SYSTEM_ADMIN = "system_admin"
ROLE_SYSTEM_SUPPORT = "system_support"
USER_AWAY_TIMEOUT = 5 * 60 * 1000 // 5 minutes
USER_OFFLINE_TIMEOUT = 5 * 60 * 1000 // 5 minutes
USER_OFFLINE = "offline"
USER_AWAY = "away"
USER_ONLINE = "online"
USER_NOTIFY_ALL = "all"
USER_NOTIFY_MENTION = "mention"
USER_NOTIFY_NONE = "none"
BOT_USERNAME = "valet"
)
type User struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
TeamId string `json:"team_id"`
Username string `json:"username"`
Password string `json:"password"`
AuthData string `json:"auth_data"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
FullName string `json:"full_name"`
Roles string `json:"roles"`
LastActivityAt int64 `json:"last_activity_at"`
LastPingAt int64 `json:"last_ping_at"`
AllowMarketing bool `json:"allow_marketing"`
Props StringMap `json:"props"`
NotifyProps StringMap `json:"notify_props"`
LastPasswordUpdate int64 `json:"last_password_update"`
}
// IsValid validates the user and returns an error if it isn't configured
// correctly.
func (u *User) IsValid() *AppError {
if len(u.Id) != 26 {
return NewAppError("User.IsValid", "Invalid user id", "")
}
if u.CreateAt == 0 {
return NewAppError("User.IsValid", "Create at must be a valid time", "user_id="+u.Id)
}
if u.UpdateAt == 0 {
return NewAppError("User.IsValid", "Update at must be a valid time", "user_id="+u.Id)
}
if len(u.TeamId) != 26 {
return NewAppError("User.IsValid", "Invalid team id", "")
}
if len(u.Username) == 0 || len(u.Username) > 64 {
return NewAppError("User.IsValid", "Invalid username", "user_id="+u.Id)
}
validChars, _ := regexp.Compile("^[a-z0-9\\.\\-\\_]+$")
if !validChars.MatchString(u.Username) {
return NewAppError("User.IsValid", "Invalid username", "user_id="+u.Id)
}
if len(u.Email) > 128 || len(u.Email) == 0 {
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)
}
return nil
}
// PreSave will set the Id and Username if missing. It will also fill
// in the CreateAt, UpdateAt times. It will also hash the password. It should
// be run before saving the user to the db.
func (u *User) PreSave() {
if u.Id == "" {
u.Id = NewId()
}
if u.Username == "" {
u.Username = NewId()
}
u.Username = strings.ToLower(u.Username)
u.Email = strings.ToLower(u.Email)
u.CreateAt = GetMillis()
u.UpdateAt = u.CreateAt
u.LastPasswordUpdate = u.CreateAt
if u.Props == nil {
u.Props = make(map[string]string)
}
if u.NotifyProps == nil || len(u.NotifyProps) == 0 {
u.SetDefaultNotifications()
}
if len(u.Password) > 0 {
u.Password = HashPassword(u.Password)
}
}
// PreUpdate should be run before updating the user in the db.
func (u *User) PreUpdate() {
u.Username = strings.ToLower(u.Username)
u.Email = strings.ToLower(u.Email)
u.UpdateAt = GetMillis()
if u.NotifyProps == nil || len(u.NotifyProps) == 0 {
u.SetDefaultNotifications()
} else if _, ok := u.NotifyProps["mention_keys"]; ok {
// Remove any blank mention keys
splitKeys := strings.Split(u.NotifyProps["mention_keys"], ",")
goodKeys := []string{}
for _, key := range splitKeys {
if len(key) > 0 {
goodKeys = append(goodKeys, strings.ToLower(key))
}
}
u.NotifyProps["mention_keys"] = strings.Join(goodKeys, ",")
}
}
func (u *User) SetDefaultNotifications() {
u.NotifyProps = make(map[string]string)
u.NotifyProps["email"] = "true"
u.NotifyProps["desktop"] = USER_NOTIFY_ALL
u.NotifyProps["desktop_sound"] = "true"
u.NotifyProps["mention_keys"] = u.Username
u.NotifyProps["first_name"] = "true"
splitName := strings.Split(u.FullName, " ")
if len(splitName) > 0 && splitName[0] != "" {
u.NotifyProps["mention_keys"] += "," + splitName[0]
}
}
// ToJson convert a User to a json string
func (u *User) ToJson() string {
b, err := json.Marshal(u)
if err != nil {
return ""
} else {
return string(b)
}
}
// Generate a valid strong etag so the browser can cache the results
func (u *User) Etag() string {
return Etag(u.Id, u.UpdateAt)
}
func (u *User) IsOffline() bool {
return (GetMillis()-u.LastPingAt) > USER_OFFLINE_TIMEOUT && (GetMillis()-u.LastActivityAt) > USER_OFFLINE_TIMEOUT
}
func (u *User) IsAway() bool {
return (GetMillis() - u.LastActivityAt) > USER_AWAY_TIMEOUT
}
// Remove any private data from the user object
func (u *User) Sanitize(options map[string]bool) {
u.Password = ""
u.AuthData = ""
if len(options) != 0 && !options["email"] {
u.Email = ""
}
if len(options) != 0 && !options["fullname"] {
u.FullName = ""
}
if len(options) != 0 && !options["skypeid"] {
// TODO - fill in when SkypeId is added to user model
}
if len(options) != 0 && !options["phonenumber"] {
// TODO - fill in when PhoneNumber is added to user model
}
if len(options) != 0 && !options["passwordupadte"] {
u.LastPasswordUpdate = 0
}
}
func (u *User) MakeNonNil() {
if u.Props == nil {
u.Props = make(map[string]string)
}
if u.NotifyProps == nil {
u.NotifyProps = make(map[string]string)
}
}
func (u *User) AddProp(key string, value string) {
u.MakeNonNil()
u.Props[key] = value
}
func (u *User) AddNotifyProp(key string, value string) {
u.MakeNonNil()
u.NotifyProps[key] = value
}
// UserFromJson will decode the input and return a User
func UserFromJson(data io.Reader) *User {
decoder := json.NewDecoder(data)
var user User
err := decoder.Decode(&user)
if err == nil {
return &user
} else {
return nil
}
}
func UserMapToJson(u map[string]*User) string {
b, err := json.Marshal(u)
if err != nil {
return ""
} else {
return string(b)
}
}
func UserMapFromJson(data io.Reader) map[string]*User {
decoder := json.NewDecoder(data)
var users map[string]*User
err := decoder.Decode(&users)
if err == nil {
return users
} else {
return nil
}
}
// HashPassword generates a hash using the bcrypt.GenerateFromPassword
func HashPassword(password string) string {
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
if err != nil {
panic(err)
}
return string(hash)
}
// ComparePassword compares the hash
func ComparePassword(hash string, password string) bool {
if len(password) == 0 {
return false
}
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
func IsUsernameValid(username string) bool {
var restrictedUsernames = []string {
BOT_USERNAME,
"all",
"channel",
}
for _,restrictedUsername := range restrictedUsernames {
if username == restrictedUsername {
return false
}
}
return true
}

92
model/user_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,92 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestPasswordHash(t *testing.T) {
hash := HashPassword("Test")
if !ComparePassword(hash, "Test") {
t.Fatal("Passwords don't match")
}
if ComparePassword(hash, "Test2") {
t.Fatal("Passwords should not have matched")
}
}
func TestUserJson(t *testing.T) {
user := User{Id: NewId(), Username: NewId()}
json := user.ToJson()
ruser := UserFromJson(strings.NewReader(json))
if user.Id != ruser.Id {
t.Fatal("Ids do not match")
}
}
func TestUserPreSave(t *testing.T) {
user := User{Password: "test"}
user.PreSave()
user.Etag()
}
func TestUserPreUpdate(t *testing.T) {
user := User{Password: "test"}
user.PreUpdate()
}
func TestUserIsValid(t *testing.T) {
user := User{}
if err := user.IsValid(); err == nil {
t.Fatal()
}
user.Id = NewId()
if err := user.IsValid(); err == nil {
t.Fatal()
}
user.CreateAt = GetMillis()
if err := user.IsValid(); err == nil {
t.Fatal()
}
user.UpdateAt = GetMillis()
if err := user.IsValid(); err == nil {
t.Fatal()
}
user.TeamId = NewId()
if err := user.IsValid(); err == nil {
t.Fatal()
}
user.Username = NewId() + "^hello#"
if err := user.IsValid(); err == nil {
t.Fatal()
}
user.Username = NewId()
user.Email = strings.Repeat("01234567890", 20)
if err := user.IsValid(); err == nil {
t.Fatal()
}
user.Email = "test@nowhere.com"
user.FullName = strings.Repeat("01234567890", 20)
if err := user.IsValid(); err == nil {
t.Fatal()
}
user.FullName = ""
if err := user.IsValid(); err != nil {
t.Fatal(err)
}
}

320
model/utils.go Обычный файл
Просмотреть файл

@@ -0,0 +1,320 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"bytes"
"code.google.com/p/go-uuid/uuid"
"encoding/base32"
"encoding/json"
"fmt"
"io"
"net/mail"
"regexp"
"strings"
"time"
)
const (
ETAG_ROOT_VERSION = "10"
)
type StringMap map[string]string
type StringArray []string
type EncryptStringMap map[string]string
// AppError is returned for any http response that's not in the 200 range.
type AppError struct {
Message string `json:"message"` // Message to be display to the end user without debugging information
DetailedError string `json:"detailed_error"` // Internal error string to help the developer
RequestId string `json:"request_id"` // The RequestId that's also set in the header
StatusCode int `json:"status_code"` // The http status code
Where string `json:"-"` // The function where it happened in the form of Struct.Func
}
func (er *AppError) Error() string {
return er.Where + ": " + er.Message + ", " + er.DetailedError
}
func (er *AppError) ToJson() string {
b, err := json.Marshal(er)
if err != nil {
return ""
} else {
return string(b)
}
}
// AppErrorFromJson will decode the input and return an AppError
func AppErrorFromJson(data io.Reader) *AppError {
decoder := json.NewDecoder(data)
var er AppError
err := decoder.Decode(&er)
if err == nil {
return &er
} else {
return nil
}
}
func NewAppError(where string, message string, details string) *AppError {
ap := &AppError{}
ap.Message = message
ap.Where = where
ap.DetailedError = details
ap.StatusCode = 500
return ap
}
var encoding = base32.NewEncoding("ybndrfg8ejkmcpqxot1uwisza345h769")
// NewId is a globally unique identifier. It is a [A-Z0-9] string 26
// characters long. It is a UUID version 4 Guid that is zbased32 encoded
// with the padding stripped off.
func NewId() string {
var b bytes.Buffer
encoder := base32.NewEncoder(encoding, &b)
encoder.Write(uuid.NewRandom())
encoder.Close()
b.Truncate(26) // removes the '==' padding
return b.String()
}
// GetMillis is a convience method to get milliseconds since epoch.
func GetMillis() int64 {
return time.Now().UnixNano() / int64(time.Millisecond)
}
// MapToJson converts a map to a json string
func MapToJson(objmap map[string]string) string {
if b, err := json.Marshal(objmap); err != nil {
return ""
} else {
return string(b)
}
}
// MapFromJson will decode the key/value pair map
func MapFromJson(data io.Reader) map[string]string {
decoder := json.NewDecoder(data)
var objmap map[string]string
if err := decoder.Decode(&objmap); err != nil {
return make(map[string]string)
} else {
return objmap
}
}
func ArrayToJson(objmap []string) string {
if b, err := json.Marshal(objmap); err != nil {
return ""
} else {
return string(b)
}
}
func ArrayFromJson(data io.Reader) []string {
decoder := json.NewDecoder(data)
var objmap []string
if err := decoder.Decode(&objmap); err != nil {
return make([]string, 0)
} else {
return objmap
}
}
func IsLower(s string) bool {
if strings.ToLower(s) == s {
return true
}
return false
}
func IsValidEmail(email string) bool {
if !IsLower(email) {
return false
}
if _, err := mail.ParseAddress(email); err == nil {
return true
}
return false
}
var reservedDomains = []string{
"www",
"web",
"admin",
"support",
"notify",
"test",
"demo",
"mail",
"team",
"channel",
"internal",
"localhost",
"stag",
"post",
"cluster",
"api",
}
func IsReservedDomain(s string) bool {
s = strings.ToLower(s)
for _, value := range reservedDomains {
if strings.Index(s, value) == 0 {
return true
}
}
return false
}
func IsValidDomain(s string) bool {
if !IsValidAlphaNum(s) {
return false
}
if len(s) <= 3 {
return false
}
return true
}
var wwwStart = regexp.MustCompile(`^www`)
var betaStart = regexp.MustCompile(`^beta`)
var ciStart = regexp.MustCompile(`^ci`)
func GetSubDomain(s string) (string, string) {
s = strings.Replace(s, "http://", "", 1)
s = strings.Replace(s, "https://", "", 1)
match := wwwStart.MatchString(s)
if match {
return "", ""
}
match = betaStart.MatchString(s)
if match {
return "", ""
}
match = ciStart.MatchString(s)
if match {
return "", ""
}
parts := strings.Split(s, ".")
if len(parts) != 3 {
return "", ""
}
return parts[0], parts[1]
}
func IsValidChannelIdentifier(s string) bool {
if !IsValidAlphaNum(s) {
return false
}
if len(s) < 2 {
return false
}
return true
}
var validAlphaNum = regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`)
func IsValidAlphaNum(s string) bool {
match := validAlphaNum.MatchString(s)
if !match {
return false
}
return true
}
func Etag(parts ...interface{}) string {
etag := ETAG_ROOT_VERSION
for _, part := range parts {
etag += fmt.Sprintf(".%v", part)
}
return etag
}
var validHashtag = regexp.MustCompile(`^(#[A-Za-z]+[A-Za-z0-9_]*[A-Za-z0-9])$`)
var puncStart = regexp.MustCompile(`^[.,()&$!\[\]{}"':;\\]+`)
var puncEnd = regexp.MustCompile(`[.,()&$#!\[\]{}"':;\\]+$`)
func ParseHashtags(text string) (string, string) {
words := strings.Split(strings.Replace(text, "\n", " ", -1), " ")
hashtagString := ""
plainString := ""
for _, word := range words {
word = puncStart.ReplaceAllString(word, "")
word = puncEnd.ReplaceAllString(word, "")
if validHashtag.MatchString(word) {
hashtagString += " " + word
} else {
plainString += " " + word
}
}
if len(hashtagString) > 1000 {
hashtagString = hashtagString[:999]
lastSpace := strings.LastIndex(hashtagString, " ")
if lastSpace > -1 {
hashtagString = hashtagString[:lastSpace]
} else {
hashtagString = ""
}
}
return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString)
}
func IsFileExtImage(ext string) bool {
ext = strings.ToLower(ext)
for _, imgExt := range IMAGE_EXTENSIONS {
if ext == imgExt {
return true
}
}
return false
}
func GetImageMimeType(ext string) string {
ext = strings.ToLower(ext)
if len(IMAGE_MIME_TYPES[ext]) == 0 {
return "image"
} else {
return IMAGE_MIME_TYPES[ext]
}
}
func ClearMentionTags(post string) string {
post = strings.Replace(post, "<mention>", "", -1)
post = strings.Replace(post, "</mention>", "", -1)
return post
}
var UrlRegex = regexp.MustCompile(`^((?:[a-z]+:\/\/)?(?:(?:[a-z0-9\-]+\.)+(?:[a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|local|internal))(:[0-9]{1,5})?(?:\/[a-z0-9_\-\.~]+)*(\/([a-z0-9_\-\.]*)(?:\?[a-z0-9+_~\-\.%=&amp;]*)?)?(?:#[a-zA-Z0-9!$&'()*+.=-_~:@/?]*)?)(?:\s+|$)$`)
var PartialUrlRegex = regexp.MustCompile(`/api/v1/files/(get|get_image)/([A-Za-z0-9]{26})/([A-Za-z0-9]{26})/(([A-Za-z0-9]+/)?.+\.[A-Za-z0-9]{3,})`)

118
model/utils_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,118 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestNewId(t *testing.T) {
for i := 0; i < 1000; i++ {
id := NewId()
if len(id) > 26 {
t.Fatal("ids shouldn't be longer than 26 chars")
}
}
}
func TestAppError(t *testing.T) {
err := NewAppError("TestAppError", "message", "")
json := err.ToJson()
rerr := AppErrorFromJson(strings.NewReader(json))
if err.Message != rerr.Message {
t.Fatal()
}
err.Error()
}
func TestMapJson(t *testing.T) {
m := make(map[string]string)
m["id"] = "test_id"
json := MapToJson(m)
rm := MapFromJson(strings.NewReader(json))
if rm["id"] != "test_id" {
t.Fatal("map should be valid")
}
rm2 := MapFromJson(strings.NewReader(""))
if len(rm2) > 0 {
t.Fatal("make should be ivalid")
}
}
func TestValidEmail(t *testing.T) {
if !IsValidEmail("corey@hulen.com") {
t.Error("email should be valid")
}
if IsValidEmail("@corey@hulen.com") {
t.Error("should be invalid")
}
}
func TestValidLower(t *testing.T) {
if !IsLower("corey@hulen.com") {
t.Error("should be valid")
}
if IsLower("Corey@hulen.com") {
t.Error("should be invalid")
}
}
var domains = []struct {
value string
expected bool
}{
{"spin-punch", true},
{"-spin-punch", false},
{"spin-punch-", false},
{"spin_punch", false},
{"a", false},
{"aa", false},
{"aaa", false},
{"aaa-999b", true},
{"b00b", true},
{"b))b", false},
{"test", true},
}
func TestValidDomain(t *testing.T) {
for _, v := range domains {
if IsValidDomain(v.value) != v.expected {
t.Errorf("expect %v as %v", v.value, v.expected)
}
}
}
var tReservedDomains = []struct {
value string
expected bool
}{
{"test-hello", true},
{"test", true},
{"admin", true},
{"Admin-punch", true},
{"spin-punch-admin", false},
}
func TestReservedDomain(t *testing.T) {
for _, v := range tReservedDomains {
if IsReservedDomain(v.value) != v.expected {
t.Errorf("expect %v as %v", v.value, v.expected)
}
}
}
func TestEtag(t *testing.T) {
etag := Etag("hello", 24)
if len(etag) <= 0 {
t.Fatal()
}
}