CreateSubscription(), UpdateSubscription(), GetMessage(), DeleteMessage()
This commit is contained in:
parent
8278c059ad
commit
0d641b727f
@ -4,10 +4,11 @@
|
|||||||
|
|
||||||
|
|
||||||
- background job for re-delivery
|
- background job for re-delivery
|
||||||
- accept/decline subscribtions (PATCH subs)
|
- accept/decline subscriptions (PATCH subs)
|
||||||
- (message.go) api routes
|
- (message.go) api routes
|
||||||
- (compat.go) api routes
|
- (compat.go) api routes
|
||||||
- https://firebase.google.com/docs/cloud-messaging/send-message#rest
|
- https://firebase.google.com/docs/cloud-messaging/send-message#rest
|
||||||
- List subscribtions on owned channels /RESTful?)
|
- List subscriptions on owned channels /RESTful?)
|
||||||
- deploy
|
- deploy
|
||||||
- Dockerfile
|
- Dockerfile
|
||||||
|
- php in html
|
@ -26,6 +26,7 @@ const (
|
|||||||
CLIENT_NOT_FOUND APIError = 1302
|
CLIENT_NOT_FOUND APIError = 1302
|
||||||
CHANNEL_NOT_FOUND APIError = 1303
|
CHANNEL_NOT_FOUND APIError = 1303
|
||||||
SUBSCRIPTION_NOT_FOUND APIError = 1304
|
SUBSCRIPTION_NOT_FOUND APIError = 1304
|
||||||
|
MESSAGE_NOT_FOUND APIError = 1305
|
||||||
USER_AUTH_FAILED APIError = 1311
|
USER_AUTH_FAILED APIError = 1311
|
||||||
|
|
||||||
NO_DEVICE_LINKED APIError = 1401
|
NO_DEVICE_LINKED APIError = 1401
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type APIHandler struct {
|
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 {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to create user in db", err)
|
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 {
|
if b.Username != nil {
|
||||||
username := langext.Ptr(regexp.MustCompile(`[[:alnum:]\-_]`).ReplaceAllString(*b.Username, ""))
|
username := langext.Ptr(h.app.NormalizeUsername(*b.Username))
|
||||||
if *username == "" {
|
if *username == "" {
|
||||||
username = nil
|
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)
|
return ginresp.InternAPIError(404, apierr.SUBSCRIPTION_NOT_FOUND, "Subscription not found", err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
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 {
|
||||||
@ -672,7 +676,7 @@ func (h APIHandler) CancelSubscription(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
}
|
}
|
||||||
defer ctx.Cancel()
|
defer ctx.Cancel()
|
||||||
|
|
||||||
if permResp := ctx.CheckPermissionUserRead(u.UserID); permResp != nil {
|
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
|
||||||
return *permResp
|
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)
|
return ginresp.InternAPIError(404, apierr.SUBSCRIPTION_NOT_FOUND, "Subscription not found", err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
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)
|
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()))
|
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 {
|
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 {
|
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 {
|
func (h APIHandler) ListMessages(g *gin.Context) ginresp.HTTPResponse {
|
||||||
|
//also update last_read
|
||||||
return ginresp.NotImplemented() //TODO
|
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 {
|
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 {
|
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 {
|
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")
|
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 {
|
for _, sub := range subscriptions {
|
||||||
clients, err := h.database.ListClients(ctx, sub.SubscriberUserID)
|
clients, err := h.database.ListClients(ctx, sub.SubscriberUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query clients")
|
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query clients")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !sub.Confirmed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
|
|
||||||
fcmDelivID, err := h.deliverMessage(ctx, client, msg)
|
fcmDelivID, err := h.deliverMessage(ctx, client, msg)
|
||||||
@ -220,8 +234,8 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
ErrorHighlight: -1,
|
ErrorHighlight: -1,
|
||||||
Message: "Message sent",
|
Message: "Message sent",
|
||||||
SuppressSend: false,
|
SuppressSend: false,
|
||||||
MessageCount: user.MessagesSent,
|
MessageCount: user.MessagesSent + 1,
|
||||||
Quota: user.QuotaUsedToday(),
|
Quota: user.QuotaUsedToday() + 1,
|
||||||
IsPro: user.IsPro,
|
IsPro: user.IsPro,
|
||||||
QuotaMax: user.QuotaPerDay(),
|
QuotaMax: user.QuotaPerDay(),
|
||||||
SCNMessageID: msg.SCNMessageID,
|
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,
|
send_key TEXT NOT NULL,
|
||||||
|
|
||||||
timestamp_created INTEGER NOT NULL,
|
timestamp_created INTEGER NOT NULL,
|
||||||
timestamp_lastread INTEGER NULL DEFAULT NULL,
|
|
||||||
timestamp_lastsent INTEGER NULL DEFAULT NULL,
|
timestamp_lastsent INTEGER NULL DEFAULT NULL,
|
||||||
|
|
||||||
messages_sent INTEGER NOT NULL DEFAULT '0'
|
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),
|
"priority": strconv.Itoa(msg.Priority),
|
||||||
"trimmed": langext.Conditional(msg.NeedsTrim(), "true", "false"),
|
"trimmed": langext.Conditional(msg.NeedsTrim(), "true", "false"),
|
||||||
"title": msg.Title,
|
"title": msg.Title,
|
||||||
"body": msg.TrimmedBody(),
|
"body": langext.Coalesce(msg.TrimmedContent(), ""),
|
||||||
},
|
},
|
||||||
Notification: &messaging.Notification{
|
Notification: &messaging.Notification{
|
||||||
Title: msg.Title,
|
Title: msg.Title,
|
||||||
Body: msg.ShortBody(),
|
Body: msg.ShortContent(),
|
||||||
},
|
},
|
||||||
Android: nil,
|
Android: nil,
|
||||||
APNS: nil,
|
APNS: nil,
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@ -152,32 +153,20 @@ func (app *Application) getPermissions(ctx *AppContext, hdr string) (PermissionS
|
|||||||
}
|
}
|
||||||
|
|
||||||
if user != nil && user.SendKey == key {
|
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 {
|
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 {
|
if user != nil && user.AdminKey == key {
|
||||||
return PermissionSet{ReferenceID: langext.Ptr(user.UserID), KeyType: PermKeyTypeUserAdmin}, nil
|
return PermissionSet{UserID: 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 NewEmptyPermissions(), nil
|
return NewEmptyPermissions(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *Application) GetOrCreateChannel(ctx *AppContext, userid int64, chanName string) (models.Channel, error) {
|
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)
|
existingChan, err := app.Database.GetChannelByName(ctx, userid, chanName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -196,10 +185,29 @@ func (app *Application) GetOrCreateChannel(ctx *AppContext, userid int64, chanNa
|
|||||||
return models.Channel{}, err
|
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 {
|
if err != nil {
|
||||||
return models.Channel{}, err
|
return models.Channel{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return newChan, nil
|
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 (
|
import (
|
||||||
"blackforestbytes.com/simplecloudnotifier/api/apierr"
|
"blackforestbytes.com/simplecloudnotifier/api/apierr"
|
||||||
"blackforestbytes.com/simplecloudnotifier/common/ginresp"
|
"blackforestbytes.com/simplecloudnotifier/common/ginresp"
|
||||||
|
"blackforestbytes.com/simplecloudnotifier/models"
|
||||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,18 +14,16 @@ const (
|
|||||||
PermKeyTypeUserSend PermKeyType = "USER_SEND" // send-messages
|
PermKeyTypeUserSend PermKeyType = "USER_SEND" // send-messages
|
||||||
PermKeyTypeUserRead PermKeyType = "USER_READ" // send-messages, list-messages, read-user
|
PermKeyTypeUserRead PermKeyType = "USER_READ" // send-messages, list-messages, read-user
|
||||||
PermKeyTypeUserAdmin PermKeyType = "USER_ADMIN" // send-messages, list-messages, read-user, delete-messages, update-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 {
|
type PermissionSet struct {
|
||||||
ReferenceID *int64
|
UserID *int64
|
||||||
KeyType PermKeyType
|
KeyType PermKeyType
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEmptyPermissions() PermissionSet {
|
func NewEmptyPermissions() PermissionSet {
|
||||||
return PermissionSet{
|
return PermissionSet{
|
||||||
ReferenceID: nil,
|
UserID: nil,
|
||||||
KeyType: PermKeyTypeNone,
|
KeyType: PermKeyTypeNone,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,10 +32,10 @@ var respoNotAuthorized = ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "Y
|
|||||||
|
|
||||||
func (ac *AppContext) CheckPermissionUserRead(userid int64) *ginresp.HTTPResponse {
|
func (ac *AppContext) CheckPermissionUserRead(userid int64) *ginresp.HTTPResponse {
|
||||||
p := ac.permissions
|
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
|
return nil
|
||||||
}
|
}
|
||||||
if p.ReferenceID != nil && *p.ReferenceID == userid && p.KeyType == PermKeyTypeUserAdmin {
|
if p.UserID != nil && *p.UserID == userid && p.KeyType == PermKeyTypeUserAdmin {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,9 +44,53 @@ func (ac *AppContext) CheckPermissionUserRead(userid int64) *ginresp.HTTPRespons
|
|||||||
|
|
||||||
func (ac *AppContext) CheckPermissionUserAdmin(userid int64) *ginresp.HTTPResponse {
|
func (ac *AppContext) CheckPermissionUserAdmin(userid int64) *ginresp.HTTPResponse {
|
||||||
p := ac.permissions
|
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 nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return langext.Ptr(respoNotAuthorized)
|
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
|
SubscribeKey string
|
||||||
SendKey string
|
SendKey string
|
||||||
TimestampCreated time.Time
|
TimestampCreated time.Time
|
||||||
TimestampLastRead *time.Time
|
|
||||||
TimestampLastSent *time.Time
|
TimestampLastSent *time.Time
|
||||||
MessagesSent int
|
MessagesSent int
|
||||||
}
|
}
|
||||||
@ -27,7 +26,6 @@ func (c Channel) JSON() ChannelJSON {
|
|||||||
SubscribeKey: c.SubscribeKey,
|
SubscribeKey: c.SubscribeKey,
|
||||||
SendKey: c.SendKey,
|
SendKey: c.SendKey,
|
||||||
TimestampCreated: c.TimestampCreated.Format(time.RFC3339Nano),
|
TimestampCreated: c.TimestampCreated.Format(time.RFC3339Nano),
|
||||||
TimestampLastRead: timeOptFmt(c.TimestampLastRead, time.RFC3339Nano),
|
|
||||||
TimestampLastSent: timeOptFmt(c.TimestampLastSent, time.RFC3339Nano),
|
TimestampLastSent: timeOptFmt(c.TimestampLastSent, time.RFC3339Nano),
|
||||||
MessagesSent: c.MessagesSent,
|
MessagesSent: c.MessagesSent,
|
||||||
}
|
}
|
||||||
@ -40,7 +38,6 @@ type ChannelJSON struct {
|
|||||||
SubscribeKey string `json:"subscribe_key"`
|
SubscribeKey string `json:"subscribe_key"`
|
||||||
SendKey string `json:"send_key"`
|
SendKey string `json:"send_key"`
|
||||||
TimestampCreated string `json:"timestamp_created"`
|
TimestampCreated string `json:"timestamp_created"`
|
||||||
TimestampLastRead *string `json:"timestamp_last_read"`
|
|
||||||
TimestampLastSent *string `json:"timestamp_last_sent"`
|
TimestampLastSent *string `json:"timestamp_last_sent"`
|
||||||
MessagesSent int `json:"messages_sent"`
|
MessagesSent int `json:"messages_sent"`
|
||||||
}
|
}
|
||||||
@ -65,7 +62,6 @@ func (c ChannelDB) Model() Channel {
|
|||||||
SubscribeKey: c.SubscribeKey,
|
SubscribeKey: c.SubscribeKey,
|
||||||
SendKey: c.SendKey,
|
SendKey: c.SendKey,
|
||||||
TimestampCreated: time.UnixMilli(c.TimestampCreated),
|
TimestampCreated: time.UnixMilli(c.TimestampCreated),
|
||||||
TimestampLastRead: timeOptFromMilli(c.TimestampLastRead),
|
|
||||||
TimestampLastSent: timeOptFromMilli(c.TimestampLastSent),
|
TimestampLastSent: timeOptFromMilli(c.TimestampLastSent),
|
||||||
MessagesSent: c.MessagesSent,
|
MessagesSent: c.MessagesSent,
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ContentLengthTrim = 1900
|
||||||
|
ContentLengthShort = 200
|
||||||
|
)
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
SCNMessageID int64
|
SCNMessageID int64
|
||||||
SenderUserID int64
|
SenderUserID int64
|
||||||
@ -21,7 +26,7 @@ type Message struct {
|
|||||||
UserMessageID *string
|
UserMessageID *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Message) JSON() MessageJSON {
|
func (m Message) FullJSON() MessageJSON {
|
||||||
return MessageJSON{
|
return MessageJSON{
|
||||||
SCNMessageID: m.SCNMessageID,
|
SCNMessageID: m.SCNMessageID,
|
||||||
SenderUserID: m.SenderUserID,
|
SenderUserID: m.SenderUserID,
|
||||||
@ -33,6 +38,23 @@ func (m Message) JSON() MessageJSON {
|
|||||||
Content: m.Content,
|
Content: m.Content,
|
||||||
Priority: m.Priority,
|
Priority: m.Priority,
|
||||||
UserMessageID: m.UserMessageID,
|
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 {
|
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 {
|
func (m Message) TrimmedContent() *string {
|
||||||
|
if m.Content == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if !m.NeedsTrim() {
|
if !m.NeedsTrim() {
|
||||||
return langext.Coalesce(m.Content, "")
|
return m.Content
|
||||||
}
|
}
|
||||||
return langext.Coalesce(m.Content, "")[0:1900-3] + "..."
|
return langext.Ptr(langext.Coalesce(m.Content, "")[0:ContentLengthTrim-3] + "...")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Message) ShortBody() string {
|
func (m Message) ShortContent() string {
|
||||||
if m.Content == nil {
|
if m.Content == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if len(*m.Content) < 200 {
|
if len(*m.Content) < ContentLengthShort {
|
||||||
return *m.Content
|
return *m.Content
|
||||||
}
|
}
|
||||||
return (*m.Content)[0:200-3] + "..."
|
return (*m.Content)[0:ContentLengthShort-3] + "..."
|
||||||
}
|
}
|
||||||
|
|
||||||
type MessageJSON struct {
|
type MessageJSON struct {
|
||||||
@ -72,6 +97,7 @@ type MessageJSON struct {
|
|||||||
Content *string `json:"body"`
|
Content *string `json:"body"`
|
||||||
Priority int `json:"priority"`
|
Priority int `json:"priority"`
|
||||||
UserMessageID *string `json:"usr_message_id"`
|
UserMessageID *string `json:"usr_message_id"`
|
||||||
|
Trimmed bool `json:"trimmed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MessageDB struct {
|
type MessageDB struct {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
scn "blackforestbytes.com/simplecloudnotifier"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"github.com/blockloop/scan"
|
"github.com/blockloop/scan"
|
||||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
"gogs.mikescher.com/BlackForestBytes/goext/timeext"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ func (u User) QuotaPerDay() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u User) QuotaUsedToday() 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 {
|
if u.QuotaUsedDay != nil && *u.QuotaUsedDay == now {
|
||||||
return u.QuotaUsed
|
return u.QuotaUsed
|
||||||
} else {
|
} 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