firebase via REST (less dependencies)
This commit is contained in:
parent
08a93551e7
commit
d30e2cefc0
@ -4,6 +4,8 @@ type APIError int
|
|||||||
|
|
||||||
//goland:noinspection GoSnakeCaseUsage
|
//goland:noinspection GoSnakeCaseUsage
|
||||||
const (
|
const (
|
||||||
|
UNDEFINED APIError = -1
|
||||||
|
|
||||||
NO_ERROR APIError = 0000
|
NO_ERROR APIError = 0000
|
||||||
|
|
||||||
MISSING_UID APIError = 1101
|
MISSING_UID APIError = 1101
|
||||||
|
@ -61,17 +61,17 @@ func (h APIHandler) CreateUser(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
} else if b.ClientType == string(models.ClientTypeIOS) {
|
} else if b.ClientType == string(models.ClientTypeIOS) {
|
||||||
clientType = models.ClientTypeIOS
|
clientType = models.ClientTypeIOS
|
||||||
} else {
|
} else {
|
||||||
return ginresp.InternAPIError(400, apierr.INVALID_CLIENTTYPE, "Invalid ClientType", nil)
|
return ginresp.InternAPIError(g, 400, apierr.INVALID_CLIENTTYPE, "Invalid ClientType", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.ProToken != nil {
|
if b.ProToken != nil {
|
||||||
ptok, err := h.app.VerifyProToken(*b.ProToken)
|
ptok, err := h.app.VerifyProToken(*b.ProToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.FAILED_VERIFY_PRO_TOKEN, "Failed to query purchase status", err)
|
return ginresp.InternAPIError(g, 500, apierr.FAILED_VERIFY_PRO_TOKEN, "Failed to query purchase status", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ptok {
|
if !ptok {
|
||||||
return ginresp.InternAPIError(400, apierr.INVALID_PRO_TOKEN, "Purchase token could not be verified", nil)
|
return ginresp.InternAPIError(g, 400, apierr.INVALID_PRO_TOKEN, "Purchase token could not be verified", nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,13 +81,13 @@ func (h APIHandler) CreateUser(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
err := h.database.ClearFCMTokens(ctx, b.FCMToken)
|
err := h.database.ClearFCMTokens(ctx, b.FCMToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to clear existing fcm tokens", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to clear existing fcm tokens", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.ProToken != nil {
|
if b.ProToken != nil {
|
||||||
err := h.database.ClearProTokens(ctx, *b.ProToken)
|
err := h.database.ClearProTokens(ctx, *b.ProToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to clear existing fcm tokens", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to clear existing fcm tokens", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,12 +98,12 @@ func (h APIHandler) CreateUser(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
userobj, err := h.database.CreateUser(ctx, readKey, sendKey, adminKey, b.ProToken, 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(g, 500, apierr.DATABASE_ERROR, "Failed to create user in db", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = h.database.CreateClient(ctx, userobj.UserID, clientType, b.FCMToken, b.AgentModel, b.AgentVersion)
|
_, err = h.database.CreateClient(ctx, userobj.UserID, clientType, b.FCMToken, b.AgentModel, b.AgentVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to create user in db", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to create user in db", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, userobj.JSON()))
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, userobj.JSON()))
|
||||||
@ -141,10 +141,10 @@ func (h APIHandler) GetUser(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
user, err := h.database.GetUser(ctx, u.UserID)
|
user, err := h.database.GetUser(ctx, u.UserID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return ginresp.InternAPIError(404, apierr.USER_NOT_FOUND, "User not found", err)
|
return ginresp.InternAPIError(g, 404, apierr.USER_NOT_FOUND, "User not found", err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query user", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query user", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, user.JSON()))
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, user.JSON()))
|
||||||
@ -194,34 +194,34 @@ func (h APIHandler) UpdateUser(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
err := h.database.UpdateUserUsername(ctx, u.UserID, b.Username)
|
err := h.database.UpdateUserUsername(ctx, u.UserID, b.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to update user", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to update user", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.ProToken != nil {
|
if b.ProToken != nil {
|
||||||
ptok, err := h.app.VerifyProToken(*b.ProToken)
|
ptok, err := h.app.VerifyProToken(*b.ProToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.FAILED_VERIFY_PRO_TOKEN, "Failed to query purchase status", err)
|
return ginresp.InternAPIError(g, 500, apierr.FAILED_VERIFY_PRO_TOKEN, "Failed to query purchase status", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ptok {
|
if !ptok {
|
||||||
return ginresp.InternAPIError(400, apierr.INVALID_PRO_TOKEN, "Purchase token could not be verified", nil)
|
return ginresp.InternAPIError(g, 400, apierr.INVALID_PRO_TOKEN, "Purchase token could not be verified", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.database.ClearProTokens(ctx, *b.ProToken)
|
err = h.database.ClearProTokens(ctx, *b.ProToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to clear existing fcm tokens", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to clear existing fcm tokens", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.database.UpdateUserProToken(ctx, u.UserID, b.ProToken)
|
err = h.database.UpdateUserProToken(ctx, u.UserID, b.ProToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to update user", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to update user", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := h.database.GetUser(ctx, u.UserID)
|
user, err := h.database.GetUser(ctx, u.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query (updated) user", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query (updated) user", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, user.JSON()))
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, user.JSON()))
|
||||||
@ -262,7 +262,7 @@ func (h APIHandler) ListClients(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
clients, err := h.database.ListClients(ctx, u.UserID)
|
clients, err := h.database.ListClients(ctx, u.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query clients", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query clients", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res := langext.ArrMap(clients, func(v models.Client) models.ClientJSON { return v.JSON() })
|
res := langext.ArrMap(clients, func(v models.Client) models.ClientJSON { return v.JSON() })
|
||||||
@ -304,10 +304,10 @@ func (h APIHandler) GetClient(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
client, err := h.database.GetClient(ctx, u.UserID, u.ClientID)
|
client, err := h.database.GetClient(ctx, u.UserID, u.ClientID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return ginresp.InternAPIError(404, apierr.CLIENT_NOT_FOUND, "Client not found", err)
|
return ginresp.InternAPIError(g, 404, apierr.CLIENT_NOT_FOUND, "Client not found", err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query client", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query client", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, client.JSON()))
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, client.JSON()))
|
||||||
@ -354,7 +354,7 @@ func (h APIHandler) AddClient(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
} else if b.ClientType == string(models.ClientTypeIOS) {
|
} else if b.ClientType == string(models.ClientTypeIOS) {
|
||||||
clientType = models.ClientTypeIOS
|
clientType = models.ClientTypeIOS
|
||||||
} else {
|
} else {
|
||||||
return ginresp.InternAPIError(400, apierr.INVALID_CLIENTTYPE, "Invalid ClientType", nil)
|
return ginresp.InternAPIError(g, 400, apierr.INVALID_CLIENTTYPE, "Invalid ClientType", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
|
if permResp := ctx.CheckPermissionUserAdmin(u.UserID); permResp != nil {
|
||||||
@ -363,7 +363,7 @@ func (h APIHandler) AddClient(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
client, err := h.database.CreateClient(ctx, u.UserID, clientType, b.FCMToken, b.AgentModel, b.AgentVersion)
|
client, err := h.database.CreateClient(ctx, u.UserID, clientType, b.FCMToken, b.AgentModel, b.AgentVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to create user in db", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to create user in db", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, client.JSON()))
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, client.JSON()))
|
||||||
@ -403,15 +403,15 @@ func (h APIHandler) DeleteClient(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
client, err := h.database.GetClient(ctx, u.UserID, u.ClientID)
|
client, err := h.database.GetClient(ctx, u.UserID, u.ClientID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return ginresp.InternAPIError(404, apierr.CLIENT_NOT_FOUND, "Client not found", err)
|
return ginresp.InternAPIError(g, 404, apierr.CLIENT_NOT_FOUND, "Client not found", err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query client", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query client", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.database.DeleteClient(ctx, u.ClientID)
|
err = h.database.DeleteClient(ctx, u.ClientID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to delete client", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to delete client", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, client.JSON()))
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, client.JSON()))
|
||||||
@ -452,7 +452,7 @@ func (h APIHandler) ListChannels(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
clients, err := h.database.ListChannels(ctx, u.UserID)
|
clients, err := h.database.ListChannels(ctx, u.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query channels", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channels", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res := langext.ArrMap(clients, func(v models.Channel) models.ChannelJSON { return v.JSON() })
|
res := langext.ArrMap(clients, func(v models.Channel) models.ChannelJSON { return v.JSON() })
|
||||||
@ -494,10 +494,10 @@ func (h APIHandler) GetChannel(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
channel, err := h.database.GetChannel(ctx, u.UserID, u.ChannelID)
|
channel, err := h.database.GetChannel(ctx, u.UserID, u.ChannelID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return ginresp.InternAPIError(404, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
|
return ginresp.InternAPIError(g, 404, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query channel", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channel", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, channel.JSON()))
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, channel.JSON()))
|
||||||
@ -558,33 +558,33 @@ func (h APIHandler) ListChannelMessages(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
channel, err := h.database.GetChannel(ctx, u.ChannelUserID, u.ChannelID)
|
channel, err := h.database.GetChannel(ctx, u.ChannelUserID, u.ChannelID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return ginresp.InternAPIError(404, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
|
return ginresp.InternAPIError(g, 404, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query channel", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channel", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
userid := *ctx.GetPermissionUserID()
|
userid := *ctx.GetPermissionUserID()
|
||||||
|
|
||||||
sub, err := h.database.GetSubscriptionBySubscriber(ctx, userid, channel.ChannelID)
|
sub, err := h.database.GetSubscriptionBySubscriber(ctx, userid, channel.ChannelID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
return ginresp.InternAPIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||||
}
|
}
|
||||||
if !sub.Confirmed {
|
if !sub.Confirmed {
|
||||||
return ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
return ginresp.InternAPIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
tok, err := cursortoken.Decode(langext.Coalesce(q.NextPageToken, ""))
|
tok, err := cursortoken.Decode(langext.Coalesce(q.NextPageToken, ""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.PAGETOKEN_ERROR, "Failed to decode next_page_token", err)
|
return ginresp.InternAPIError(g, 500, apierr.PAGETOKEN_ERROR, "Failed to decode next_page_token", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
messages, npt, err := h.database.ListChannelMessages(ctx, channel.ChannelID, pageSize, tok)
|
messages, npt, err := h.database.ListChannelMessages(ctx, channel.ChannelID, pageSize, tok)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query messages", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query messages", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var res []models.MessageJSON
|
var res []models.MessageJSON
|
||||||
@ -632,7 +632,7 @@ func (h APIHandler) ListUserSubscriptions(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
clients, err := h.database.ListSubscriptionsByOwner(ctx, u.UserID)
|
clients, err := h.database.ListSubscriptionsByOwner(ctx, u.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query channels", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channels", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res := langext.ArrMap(clients, func(v models.Subscription) models.SubscriptionJSON { return v.JSON() })
|
res := langext.ArrMap(clients, func(v models.Subscription) models.SubscriptionJSON { return v.JSON() })
|
||||||
@ -677,15 +677,15 @@ func (h APIHandler) ListChannelSubscriptions(g *gin.Context) ginresp.HTTPRespons
|
|||||||
|
|
||||||
_, err := h.database.GetChannel(ctx, u.UserID, u.ChannelID)
|
_, err := h.database.GetChannel(ctx, u.UserID, u.ChannelID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return ginresp.InternAPIError(404, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
|
return ginresp.InternAPIError(g, 404, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query channels", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channels", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
clients, err := h.database.ListSubscriptionsByChannel(ctx, u.ChannelID)
|
clients, err := h.database.ListSubscriptionsByChannel(ctx, u.ChannelID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query channels", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channels", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res := langext.ArrMap(clients, func(v models.Subscription) models.SubscriptionJSON { return v.JSON() })
|
res := langext.ArrMap(clients, func(v models.Subscription) models.SubscriptionJSON { return v.JSON() })
|
||||||
@ -727,14 +727,14 @@ func (h APIHandler) GetSubscription(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
subscription, err := h.database.GetSubscription(ctx, u.SubscriptionID)
|
subscription, err := h.database.GetSubscription(ctx, u.SubscriptionID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return ginresp.InternAPIError(404, apierr.SUBSCRIPTION_NOT_FOUND, "Subscription not found", err)
|
return ginresp.InternAPIError(g, 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 subscription", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if subscription.SubscriberUserID != u.UserID {
|
if subscription.SubscriberUserID != u.UserID {
|
||||||
return ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
return ginresp.InternAPIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, subscription.JSON()))
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, subscription.JSON()))
|
||||||
@ -774,19 +774,19 @@ func (h APIHandler) CancelSubscription(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
subscription, err := h.database.GetSubscription(ctx, u.SubscriptionID)
|
subscription, err := h.database.GetSubscription(ctx, u.SubscriptionID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return ginresp.InternAPIError(404, apierr.SUBSCRIPTION_NOT_FOUND, "Subscription not found", err)
|
return ginresp.InternAPIError(g, 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 subscription", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if subscription.SubscriberUserID != u.UserID && subscription.ChannelOwnerUserID != 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(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.database.DeleteSubscription(ctx, u.SubscriptionID)
|
err = h.database.DeleteSubscription(ctx, u.SubscriptionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to delete subscription", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to delete subscription", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, subscription.JSON()))
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, subscription.JSON()))
|
||||||
@ -835,19 +835,19 @@ func (h APIHandler) CreateSubscription(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
channel, err := h.database.GetChannelByName(ctx, b.ChannelOwnerUserID, h.app.NormalizeChannelName(b.Channel))
|
channel, err := h.database.GetChannelByName(ctx, b.ChannelOwnerUserID, h.app.NormalizeChannelName(b.Channel))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query channel", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query channel", err)
|
||||||
}
|
}
|
||||||
if channel == nil {
|
if channel == nil {
|
||||||
return ginresp.InternAPIError(400, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
|
return ginresp.InternAPIError(g, 400, apierr.CHANNEL_NOT_FOUND, "Channel not found", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if channel.OwnerUserID != u.UserID && (q.ChanSubscribeKey == nil || *q.ChanSubscribeKey != channel.SubscribeKey) {
|
if channel.OwnerUserID != u.UserID && (q.ChanSubscribeKey == nil || *q.ChanSubscribeKey != channel.SubscribeKey) {
|
||||||
ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
ginresp.InternAPIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
sub, err := h.database.CreateSubscription(ctx, u.UserID, *channel, channel.OwnerUserID == u.UserID)
|
sub, err := h.database.CreateSubscription(ctx, u.UserID, *channel, channel.OwnerUserID == u.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to create subscription", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to create subscription", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, sub.JSON()))
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, sub.JSON()))
|
||||||
@ -891,26 +891,26 @@ func (h APIHandler) UpdateSubscription(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
subscription, err := h.database.GetSubscription(ctx, u.SubscriptionID)
|
subscription, err := h.database.GetSubscription(ctx, u.SubscriptionID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return ginresp.InternAPIError(404, apierr.SUBSCRIPTION_NOT_FOUND, "Subscription not found", err)
|
return ginresp.InternAPIError(g, 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 subscription", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if subscription.ChannelOwnerUserID != u.UserID {
|
if subscription.ChannelOwnerUserID != u.UserID {
|
||||||
return ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
return ginresp.InternAPIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.Confirmed != nil {
|
if b.Confirmed != nil {
|
||||||
err = h.database.UpdateSubscriptionConfirmed(ctx, u.SubscriptionID, *b.Confirmed)
|
err = h.database.UpdateSubscriptionConfirmed(ctx, u.SubscriptionID, *b.Confirmed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to update subscription", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to update subscription", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subscription, err = h.database.GetSubscription(ctx, u.SubscriptionID)
|
subscription, err = h.database.GetSubscription(ctx, u.SubscriptionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, subscription.JSON()))
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, subscription.JSON()))
|
||||||
@ -968,17 +968,17 @@ func (h APIHandler) ListMessages(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
tok, err := cursortoken.Decode(langext.Coalesce(q.NextPageToken, ""))
|
tok, err := cursortoken.Decode(langext.Coalesce(q.NextPageToken, ""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.PAGETOKEN_ERROR, "Failed to decode next_page_token", err)
|
return ginresp.InternAPIError(g, 500, apierr.PAGETOKEN_ERROR, "Failed to decode next_page_token", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.database.UpdateUserLastRead(ctx, userid)
|
err = h.database.UpdateUserLastRead(ctx, userid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to update last-read", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to update last-read", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
messages, npt, err := h.database.ListMessages(ctx, userid, pageSize, tok)
|
messages, npt, err := h.database.ListMessages(ctx, userid, pageSize, tok)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query messages", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query messages", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var res []models.MessageJSON
|
var res []models.MessageJSON
|
||||||
@ -1026,10 +1026,10 @@ func (h APIHandler) GetMessage(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
msg, err := h.database.GetMessage(ctx, u.MessageID)
|
msg, err := h.database.GetMessage(ctx, u.MessageID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return ginresp.InternAPIError(404, apierr.MESSAGE_NOT_FOUND, "message not found", err)
|
return ginresp.InternAPIError(g, 404, apierr.MESSAGE_NOT_FOUND, "message not found", err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query message", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query message", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.CheckPermissionMessageReadDirect(msg) {
|
if !ctx.CheckPermissionMessageReadDirect(msg) {
|
||||||
@ -1040,21 +1040,21 @@ func (h APIHandler) GetMessage(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
if uid := ctx.GetPermissionUserID(); uid != nil && ctx.IsPermissionUserRead() {
|
if uid := ctx.GetPermissionUserID(); uid != nil && ctx.IsPermissionUserRead() {
|
||||||
sub, err := h.database.GetSubscriptionBySubscriber(ctx, *uid, msg.ChannelID)
|
sub, err := h.database.GetSubscriptionBySubscriber(ctx, *uid, msg.ChannelID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query subscription", err)
|
||||||
}
|
}
|
||||||
if sub == nil {
|
if sub == nil {
|
||||||
// not subbed
|
// not subbed
|
||||||
return ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
return ginresp.InternAPIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||||
}
|
}
|
||||||
if !sub.Confirmed {
|
if !sub.Confirmed {
|
||||||
// sub not confirmed
|
// sub not confirmed
|
||||||
return ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
return ginresp.InternAPIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||||
}
|
}
|
||||||
// => perm okay
|
// => perm okay
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// auth-key is not set or not a user:x variant
|
// 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 ginresp.InternAPIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1095,29 +1095,29 @@ func (h APIHandler) DeleteMessage(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
msg, err := h.database.GetMessage(ctx, u.MessageID)
|
msg, err := h.database.GetMessage(ctx, u.MessageID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return ginresp.InternAPIError(404, apierr.MESSAGE_NOT_FOUND, "message not found", err)
|
return ginresp.InternAPIError(g, 404, apierr.MESSAGE_NOT_FOUND, "message not found", err)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to query message", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to query message", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.CheckPermissionMessageReadDirect(msg) {
|
if !ctx.CheckPermissionMessageReadDirect(msg) {
|
||||||
return ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
return ginresp.InternAPIError(g, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.database.DeleteMessage(ctx, msg.SCNMessageID)
|
err = h.database.DeleteMessage(ctx, msg.SCNMessageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to delete message", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to delete message", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.database.CancelPendingDeliveries(ctx, msg.SCNMessageID)
|
err = h.database.CancelPendingDeliveries(ctx, msg.SCNMessageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.DATABASE_ERROR, "Failed to cancel deliveries", err)
|
return ginresp.InternAPIError(g, 500, apierr.DATABASE_ERROR, "Failed to cancel deliveries", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, msg.FullJSON()))
|
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 {
|
||||||
return ginresp.NotImplemented() //TODO
|
return ginresp.NotImplemented(g) //TODO
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ func (h MessageHandler) SendMessageCompat(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
data := dataext.ObjectMerge(f, q)
|
data := dataext.ObjectMerge(f, q)
|
||||||
|
|
||||||
return h.sendMessageInternal(ctx, data.UserID, data.UserKey, langext.Ptr(h.app.DefaultChannel), nil, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp)
|
return h.sendMessageInternal(g, ctx, data.UserID, data.UserKey, langext.Ptr(h.app.DefaultChannel), nil, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,11 +132,11 @@ func (h MessageHandler) SendMessage(g *gin.Context) ginresp.HTTPResponse {
|
|||||||
|
|
||||||
data := dataext.ObjectMerge(b, q)
|
data := dataext.ObjectMerge(b, q)
|
||||||
|
|
||||||
return h.sendMessageInternal(ctx, data.UserID, data.UserKey, data.Channel, data.ChanKey, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp)
|
return h.sendMessageInternal(g, ctx, data.UserID, data.UserKey, data.Channel, data.ChanKey, data.Title, data.Content, data.Priority, data.UserMessageID, data.SendTimestamp)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h MessageHandler) sendMessageInternal(ctx *logic.AppContext, UserID *int64, UserKey *string, Channel *string, ChanKey *string, Title *string, Content *string, Priority *int, UserMessageID *string, SendTimestamp *float64) ginresp.HTTPResponse {
|
func (h MessageHandler) sendMessageInternal(g *gin.Context, ctx *logic.AppContext, UserID *int64, UserKey *string, Channel *string, ChanKey *string, Title *string, Content *string, Priority *int, UserMessageID *string, SendTimestamp *float64) ginresp.HTTPResponse {
|
||||||
type response struct {
|
type response struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
ErrorID apierr.APIError `json:"error"`
|
ErrorID apierr.APIError `json:"error"`
|
||||||
@ -158,25 +158,25 @@ func (h MessageHandler) sendMessageInternal(ctx *logic.AppContext, UserID *int64
|
|||||||
}
|
}
|
||||||
|
|
||||||
if UserID == nil {
|
if UserID == nil {
|
||||||
return ginresp.SendAPIError(400, apierr.MISSING_UID, 101, "Missing parameter [[user_id]]", nil)
|
return ginresp.SendAPIError(g, 400, apierr.MISSING_UID, 101, "Missing parameter [[user_id]]", nil)
|
||||||
}
|
}
|
||||||
if UserKey == nil {
|
if UserKey == nil {
|
||||||
return ginresp.SendAPIError(400, apierr.MISSING_UID, 102, "Missing parameter [[user_token]]", nil)
|
return ginresp.SendAPIError(g, 400, apierr.MISSING_UID, 102, "Missing parameter [[user_token]]", nil)
|
||||||
}
|
}
|
||||||
if Title == nil {
|
if Title == nil {
|
||||||
return ginresp.SendAPIError(400, apierr.MISSING_UID, 103, "Missing parameter [[title]]", nil)
|
return ginresp.SendAPIError(g, 400, apierr.MISSING_UID, 103, "Missing parameter [[title]]", nil)
|
||||||
}
|
}
|
||||||
if SendTimestamp != nil && mathext.Abs(*SendTimestamp-float64(time.Now().Unix())) > (24*time.Hour).Seconds() {
|
if SendTimestamp != nil && mathext.Abs(*SendTimestamp-float64(time.Now().Unix())) > (24*time.Hour).Seconds() {
|
||||||
return ginresp.SendAPIError(400, apierr.TIMESTAMP_OUT_OF_RANGE, -1, "The timestamp mus be within 24 hours of now()", nil)
|
return ginresp.SendAPIError(g, 400, apierr.TIMESTAMP_OUT_OF_RANGE, -1, "The timestamp mus be within 24 hours of now()", nil)
|
||||||
}
|
}
|
||||||
if Priority != nil && (*Priority != 0 && *Priority != 1 && *Priority != 2) {
|
if Priority != nil && (*Priority != 0 && *Priority != 1 && *Priority != 2) {
|
||||||
return ginresp.SendAPIError(400, apierr.INVALID_PRIO, 105, "Invalid priority", nil)
|
return ginresp.SendAPIError(g, 400, apierr.INVALID_PRIO, 105, "Invalid priority", nil)
|
||||||
}
|
}
|
||||||
if len(*Title) == 0 {
|
if len(*Title) == 0 {
|
||||||
return ginresp.SendAPIError(400, apierr.NO_TITLE, 103, "No title specified", nil)
|
return ginresp.SendAPIError(g, 400, apierr.NO_TITLE, 103, "No title specified", nil)
|
||||||
}
|
}
|
||||||
if UserMessageID != nil && len(*UserMessageID) > 64 {
|
if UserMessageID != nil && len(*UserMessageID) > 64 {
|
||||||
return ginresp.SendAPIError(400, apierr.USR_MSG_ID_TOO_LONG, -1, "MessageID too long (64 characters)", nil)
|
return ginresp.SendAPIError(g, 400, apierr.USR_MSG_ID_TOO_LONG, -1, "MessageID too long (64 characters)", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
channelName := h.app.DefaultChannel
|
channelName := h.app.DefaultChannel
|
||||||
@ -186,23 +186,23 @@ func (h MessageHandler) sendMessageInternal(ctx *logic.AppContext, UserID *int64
|
|||||||
|
|
||||||
user, err := h.database.GetUser(ctx, *UserID)
|
user, err := h.database.GetUser(ctx, *UserID)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return ginresp.SendAPIError(400, apierr.USER_NOT_FOUND, -1, "User not found", nil)
|
return ginresp.SendAPIError(g, 400, apierr.USER_NOT_FOUND, -1, "User not found", nil)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query user", err)
|
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, -1, "Failed to query user", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(*Title) > 120 {
|
if len(*Title) > 120 {
|
||||||
return ginresp.SendAPIError(400, apierr.TITLE_TOO_LONG, 103, "Title too long (120 characters)", nil)
|
return ginresp.SendAPIError(g, 400, apierr.TITLE_TOO_LONG, 103, "Title too long (120 characters)", nil)
|
||||||
}
|
}
|
||||||
if Content != nil && len(*Content) > user.MaxContentLength() {
|
if Content != nil && len(*Content) > user.MaxContentLength() {
|
||||||
return ginresp.SendAPIError(400, apierr.CONTENT_TOO_LONG, 104, fmt.Sprintf("Content too long (%d characters; max := %d characters)", len(*Content), user.MaxContentLength()), nil)
|
return ginresp.SendAPIError(g, 400, apierr.CONTENT_TOO_LONG, 104, fmt.Sprintf("Content too long (%d characters; max := %d characters)", len(*Content), user.MaxContentLength()), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if UserMessageID != nil {
|
if UserMessageID != nil {
|
||||||
msg, err := h.database.GetMessageByUserMessageID(ctx, *UserMessageID)
|
msg, err := h.database.GetMessageByUserMessageID(ctx, *UserMessageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query existing message", err)
|
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, -1, "Failed to query existing message", err)
|
||||||
}
|
}
|
||||||
if msg != nil {
|
if msg != nil {
|
||||||
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{
|
return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, response{
|
||||||
@ -221,12 +221,12 @@ func (h MessageHandler) sendMessageInternal(ctx *logic.AppContext, UserID *int64
|
|||||||
}
|
}
|
||||||
|
|
||||||
if user.QuotaRemainingToday() <= 0 {
|
if user.QuotaRemainingToday() <= 0 {
|
||||||
return ginresp.SendAPIError(403, apierr.QUOTA_REACHED, -1, fmt.Sprintf("Daily quota reached (%d)", user.QuotaPerDay()), nil)
|
return ginresp.SendAPIError(g, 403, apierr.QUOTA_REACHED, -1, fmt.Sprintf("Daily quota reached (%d)", user.QuotaPerDay()), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
channel, err := h.app.GetOrCreateChannel(ctx, *UserID, channelName)
|
channel, err := h.app.GetOrCreateChannel(ctx, *UserID, channelName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query/create channel", err)
|
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, -1, "Failed to query/create channel", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
selfChanAdmin := *UserID == channel.OwnerUserID && *UserKey == user.AdminKey
|
selfChanAdmin := *UserID == channel.OwnerUserID && *UserKey == user.AdminKey
|
||||||
@ -234,7 +234,7 @@ func (h MessageHandler) sendMessageInternal(ctx *logic.AppContext, UserID *int64
|
|||||||
forgChanSend := *UserID != channel.OwnerUserID && ChanKey != nil && *ChanKey == channel.SendKey
|
forgChanSend := *UserID != channel.OwnerUserID && ChanKey != nil && *ChanKey == channel.SendKey
|
||||||
|
|
||||||
if !selfChanAdmin && !selfChanSend && !forgChanSend {
|
if !selfChanAdmin && !selfChanSend && !forgChanSend {
|
||||||
return ginresp.SendAPIError(401, apierr.USER_AUTH_FAILED, 102, fmt.Sprintf("Daily quota reached (%d)", user.QuotaPerDay()), nil)
|
return ginresp.SendAPIError(g, 401, apierr.USER_AUTH_FAILED, 102, fmt.Sprintf("Daily quota reached (%d)", user.QuotaPerDay()), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
var sendTimestamp *time.Time = nil
|
var sendTimestamp *time.Time = nil
|
||||||
@ -246,28 +246,28 @@ func (h MessageHandler) sendMessageInternal(ctx *logic.AppContext, UserID *int64
|
|||||||
|
|
||||||
msg, err := h.database.CreateMessage(ctx, *UserID, channel, sendTimestamp, *Title, Content, priority, UserMessageID)
|
msg, err := h.database.CreateMessage(ctx, *UserID, channel, sendTimestamp, *Title, Content, priority, UserMessageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to create message in db", err)
|
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, -1, "Failed to create message in db", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriptions, err := h.database.ListSubscriptionsByChannel(ctx, channel.ChannelID)
|
subscriptions, err := h.database.ListSubscriptionsByChannel(ctx, channel.ChannelID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to query subscriptions", err)
|
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, -1, "Failed to query subscriptions", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.database.IncUserMessageCounter(ctx, user)
|
err = h.database.IncUserMessageCounter(ctx, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to inc user msg-counter", err)
|
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, -1, "Failed to inc user msg-counter", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.database.IncChannelMessageCounter(ctx, channel)
|
err = h.database.IncChannelMessageCounter(ctx, channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to inc channel msg-counter", err)
|
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, -1, "Failed to inc channel msg-counter", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
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", err)
|
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, -1, "Failed to query clients", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !sub.Confirmed {
|
if !sub.Confirmed {
|
||||||
@ -280,12 +280,12 @@ func (h MessageHandler) sendMessageInternal(ctx *logic.AppContext, UserID *int64
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
_, err = h.database.CreateRetryDelivery(ctx, client, msg)
|
_, err = h.database.CreateRetryDelivery(ctx, client, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to create delivery", err)
|
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, -1, "Failed to create delivery", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err = h.database.CreateSuccessDelivery(ctx, client, msg, *fcmDelivID)
|
_, err = h.database.CreateSuccessDelivery(ctx, client, msg, *fcmDelivID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.SendAPIError(500, apierr.DATABASE_ERROR, -1, "Failed to create delivery", err)
|
return ginresp.SendAPIError(g, 500, apierr.DATABASE_ERROR, -1, "Failed to create delivery", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,18 +28,23 @@ func main() {
|
|||||||
app := logic.NewApp(sqlite)
|
app := logic.NewApp(sqlite)
|
||||||
|
|
||||||
if err := app.Migrate(); err != nil {
|
if err := app.Migrate(); err != nil {
|
||||||
panic(err)
|
log.Fatal().Err(err).Msg("failed to migrate DB")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ginengine := ginext.NewEngine(conf)
|
ginengine := ginext.NewEngine(conf)
|
||||||
|
|
||||||
router := api.NewRouter(app)
|
router := api.NewRouter(app)
|
||||||
|
|
||||||
fb := firebase.NewFirebaseApp()
|
fb, err := firebase.NewFirebase(conf)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("failed to init firebase")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
jobRetry := jobs.NewDeliveryRetryJob(app)
|
jobRetry := jobs.NewDeliveryRetryJob(app)
|
||||||
|
|
||||||
app.Init(conf, ginengine, &fb, []logic.Job{jobRetry})
|
app.Init(conf, ginengine, fb, []logic.Job{jobRetry})
|
||||||
|
|
||||||
router.Init(ginengine)
|
router.Init(ginengine)
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"net/http"
|
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -67,39 +66,60 @@ func Text(sc int, data string) HTTPResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func InternalError(e error) HTTPResponse {
|
func InternalError(e error) HTTPResponse {
|
||||||
log.Error().Err(e).Msg("[InternalError] " + e.Error())
|
return createApiError(nil, "InternalError", 500, apierr.INTERNAL_EXCEPTION, 0, e.Error(), e)
|
||||||
|
|
||||||
return &jsonHTTPResponse{statusCode: http.StatusInternalServerError, data: apiError{Success: false, Error: int(apierr.INTERNAL_EXCEPTION), Message: e.Error()}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func InternAPIError(status int, errorid apierr.APIError, msg string, e error) HTTPResponse {
|
func InternAPIError(g *gin.Context, status int, errorid apierr.APIError, msg string, e error) HTTPResponse {
|
||||||
log.Error().Int("errorid", int(errorid)).Err(e).Msg("[InternAPIError] " + msg)
|
return createApiError(g, "InternAPIError", status, errorid, 0, msg, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendAPIError(g *gin.Context, status int, errorid apierr.APIError, highlight int, msg string, e error) HTTPResponse {
|
||||||
|
return createApiError(g, "SendAPIError", status, errorid, highlight, msg, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NotImplemented(g *gin.Context) HTTPResponse {
|
||||||
|
return createApiError(g, "NotImplemented", 500, apierr.UNDEFINED, 0, "Not Implemented", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createApiError(g *gin.Context, ident string, status int, errorid apierr.APIError, highlight int, msg string, e error) HTTPResponse {
|
||||||
|
reqUri := ""
|
||||||
|
if g != nil && g.Request != nil {
|
||||||
|
reqUri = g.Request.Method + " :: " + g.Request.RequestURI
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Error().
|
||||||
|
Int("errorid", int(errorid)).
|
||||||
|
Int("highlight", highlight).
|
||||||
|
Str("uri", reqUri).
|
||||||
|
AnErr("err", e).
|
||||||
|
Stack().
|
||||||
|
Msg(fmt.Sprintf("[%s] %s", ident, msg))
|
||||||
|
|
||||||
if scn.Conf.ReturnRawErrors {
|
if scn.Conf.ReturnRawErrors {
|
||||||
return &jsonHTTPResponse{statusCode: status, data: apiError{Success: false, Error: int(errorid), Message: msg, RawError: fmt.Sprintf("%+v", e), Trace: string(debug.Stack())}}
|
return &jsonHTTPResponse{
|
||||||
|
statusCode: status,
|
||||||
|
data: apiError{
|
||||||
|
Success: false,
|
||||||
|
Error: int(errorid),
|
||||||
|
ErrorHighlight: highlight,
|
||||||
|
Message: msg,
|
||||||
|
RawError: fmt.Sprintf("%+v", e),
|
||||||
|
Trace: string(debug.Stack()),
|
||||||
|
},
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return &jsonHTTPResponse{statusCode: status, data: apiError{Success: false, Error: int(errorid), Message: msg}}
|
return &jsonHTTPResponse{
|
||||||
|
statusCode: status,
|
||||||
|
data: apiError{
|
||||||
|
Success: false,
|
||||||
|
Error: int(errorid),
|
||||||
|
ErrorHighlight: highlight,
|
||||||
|
Message: msg,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CompatAPIError(errid int, msg string) HTTPResponse {
|
func CompatAPIError(errid int, msg string) HTTPResponse {
|
||||||
log.Error().Int("errid", errid).Msg("[CompatAPIError] " + msg)
|
|
||||||
|
|
||||||
return &jsonHTTPResponse{statusCode: 200, data: compatAPIError{Success: false, ErrorID: errid, Message: msg}}
|
return &jsonHTTPResponse{statusCode: 200, data: compatAPIError{Success: false, ErrorID: errid, Message: msg}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendAPIError(status int, errorid apierr.APIError, highlight int, msg string, e error) HTTPResponse {
|
|
||||||
log.Error().Int("errorid", int(errorid)).Int("highlight", highlight).Err(e).Msg("[SendAPIError] " + msg)
|
|
||||||
|
|
||||||
if scn.Conf.ReturnRawErrors {
|
|
||||||
return &jsonHTTPResponse{statusCode: status, data: apiError{Success: false, Error: int(errorid), ErrorHighlight: highlight, Message: msg, RawError: fmt.Sprintf("%+v", e), Trace: string(debug.Stack())}}
|
|
||||||
} else {
|
|
||||||
return &jsonHTTPResponse{statusCode: status, data: apiError{Success: false, Error: int(errorid), ErrorHighlight: highlight, Message: msg}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NotImplemented() HTTPResponse {
|
|
||||||
log.Error().Msg("[NotImplemented]")
|
|
||||||
|
|
||||||
return &jsonHTTPResponse{statusCode: http.StatusInternalServerError, data: apiError{Success: false, Error: -1, ErrorHighlight: 0, Message: "Not Implemented"}}
|
|
||||||
}
|
|
||||||
|
114
server/config.go
114
server/config.go
@ -7,65 +7,95 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Namespace string
|
Namespace string
|
||||||
GinDebug bool
|
GinDebug bool
|
||||||
ServerIP string
|
ServerIP string
|
||||||
ServerPort string
|
ServerPort string
|
||||||
DBFile string
|
DBFile string
|
||||||
RequestTimeout time.Duration
|
RequestTimeout time.Duration
|
||||||
ReturnRawErrors bool
|
ReturnRawErrors bool
|
||||||
|
FirebaseProjectID string
|
||||||
|
FirebaseTokenURI string
|
||||||
|
FirebasePrivKeyID string
|
||||||
|
FirebaseClientMail string
|
||||||
|
FirebasePrivateKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
var Conf Config
|
var Conf Config
|
||||||
|
|
||||||
var configLocHost = Config{
|
var configLocHost = Config{
|
||||||
Namespace: "local-host",
|
Namespace: "local-host",
|
||||||
GinDebug: true,
|
GinDebug: true,
|
||||||
ServerIP: "0.0.0.0",
|
ServerIP: "0.0.0.0",
|
||||||
ServerPort: "8080",
|
ServerPort: "8080",
|
||||||
DBFile: ".run-data/db.sqlite3",
|
DBFile: ".run-data/db.sqlite3",
|
||||||
RequestTimeout: 16 * time.Second,
|
RequestTimeout: 16 * time.Second,
|
||||||
ReturnRawErrors: true,
|
ReturnRawErrors: true,
|
||||||
|
FirebaseProjectID: "simplecloudnotifier-ea7ef",
|
||||||
|
FirebaseTokenURI: "https://oauth2.googleapis.com/token",
|
||||||
|
FirebasePrivKeyID: "5bfab19fca25034e87c5b3bd1a4334499d2d1f85",
|
||||||
|
FirebaseClientMail: "firebase-adminsdk-42grv@simplecloudnotifier-ea7ef.iam.gserviceaccount.com",
|
||||||
|
FirebasePrivateKey: "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQD2NWOQDcalRdkp\nHtQHABLlu3GMBQBJrGiCxzOZhi/lLwrw2MJEmg1VFz6TVkX2z3SCzXCPOgGriM70\nuWCNLyZQvUng7u6/WH9hlpCg0vJpkw6BvOBt1zYu3gbb5M0SKEOR+lDVccEjAnT4\nexebXdJHJcbaYAcPnBQ9tgP+cozQBnr2EfxYL0bGMgiH9fErJSGMBDFI996uUW9a\nbtfkZ/XpZqYAvyGQMEjknGnQ8t8PHAnsS9dc1PXSWfBvz07ba3fkypWcpTsIYUiZ\nSpwTLV8awihKHJuphoTWb4x6p/ijop05qr1p3fe8gZd9qOGgALe+JT4IBLgNYKrP\nLMSKH3TdAgMBAAECggEAdFcWDOP1kfNHgl7G4efvBg9kwD08vZNybxmiEFGQIEPy\nb4x9f90rn6G0N/r0ZIPzEjvxjDxkvaGP6aQPM6er+0r2tgsxVcmDp6F2Bgin86tB\nl5ygkEa5m7vekdmz7XiJNVmLCNEP6nMmwqOnrArRaj03kcj+jSm7hs2TZZDLaSA5\nf+2q7h0jaU7Nm0ZwCNJqfPJEGdu1J3fR29Ej0rI8N0w/BuYRet1VYDO09lquqOPS\n0WirOOWV6eyqijqRT+RCt0vVzAppS6guhN7J7RS0V9GLJ/13sdvHuJy/WTjBb7gQ\na6QTo8D3yYF+cn3+0BmgP55uW7N6tsYwXIRZcTI3IQKBgQD+tDKMx0puZu+8zTX9\nC2oHSb4Frl2xq17ZpbkfFmOBPWfQbAHNiQTUoQlzCOQM6QejykXFvfsddP7EY2tL\npgLUrBh81wSCAOOo19vYwQB3YKa5ZZucKxh2VxFSefL/+BYHijFb0mWBj5HmqWS6\n7l6IYT3L04aRK9kxj0Cg6L/z6wKBgQD3dh/kQlPemfdxRpZUJ6WEE5x3Bv7WjLop\nnWgE02Pk8+DB+s50GD3nOR276ADCYS6OkBsgfMkwhhKWZigiEoK9DMul5n587jc9\no5AalZN3IbBGAoXk+u3g1GC9bOY3454K6IJyhehDTImEFyfm00qfUL8fMNcdEx8O\nnwxtyRawVwKBgGqsnd9IOGw0wIOajtoERcv3npZSiPs4guk092uFvPcL+MbZ9YdX\ns6Y6K/L57klZ79ExjjdbcijML0ehO/ba+KSJz1e51jF8ndzBS1pkuwVEfY94dsvZ\nYM1vednJKXT7On696h5C6DBzKPAqUf3Yh88mqvMLDHkQnE6daLv7vykxAoGAOPmA\ndDx1NO48E1+OIwgRyqv9PUZmDB3Qit5L4biN6lvgJqlJOV+PeRokZ2wOKLLZVkeF\nh2BTrhFgXDJfESEz6rT0eljsTHVIUK/E8On5Ttd5z1SrYUII3NfpAhP9mWaVr6tC\nxX1hMYWAr+Ho9PM23iFoL5U+IdqSLvqdkPVYfPcCgYB1ANKNYPIJNx/wLxYWNS0r\nI98HwKfv2TxxE/l+2459NMMHY5wlpFl7MNoeK2SdY+ghWPlxC6u5Nxpnk+bZ8TJe\np7U2nY0SQDLCmPgGWs3KBb/zR49X2b7JS3CXXqQSrLxBe2phZg6kE5nB6NPUDc/i\n6WG8tG20rCfgwlXeXl0+Ow==\n-----END PRIVATE KEY-----\n",
|
||||||
}
|
}
|
||||||
|
|
||||||
var configLocDocker = Config{
|
var configLocDocker = Config{
|
||||||
Namespace: "local-docker",
|
Namespace: "local-docker",
|
||||||
GinDebug: true,
|
GinDebug: true,
|
||||||
ServerIP: "0.0.0.0",
|
ServerIP: "0.0.0.0",
|
||||||
ServerPort: "80",
|
ServerPort: "80",
|
||||||
DBFile: "/data/scn_docker.sqlite3",
|
DBFile: "/data/scn_docker.sqlite3",
|
||||||
RequestTimeout: 16 * time.Second,
|
RequestTimeout: 16 * time.Second,
|
||||||
ReturnRawErrors: true,
|
ReturnRawErrors: true,
|
||||||
|
FirebaseProjectID: "simplecloudnotifier-ea7ef",
|
||||||
|
FirebaseTokenURI: "https://oauth2.googleapis.com/token",
|
||||||
|
FirebasePrivKeyID: "5bfab19fca25034e87c5b3bd1a4334499d2d1f85",
|
||||||
|
FirebaseClientMail: "firebase-adminsdk-42grv@simplecloudnotifier-ea7ef.iam.gserviceaccount.com",
|
||||||
|
FirebasePrivateKey: "TODO",
|
||||||
}
|
}
|
||||||
|
|
||||||
var configDev = Config{
|
var configDev = Config{
|
||||||
Namespace: "develop",
|
Namespace: "develop",
|
||||||
GinDebug: true,
|
GinDebug: true,
|
||||||
ServerIP: "0.0.0.0",
|
ServerIP: "0.0.0.0",
|
||||||
ServerPort: "80",
|
ServerPort: "80",
|
||||||
DBFile: "/data/scn.sqlite3",
|
DBFile: "/data/scn.sqlite3",
|
||||||
RequestTimeout: 16 * time.Second,
|
RequestTimeout: 16 * time.Second,
|
||||||
ReturnRawErrors: true,
|
ReturnRawErrors: true,
|
||||||
|
FirebaseProjectID: "simplecloudnotifier-ea7ef",
|
||||||
|
FirebaseTokenURI: "https://oauth2.googleapis.com/token",
|
||||||
|
FirebasePrivKeyID: "5bfab19fca25034e87c5b3bd1a4334499d2d1f85",
|
||||||
|
FirebaseClientMail: "firebase-adminsdk-42grv@simplecloudnotifier-ea7ef.iam.gserviceaccount.com",
|
||||||
|
FirebasePrivateKey: "TODO",
|
||||||
}
|
}
|
||||||
|
|
||||||
var configStag = Config{
|
var configStag = Config{
|
||||||
Namespace: "staging",
|
Namespace: "staging",
|
||||||
GinDebug: true,
|
GinDebug: true,
|
||||||
ServerIP: "0.0.0.0",
|
ServerIP: "0.0.0.0",
|
||||||
ServerPort: "80",
|
ServerPort: "80",
|
||||||
DBFile: "/data/scn.sqlite3",
|
DBFile: "/data/scn.sqlite3",
|
||||||
RequestTimeout: 16 * time.Second,
|
RequestTimeout: 16 * time.Second,
|
||||||
ReturnRawErrors: true,
|
ReturnRawErrors: true,
|
||||||
|
FirebaseProjectID: "simplecloudnotifier-ea7ef",
|
||||||
|
FirebaseTokenURI: "https://oauth2.googleapis.com/token",
|
||||||
|
FirebasePrivKeyID: "5bfab19fca25034e87c5b3bd1a4334499d2d1f85",
|
||||||
|
FirebaseClientMail: "firebase-adminsdk-42grv@simplecloudnotifier-ea7ef.iam.gserviceaccount.com",
|
||||||
|
FirebasePrivateKey: "TODO",
|
||||||
}
|
}
|
||||||
|
|
||||||
var configProd = Config{
|
var configProd = Config{
|
||||||
Namespace: "production",
|
Namespace: "production",
|
||||||
GinDebug: false,
|
GinDebug: false,
|
||||||
ServerIP: "0.0.0.0",
|
ServerIP: "0.0.0.0",
|
||||||
ServerPort: "80",
|
ServerPort: "80",
|
||||||
DBFile: "/data/scn.sqlite3",
|
DBFile: "/data/scn.sqlite3",
|
||||||
RequestTimeout: 16 * time.Second,
|
RequestTimeout: 16 * time.Second,
|
||||||
ReturnRawErrors: false,
|
ReturnRawErrors: false,
|
||||||
|
FirebaseProjectID: "simplecloudnotifier-ea7ef",
|
||||||
|
FirebaseTokenURI: "https://oauth2.googleapis.com/token",
|
||||||
|
FirebasePrivKeyID: "5bfab19fca25034e87c5b3bd1a4334499d2d1f85",
|
||||||
|
FirebaseClientMail: "firebase-adminsdk-42grv@simplecloudnotifier-ea7ef.iam.gserviceaccount.com",
|
||||||
|
FirebasePrivateKey: "TODO",
|
||||||
}
|
}
|
||||||
|
|
||||||
var allConfig = []Config{
|
var allConfig = []Config{
|
||||||
|
179
server/firebase/FBOAuth.go
Normal file
179
server/firebase/FBOAuth.go
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
package firebase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/timeext"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FBOAuth2 struct {
|
||||||
|
client *http.Client
|
||||||
|
|
||||||
|
scopes []string
|
||||||
|
tokenURL string
|
||||||
|
privateKeyID string
|
||||||
|
clientMail string
|
||||||
|
|
||||||
|
currToken *string
|
||||||
|
tokenExpiry *time.Time
|
||||||
|
privateKey *rsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuth(tokenURL string, privKeyID string, cmail string, pemstr string) (*FBOAuth2, error) {
|
||||||
|
|
||||||
|
pkey, err := decodePemKey(pemstr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FBOAuth2{
|
||||||
|
client: &http.Client{Timeout: 3 * time.Second},
|
||||||
|
tokenURL: tokenURL,
|
||||||
|
privateKey: pkey,
|
||||||
|
privateKeyID: privKeyID,
|
||||||
|
clientMail: cmail,
|
||||||
|
scopes: []string{
|
||||||
|
"https://www.googleapis.com/auth/cloud-platform",
|
||||||
|
"https://www.googleapis.com/auth/datastore",
|
||||||
|
"https://www.googleapis.com/auth/devstorage.full_control",
|
||||||
|
"https://www.googleapis.com/auth/firebase",
|
||||||
|
"https://www.googleapis.com/auth/identitytoolkit",
|
||||||
|
"https://www.googleapis.com/auth/userinfo.email",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodePemKey(pemstr string) (*rsa.PrivateKey, error) {
|
||||||
|
var raw []byte
|
||||||
|
|
||||||
|
block, _ := pem.Decode([]byte(pemstr))
|
||||||
|
|
||||||
|
if block != nil {
|
||||||
|
raw = block.Bytes
|
||||||
|
} else {
|
||||||
|
raw = []byte(pemstr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pkey8, err1 := x509.ParsePKCS8PrivateKey(raw)
|
||||||
|
if err1 == nil {
|
||||||
|
privkey, ok := pkey8.(*rsa.PrivateKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("private key is invalid")
|
||||||
|
}
|
||||||
|
return privkey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pkey1, err2 := x509.ParsePKCS1PrivateKey(raw)
|
||||||
|
if err2 == nil {
|
||||||
|
return pkey1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New(fmt.Sprintf("failed to parse private-key: [ %v | %v ]", err1, err2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *FBOAuth2) Token(ctx context.Context) (string, error) {
|
||||||
|
if a.currToken == nil || a.tokenExpiry == nil || a.tokenExpiry.Before(time.Now()) {
|
||||||
|
err := a.Refresh(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *a.currToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *FBOAuth2) Refresh(ctx context.Context) error {
|
||||||
|
|
||||||
|
assertion, err := a.encodeAssertion(a.privateKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
body := url.Values{
|
||||||
|
"assertion": []string{assertion},
|
||||||
|
"grant_type": []string{"urn:ietf:params:oauth:grant-type:jwt-bearer"},
|
||||||
|
}.Encode()
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", a.tokenURL, strings.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
reqNow := time.Now()
|
||||||
|
|
||||||
|
resp, err := a.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
if bstr, err := io.ReadAll(resp.Body); err == nil {
|
||||||
|
return errors.New(fmt.Sprintf("Auth-Request returned %d: %s", resp.StatusCode, string(bstr)))
|
||||||
|
} else {
|
||||||
|
return errors.New(fmt.Sprintf("Auth-Request returned %d", resp.StatusCode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
respBodyBin, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var respBody struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(respBodyBin, &respBody); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.currToken = langext.Ptr(respBody.AccessToken)
|
||||||
|
a.tokenExpiry = langext.Ptr(reqNow.Add(timeext.FromSeconds(respBody.ExpiresIn)))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *FBOAuth2) encodeAssertion(key *rsa.PrivateKey) (string, error) {
|
||||||
|
headBin, err := json.Marshal(gin.H{"alg": "RS256", "typ": "JWT", "kid": a.privateKeyID})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
head := base64.RawURLEncoding.EncodeToString(headBin)
|
||||||
|
|
||||||
|
now := time.Now().Add(-10 * time.Second) // jwt hack against unsynced clocks
|
||||||
|
|
||||||
|
claimBin, err := json.Marshal(gin.H{"iss": a.clientMail, "scope": strings.Join(a.scopes, " "), "aud": a.tokenURL, "exp": now.Add(time.Hour).Unix(), "iat": now.Unix()})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
claim := base64.RawURLEncoding.EncodeToString(claimBin)
|
||||||
|
|
||||||
|
checksum := sha256.New()
|
||||||
|
checksum.Write([]byte(head + "." + claim))
|
||||||
|
sig, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, checksum.Sum(nil))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return head + "." + claim + "." + base64.RawURLEncoding.EncodeToString(sig), nil
|
||||||
|
}
|
@ -1,40 +1,44 @@
|
|||||||
package firebase
|
package firebase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
scn "blackforestbytes.com/simplecloudnotifier"
|
||||||
"blackforestbytes.com/simplecloudnotifier/models"
|
"blackforestbytes.com/simplecloudnotifier/models"
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
fb "firebase.google.com/go"
|
"encoding/json"
|
||||||
"firebase.google.com/go/messaging"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
"google.golang.org/api/option"
|
"io"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed scnserviceaccountkey.json
|
// https://firebase.google.com/docs/cloud-messaging/send-message#rest
|
||||||
var scnserviceaccountkey []byte
|
// https://firebase.google.com/docs/cloud-messaging/auth-server
|
||||||
|
|
||||||
type App struct {
|
type FBConnector struct {
|
||||||
app *fb.App
|
fbProject string
|
||||||
messaging *messaging.Client
|
client http.Client
|
||||||
|
auth *FBOAuth2
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFirebaseApp() App {
|
func NewFirebase(conf scn.Config) (*FBConnector, error) {
|
||||||
opt := option.WithCredentialsJSON(scnserviceaccountkey)
|
|
||||||
app, err := fb.NewApp(context.Background(), nil, opt)
|
fbauth, err := NewAuth(conf.FirebaseTokenURI, conf.FirebaseProjectID, conf.FirebaseClientMail, conf.FirebasePrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("failed to init firebase app")
|
return nil, err
|
||||||
}
|
|
||||||
msg, err := app.Messaging(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("failed to init messaging client")
|
|
||||||
}
|
|
||||||
log.Info().Msg("Initialized Firebase")
|
|
||||||
return App{
|
|
||||||
app: app,
|
|
||||||
messaging: msg,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return &FBConnector{
|
||||||
|
fbProject: conf.FirebaseProjectID,
|
||||||
|
client: http.Client{Timeout: 5 * time.Second},
|
||||||
|
auth: fbauth,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
@ -46,10 +50,12 @@ type Notification struct {
|
|||||||
Priority int
|
Priority int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fb App) SendNotification(ctx context.Context, client models.Client, msg models.Message) (string, error) {
|
func (fb FBConnector) SendNotification(ctx context.Context, client models.Client, msg models.Message) (string, error) {
|
||||||
|
|
||||||
n := messaging.Message{
|
uri := "https://fcm.googleapis.com/v1/projects/" + fb.fbProject + "/messages:send"
|
||||||
Data: map[string]string{
|
|
||||||
|
jsonBody := gin.H{
|
||||||
|
"data": gin.H{
|
||||||
"scn_msg_id": strconv.FormatInt(msg.SCNMessageID, 10),
|
"scn_msg_id": strconv.FormatInt(msg.SCNMessageID, 10),
|
||||||
"usr_msg_id": langext.Coalesce(msg.UserMessageID, ""),
|
"usr_msg_id": langext.Coalesce(msg.UserMessageID, ""),
|
||||||
"client_id": strconv.FormatInt(client.ClientID, 10),
|
"client_id": strconv.FormatInt(client.ClientID, 10),
|
||||||
@ -59,29 +65,64 @@ func (fb App) SendNotification(ctx context.Context, client models.Client, msg mo
|
|||||||
"title": msg.Title,
|
"title": msg.Title,
|
||||||
"body": langext.Coalesce(msg.TrimmedContent(), ""),
|
"body": langext.Coalesce(msg.TrimmedContent(), ""),
|
||||||
},
|
},
|
||||||
Notification: &messaging.Notification{
|
"token": *client.FCMToken,
|
||||||
Title: msg.Title,
|
"android": gin.H{
|
||||||
Body: msg.ShortContent(),
|
"priority": "high",
|
||||||
},
|
},
|
||||||
Android: nil,
|
"apns": gin.H{},
|
||||||
APNS: nil,
|
|
||||||
Webpush: nil,
|
|
||||||
FCMOptions: nil,
|
|
||||||
Token: *client.FCMToken,
|
|
||||||
Topic: "",
|
|
||||||
Condition: "",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.Type == models.ClientTypeIOS {
|
if client.Type == models.ClientTypeIOS {
|
||||||
n.APNS = nil
|
jsonBody["notification"] = gin.H{
|
||||||
} else if client.Type == models.ClientTypeAndroid {
|
"title": msg.Title,
|
||||||
n.Notification = nil
|
"body": msg.ShortContent(),
|
||||||
n.Android = &messaging.AndroidConfig{Priority: "high"}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := fb.messaging.Send(ctx, &n)
|
bytesBody, err := json.Marshal(gin.H{"message": jsonBody})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("failed to send push")
|
return "", err
|
||||||
}
|
}
|
||||||
return res, err
|
|
||||||
|
request, err := http.NewRequestWithContext(ctx, "POST", uri, bytes.NewBuffer(bytesBody))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
tok, err := fb.auth.Token(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err).Msg("Refreshing FB token failed")
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Header.Set("Authorization", "Bearer "+tok)
|
||||||
|
request.Header.Set("Content-Type", "application/json")
|
||||||
|
request.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
|
response, err := fb.client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer func() { _ = response.Body.Close() }()
|
||||||
|
|
||||||
|
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
||||||
|
if bstr, err := io.ReadAll(response.Body); err == nil {
|
||||||
|
return "", errors.New(fmt.Sprintf("FCM-Request returned %d: %s", response.StatusCode, string(bstr)))
|
||||||
|
} else {
|
||||||
|
return "", errors.New(fmt.Sprintf("FCM-Request returned %d", response.StatusCode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
respBodyBin, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var respBody struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(respBodyBin, &respBody); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return respBody.Name, nil
|
||||||
}
|
}
|
||||||
|
@ -3,24 +3,15 @@ module blackforestbytes.com/simplecloudnotifier
|
|||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
firebase.google.com/go v3.13.0+incompatible
|
|
||||||
github.com/blockloop/scan v1.3.0
|
github.com/blockloop/scan v1.3.0
|
||||||
github.com/gin-gonic/gin v1.8.1
|
github.com/gin-gonic/gin v1.8.1
|
||||||
github.com/mattn/go-sqlite3 v1.14.16
|
github.com/mattn/go-sqlite3 v1.14.16
|
||||||
github.com/rs/zerolog v1.28.0
|
github.com/rs/zerolog v1.28.0
|
||||||
github.com/swaggo/swag v1.8.7
|
github.com/swaggo/swag v1.8.7
|
||||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.21
|
gogs.mikescher.com/BlackForestBytes/goext v0.0.21
|
||||||
google.golang.org/api v0.103.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.105.0 // indirect
|
|
||||||
cloud.google.com/go/compute v1.12.1 // indirect
|
|
||||||
cloud.google.com/go/compute/metadata v0.2.1 // indirect
|
|
||||||
cloud.google.com/go/firestore v1.8.0 // indirect
|
|
||||||
cloud.google.com/go/iam v0.7.0 // indirect
|
|
||||||
cloud.google.com/go/longrunning v0.3.0 // indirect
|
|
||||||
cloud.google.com/go/storage v1.28.0 // indirect
|
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
@ -33,12 +24,7 @@ require (
|
|||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.10.0 // indirect
|
github.com/go-playground/validator/v10 v10.10.0 // indirect
|
||||||
github.com/goccy/go-json v0.9.7 // indirect
|
github.com/goccy/go-json v0.9.7 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
|
|
||||||
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
|
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
@ -49,19 +35,11 @@ require (
|
|||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||||
golang.org/x/net v0.2.0 // indirect
|
golang.org/x/net v0.2.0 // indirect
|
||||||
golang.org/x/oauth2 v0.2.0 // indirect
|
|
||||||
golang.org/x/sync v0.1.0 // indirect
|
|
||||||
golang.org/x/sys v0.2.0 // indirect
|
golang.org/x/sys v0.2.0 // indirect
|
||||||
golang.org/x/text v0.4.0 // indirect
|
golang.org/x/text v0.4.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
|
||||||
golang.org/x/tools v0.1.12 // indirect
|
golang.org/x/tools v0.1.12 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
|
||||||
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect
|
|
||||||
google.golang.org/grpc v1.51.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
148
server/go.sum
148
server/go.sum
@ -1,25 +1,3 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y=
|
|
||||||
cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
|
|
||||||
cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0=
|
|
||||||
cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
|
|
||||||
cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48=
|
|
||||||
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
|
|
||||||
cloud.google.com/go/firestore v1.8.0 h1:HokMB9Io0hAyYzlGFeFVMgE3iaPXNvaIsDx5JzblGLI=
|
|
||||||
cloud.google.com/go/firestore v1.8.0/go.mod h1:r3KB8cAdRIe8znzoPWLw8S6gpDVd9treohhn8b09424=
|
|
||||||
cloud.google.com/go/iam v0.6.0 h1:nsqQC88kT5Iwlm4MeNGTpfMWddp6NB/UOLFTH6m1QfQ=
|
|
||||||
cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc=
|
|
||||||
cloud.google.com/go/iam v0.7.0 h1:k4MuwOsS7zGJJ+QfZ5vBK8SgHBAvYN/23BWsiihJ1vs=
|
|
||||||
cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg=
|
|
||||||
cloud.google.com/go/longrunning v0.1.1 h1:y50CXG4j0+qvEukslYFBCrzaXX0qpFbBzc3PchSu/LE=
|
|
||||||
cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE=
|
|
||||||
cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs=
|
|
||||||
cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=
|
|
||||||
cloud.google.com/go/storage v1.28.0 h1:DLrIZ6xkeZX6K70fU/boWx5INJumt6f+nwwWSHXzzGY=
|
|
||||||
cloud.google.com/go/storage v1.28.0/go.mod h1:qlgZML35PXA3zoEnIkiPLY4/TOkUleufRlu6qmcf7sI=
|
|
||||||
firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4=
|
|
||||||
firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||||
@ -28,18 +6,11 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV
|
|||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
github.com/blockloop/scan v1.3.0 h1:p8xnajpGA3d/V6o23IBFdQ764+JnNJ+PQj+OwT+rkdg=
|
github.com/blockloop/scan v1.3.0 h1:p8xnajpGA3d/V6o23IBFdQ764+JnNJ+PQj+OwT+rkdg=
|
||||||
github.com/blockloop/scan v1.3.0/go.mod h1:qd+3w68+o7m5Xhj9X5SlJH2rbFyK8w0WT47Rkuer010=
|
github.com/blockloop/scan v1.3.0/go.mod h1:qd+3w68+o7m5Xhj9X5SlJH2rbFyK8w0WT47Rkuer010=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
|
||||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
|
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
|
||||||
@ -65,41 +36,11 @@ github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXS
|
|||||||
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
|
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
|
||||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
|
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs=
|
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
|
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
@ -136,7 +77,6 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
@ -144,64 +84,25 @@ github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
|||||||
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
|
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
|
||||||
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU=
|
github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU=
|
||||||
github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
|
github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
|
||||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
|
||||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.17 h1:jsfbvII7aa0SH9qY0fnXBdtNnQe1YY3DgXDThEwLICc=
|
|
||||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.17/go.mod h1:TMBOjo3FRFh/GiTT0z3nwLmgcFJB87oSF2VMs4XUCTQ=
|
|
||||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.18 h1:fprrLoAPGdI4ObveHR1DjiP9WhlTJppWtjqMA6ZkyS8=
|
|
||||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.18/go.mod h1:TMBOjo3FRFh/GiTT0z3nwLmgcFJB87oSF2VMs4XUCTQ=
|
|
||||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.19 h1:IvCHlIHDviHQXntZFTNdV7qNq5yQnSEMxF8LA0Tf3IY=
|
|
||||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.19/go.mod h1:TMBOjo3FRFh/GiTT0z3nwLmgcFJB87oSF2VMs4XUCTQ=
|
|
||||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.20 h1:HxJ0iZ838TQnp/a+/DNajdZjZkV43OsK4VbHarOiHTs=
|
|
||||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.20/go.mod h1:TMBOjo3FRFh/GiTT0z3nwLmgcFJB87oSF2VMs4XUCTQ=
|
|
||||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.21 h1:OibsssmorZsTdFYRiQFlkXtjUYweQg9SBkWO40ONe0Y=
|
gogs.mikescher.com/BlackForestBytes/goext v0.0.21 h1:OibsssmorZsTdFYRiQFlkXtjUYweQg9SBkWO40ONe0Y=
|
||||||
gogs.mikescher.com/BlackForestBytes/goext v0.0.21/go.mod h1:TMBOjo3FRFh/GiTT0z3nwLmgcFJB87oSF2VMs4XUCTQ=
|
gogs.mikescher.com/BlackForestBytes/goext v0.0.21/go.mod h1:TMBOjo3FRFh/GiTT0z3nwLmgcFJB87oSF2VMs4XUCTQ=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU=
|
|
||||||
golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@ -211,58 +112,16 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
|
|
||||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
|
||||||
google.golang.org/api v0.103.0 h1:9yuVqlu2JCvcLg9p8S3fcFLZij8EPSyvODIY1rkMizQ=
|
|
||||||
google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo=
|
|
||||||
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo=
|
|
||||||
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 h1:a2S6M0+660BgMNl++4JPlcAO/CjkqYItDEZwkoDQK7c=
|
|
||||||
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
|
||||||
google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
|
|
||||||
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
|
|
||||||
google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U=
|
|
||||||
google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@ -276,8 +135,5 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
|
@ -54,7 +54,7 @@ func (j *DeliveryRetryJob) mainLoop() {
|
|||||||
func (j *DeliveryRetryJob) run() bool {
|
func (j *DeliveryRetryJob) run() bool {
|
||||||
defer func() {
|
defer func() {
|
||||||
if rec := recover(); rec != nil {
|
if rec := recover(); rec != nil {
|
||||||
log.Error().Interface("rec", rec).Msg("Recovered panic in DeliveryRetryJob")
|
log.Error().Interface("recover", rec).Msg("Recovered panic in DeliveryRetryJob")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ func (j *DeliveryRetryJob) redeliver(ctx *logic.SimpleContext, delivery models.D
|
|||||||
}
|
}
|
||||||
|
|
||||||
fcmDelivID, err := j.app.DeliverMessage(ctx, client, msg)
|
fcmDelivID, err := j.app.DeliverMessage(ctx, client, msg)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
err = j.app.Database.SetDeliverySuccess(ctx, delivery, *fcmDelivID)
|
err = j.app.Database.SetDeliverySuccess(ctx, delivery, *fcmDelivID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Int64("SCNMessageID", delivery.SCNMessageID).Int64("DeliveryID", delivery.DeliveryID).Msg("Failed to update delivery")
|
log.Err(err).Int64("SCNMessageID", delivery.SCNMessageID).Int64("DeliveryID", delivery.DeliveryID).Msg("Failed to update delivery")
|
||||||
@ -115,6 +115,7 @@ func (j *DeliveryRetryJob) redeliver(ctx *logic.SimpleContext, delivery models.D
|
|||||||
ctx.RollbackTransaction()
|
ctx.RollbackTransaction()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Warn().Int64("SCNMessageID", delivery.SCNMessageID).Int64("DeliveryID", delivery.DeliveryID).Msg("Delivery failed after <max> retries (set to FAILURE)")
|
||||||
} else {
|
} else {
|
||||||
err = j.app.Database.SetDeliveryRetry(ctx, delivery)
|
err = j.app.Database.SetDeliveryRetry(ctx, delivery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -17,15 +18,17 @@ type AppContext struct {
|
|||||||
cancelled bool
|
cancelled bool
|
||||||
transaction *sql.Tx
|
transaction *sql.Tx
|
||||||
permissions PermissionSet
|
permissions PermissionSet
|
||||||
|
ginContext *gin.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateAppContext(innerCtx context.Context, cancelFn context.CancelFunc) *AppContext {
|
func CreateAppContext(g *gin.Context, innerCtx context.Context, cancelFn context.CancelFunc) *AppContext {
|
||||||
return &AppContext{
|
return &AppContext{
|
||||||
inner: innerCtx,
|
inner: innerCtx,
|
||||||
cancelFunc: cancelFn,
|
cancelFunc: cancelFn,
|
||||||
cancelled: false,
|
cancelled: false,
|
||||||
transaction: nil,
|
transaction: nil,
|
||||||
permissions: NewEmptyPermissions(),
|
permissions: NewEmptyPermissions(),
|
||||||
|
ginContext: g,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,16 +51,24 @@ func (ac *AppContext) Value(key any) any {
|
|||||||
func (ac *AppContext) Cancel() {
|
func (ac *AppContext) Cancel() {
|
||||||
ac.cancelled = true
|
ac.cancelled = true
|
||||||
if ac.transaction != nil {
|
if ac.transaction != nil {
|
||||||
log.Error().Msg("Rollback transaction")
|
log.Error().Str("uri", ac.RequestURI()).Msg("Rollback transaction (ctx-cancel)")
|
||||||
err := ac.transaction.Rollback()
|
err := ac.transaction.Rollback()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("failed to rollback transaction: " + err.Error())
|
log.Err(err).Stack().Msg("Failed to rollback transaction")
|
||||||
}
|
}
|
||||||
ac.transaction = nil
|
ac.transaction = nil
|
||||||
}
|
}
|
||||||
ac.cancelFunc()
|
ac.cancelFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ac *AppContext) RequestURI() string {
|
||||||
|
if ac.ginContext != nil && ac.ginContext.Request != nil {
|
||||||
|
return ac.ginContext.Request.Method + " :: " + ac.ginContext.Request.RequestURI
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ac *AppContext) FinishSuccess(res ginresp.HTTPResponse) ginresp.HTTPResponse {
|
func (ac *AppContext) FinishSuccess(res ginresp.HTTPResponse) ginresp.HTTPResponse {
|
||||||
if ac.cancelled {
|
if ac.cancelled {
|
||||||
panic("Cannot finish a cancelled request")
|
panic("Cannot finish a cancelled request")
|
||||||
@ -65,7 +76,7 @@ func (ac *AppContext) FinishSuccess(res ginresp.HTTPResponse) ginresp.HTTPRespon
|
|||||||
if ac.transaction != nil {
|
if ac.transaction != nil {
|
||||||
err := ac.transaction.Commit()
|
err := ac.transaction.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ginresp.InternAPIError(500, apierr.COMMIT_FAILED, "Failed to comit changes to DB", err)
|
return ginresp.InternAPIError(ac.ginContext, 500, apierr.COMMIT_FAILED, "Failed to comit changes to DB", err)
|
||||||
}
|
}
|
||||||
ac.transaction = nil
|
ac.transaction = nil
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ type Application struct {
|
|||||||
Config scn.Config
|
Config scn.Config
|
||||||
Gin *gin.Engine
|
Gin *gin.Engine
|
||||||
Database *db.Database
|
Database *db.Database
|
||||||
Firebase *firebase.App
|
Firebase *firebase.FBConnector
|
||||||
DefaultChannel string
|
DefaultChannel string
|
||||||
Jobs []Job
|
Jobs []Job
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ func NewApp(db *db.Database) *Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *Application) Init(cfg scn.Config, g *gin.Engine, fb *firebase.App, jobs []Job) {
|
func (app *Application) Init(cfg scn.Config, g *gin.Engine, fb *firebase.FBConnector, jobs []Job) {
|
||||||
app.Config = cfg
|
app.Config = cfg
|
||||||
app.Gin = g
|
app.Gin = g
|
||||||
app.Firebase = fb
|
app.Firebase = fb
|
||||||
@ -122,37 +122,37 @@ func (app *Application) StartRequest(g *gin.Context, uri any, query any, body an
|
|||||||
|
|
||||||
if uri != nil {
|
if uri != nil {
|
||||||
if err := g.ShouldBindUri(uri); err != nil {
|
if err := g.ShouldBindUri(uri); err != nil {
|
||||||
return nil, langext.Ptr(ginresp.InternAPIError(400, apierr.BINDFAIL_URI_PARAM, "Failed to read uri", err))
|
return nil, langext.Ptr(ginresp.InternAPIError(g, 400, apierr.BINDFAIL_URI_PARAM, "Failed to read uri", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if query != nil {
|
if query != nil {
|
||||||
if err := g.ShouldBindQuery(query); err != nil {
|
if err := g.ShouldBindQuery(query); err != nil {
|
||||||
return nil, langext.Ptr(ginresp.InternAPIError(400, apierr.BINDFAIL_QUERY_PARAM, "Failed to read query", err))
|
return nil, langext.Ptr(ginresp.InternAPIError(g, 400, apierr.BINDFAIL_QUERY_PARAM, "Failed to read query", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if body != nil {
|
if body != nil && g.Request.Header.Get("Content-Type") == "application/javascript" {
|
||||||
if err := g.ShouldBindJSON(body); err != nil {
|
if err := g.ShouldBindJSON(body); err != nil {
|
||||||
return nil, langext.Ptr(ginresp.InternAPIError(400, apierr.BINDFAIL_BODY_PARAM, "Failed to read body", err))
|
return nil, langext.Ptr(ginresp.InternAPIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "Failed to read body", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if form != nil {
|
if form != nil && g.Request.Header.Get("Content-Type") == "multipart/form-data" {
|
||||||
if err := g.ShouldBindWith(form, binding.Form); err != nil {
|
if err := g.ShouldBindWith(form, binding.Form); err != nil {
|
||||||
return nil, langext.Ptr(ginresp.InternAPIError(400, apierr.BINDFAIL_BODY_PARAM, "Failed to read multipart-form", err))
|
return nil, langext.Ptr(ginresp.InternAPIError(g, 400, apierr.BINDFAIL_BODY_PARAM, "Failed to read multipart-form", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ictx, cancel := context.WithTimeout(context.Background(), app.Config.RequestTimeout)
|
ictx, cancel := context.WithTimeout(context.Background(), app.Config.RequestTimeout)
|
||||||
actx := CreateAppContext(ictx, cancel)
|
actx := CreateAppContext(g, ictx, cancel)
|
||||||
|
|
||||||
authheader := g.GetHeader("Authorization")
|
authheader := g.GetHeader("Authorization")
|
||||||
|
|
||||||
perm, err := app.getPermissions(actx, authheader)
|
perm, err := app.getPermissions(actx, authheader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
return nil, langext.Ptr(ginresp.InternAPIError(400, apierr.PERM_QUERY_FAIL, "Failed to determine permissions", err))
|
return nil, langext.Ptr(ginresp.InternAPIError(g, 400, apierr.PERM_QUERY_FAIL, "Failed to determine permissions", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
actx.permissions = perm
|
actx.permissions = perm
|
||||||
@ -245,6 +245,7 @@ func (app *Application) DeliverMessage(ctx context.Context, client models.Client
|
|||||||
if client.FCMToken != nil {
|
if client.FCMToken != nil {
|
||||||
fcmDelivID, err := app.Firebase.SendNotification(ctx, client, msg)
|
fcmDelivID, err := app.Firebase.SendNotification(ctx, client, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Warn().Int64("SCNMessageID", msg.SCNMessageID).Int64("ClientID", client.ClientID).Err(err).Msg("FCM Delivery failed")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return langext.Ptr(fcmDelivID), nil
|
return langext.Ptr(fcmDelivID), nil
|
||||||
|
@ -28,8 +28,6 @@ func NewEmptyPermissions() PermissionSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var respoNotAuthorized = ginresp.InternAPIError(401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil)
|
|
||||||
|
|
||||||
func (ac *AppContext) CheckPermissionUserRead(userid int64) *ginresp.HTTPResponse {
|
func (ac *AppContext) CheckPermissionUserRead(userid int64) *ginresp.HTTPResponse {
|
||||||
p := ac.permissions
|
p := ac.permissions
|
||||||
if p.UserID != nil && *p.UserID == userid && p.KeyType == PermKeyTypeUserRead {
|
if p.UserID != nil && *p.UserID == userid && p.KeyType == PermKeyTypeUserRead {
|
||||||
@ -39,7 +37,7 @@ func (ac *AppContext) CheckPermissionUserRead(userid int64) *ginresp.HTTPRespons
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return langext.Ptr(respoNotAuthorized)
|
return langext.Ptr(ginresp.InternAPIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac *AppContext) CheckPermissionRead() *ginresp.HTTPResponse {
|
func (ac *AppContext) CheckPermissionRead() *ginresp.HTTPResponse {
|
||||||
@ -51,7 +49,7 @@ func (ac *AppContext) CheckPermissionRead() *ginresp.HTTPResponse {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return langext.Ptr(respoNotAuthorized)
|
return langext.Ptr(ginresp.InternAPIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac *AppContext) CheckPermissionUserAdmin(userid int64) *ginresp.HTTPResponse {
|
func (ac *AppContext) CheckPermissionUserAdmin(userid int64) *ginresp.HTTPResponse {
|
||||||
@ -60,13 +58,13 @@ func (ac *AppContext) CheckPermissionUserAdmin(userid int64) *ginresp.HTTPRespon
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return langext.Ptr(respoNotAuthorized)
|
return langext.Ptr(ginresp.InternAPIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ac *AppContext) CheckPermissionAny() *ginresp.HTTPResponse {
|
func (ac *AppContext) CheckPermissionAny() *ginresp.HTTPResponse {
|
||||||
p := ac.permissions
|
p := ac.permissions
|
||||||
if p.KeyType == PermKeyTypeNone {
|
if p.KeyType == PermKeyTypeNone {
|
||||||
return langext.Ptr(respoNotAuthorized)
|
return langext.Ptr(ginresp.InternAPIError(ac.ginContext, 401, apierr.USER_AUTH_FAILED, "You are not authorized for this action", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -87,6 +87,7 @@ func (sc *SimpleContext) RollbackTransaction() {
|
|||||||
}
|
}
|
||||||
err := sc.transaction.Rollback()
|
err := sc.transaction.Rollback()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Err(err).Stack().Msg("Failed to rollback transaction")
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
sc.transaction = nil
|
sc.transaction = nil
|
||||||
|
Loading…
Reference in New Issue
Block a user