From 44801ae6c3411939076fd3eb823ae93a388642ce Mon Sep 17 00:00:00 2001 From: Roland Osborne Date: Sun, 13 Nov 2022 22:18:54 -0800 Subject: [PATCH] adding endpoint to push contact notifications --- app/mobile/src/api/setAccountAccess.js | 6 +-- app/mobile/src/api/setLogin.js | 4 +- app/mobile/src/context/useAppContext.hook.js | 12 ++++-- doc/api.oa3 | 28 +++++++++++++ net/server/internal/api_addAccountApp.go | 25 +++++------- net/server/internal/api_setAccountAccess.go | 30 ++++++-------- net/server/internal/api_setPushEvent.go | 42 ++++++++++++++++++++ net/server/internal/models.go | 14 +++---- net/server/internal/routers.go | 7 ++++ net/server/internal/store/schema.go | 10 +++-- net/server/internal/testUtil.go | 8 +--- net/web/src/api/setAccountAccess.js | 3 +- net/web/src/api/setLogin.js | 3 +- 13 files changed, 125 insertions(+), 67 deletions(-) create mode 100644 net/server/internal/api_setPushEvent.go diff --git a/app/mobile/src/api/setAccountAccess.js b/app/mobile/src/api/setAccountAccess.js index a3cca7fb..a9be0e6c 100644 --- a/app/mobile/src/api/setAccountAccess.js +++ b/app/mobile/src/api/setAccountAccess.js @@ -1,9 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; -export async function setAccountAccess(server, token, notifications) { - let app = { Name: "indicom", Description: "decentralized communication" } - let types = encodeURIComponent(JSON.stringify(notifications)); - let access = await fetchWithTimeout(`https://${server}/account/access?token=${token}&appName=${appName}&appVersion=${appVersion}&platform=${platform}&deviceToken=${deviceToken}¬ifications=${types}`, { method: 'PUT', body: JSON.stringify(app) }) +export async function setAccountAccess(server, token, appName, appVersion, platofrm, notifications) { + let access = await fetchWithTimeout(`https://${server}/account/access?token=${token}&appName=${appName}&appVersion=${appVersion}&platform=${platform}&deviceToken=${deviceToken}`, { method: 'PUT', body: JSON.stringify(notifications) }) checkResponse(access) return await access.json() } diff --git a/app/mobile/src/api/setLogin.js b/app/mobile/src/api/setLogin.js index 1c20fca1..3487da97 100644 --- a/app/mobile/src/api/setLogin.js +++ b/app/mobile/src/api/setLogin.js @@ -4,9 +4,7 @@ import base64 from 'react-native-base64' export async function setLogin(username, server, password, appName, appVersion, platform, deviceToken, notifications) { let headers = new Headers() headers.append('Authorization', 'Basic ' + base64.encode(username + ":" + password)); - let types = encodeURIComponent(JSON.stringify(notifications)); - let app = { Name: "topics", Description: "decentralized communication" } - let login = await fetchWithTimeout(`https://${server}/account/apps?appName=${appName}&appVersion=${appVersion}&platform=${platform}&deviceToken=${deviceToken}¬ifications=${types}`, { method: 'POST', body: JSON.stringify(app), headers: headers }) + let login = await fetchWithTimeout(`https://${server}/account/apps?appName=${appName}&appVersion=${appVersion}&platform=${platform}&deviceToken=${deviceToken}`, { method: 'POST', body: JSON.stringify(notifications), headers: headers }) checkResponse(login) return await login.json() } diff --git a/app/mobile/src/context/useAppContext.hook.js b/app/mobile/src/context/useAppContext.hook.js index 67043306..0bb472dc 100644 --- a/app/mobile/src/context/useAppContext.hook.js +++ b/app/mobile/src/context/useAppContext.hook.js @@ -71,23 +71,29 @@ export function useAppContext() { clearWebsocket(); } + const notifications = [ + { event: 'contact.statusChange', messageTitle: 'Contact Update' }, + { event: 'channel.addChannel.superbasic', messageTitle: 'New Topic' }, + { event: 'contact.addChannelTopic.superbasictopic', messageTitle: 'New Topic Message' }, + ]; + const actions = { available: getAvailable, username: getUsername, create: async (server, username, password, token) => { await addAccount(server, username, password, token); - const access = await setLogin(username, server, password, getApplicatioName(), getVersion(), getDeviceId(), state.deviceToken, ['contact', 'channel']) + const access = await setLogin(username, server, password, getApplicatioName(), getVersion(), getDeviceId(), state.deviceToken, notifications) await store.actions.setSession({ ...access, server}); await setSession({ ...access, server }); }, access: async (server, token) => { - const access = await setAccountAccess(server, token, getApplicationName(), getVersion(), getDeviceId(), state.deviceToken, ['contact', 'channel']); + const access = await setAccountAccess(server, token, getApplicationName(), getVersion(), getDeviceId(), state.deviceToken, notifications); await store.actions.setSession({ ...access, server}); await setSession({ ...access, server }); }, login: async (username, password) => { const acc = username.split('@'); - const access = await setLogin(acc[0], acc[1], password, getApplicationName(), getVersion(), getDeviceId(), state.deviceToken, ['contact', 'channel']) + const access = await setLogin(acc[0], acc[1], password, getApplicationName(), getVersion(), getDeviceId(), state.deviceToken, notifications) if (access.pushSupported) { messaging().requestPermission().then(status => {}) } diff --git a/doc/api.oa3 b/doc/api.oa3 index 2bb7cf9c..8d140e7e 100644 --- a/doc/api.oa3 +++ b/doc/api.oa3 @@ -2058,6 +2058,34 @@ paths: schema: type: integer format: int64 + + /contact/notification: + post: + tags: + - contact + description: Inform contact of a push notification event + operationId: set-push-event + paramters: + - name: contact + in: query + description: contact token + required: true + schema: + type: string + responses: + '200': + description: event set + '401': + description: not authorized + '410': + description: account disabled + '500': + description: internal server error + requestBody: + content: + application/json: + schema: + type: string /attribute/articles: get: diff --git a/net/server/internal/api_addAccountApp.go b/net/server/internal/api_addAccountApp.go index fc5b0ad2..d83b6f60 100644 --- a/net/server/internal/api_addAccountApp.go +++ b/net/server/internal/api_addAccountApp.go @@ -3,11 +3,9 @@ package databag import ( "databag/internal/store" "encoding/hex" - "encoding/json" "github.com/theckman/go-securerandom" "gorm.io/gorm" "net/http" - "errors" ) //AddAccountApp with access token, attach an app to an account generating agent token @@ -24,17 +22,10 @@ func AddAccountApp(w http.ResponseWriter, r *http.Request) { appVersion := r.FormValue("appVersion") platform := r.FormValue("platform") deviceToken := r.FormValue("deviceToken") - var notifications []string - if r.FormValue("notifications") != "" { - if err := json.Unmarshal([]byte(r.FormValue("notifications")), ¬ifications); err != nil { - ErrResponse(w, http.StatusBadRequest, errors.New("invalid notification types")); - return; - } - } - // parse app data - var appData AppData - if err := ParseRequest(r, w, &appData); err != nil { + // parse requested notifications + var notifications []Notification + if err := ParseRequest(r, w, ¬ifications); err != nil { ErrResponse(w, http.StatusBadRequest, err) return } @@ -70,10 +61,12 @@ func AddAccountApp(w http.ResponseWriter, r *http.Request) { login.Created = session.Created for _, notification := range notifications { - eventType := &store.EventType{} - eventType.SessionID = session.ID - eventType.Name = notification - if res := tx.Save(eventType).Error; res != nil { + pushEvent := &store.PushEvent{} + pushEvent.SessionID = session.ID + pushEvent.Event = notification.Event + pushEvent.MessageTitle = notification.MessageTitle + pushEvent.MessageBody = notification.MessageBody + if res := tx.Save(pushEvent).Error; res != nil { return res } } diff --git a/net/server/internal/api_setAccountAccess.go b/net/server/internal/api_setAccountAccess.go index 084a0e42..e1ba4859 100644 --- a/net/server/internal/api_setAccountAccess.go +++ b/net/server/internal/api_setAccountAccess.go @@ -3,7 +3,6 @@ package databag import ( "databag/internal/store" "encoding/hex" - "encoding/json" "errors" "time" "github.com/theckman/go-securerandom" @@ -31,20 +30,13 @@ func SetAccountAccess(w http.ResponseWriter, r *http.Request) { appVersion := r.FormValue("appVersion") platform := r.FormValue("platform") deviceToken := r.FormValue("deviceToken") - var notifications []string - if r.FormValue("notifications") != "" { - if err := json.Unmarshal([]byte(r.FormValue("notifications")), ¬ifications); err != nil { - ErrResponse(w, http.StatusBadRequest, errors.New("invalid notification types")); - return; - } - } - // parse app data - var appData AppData - if err := ParseRequest(r, w, &appData); err != nil { - ErrResponse(w, http.StatusBadRequest, err) - return - } + // parse requested notifications + var notifications []Notification + if err := ParseRequest(r, w, ¬ifications); err != nil { + ErrResponse(w, http.StatusBadRequest, err) + return + } // gernate app token data, err := securerandom.Bytes(APPTokenSize) @@ -70,10 +62,12 @@ func SetAccountAccess(w http.ResponseWriter, r *http.Request) { return res } for _, notification := range notifications { - eventType := &store.EventType{} - eventType.SessionID = session.ID - eventType.Name = notification - if res := tx.Save(eventType).Error; res != nil { + pushEvent := &store.PushEvent{} + pushEvent.SessionID = session.ID + pushEvent.Event = notification.Event + pushEvent.MessageTitle = notification.MessageTitle + pushEvent.MessageBody = notification.MessageBody + if res := tx.Save(pushEvent).Error; res != nil { return res } } diff --git a/net/server/internal/api_setPushEvent.go b/net/server/internal/api_setPushEvent.go new file mode 100644 index 00000000..520c0064 --- /dev/null +++ b/net/server/internal/api_setPushEvent.go @@ -0,0 +1,42 @@ +package databag + +import ( + "databag/internal/store" + "net/http" +) + +type push struct { + PushToken string + MessageTitle string + MessageBody string +} + +//AddPushEvent notify account of event to push notify +func SetPushEvent(w http.ResponseWriter, r *http.Request) { + + card, code, err := ParamContactToken(r, false) + if err != nil { + ErrResponse(w, code, err) + return + } + + var event string + if err := ParseRequest(r, w, &event); err != nil { + ErrResponse(w, http.StatusBadRequest, err) + return + } + + messages := []push{} + if err := store.DB.Model(&store.Session{}).Select("sessions.push_token, push_events.message_title, push_events.message_body").Joins("left join push_events on push_events.session_id = session.id").Where("sessions.account_id = ? AND session.push_enabled = ?", card.Account.ID, true).Scan(messages).Error; err != nil { + ErrResponse(w, http.StatusInternalServerError, err) + return + } + + // send push notification for each + for _, message := range messages { + PrintMsg(message); + } + + WriteResponse(w, nil) +} + diff --git a/net/server/internal/models.go b/net/server/internal/models.go index 8ac1115d..1b60113b 100644 --- a/net/server/internal/models.go +++ b/net/server/internal/models.go @@ -39,17 +39,13 @@ type Announce struct { AppToken string `json:"appToken"` } -//AppData describes app connected to account -type AppData struct { - Name string `json:"name,omitempty"` +//Notification describes type of notifications to receive +type Notification struct { + Event string `json:"event,omitempty"` - Description string `json:"description,omitempty"` + MessageTitle string `json:"messageTitle,omitempty"` - URL string `json:"url,omitempty"` - - Image string `json:"image,omitempty"` - - Attached int64 `json:"attached"` + MessageBody string `json:"messageBoday,omitempty"` } //Article slot for account data shared by group list diff --git a/net/server/internal/routers.go b/net/server/internal/routers.go index f6f56e40..bd5b6a70 100644 --- a/net/server/internal/routers.go +++ b/net/server/internal/routers.go @@ -524,6 +524,13 @@ var endpoints = routes{ SetViewRevision, }, + route{ + "SetPushEvent", + strings.ToUpper("Post"), + "/contact/notification", + SetPushEvent, + }, + route{ "AddChannel", strings.ToUpper("Post"), diff --git a/net/server/internal/store/schema.go b/net/server/internal/store/schema.go index 8b7fab2d..50c47741 100644 --- a/net/server/internal/store/schema.go +++ b/net/server/internal/store/schema.go @@ -7,7 +7,7 @@ func AutoMigrate(db *gorm.DB) { db.AutoMigrate(&Config{}); db.AutoMigrate(&App{}); db.AutoMigrate(&Session{}); - db.AutoMigrate(&EventType{}); + db.AutoMigrate(&PushEvent{}); db.AutoMigrate(&Account{}); db.AutoMigrate(&AccountToken{}); db.AutoMigrate(&GroupSlot{}); @@ -108,13 +108,15 @@ type Session struct { Created int64 `gorm:"autoCreateTime"` Account Account `gorm:"references:GUID"` Token string `gorm:"not null;index:sessguid,unique"` - EventTypes []EventType + PushEvents []PushEvent } -type EventType struct { +type PushEvent struct { ID uint `gorm:"primaryKey;not null;unique;autoIncrement"` SessionID uint `gorm:"not null;index:sessiontype"` - Name string + Event string + MessageTitle string + MessageBody string Session *Session } diff --git a/net/server/internal/testUtil.go b/net/server/internal/testUtil.go index 0d8e6088..69c67c42 100644 --- a/net/server/internal/testUtil.go +++ b/net/server/internal/testUtil.go @@ -614,11 +614,6 @@ func addTestAccount(username string) (guid string, token string, err error) { var w *httptest.ResponseRecorder var access LoginAccess - app := AppData{ - Name: "Appy", - Description: "A test app", - URL: "http://app.coredb.org", - } var claim Claim var msg DataMessage var profile Profile @@ -646,7 +641,8 @@ func addTestAccount(username string) (guid string, token string, err error) { guid = profile.GUID // acquire new token for attaching app - if r, w, err = NewRequest("POST", "/account/apps", &app); err != nil { + notifications := []Notification{} + if r, w, err = NewRequest("POST", "/account/apps", ¬ifications); err != nil { return } SetBasicAuth(r, login) diff --git a/net/web/src/api/setAccountAccess.js b/net/web/src/api/setAccountAccess.js index 86f2446b..761a7268 100644 --- a/net/web/src/api/setAccountAccess.js +++ b/net/web/src/api/setAccountAccess.js @@ -1,8 +1,7 @@ import { checkResponse, fetchWithTimeout } from './fetchUtil'; export async function setAccountAccess(token, appName, appVersion, platform) { - let app = { Name: "indicom", Description: "decentralized communication" } - let access = await fetchWithTimeout(`/account/access?token=${token}&appName=${appName}&appVersion=${appVersion}&platform=${platform}`, { method: 'PUT', body: JSON.stringify(app) }) + let access = await fetchWithTimeout(`/account/access?token=${token}&appName=${appName}&appVersion=${appVersion}&platform=${platform}`, { method: 'PUT', body: JSON.stringify([]) }) checkResponse(access) return await access.json() } diff --git a/net/web/src/api/setLogin.js b/net/web/src/api/setLogin.js index 96af1f54..df40a604 100644 --- a/net/web/src/api/setLogin.js +++ b/net/web/src/api/setLogin.js @@ -5,8 +5,7 @@ export async function setLogin(username, password, appName, appVersion, userAgen const platform = encodeURIComponent(userAgent); let headers = new Headers() headers.append('Authorization', 'Basic ' + base64.encode(username + ":" + password)); - let app = { Name: "indicom", Description: "decentralized communication" } - let login = await fetchWithTimeout(`/account/apps?appName=${appName}&appVersion=${appVersion}&platform=${platform}`, { method: 'POST', body: JSON.stringify(app), headers: headers }) + let login = await fetchWithTimeout(`/account/apps?appName=${appName}&appVersion=${appVersion}&platform=${platform}`, { method: 'POST', body: JSON.stringify([]), headers: headers }) checkResponse(login) return await login.json() }