CreateSubscription(), UpdateSubscription(), GetMessage(), DeleteMessage()
This commit is contained in:
parent
8278c059ad
commit
0d641b727f
@ -4,10 +4,11 @@
|
||||
|
||||
|
||||
- background job for re-delivery
|
||||
- accept/decline subscribtions (PATCH subs)
|
||||
- accept/decline subscriptions (PATCH subs)
|
||||
- (message.go) api routes
|
||||
- (compat.go) api routes
|
||||
- https://firebase.google.com/docs/cloud-messaging/send-message#rest
|
||||
- List subscribtions on owned channels /RESTful?)
|
||||
- List subscriptions on owned channels /RESTful?)
|
||||
- deploy
|
||||
- Dockerfile
|
||||
- php in html
|
@ -26,6 +26,7 @@ const (
|
||||
CLIENT_NOT_FOUND APIError = 1302
|
||||
CHANNEL_NOT_FOUND APIError = 1303
|
||||
SUBSCRIPTION_NOT_FOUND APIError = 1304
|
||||
MESSAGE_NOT_FOUND APIError = 1305
|
||||
USER_AUTH_FAILED APIError = 1311
|
||||
|
||||
NO_DEVICE_LINKED APIError = 1401
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"net/http"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type APIHandler struct {
|
||||
@ -90,7 +89,12 @@ func (h APIHandler) CreateUser(g *gin.Context) ginresp.HTTPResponse {
|
||||
}
|
||||
}
|
||||
|
||||
userobj, err := h.database.CreateUser(ctx, readKey, sendKey, adminKey, b.ProToken, b.Username)
|
||||
username := b.Username
|
||||
if username != nil {
|
||||
username = langext.Ptr(h.app.NormalizeUsername(*username))
|
||||
}
|
||||
|
||||
userobj, err := h.database.CreateUser(ctx, readKey, sendKey, adminKey, b.ProToken, username)
|
||||
if err != nil {
|
||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to create user in db", err)
|
||||
}
|
||||
@ -181,7 +185,7 @@ func (h APIHandler) UpdateUser(g *gin.Context) ginresp.HTTPResponse {
|
||||
}
|
||||
|
||||
if b.Username != nil {
|
||||
username := langext.Ptr(regexp.MustCompile(`[[:alnum:]\-_]`).ReplaceAllString(*b.Username, ""))
|
||||
username := langext.Ptr(h.app.NormalizeUsername(*b.Username))
|
||||
if *username == "" {
|
||||
username = nil
|
||||
}
|
||||
@ -634,7 +638,7 @@ func (h APIHandler) GetSubscription(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.InternAPIError(404, apierr.SUBSCRIPTION_NOT_FOUND, "Subscription not found", err)
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query channel", err)
|
||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||
}
|
||||
|
||||
if subscription.SubscriberUserID != u.UserID {
|
||||
@ -672,7 +676,7 @@ func (h APIHandler) CancelSubscription(g *gin.Context) ginresp.HTTPResponse {
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
if permResp := ctx.CheckPermissionUserRead(u.UserID); permResp != nil {
|
||||
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
@ -681,10 +685,10 @@ func (h APIHandler) CancelSubscription(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.InternAPIError(404, apierr.SUBSCRIPTION_NOT_FOUND, "Subscription not found", err)
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query channel", err)
|
||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||
}
|
||||
|
||||
if subscription.SubscriberUserID != u.UserID {
|
||||
if subscription.SubscriberUserID != u.UserID && subscription.ChannelOwnerUserID != u.UserID {
|
||||
return ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
}
|
||||
|
||||
@ -696,24 +700,251 @@ func (h APIHandler) CancelSubscription(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, subscription.JSON()))
|
||||
}
|
||||
|
||||
// CreateSubscription swaggerdoc
|
||||
//
|
||||
// @Summary Creare/Request a subscription
|
||||
// @ID api-subscriptions-create
|
||||
//
|
||||
// @Param uid path int true "UserID"
|
||||
// @Param query_data query handler.CreateSubscription.query false " "
|
||||
// @Param post_data body handler.CreateSubscription.body false " "
|
||||
//
|
||||
// @Success 200 {object} models.SubscriptionJSON
|
||||
// @Failure 400 {object} ginresp.apiError
|
||||
// @Failure 401 {object} ginresp.apiError
|
||||
// @Failure 404 {object} ginresp.apiError
|
||||
// @Failure 500 {object} ginresp.apiError
|
||||
//
|
||||
// @Router /api-v2/users/{uid}/subscriptions [POST]
|
||||
func (h APIHandler) CreateSubscription(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.NotImplemented() //TODO
|
||||
type uri struct {
|
||||
UserID int64 `uri:"uid"`
|
||||
}
|
||||
type body struct {
|
||||
ChannelOwnerUserID int64 `form:"channel_owner_user_id"`
|
||||
Channel string `form:"channel_name"`
|
||||
}
|
||||
type query struct {
|
||||
ChanSubscribeKey *string `form:"chan_subscribe_key"`
|
||||
}
|
||||
|
||||
var u uri
|
||||
var q query
|
||||
var b body
|
||||
ctx, errResp := h.app.StartRequest(g, &u, &q, &b)
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
channel, err := h.database.GetChannelByName(ctx, b.ChannelOwnerUserID, h.app.NormalizeChannelName(b.Channel))
|
||||
if err != nil {
|
||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query channel", err)
|
||||
}
|
||||
if channel == nil {
|
||||
return ginresp.InternAPIError(400, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
|
||||
}
|
||||
|
||||
sub, err := h.database.CreateSubscription(ctx, u.UserID, *channel, channel.OwnerUserID == u.UserID)
|
||||
if err != nil {
|
||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to create subscription", err)
|
||||
}
|
||||
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, sub.JSON()))
|
||||
}
|
||||
|
||||
// UpdateSubscription swaggerdoc
|
||||
//
|
||||
// @Summary Update a subscription (e.g. confirm)
|
||||
// @ID api-subscriptions-update
|
||||
//
|
||||
// @Param uid path int true "UserID"
|
||||
// @Param sid path int true "SubscriptionID"
|
||||
//
|
||||
// @Success 200 {object} models.SubscriptionJSON
|
||||
// @Failure 400 {object} ginresp.apiError
|
||||
// @Failure 401 {object} ginresp.apiError
|
||||
// @Failure 404 {object} ginresp.apiError
|
||||
// @Failure 500 {object} ginresp.apiError
|
||||
//
|
||||
// @Router /api-v2/users/{uid}/subscriptions/{sid} [PATCH]
|
||||
func (h APIHandler) UpdateSubscription(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.NotImplemented() //TODO
|
||||
type uri struct {
|
||||
UserID int64 `uri:"uid"`
|
||||
SubscriptionID int64 `uri:"sid"`
|
||||
}
|
||||
type body struct {
|
||||
Confirmed *bool `form:"confirmed"`
|
||||
}
|
||||
|
||||
var u uri
|
||||
var b body
|
||||
ctx, errResp := h.app.StartRequest(g, &u, nil, &b)
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
subscription, err := h.database.GetSubscription(ctx, u.SubscriptionID)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.InternAPIError(404, apierr.SUBSCRIPTION_NOT_FOUND, "Subscription not found", err)
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||
}
|
||||
|
||||
if subscription.ChannelOwnerUserID != u.UserID {
|
||||
return ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
}
|
||||
|
||||
if b.Confirmed != nil {
|
||||
err = h.database.UpdateSubscriptionConfirmed(ctx, u.SubscriptionID, *b.Confirmed)
|
||||
if err != nil {
|
||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to update subscription", err)
|
||||
}
|
||||
}
|
||||
|
||||
subscription, err = h.database.GetSubscription(ctx, u.SubscriptionID)
|
||||
if err != nil {
|
||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||
}
|
||||
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, subscription.JSON()))
|
||||
}
|
||||
|
||||
func (h APIHandler) ListMessages(g *gin.Context) ginresp.HTTPResponse {
|
||||
//also update last_read
|
||||
return ginresp.NotImplemented() //TODO
|
||||
}
|
||||
|
||||
// GetMessage swaggerdoc
|
||||
//
|
||||
// @Summary Get a single message (untrimmed)
|
||||
// @Description The user must either own the message and request the resource with the READ or ADMIN Key
|
||||
// @Description Or the user must subscribe to the corresponding channel (and be confirmed) and request the resource with the READ or ADMIN Key
|
||||
// @Description The returned message is never trimmed
|
||||
// @ID api-message-get
|
||||
//
|
||||
// @Param mid path int true "SCNMessageID"
|
||||
//
|
||||
// @Success 200 {object} models.MessageJSON
|
||||
// @Failure 400 {object} ginresp.apiError
|
||||
// @Failure 401 {object} ginresp.apiError
|
||||
// @Failure 404 {object} ginresp.apiError
|
||||
// @Failure 500 {object} ginresp.apiError
|
||||
//
|
||||
// @Router /api-v2/messages/{mid} [PATCH]
|
||||
func (h APIHandler) GetMessage(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.NotImplemented() //TODO
|
||||
type uri struct {
|
||||
MessageID int64 `uri:"mid"`
|
||||
}
|
||||
|
||||
var u uri
|
||||
ctx, errResp := h.app.StartRequest(g, &u, nil, nil)
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
if permResp := ctx.CheckPermissionAny(); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
msg, err := h.database.GetMessage(ctx, u.MessageID)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.InternAPIError(404, apierr.MESSAGE_NOT_FOUND, "message not found", err)
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query message", err)
|
||||
}
|
||||
|
||||
if !ctx.CheckPermissionMessageReadDirect(msg) {
|
||||
|
||||
// either we have direct read permissions (it is our message + read/admin key)
|
||||
// or we subscribe (+confirmed) to the channel and have read/admin key
|
||||
|
||||
if uid := ctx.GetPermissionUserID(); uid != nil && ctx.IsPermissionUserRead() {
|
||||
sub, err := h.database.GetSubscriptionBySubscriber(ctx, *uid, msg.ChannelID)
|
||||
if err != nil {
|
||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||
}
|
||||
if sub == nil {
|
||||
// not subbed
|
||||
return ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
}
|
||||
if !sub.Confirmed {
|
||||
// sub not confirmed
|
||||
return ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
}
|
||||
// => perm okay
|
||||
|
||||
} else {
|
||||
// auth-key is not set or not a user:x variant
|
||||
return ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, msg.FullJSON()))
|
||||
}
|
||||
|
||||
// DeleteMessage swaggerdoc
|
||||
//
|
||||
// @Summary Delete a single message
|
||||
// @Description The user must own the message and request the resource with the ADMIN Key
|
||||
// @ID api-message-delete
|
||||
//
|
||||
// @Param mid path int true "SCNMessageID"
|
||||
//
|
||||
// @Success 200 {object} models.MessageJSON
|
||||
// @Failure 400 {object} ginresp.apiError
|
||||
// @Failure 401 {object} ginresp.apiError
|
||||
// @Failure 404 {object} ginresp.apiError
|
||||
// @Failure 500 {object} ginresp.apiError
|
||||
//
|
||||
// @Router /api-v2/messages/{mid} [PATCH]
|
||||
func (h APIHandler) DeleteMessage(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.NotImplemented() //TODO
|
||||
type uri struct {
|
||||
MessageID int64 `uri:"mid"`
|
||||
}
|
||||
|
||||
var u uri
|
||||
ctx, errResp := h.app.StartRequest(g, &u, nil, nil)
|
||||
if errResp != nil {
|
||||
return *errResp
|
||||
}
|
||||
defer ctx.Cancel()
|
||||
|
||||
if permResp := ctx.CheckPermissionAny(); permResp != nil {
|
||||
return *permResp
|
||||
}
|
||||
|
||||
msg, err := h.database.GetMessage(ctx, u.MessageID)
|
||||
if err == sql.ErrNoRows {
|
||||
return ginresp.InternAPIError(404, apierr.MESSAGE_NOT_FOUND, "message not found", err)
|
||||
}
|
||||
if err != nil {
|
||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query message", err)
|
||||
}
|
||||
|
||||
if !ctx.CheckPermissionMessageReadDirect(msg) {
|
||||
return ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||
}
|
||||
|
||||
err = h.database.DeleteMessage(ctx, msg.SCNMessageID)
|
||||
if err != nil {
|
||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to delete message", err)
|
||||
}
|
||||
|
||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, msg.FullJSON()))
|
||||
}
|
||||
|
||||
func (h APIHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse {
|
||||
|
@ -190,12 +190,26 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse {
|
||||
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query subscriptions")
|
||||
}
|
||||
|
||||
err = h.database.IncUserMessageCounter(ctx, user)
|
||||
if err != nil {
|
||||
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to inc user msg-counter")
|
||||
}
|
||||
|
||||
err = h.database.IncChannelMessageCounter(ctx, channel)
|
||||
if err != nil {
|
||||
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to channel msg-counter")
|
||||
}
|
||||
|
||||
for _, sub := range subscriptions {
|
||||
clients, err := h.database.ListClients(ctx, sub.SubscriberUserID)
|
||||
if err != nil {
|
||||
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query clients")
|
||||
}
|
||||
|
||||
if !sub.Confirmed {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, client := range clients {
|
||||
|
||||
fcmDelivID, err := h.deliverMessage(ctx, client, msg)
|
||||
@ -220,8 +234,8 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse {
|
||||
ErrorHighlight: -1,
|
||||
Message: "Message sent",
|
||||
SuppressSend: false,
|
||||
MessageCount: user.MessagesSent,
|
||||
Quota: user.QuotaUsedToday(),
|
||||
MessageCount: user.MessagesSent + 1,
|
||||
Quota: user.QuotaUsedToday() + 1,
|
||||
IsPro: user.IsPro,
|
||||
QuotaMax: user.QuotaPerDay(),
|
||||
SCNMessageID: msg.SCNMessageID,
|
||||
|
141
server/db/channels.go
Normal file
141
server/db/channels.go
Normal file
@ -0,0 +1,141 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) GetChannelByKey(ctx TxContext, key string) (*models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM channels WHERE subscribe_key = ? OR send_key = ? LIMIT 1", key, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channel, err := models.DecodeChannel(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetChannelByName(ctx TxContext, userid int64, chanName string) (*models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM channels WHERE owner_user_id = ? OR name = ? LIMIT 1", userid, chanName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channel, err := models.DecodeChannel(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateChannel(ctx TxContext, userid int64, name string, subscribeKey string, sendKey string) (models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.ExecContext(ctx, "INSERT INTO channels (owner_user_id, name, subscribe_key, send_key, timestamp_created) VALUES (?, ?, ?, ?, ?)",
|
||||
userid,
|
||||
name,
|
||||
subscribeKey,
|
||||
sendKey,
|
||||
time2DB(now))
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
return models.Channel{
|
||||
ChannelID: liid,
|
||||
OwnerUserID: userid,
|
||||
Name: name,
|
||||
SubscribeKey: subscribeKey,
|
||||
SendKey: sendKey,
|
||||
TimestampCreated: now,
|
||||
TimestampLastSent: nil,
|
||||
MessagesSent: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) ListChannels(ctx TxContext, userid int64) ([]models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM channels WHERE owner_user_id = ?", userid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeChannels(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetChannel(ctx TxContext, userid int64, channelid int64) (models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM channels WHERE owner_user_id = ? AND channel_id = ? LIMIT 1", userid, channelid)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
client, err := models.DecodeChannel(rows)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (db *Database) IncChannelMessageCounter(ctx TxContext, channel models.Channel) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "UPDATE channels SET messages_sent = ? AND timestamp_lastsent = ? WHERE channel_id = ?",
|
||||
channel.MessagesSent+1,
|
||||
time2DB(time.Now()),
|
||||
channel.ChannelID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
108
server/db/clients.go
Normal file
108
server/db/clients.go
Normal file
@ -0,0 +1,108 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) CreateClient(ctx TxContext, userid int64, ctype models.ClientType, fcmToken string, agentModel string, agentVersion string) (models.Client, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.ExecContext(ctx, "INSERT INTO clients (user_id, type, fcm_token, timestamp_created, agent_model, agent_version) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
userid,
|
||||
string(ctype),
|
||||
fcmToken,
|
||||
time2DB(now),
|
||||
agentModel,
|
||||
agentVersion)
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
return models.Client{
|
||||
ClientID: liid,
|
||||
UserID: userid,
|
||||
Type: ctype,
|
||||
FCMToken: langext.Ptr(fcmToken),
|
||||
TimestampCreated: now,
|
||||
AgentModel: agentModel,
|
||||
AgentVersion: agentVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) ClearFCMTokens(ctx TxContext, fcmtoken string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM clients WHERE fcm_token = ?", fcmtoken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) ListClients(ctx TxContext, userid int64) ([]models.Client, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM clients WHERE user_id = ?", userid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeClients(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetClient(ctx TxContext, userid int64, clientid int64) (models.Client, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM clients WHERE user_id = ? AND client_id = ? LIMIT 1", userid, clientid)
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
client, err := models.DecodeClient(rows)
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (db *Database) DeleteClient(ctx TxContext, clientid int64) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM clients WHERE client_id = ?", clientid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
88
server/db/deliveries.go
Normal file
88
server/db/deliveries.go
Normal file
@ -0,0 +1,88 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) CreateRetryDelivery(ctx TxContext, client models.Client, msg models.Message) (models.Delivery, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
next := now.Add(5 * time.Second)
|
||||
|
||||
res, err := tx.ExecContext(ctx, "INSERT INTO deliveries (scn_message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
msg.SCNMessageID,
|
||||
client.UserID,
|
||||
client.ClientID,
|
||||
time2DB(now),
|
||||
nil,
|
||||
models.DeliveryStatusRetry,
|
||||
nil,
|
||||
time2DB(next))
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
return models.Delivery{
|
||||
DeliveryID: liid,
|
||||
SCNMessageID: msg.SCNMessageID,
|
||||
ReceiverUserID: client.UserID,
|
||||
ReceiverClientID: client.ClientID,
|
||||
TimestampCreated: now,
|
||||
TimestampFinalized: nil,
|
||||
Status: models.DeliveryStatusRetry,
|
||||
RetryCount: 0,
|
||||
NextDelivery: langext.Ptr(next),
|
||||
FCMMessageID: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateSuccessDelivery(ctx TxContext, client models.Client, msg models.Message, fcmDelivID string) (models.Delivery, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.ExecContext(ctx, "INSERT INTO deliveries (scn_message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
msg.SCNMessageID,
|
||||
client.UserID,
|
||||
client.ClientID,
|
||||
time2DB(now),
|
||||
time2DB(now),
|
||||
models.DeliveryStatusSuccess,
|
||||
fcmDelivID,
|
||||
nil)
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
return models.Delivery{
|
||||
DeliveryID: liid,
|
||||
SCNMessageID: msg.SCNMessageID,
|
||||
ReceiverUserID: client.UserID,
|
||||
ReceiverClientID: client.ClientID,
|
||||
TimestampCreated: now,
|
||||
TimestampFinalized: langext.Ptr(now),
|
||||
Status: models.DeliveryStatusSuccess,
|
||||
RetryCount: 0,
|
||||
NextDelivery: nil,
|
||||
FCMMessageID: langext.Ptr(fcmDelivID),
|
||||
}, nil
|
||||
}
|
105
server/db/messages.go
Normal file
105
server/db/messages.go
Normal file
@ -0,0 +1,105 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) GetMessageByUserMessageID(ctx TxContext, usrMsgId string) (*models.Message, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM messages WHERE usr_message_id = ? LIMIT 1", usrMsgId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg, err := models.DecodeMessage(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &msg, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetMessage(ctx TxContext, scnMessageID int64) (models.Message, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM messages WHERE scn_message_id = ? LIMIT 1", scnMessageID)
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
msg, err := models.DecodeMessage(rows)
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateMessage(ctx TxContext, senderUserID int64, channel models.Channel, timestampSend *time.Time, title string, content *string, priority int, userMsgId *string) (models.Message, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.ExecContext(ctx, "INSERT INTO messages (sender_user_id, owner_user_id, channel_name, channel_id, timestamp_real, timestamp_client, title, content, priority, usr_message_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
senderUserID,
|
||||
channel.OwnerUserID,
|
||||
channel.Name,
|
||||
channel.ChannelID,
|
||||
time2DB(now),
|
||||
time2DBOpt(timestampSend),
|
||||
title,
|
||||
content,
|
||||
priority,
|
||||
userMsgId)
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
return models.Message{
|
||||
SCNMessageID: liid,
|
||||
SenderUserID: senderUserID,
|
||||
OwnerUserID: channel.OwnerUserID,
|
||||
ChannelName: channel.Name,
|
||||
ChannelID: channel.ChannelID,
|
||||
TimestampReal: now,
|
||||
TimestampClient: timestampSend,
|
||||
Title: title,
|
||||
Content: content,
|
||||
Priority: priority,
|
||||
UserMessageID: userMsgId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) DeleteMessage(ctx TxContext, scnMessageID int64) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM messages WHERE scn_message_id = ?", scnMessageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,604 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"database/sql"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) CreateUser(ctx TxContext, readKey string, sendKey string, adminKey string, protoken *string, username *string) (models.User, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.ExecContext(ctx, "INSERT INTO users (username, read_key, send_key, admin_key, is_pro, pro_token, timestamp_created) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
username,
|
||||
readKey,
|
||||
sendKey,
|
||||
adminKey,
|
||||
bool2DB(protoken != nil),
|
||||
protoken,
|
||||
time2DB(now))
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
return models.User{
|
||||
UserID: liid,
|
||||
Username: username,
|
||||
ReadKey: readKey,
|
||||
SendKey: sendKey,
|
||||
AdminKey: adminKey,
|
||||
TimestampCreated: now,
|
||||
TimestampLastRead: nil,
|
||||
TimestampLastSent: nil,
|
||||
MessagesSent: 0,
|
||||
QuotaUsed: 0,
|
||||
QuotaUsedDay: nil,
|
||||
IsPro: protoken != nil,
|
||||
ProToken: protoken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateClient(ctx TxContext, userid int64, ctype models.ClientType, fcmToken string, agentModel string, agentVersion string) (models.Client, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.ExecContext(ctx, "INSERT INTO clients (user_id, type, fcm_token, timestamp_created, agent_model, agent_version) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
userid,
|
||||
string(ctype),
|
||||
fcmToken,
|
||||
time2DB(now),
|
||||
agentModel,
|
||||
agentVersion)
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
return models.Client{
|
||||
ClientID: liid,
|
||||
UserID: userid,
|
||||
Type: ctype,
|
||||
FCMToken: langext.Ptr(fcmToken),
|
||||
TimestampCreated: now,
|
||||
AgentModel: agentModel,
|
||||
AgentVersion: agentVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) ClearFCMTokens(ctx TxContext, fcmtoken string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM clients WHERE fcm_token = ?", fcmtoken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) ClearProTokens(ctx TxContext, protoken string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "UPDATE users SET is_pro=0, pro_token=NULL WHERE pro_token = ?", protoken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) GetUserByKey(ctx TxContext, key string) (*models.User, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM users WHERE admin_key = ? OR send_key = ? OR read_key = ? LIMIT 1", key, key, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := models.DecodeUser(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetChannelByKey(ctx TxContext, key string) (*models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM channels WHERE subscribe_key = ? OR send_key = ? LIMIT 1", key, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channel, err := models.DecodeChannel(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetUser(ctx TxContext, userid int64) (models.User, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM users WHERE user_id = ? LIMIT 1", userid)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
user, err := models.DecodeUser(rows)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserUsername(ctx TxContext, userid int64, username *string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "UPDATE users SET username = ? WHERE user_id = ?", username, userid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserProToken(ctx TxContext, userid int64, protoken *string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "UPDATE users SET pro_token = ? AND is_pro = ? WHERE user_id = ?", protoken, bool2DB(protoken != nil), userid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) ListClients(ctx TxContext, userid int64) ([]models.Client, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM clients WHERE user_id = ?", userid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeClients(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetClient(ctx TxContext, userid int64, clientid int64) (models.Client, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM clients WHERE user_id = ? AND client_id = ? LIMIT 1", userid, clientid)
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
client, err := models.DecodeClient(rows)
|
||||
if err != nil {
|
||||
return models.Client{}, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (db *Database) DeleteClient(ctx TxContext, clientid int64) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM clients WHERE client_id = ?", clientid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) GetMessageByUserMessageID(ctx TxContext, usrMsgId string) (*models.Message, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM messages WHERE usr_message_id = ? LIMIT 1", usrMsgId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg, err := models.DecodeMessage(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &msg, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetChannelByName(ctx TxContext, userid int64, chanName string) (*models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM channels WHERE owner_user_id = ? OR name = ? LIMIT 1", userid, chanName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channel, err := models.DecodeChannel(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateChannel(ctx TxContext, userid int64, name string, subscribeKey string, sendKey string) (models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.ExecContext(ctx, "INSERT INTO channels (owner_user_id, name, subscribe_key, send_key, timestamp_created) VALUES (?, ?, ?, ?, ?)",
|
||||
userid,
|
||||
name,
|
||||
subscribeKey,
|
||||
sendKey,
|
||||
time2DB(now))
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
return models.Channel{
|
||||
ChannelID: liid,
|
||||
OwnerUserID: userid,
|
||||
Name: name,
|
||||
SubscribeKey: subscribeKey,
|
||||
SendKey: sendKey,
|
||||
TimestampCreated: now,
|
||||
TimestampLastRead: nil,
|
||||
TimestampLastSent: nil,
|
||||
MessagesSent: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateSubscribtion(ctx TxContext, subscriberUID int64, ownerUID int64, chanName string, chanID int64, confirmed bool) (models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.ExecContext(ctx, "INSERT INTO subscriptions (subscriber_user_id, channel_owner_user_id, channel_name, channel_id, timestamp_created, confirmed) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
subscriberUID,
|
||||
ownerUID,
|
||||
chanName,
|
||||
chanID,
|
||||
time2DB(now),
|
||||
confirmed)
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
return models.Subscription{
|
||||
SubscriptionID: liid,
|
||||
SubscriberUserID: subscriberUID,
|
||||
ChannelOwnerUserID: ownerUID,
|
||||
ChannelID: chanID,
|
||||
ChannelName: chanName,
|
||||
TimestampCreated: now,
|
||||
Confirmed: confirmed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateMessage(ctx TxContext, senderUserID int64, channel models.Channel, timestampSend *time.Time, title string, content *string, priority int, userMsgId *string) (models.Message, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.ExecContext(ctx, "INSERT INTO messages (sender_user_id, owner_user_id, channel_name, channel_id, timestamp_real, timestamp_client, title, content, priority, usr_message_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
senderUserID,
|
||||
channel.OwnerUserID,
|
||||
channel.Name,
|
||||
channel.ChannelID,
|
||||
time2DB(now),
|
||||
time2DBOpt(timestampSend),
|
||||
title,
|
||||
content,
|
||||
priority,
|
||||
userMsgId)
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Message{}, err
|
||||
}
|
||||
|
||||
return models.Message{
|
||||
SCNMessageID: liid,
|
||||
SenderUserID: senderUserID,
|
||||
OwnerUserID: channel.OwnerUserID,
|
||||
ChannelName: channel.Name,
|
||||
ChannelID: channel.ChannelID,
|
||||
TimestampReal: now,
|
||||
TimestampClient: timestampSend,
|
||||
Title: title,
|
||||
Content: content,
|
||||
Priority: priority,
|
||||
UserMessageID: userMsgId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) ListSubscriptionsByChannel(ctx TxContext, channelID int64) ([]models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM subscriptions WHERE channel_id = ?", channelID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeSubscriptions(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) ListSubscriptionsByOwner(ctx TxContext, ownerUserID int64) ([]models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM subscriptions WHERE channel_owner_user_id = ?", ownerUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeSubscriptions(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateRetryDelivery(ctx TxContext, client models.Client, msg models.Message) (models.Delivery, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
next := now.Add(5 * time.Second)
|
||||
|
||||
res, err := tx.ExecContext(ctx, "INSERT INTO deliveries (scn_message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
msg.SCNMessageID,
|
||||
client.UserID,
|
||||
client.ClientID,
|
||||
time2DB(now),
|
||||
nil,
|
||||
models.DeliveryStatusRetry,
|
||||
nil,
|
||||
time2DB(next))
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
return models.Delivery{
|
||||
DeliveryID: liid,
|
||||
SCNMessageID: msg.SCNMessageID,
|
||||
ReceiverUserID: client.UserID,
|
||||
ReceiverClientID: client.ClientID,
|
||||
TimestampCreated: now,
|
||||
TimestampFinalized: nil,
|
||||
Status: models.DeliveryStatusRetry,
|
||||
RetryCount: 0,
|
||||
NextDelivery: langext.Ptr(next),
|
||||
FCMMessageID: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) CreateSuccessDelivery(ctx TxContext, client models.Client, msg models.Message, fcmDelivID string) (models.Delivery, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.ExecContext(ctx, "INSERT INTO deliveries (scn_message_id, receiver_user_id, receiver_client_id, timestamp_created, timestamp_finalized, status, fcm_message_id, next_delivery) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
msg.SCNMessageID,
|
||||
client.UserID,
|
||||
client.ClientID,
|
||||
time2DB(now),
|
||||
time2DB(now),
|
||||
models.DeliveryStatusSuccess,
|
||||
fcmDelivID,
|
||||
nil)
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Delivery{}, err
|
||||
}
|
||||
|
||||
return models.Delivery{
|
||||
DeliveryID: liid,
|
||||
SCNMessageID: msg.SCNMessageID,
|
||||
ReceiverUserID: client.UserID,
|
||||
ReceiverClientID: client.ClientID,
|
||||
TimestampCreated: now,
|
||||
TimestampFinalized: langext.Ptr(now),
|
||||
Status: models.DeliveryStatusSuccess,
|
||||
RetryCount: 0,
|
||||
NextDelivery: nil,
|
||||
FCMMessageID: langext.Ptr(fcmDelivID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) ListChannels(ctx TxContext, userid int64) ([]models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM channels WHERE owner_user_id = ?", userid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeChannels(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetChannel(ctx TxContext, userid int64, channelid int64) (models.Channel, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM channels WHERE owner_user_id = ? AND channel_id = ? LIMIT 1", userid, channelid)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
client, err := models.DecodeChannel(rows)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetSubscription(ctx TxContext, subid int64) (models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM subscriptions WHERE subscription_id = ? LIMIT 1", subid)
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
sub, err := models.DecodeSubscription(rows)
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
func (db *Database) DeleteSubscription(ctx TxContext, subid int64) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM subscriptions WHERE subscription_id = ?", subid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -52,7 +52,6 @@ CREATE TABLE channels
|
||||
send_key TEXT NOT NULL,
|
||||
|
||||
timestamp_created INTEGER NOT NULL,
|
||||
timestamp_lastread INTEGER NULL DEFAULT NULL,
|
||||
timestamp_lastsent INTEGER NULL DEFAULT NULL,
|
||||
|
||||
messages_sent INTEGER NOT NULL DEFAULT '0'
|
||||
|
149
server/db/subscriptions.go
Normal file
149
server/db/subscriptions.go
Normal file
@ -0,0 +1,149 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) CreateSubscription(ctx TxContext, subscriberUID int64, channel models.Channel, confirmed bool) (models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.ExecContext(ctx, "INSERT INTO subscriptions (subscriber_user_id, channel_owner_user_id, channel_name, channel_id, timestamp_created, confirmed) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
subscriberUID,
|
||||
channel.OwnerUserID,
|
||||
channel.Name,
|
||||
channel.ChannelID,
|
||||
time2DB(now),
|
||||
confirmed)
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
return models.Subscription{
|
||||
SubscriptionID: liid,
|
||||
SubscriberUserID: subscriberUID,
|
||||
ChannelOwnerUserID: channel.OwnerUserID,
|
||||
ChannelID: channel.ChannelID,
|
||||
ChannelName: channel.Name,
|
||||
TimestampCreated: now,
|
||||
Confirmed: confirmed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) ListSubscriptionsByChannel(ctx TxContext, channelID int64) ([]models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM subscriptions WHERE channel_id = ?", channelID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeSubscriptions(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) ListSubscriptionsByOwner(ctx TxContext, ownerUserID int64) ([]models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM subscriptions WHERE channel_owner_user_id = ?", ownerUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := models.DecodeSubscriptions(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetSubscription(ctx TxContext, subid int64) (models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM subscriptions WHERE subscription_id = ? LIMIT 1", subid)
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
sub, err := models.DecodeSubscription(rows)
|
||||
if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetSubscriptionBySubscriber(ctx TxContext, subscriberId int64, channelId int64) (*models.Subscription, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM subscriptions WHERE subscriber_user_id = ? AND channel_id = ? LIMIT 1", subscriberId, channelId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := models.DecodeSubscription(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (db *Database) DeleteSubscription(ctx TxContext, subid int64) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM subscriptions WHERE subscription_id = ?", subid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateSubscriptionConfirmed(ctx TxContext, subscriptionID int64, confirmed bool) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "UPDATE subscriptions SET confirmed = ? WHERE subscription_id = ?", confirmed, subscriptionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
170
server/db/users.go
Normal file
170
server/db/users.go
Normal file
@ -0,0 +1,170 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
scn "blackforestbytes.com/simplecloudnotifier"
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (db *Database) CreateUser(ctx TxContext, readKey string, sendKey string, adminKey string, protoken *string, username *string) (models.User, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
res, err := tx.ExecContext(ctx, "INSERT INTO users (username, read_key, send_key, admin_key, is_pro, pro_token, timestamp_created) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
username,
|
||||
readKey,
|
||||
sendKey,
|
||||
adminKey,
|
||||
bool2DB(protoken != nil),
|
||||
protoken,
|
||||
time2DB(now))
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
liid, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
return models.User{
|
||||
UserID: liid,
|
||||
Username: username,
|
||||
ReadKey: readKey,
|
||||
SendKey: sendKey,
|
||||
AdminKey: adminKey,
|
||||
TimestampCreated: now,
|
||||
TimestampLastRead: nil,
|
||||
TimestampLastSent: nil,
|
||||
MessagesSent: 0,
|
||||
QuotaUsed: 0,
|
||||
QuotaUsedDay: nil,
|
||||
IsPro: protoken != nil,
|
||||
ProToken: protoken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *Database) ClearProTokens(ctx TxContext, protoken string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "UPDATE users SET is_pro=0, pro_token=NULL WHERE pro_token = ?", protoken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) GetUserByKey(ctx TxContext, key string) (*models.User, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM users WHERE admin_key = ? OR send_key = ? OR read_key = ? LIMIT 1", key, key, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := models.DecodeUser(rows)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (db *Database) GetUser(ctx TxContext, userid int64) (models.User, error) {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
rows, err := tx.QueryContext(ctx, "SELECT * FROM users WHERE user_id = ? LIMIT 1", userid)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
user, err := models.DecodeUser(rows)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserUsername(ctx TxContext, userid int64, username *string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "UPDATE users SET username = ? WHERE user_id = ?", username, userid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserProToken(ctx TxContext, userid int64, protoken *string) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "UPDATE users SET pro_token = ? AND is_pro = ? WHERE user_id = ?", protoken, bool2DB(protoken != nil), userid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) IncUserMessageCounter(ctx TxContext, user models.User) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
quota := user.QuotaUsedToday() + 1
|
||||
|
||||
_, err = tx.ExecContext(ctx, "UPDATE users SET timestamp_lastsent = ? AND messages_sent = ? AND quota_used = ? AND quota_used_day = ? WHERE user_id = ?",
|
||||
time2DB(time.Now()),
|
||||
user.MessagesSent+1,
|
||||
quota,
|
||||
scn.QuotaDayString(),
|
||||
user.UserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) UpdateUserLastRead(ctx TxContext, userid int64) error {
|
||||
tx, err := ctx.GetOrCreateTransaction(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "UPDATE users SET timestamp_lastread = ? WHERE user_id = ?",
|
||||
time2DB(time.Now()),
|
||||
userid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -56,11 +56,11 @@ func (fb App) SendNotification(ctx context.Context, client models.Client, msg mo
|
||||
"priority": strconv.Itoa(msg.Priority),
|
||||
"trimmed": langext.Conditional(msg.NeedsTrim(), "true", "false"),
|
||||
"title": msg.Title,
|
||||
"body": msg.TrimmedBody(),
|
||||
"body": langext.Coalesce(msg.TrimmedContent(), ""),
|
||||
},
|
||||
Notification: &messaging.Notification{
|
||||
Title: msg.Title,
|
||||
Body: msg.ShortBody(),
|
||||
Body: msg.ShortContent(),
|
||||
},
|
||||
Android: nil,
|
||||
APNS: nil,
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
@ -152,32 +153,20 @@ func (app *Application) getPermissions(ctx *AppContext, hdr string) (PermissionS
|
||||
}
|
||||
|
||||
if user != nil && user.SendKey == key {
|
||||
return PermissionSet{ReferenceID: langext.Ptr(user.UserID), KeyType: PermKeyTypeUserSend}, nil
|
||||
return PermissionSet{UserID: langext.Ptr(user.UserID), KeyType: PermKeyTypeUserSend}, nil
|
||||
}
|
||||
if user != nil && user.ReadKey == key {
|
||||
return PermissionSet{ReferenceID: langext.Ptr(user.UserID), KeyType: PermKeyTypeUserRead}, nil
|
||||
return PermissionSet{UserID: langext.Ptr(user.UserID), KeyType: PermKeyTypeUserRead}, nil
|
||||
}
|
||||
if user != nil && user.AdminKey == key {
|
||||
return PermissionSet{ReferenceID: langext.Ptr(user.UserID), KeyType: PermKeyTypeUserAdmin}, nil
|
||||
}
|
||||
|
||||
channel, err := app.Database.GetChannelByKey(ctx, key)
|
||||
if err != nil {
|
||||
return PermissionSet{}, err
|
||||
}
|
||||
|
||||
if channel != nil && channel.SendKey == key {
|
||||
return PermissionSet{ReferenceID: langext.Ptr(channel.ChannelID), KeyType: PermKeyTypeChannelSend}, nil
|
||||
}
|
||||
if channel != nil && channel.SubscribeKey == key {
|
||||
return PermissionSet{ReferenceID: langext.Ptr(channel.ChannelID), KeyType: PermKeyTypeChannelSub}, nil
|
||||
return PermissionSet{UserID: langext.Ptr(user.UserID), KeyType: PermKeyTypeUserAdmin}, nil
|
||||
}
|
||||
|
||||
return NewEmptyPermissions(), nil
|
||||
}
|
||||
|
||||
func (app *Application) GetOrCreateChannel(ctx *AppContext, userid int64, chanName string) (models.Channel, error) {
|
||||
chanName = strings.ToLower(strings.TrimSpace(chanName))
|
||||
chanName = app.NormalizeChannelName(chanName)
|
||||
|
||||
existingChan, err := app.Database.GetChannelByName(ctx, userid, chanName)
|
||||
if err != nil {
|
||||
@ -196,10 +185,29 @@ func (app *Application) GetOrCreateChannel(ctx *AppContext, userid int64, chanNa
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
_, err = app.Database.CreateSubscribtion(ctx, userid, userid, newChan.Name, newChan.ChannelID, true)
|
||||
_, err = app.Database.CreateSubscription(ctx, userid, newChan, true)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
}
|
||||
|
||||
return newChan, nil
|
||||
}
|
||||
|
||||
func (app *Application) NormalizeChannelName(v string) string {
|
||||
rex := regexp.MustCompile("[^[:alnum:]\\-_]")
|
||||
|
||||
v = strings.TrimSpace(v)
|
||||
v = strings.ToLower(v)
|
||||
v = rex.ReplaceAllString(v, "")
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func (app *Application) NormalizeUsername(v string) string {
|
||||
rex := regexp.MustCompile("[^[:alnum:]\\-_ ]")
|
||||
|
||||
v = strings.TrimSpace(v)
|
||||
v = rex.ReplaceAllString(v, "")
|
||||
|
||||
return v
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package logic
|
||||
import (
|
||||
"blackforestbytes.com/simplecloudnotifier/api/apierr"
|
||||
"blackforestbytes.com/simplecloudnotifier/common/ginresp"
|
||||
"blackforestbytes.com/simplecloudnotifier/models"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
)
|
||||
|
||||
@ -13,18 +14,16 @@ const (
|
||||
PermKeyTypeUserSend PermKeyType = "USER_SEND" // send-messages
|
||||
PermKeyTypeUserRead PermKeyType = "USER_READ" // send-messages, list-messages, read-user
|
||||
PermKeyTypeUserAdmin PermKeyType = "USER_ADMIN" // send-messages, list-messages, read-user, delete-messages, update-user
|
||||
PermKeyTypeChannelSub PermKeyType = "CHAN_SUBSCRIBE" // subscribe-channel
|
||||
PermKeyTypeChannelSend PermKeyType = "CHAN_SEND" // send-messages
|
||||
)
|
||||
|
||||
type PermissionSet struct {
|
||||
ReferenceID *int64
|
||||
UserID *int64
|
||||
KeyType PermKeyType
|
||||
}
|
||||
|
||||
func NewEmptyPermissions() PermissionSet {
|
||||
return PermissionSet{
|
||||
ReferenceID: nil,
|
||||
UserID: nil,
|
||||
KeyType: PermKeyTypeNone,
|
||||
}
|
||||
}
|
||||
@ -33,10 +32,10 @@ var respoNotAuthorized = ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "Y
|
||||
|
||||
func (ac *AppContext) CheckPermissionUserRead(userid int64) *ginresp.HTTPResponse {
|
||||
p := ac.permissions
|
||||
if p.ReferenceID != nil && *p.ReferenceID == userid && p.KeyType == PermKeyTypeUserRead {
|
||||
if p.UserID != nil && *p.UserID == userid && p.KeyType == PermKeyTypeUserRead {
|
||||
return nil
|
||||
}
|
||||
if p.ReferenceID != nil && *p.ReferenceID == userid && p.KeyType == PermKeyTypeUserAdmin {
|
||||
if p.UserID != nil && *p.UserID == userid && p.KeyType == PermKeyTypeUserAdmin {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -45,9 +44,53 @@ func (ac *AppContext) CheckPermissionUserRead(userid int64) *ginresp.HTTPRespons
|
||||
|
||||
func (ac *AppContext) CheckPermissionUserAdmin(userid int64) *ginresp.HTTPResponse {
|
||||
p := ac.permissions
|
||||
if p.ReferenceID != nil && *p.ReferenceID == userid && p.KeyType == PermKeyTypeUserAdmin {
|
||||
if p.UserID != nil && *p.UserID == userid && p.KeyType == PermKeyTypeUserAdmin {
|
||||
return nil
|
||||
}
|
||||
|
||||
return langext.Ptr(respoNotAuthorized)
|
||||
}
|
||||
|
||||
func (ac *AppContext) CheckPermissionAny() *ginresp.HTTPResponse {
|
||||
p := ac.permissions
|
||||
if p.KeyType == PermKeyTypeNone {
|
||||
return langext.Ptr(respoNotAuthorized)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ac *AppContext) CheckPermissionMessageReadDirect(msg models.Message) bool {
|
||||
p := ac.permissions
|
||||
if p.UserID != nil && msg.OwnerUserID == *p.UserID && p.KeyType == PermKeyTypeUserRead {
|
||||
return true
|
||||
}
|
||||
if p.UserID != nil && msg.OwnerUserID == *p.UserID && p.KeyType == PermKeyTypeUserAdmin {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (ac *AppContext) GetPermissionUserID() *int64 {
|
||||
if ac.permissions.UserID == nil {
|
||||
return nil
|
||||
} else {
|
||||
return langext.Ptr(*ac.permissions.UserID)
|
||||
}
|
||||
}
|
||||
|
||||
func (ac *AppContext) IsPermissionUserRead() bool {
|
||||
p := ac.permissions
|
||||
return p.KeyType == PermKeyTypeUserRead || p.KeyType == PermKeyTypeUserAdmin
|
||||
}
|
||||
|
||||
func (ac *AppContext) IsPermissionUserSend() bool {
|
||||
p := ac.permissions
|
||||
return p.KeyType == PermKeyTypeUserSend || p.KeyType == PermKeyTypeUserAdmin
|
||||
}
|
||||
|
||||
func (ac *AppContext) IsPermissionUserAdmin() bool {
|
||||
p := ac.permissions
|
||||
return p.KeyType == PermKeyTypeUserAdmin
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ type Channel struct {
|
||||
SubscribeKey string
|
||||
SendKey string
|
||||
TimestampCreated time.Time
|
||||
TimestampLastRead *time.Time
|
||||
TimestampLastSent *time.Time
|
||||
MessagesSent int
|
||||
}
|
||||
@ -27,7 +26,6 @@ func (c Channel) JSON() ChannelJSON {
|
||||
SubscribeKey: c.SubscribeKey,
|
||||
SendKey: c.SendKey,
|
||||
TimestampCreated: c.TimestampCreated.Format(time.RFC3339Nano),
|
||||
TimestampLastRead: timeOptFmt(c.TimestampLastRead, time.RFC3339Nano),
|
||||
TimestampLastSent: timeOptFmt(c.TimestampLastSent, time.RFC3339Nano),
|
||||
MessagesSent: c.MessagesSent,
|
||||
}
|
||||
@ -40,7 +38,6 @@ type ChannelJSON struct {
|
||||
SubscribeKey string `json:"subscribe_key"`
|
||||
SendKey string `json:"send_key"`
|
||||
TimestampCreated string `json:"timestamp_created"`
|
||||
TimestampLastRead *string `json:"timestamp_last_read"`
|
||||
TimestampLastSent *string `json:"timestamp_last_sent"`
|
||||
MessagesSent int `json:"messages_sent"`
|
||||
}
|
||||
@ -65,7 +62,6 @@ func (c ChannelDB) Model() Channel {
|
||||
SubscribeKey: c.SubscribeKey,
|
||||
SendKey: c.SendKey,
|
||||
TimestampCreated: time.UnixMilli(c.TimestampCreated),
|
||||
TimestampLastRead: timeOptFromMilli(c.TimestampLastRead),
|
||||
TimestampLastSent: timeOptFromMilli(c.TimestampLastSent),
|
||||
MessagesSent: c.MessagesSent,
|
||||
}
|
||||
|
@ -7,6 +7,11 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ContentLengthTrim = 1900
|
||||
ContentLengthShort = 200
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
SCNMessageID int64
|
||||
SenderUserID int64
|
||||
@ -21,7 +26,7 @@ type Message struct {
|
||||
UserMessageID *string
|
||||
}
|
||||
|
||||
func (m Message) JSON() MessageJSON {
|
||||
func (m Message) FullJSON() MessageJSON {
|
||||
return MessageJSON{
|
||||
SCNMessageID: m.SCNMessageID,
|
||||
SenderUserID: m.SenderUserID,
|
||||
@ -33,6 +38,23 @@ func (m Message) JSON() MessageJSON {
|
||||
Content: m.Content,
|
||||
Priority: m.Priority,
|
||||
UserMessageID: m.UserMessageID,
|
||||
Trimmed: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (m Message) TrimmedJSON() MessageJSON {
|
||||
return MessageJSON{
|
||||
SCNMessageID: m.SCNMessageID,
|
||||
SenderUserID: m.SenderUserID,
|
||||
OwnerUserID: m.OwnerUserID,
|
||||
ChannelName: m.ChannelName,
|
||||
ChannelID: m.ChannelID,
|
||||
Timestamp: m.Timestamp().Format(time.RFC3339Nano),
|
||||
Title: m.Title,
|
||||
Content: m.TrimmedContent(),
|
||||
Priority: m.Priority,
|
||||
UserMessageID: m.UserMessageID,
|
||||
Trimmed: m.NeedsTrim(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,24 +63,27 @@ func (m Message) Timestamp() time.Time {
|
||||
}
|
||||
|
||||
func (m Message) NeedsTrim() bool {
|
||||
return m.Content != nil && len(*m.Content) > 1900
|
||||
return m.Content != nil && len(*m.Content) > ContentLengthTrim
|
||||
}
|
||||
|
||||
func (m Message) TrimmedBody() string {
|
||||
if !m.NeedsTrim() {
|
||||
return langext.Coalesce(m.Content, "")
|
||||
func (m Message) TrimmedContent() *string {
|
||||
if m.Content == nil {
|
||||
return nil
|
||||
}
|
||||
return langext.Coalesce(m.Content, "")[0:1900-3] + "..."
|
||||
if !m.NeedsTrim() {
|
||||
return m.Content
|
||||
}
|
||||
return langext.Ptr(langext.Coalesce(m.Content, "")[0:ContentLengthTrim-3] + "...")
|
||||
}
|
||||
|
||||
func (m Message) ShortBody() string {
|
||||
func (m Message) ShortContent() string {
|
||||
if m.Content == nil {
|
||||
return ""
|
||||
}
|
||||
if len(*m.Content) < 200 {
|
||||
if len(*m.Content) < ContentLengthShort {
|
||||
return *m.Content
|
||||
}
|
||||
return (*m.Content)[0:200-3] + "..."
|
||||
return (*m.Content)[0:ContentLengthShort-3] + "..."
|
||||
}
|
||||
|
||||
type MessageJSON struct {
|
||||
@ -72,6 +97,7 @@ type MessageJSON struct {
|
||||
Content *string `json:"body"`
|
||||
Priority int `json:"priority"`
|
||||
UserMessageID *string `json:"usr_message_id"`
|
||||
Trimmed bool `json:"trimmed"`
|
||||
}
|
||||
|
||||
type MessageDB struct {
|
||||
|
@ -1,10 +1,10 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
scn "blackforestbytes.com/simplecloudnotifier"
|
||||
"database/sql"
|
||||
"github.com/blockloop/scan"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/timeext"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -58,7 +58,7 @@ func (u User) QuotaPerDay() int {
|
||||
}
|
||||
|
||||
func (u User) QuotaUsedToday() int {
|
||||
now := time.Now().In(timeext.TimezoneBerlin).Format("2006-01-02")
|
||||
now := scn.QuotaDayString()
|
||||
if u.QuotaUsedDay != nil && *u.QuotaUsedDay == now {
|
||||
return u.QuotaUsed
|
||||
} else {
|
||||
|
10
server/util.go
Normal file
10
server/util.go
Normal file
@ -0,0 +1,10 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"gogs.mikescher.com/BlackForestBytes/goext/timeext"
|
||||
"time"
|
||||
)
|
||||
|
||||
func QuotaDayString() string {
|
||||
return time.Now().In(timeext.TimezoneBerlin).Format("2006-01-02")
|
||||
}
|
Loading…
Reference in New Issue
Block a user