2022-11-18 21:25:40 +01:00
package handler
import (
"blackforestbytes.com/simplecloudnotifier/api/apierr"
"blackforestbytes.com/simplecloudnotifier/common/ginresp"
2022-11-19 15:13:47 +01:00
"blackforestbytes.com/simplecloudnotifier/db"
2022-11-20 00:19:41 +01:00
"blackforestbytes.com/simplecloudnotifier/db/cursortoken"
2022-11-18 21:25:40 +01:00
"blackforestbytes.com/simplecloudnotifier/logic"
2022-11-19 15:13:47 +01:00
"blackforestbytes.com/simplecloudnotifier/models"
2022-11-18 23:12:37 +01:00
"database/sql"
2022-11-18 21:25:40 +01:00
"github.com/gin-gonic/gin"
2022-11-18 23:28:37 +01:00
"gogs.mikescher.com/BlackForestBytes/goext/langext"
2022-11-20 00:19:41 +01:00
"gogs.mikescher.com/BlackForestBytes/goext/mathext"
2022-11-18 21:25:40 +01:00
"net/http"
2022-11-20 20:34:18 +01:00
"strings"
2022-11-18 21:25:40 +01:00
)
type APIHandler struct {
2022-11-19 15:13:47 +01:00
app * logic . Application
database * db . Database
}
func NewAPIHandler ( app * logic . Application ) APIHandler {
return APIHandler {
app : app ,
database : app . Database ,
}
2022-11-18 21:25:40 +01:00
}
// CreateUser swaggerdoc
//
// @Summary Create a new user
// @ID api-user-create
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-18 21:25:40 +01:00
//
2022-11-18 23:28:37 +01:00
// @Param post_body body handler.CreateUser.body false " "
2022-11-18 21:25:40 +01:00
//
2022-11-24 12:53:27 +01:00
// @Success 200 {object} models.UserJSONWithClients
2022-11-18 23:28:37 +01:00
// @Failure 400 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
2022-11-18 21:25:40 +01:00
//
2022-11-24 12:53:27 +01:00
// @Router /api/users [POST]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) CreateUser ( g * gin . Context ) ginresp . HTTPResponse {
type body struct {
2022-11-24 12:53:27 +01:00
FCMToken string ` json:"fcm_token" `
2022-11-18 23:28:37 +01:00
ProToken * string ` json:"pro_token" `
Username * string ` json:"username" `
2022-11-24 12:53:27 +01:00
AgentModel string ` json:"agent_model" `
AgentVersion string ` json:"agent_version" `
ClientType string ` json:"client_type" `
NoClient bool ` json:"no_client" `
2022-11-18 21:25:40 +01:00
}
var b body
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , nil , nil , & b , nil )
2022-11-18 23:12:37 +01:00
if errResp != nil {
return * errResp
2022-11-18 21:25:40 +01:00
}
2022-11-18 23:12:37 +01:00
defer ctx . Cancel ( )
2022-11-18 21:25:40 +01:00
var clientType models . ClientType
2022-11-24 12:53:27 +01:00
if ! b . NoClient {
if b . FCMToken == "" {
return ginresp . APIError ( g , 400 , apierr . INVALID_CLIENTTYPE , "Missing FCMToken" , nil )
}
if b . AgentVersion == "" {
return ginresp . APIError ( g , 400 , apierr . INVALID_CLIENTTYPE , "Missing AgentVersion" , nil )
}
if b . ClientType == "" {
return ginresp . APIError ( g , 400 , apierr . INVALID_CLIENTTYPE , "Missing ClientType" , nil )
}
if b . ClientType == string ( models . ClientTypeAndroid ) {
clientType = models . ClientTypeAndroid
} else if b . ClientType == string ( models . ClientTypeIOS ) {
clientType = models . ClientTypeIOS
} else {
return ginresp . APIError ( g , 400 , apierr . BINDFAIL_BODY_PARAM , "Invalid ClientType" , nil )
}
2022-11-18 21:25:40 +01:00
}
if b . ProToken != nil {
2022-11-25 22:42:21 +01:00
ptok , err := h . app . VerifyProToken ( ctx , * b . ProToken )
2022-11-18 21:25:40 +01:00
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . FAILED_VERIFY_PRO_TOKEN , "Failed to query purchase status" , err )
2022-11-18 21:25:40 +01:00
}
if ! ptok {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 400 , apierr . INVALID_PRO_TOKEN , "Purchase token could not be verified" , nil )
2022-11-18 21:25:40 +01:00
}
}
readKey := h . app . GenerateRandomAuthKey ( )
sendKey := h . app . GenerateRandomAuthKey ( )
adminKey := h . app . GenerateRandomAuthKey ( )
2022-11-19 15:13:47 +01:00
err := h . database . ClearFCMTokens ( ctx , b . FCMToken )
2022-11-18 21:25:40 +01:00
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to clear existing fcm tokens" , err )
2022-11-18 21:25:40 +01:00
}
if b . ProToken != nil {
2022-11-19 15:13:47 +01:00
err := h . database . ClearProTokens ( ctx , * b . ProToken )
2022-11-18 21:25:40 +01:00
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to clear existing fcm tokens" , err )
2022-11-18 21:25:40 +01:00
}
}
2022-11-19 23:16:54 +01:00
username := b . Username
if username != nil {
username = langext . Ptr ( h . app . NormalizeUsername ( * username ) )
}
userobj , err := h . database . CreateUser ( ctx , readKey , sendKey , adminKey , b . ProToken , username )
2022-11-18 21:25:40 +01:00
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to create user in db" , err )
2022-11-18 21:25:40 +01:00
}
2022-11-24 12:53:27 +01:00
if b . NoClient {
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , userobj . JSONWithClients ( make ( [ ] models . Client , 0 ) ) ) )
} else {
client , err := h . database . CreateClient ( ctx , userobj . UserID , clientType , b . FCMToken , b . AgentModel , b . AgentVersion )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to create client in db" , err )
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , userobj . JSONWithClients ( [ ] models . Client { client } ) ) )
2022-11-18 21:25:40 +01:00
}
}
2022-11-18 23:12:37 +01:00
// GetUser swaggerdoc
//
2022-11-19 12:47:23 +01:00
// @Summary Get a user
2022-11-18 23:28:37 +01:00
// @ID api-user-get
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-18 23:12:37 +01:00
//
2022-11-18 23:28:37 +01:00
// @Param uid path int true "UserID"
2022-11-18 23:12:37 +01:00
//
// @Success 200 {object} models.UserJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid} [GET]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) GetUser ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-18 23:12:37 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
2022-11-18 23:12:37 +01:00
}
var u uri
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , nil , nil , nil )
2022-11-18 23:12:37 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionUserRead ( u . UserID ) ; permResp != nil {
return * permResp
}
2022-11-19 15:13:47 +01:00
user , err := h . database . GetUser ( ctx , u . UserID )
2022-11-18 23:12:37 +01:00
if err == sql . ErrNoRows {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 404 , apierr . USER_NOT_FOUND , "User not found" , err )
2022-11-18 23:12:37 +01:00
}
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query user" , err )
2022-11-18 23:12:37 +01:00
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , user . JSON ( ) ) )
2022-11-18 21:25:40 +01:00
}
2022-11-18 23:28:37 +01:00
// UpdateUser swaggerdoc
//
2022-11-19 12:47:23 +01:00
// @Summary (Partially) update a user
2022-11-18 23:28:37 +01:00
// @Description The body-values are optional, only send the ones you want to update
// @ID api-user-update
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-18 23:28:37 +01:00
//
2022-11-20 21:35:08 +01:00
// @Param uid path int true "UserID"
//
// @Param username body string false "Change the username (send an empty string to clear it)"
// @Param pro_token body string false "Send a verification of permium purchase"
// @Param read_key body string false "Send `true` to create a new read_key"
// @Param send_key body string false "Send `true` to create a new send_key"
// @Param admin_key body string false "Send `true` to create a new admin_key"
2022-11-18 23:28:37 +01:00
//
// @Success 200 {object} models.UserJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid} [PATCH]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) UpdateUser ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-18 23:28:37 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
2022-11-18 23:28:37 +01:00
}
type body struct {
2022-11-20 21:35:08 +01:00
Username * string ` json:"username" `
ProToken * string ` json:"pro_token" `
RefreshReadKey * bool ` json:"read_key" `
RefreshSendKey * bool ` json:"send_key" `
RefreshAdminKey * bool ` json:"admin_key" `
2022-11-18 23:28:37 +01:00
}
var u uri
var b body
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , nil , & b , nil )
2022-11-18 23:28:37 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionUserAdmin ( u . UserID ) ; permResp != nil {
return * permResp
}
if b . Username != nil {
2022-11-19 23:16:54 +01:00
username := langext . Ptr ( h . app . NormalizeUsername ( * b . Username ) )
2022-11-18 23:28:37 +01:00
if * username == "" {
username = nil
}
2022-11-19 15:13:47 +01:00
err := h . database . UpdateUserUsername ( ctx , u . UserID , b . Username )
2022-11-18 23:28:37 +01:00
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to update user" , err )
2022-11-18 23:28:37 +01:00
}
}
if b . ProToken != nil {
2022-11-25 22:42:21 +01:00
ptok , err := h . app . VerifyProToken ( ctx , * b . ProToken )
2022-11-18 23:28:37 +01:00
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . FAILED_VERIFY_PRO_TOKEN , "Failed to query purchase status" , err )
2022-11-18 23:28:37 +01:00
}
if ! ptok {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 400 , apierr . INVALID_PRO_TOKEN , "Purchase token could not be verified" , nil )
2022-11-18 23:28:37 +01:00
}
2022-11-19 15:13:47 +01:00
err = h . database . ClearProTokens ( ctx , * b . ProToken )
2022-11-18 23:28:37 +01:00
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to clear existing fcm tokens" , err )
2022-11-18 23:28:37 +01:00
}
2022-11-19 15:13:47 +01:00
err = h . database . UpdateUserProToken ( ctx , u . UserID , b . ProToken )
2022-11-18 23:28:37 +01:00
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to update user" , err )
2022-11-18 23:28:37 +01:00
}
}
2022-11-20 21:35:08 +01:00
if langext . Coalesce ( b . RefreshSendKey , false ) {
newkey := h . app . GenerateRandomAuthKey ( )
err := h . database . UpdateUserSendKey ( ctx , u . UserID , newkey )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to update user" , err )
}
}
if langext . Coalesce ( b . RefreshReadKey , false ) {
newkey := h . app . GenerateRandomAuthKey ( )
err := h . database . UpdateUserReadKey ( ctx , u . UserID , newkey )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to update user" , err )
}
}
if langext . Coalesce ( b . RefreshAdminKey , false ) {
newkey := h . app . GenerateRandomAuthKey ( )
err := h . database . UpdateUserAdminKey ( ctx , u . UserID , newkey )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to update user" , err )
}
}
2022-11-19 15:13:47 +01:00
user , err := h . database . GetUser ( ctx , u . UserID )
2022-11-18 23:28:37 +01:00
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query (updated) user" , err )
2022-11-18 23:28:37 +01:00
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , user . JSON ( ) ) )
2022-11-18 21:25:40 +01:00
}
2022-11-19 12:47:23 +01:00
// ListClients swaggerdoc
//
// @Summary List all clients
// @ID api-clients-list
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-19 12:47:23 +01:00
//
// @Param uid path int true "UserID"
//
2022-11-19 17:07:30 +01:00
// @Success 200 {object} handler.ListClients.response
2022-11-19 12:47:23 +01:00
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid}/clients [GET]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) ListClients ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-19 12:47:23 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
2022-11-19 12:47:23 +01:00
}
2022-11-19 17:07:30 +01:00
type response struct {
2022-11-19 12:47:23 +01:00
Clients [ ] models . ClientJSON ` json:"clients" `
}
var u uri
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , nil , nil , nil )
2022-11-19 12:47:23 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionUserRead ( u . UserID ) ; permResp != nil {
return * permResp
}
2022-11-19 15:13:47 +01:00
clients , err := h . database . ListClients ( ctx , u . UserID )
2022-11-19 12:47:23 +01:00
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query clients" , err )
2022-11-19 12:47:23 +01:00
}
res := langext . ArrMap ( clients , func ( v models . Client ) models . ClientJSON { return v . JSON ( ) } )
2022-11-19 17:07:30 +01:00
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , response { Clients : res } ) )
2022-11-18 21:25:40 +01:00
}
2022-11-19 12:50:41 +01:00
// GetClient swaggerdoc
//
2022-11-19 12:59:25 +01:00
// @Summary Get a single clients
2022-11-19 12:50:41 +01:00
// @ID api-clients-get
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-19 12:50:41 +01:00
//
// @Param uid path int true "UserID"
// @Param cid path int true "ClientID"
//
// @Success 200 {object} models.ClientJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid}/clients/{cid} [GET]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) GetClient ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-19 12:50:41 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
ClientID models . ClientID ` uri:"cid" `
2022-11-19 12:50:41 +01:00
}
var u uri
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , nil , nil , nil )
2022-11-19 12:50:41 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionUserRead ( u . UserID ) ; permResp != nil {
return * permResp
}
2022-11-19 15:13:47 +01:00
client , err := h . database . GetClient ( ctx , u . UserID , u . ClientID )
2022-11-19 12:50:41 +01:00
if err == sql . ErrNoRows {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 404 , apierr . CLIENT_NOT_FOUND , "Client not found" , err )
2022-11-19 12:50:41 +01:00
}
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query client" , err )
2022-11-19 12:50:41 +01:00
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , client . JSON ( ) ) )
2022-11-18 21:25:40 +01:00
}
2022-11-19 12:56:44 +01:00
// AddClient swaggerdoc
//
2022-11-19 12:59:25 +01:00
// @Summary Add a new clients
// @ID api-clients-create
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-19 12:56:44 +01:00
//
2022-11-19 12:59:25 +01:00
// @Param uid path int true "UserID"
2022-11-19 12:56:44 +01:00
//
// @Param post_body body handler.AddClient.body false " "
//
2022-11-19 12:59:25 +01:00
// @Success 200 {object} models.ClientJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
2022-11-19 12:56:44 +01:00
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid}/clients [POST]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) AddClient ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-19 12:56:44 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
2022-11-19 12:56:44 +01:00
}
type body struct {
2022-11-20 03:06:08 +01:00
FCMToken string ` json:"fcm_token" binding:"required" `
AgentModel string ` json:"agent_model" binding:"required" `
AgentVersion string ` json:"agent_version" binding:"required" `
ClientType string ` json:"client_type" binding:"required" `
2022-11-19 12:56:44 +01:00
}
var u uri
var b body
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , nil , & b , nil )
2022-11-19 12:56:44 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
var clientType models . ClientType
if b . ClientType == string ( models . ClientTypeAndroid ) {
clientType = models . ClientTypeAndroid
} else if b . ClientType == string ( models . ClientTypeIOS ) {
clientType = models . ClientTypeIOS
} else {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 400 , apierr . INVALID_CLIENTTYPE , "Invalid ClientType" , nil )
2022-11-19 12:56:44 +01:00
}
if permResp := ctx . CheckPermissionUserAdmin ( u . UserID ) ; permResp != nil {
return * permResp
}
2022-11-19 15:13:47 +01:00
client , err := h . database . CreateClient ( ctx , u . UserID , clientType , b . FCMToken , b . AgentModel , b . AgentVersion )
2022-11-19 12:56:44 +01:00
if err != nil {
2022-11-24 12:53:27 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to create client in db" , err )
2022-11-19 12:56:44 +01:00
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , client . JSON ( ) ) )
2022-11-18 21:25:40 +01:00
}
2022-11-19 12:59:25 +01:00
// DeleteClient swaggerdoc
//
// @Summary Delete a client
// @ID api-clients-delete
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-19 12:59:25 +01:00
//
// @Param uid path int true "UserID"
// @Param cid path int true "ClientID"
//
// @Success 200 {object} models.ClientJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 22:12:47 +01:00
// @Router /api/users/{uid}/clients [DELETE]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) DeleteClient ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-19 12:59:25 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
ClientID models . ClientID ` uri:"cid" `
2022-11-19 12:59:25 +01:00
}
var u uri
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , nil , nil , nil )
2022-11-19 12:59:25 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionUserAdmin ( u . UserID ) ; permResp != nil {
return * permResp
}
2022-11-19 15:13:47 +01:00
client , err := h . database . GetClient ( ctx , u . UserID , u . ClientID )
2022-11-19 12:59:25 +01:00
if err == sql . ErrNoRows {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 404 , apierr . CLIENT_NOT_FOUND , "Client not found" , err )
2022-11-19 12:59:25 +01:00
}
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query client" , err )
2022-11-19 12:59:25 +01:00
}
2022-11-19 15:13:47 +01:00
err = h . database . DeleteClient ( ctx , u . ClientID )
2022-11-19 12:59:25 +01:00
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to delete client" , err )
2022-11-19 12:59:25 +01:00
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , client . JSON ( ) ) )
2022-11-18 21:25:40 +01:00
}
2022-11-19 17:07:30 +01:00
// ListChannels swaggerdoc
//
2022-11-20 21:15:06 +01:00
// @Summary List channels of a user (subscribed/owned)
// @Description The possible values for 'selector' are:
// @Description - "owned" Return all channels of the user
// @Description - "subscribed" Return all channels that the user is subscribing to
// @Description - "all" Return channels that the user owns or is subscribing
// @Description - "subscribed_any" Return all channels that the user is subscribing to (even unconfirmed)
// @Description - "all_any" Return channels that the user owns or is subscribing (even unconfirmed)
// @ID api-channels-list
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-20 21:15:06 +01:00
//
// @Param uid path int true "UserID"
// @Param selector query string true "Filter channels (default: owned)" Enums(owned, subscribed, all, subscribed_any, all_any)
//
// @Success 200 {object} handler.ListChannels.response
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid}/channels [GET]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) ListChannels ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-19 17:07:30 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
2022-11-19 17:07:30 +01:00
}
2022-11-20 21:15:06 +01:00
type query struct {
Selector * string ` form:"selector" `
}
2022-11-19 17:07:30 +01:00
type response struct {
Channels [ ] models . ChannelJSON ` json:"channels" `
}
var u uri
2022-11-20 21:15:06 +01:00
var q query
ctx , errResp := h . app . StartRequest ( g , & u , & q , nil , nil )
2022-11-19 17:07:30 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionUserRead ( u . UserID ) ; permResp != nil {
return * permResp
}
2022-11-20 21:15:06 +01:00
sel := strings . ToLower ( langext . Coalesce ( q . Selector , "owned" ) )
2022-11-19 17:07:30 +01:00
2022-11-20 21:15:06 +01:00
var res [ ] models . ChannelJSON
if sel == "owned" {
channels , err := h . database . ListChannelsByOwner ( ctx , u . UserID )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channels" , err )
}
res = langext . ArrMap ( channels , func ( v models . Channel ) models . ChannelJSON { return v . JSON ( true ) } )
} else if sel == "subscribed_any" {
channels , err := h . database . ListChannelsBySubscriber ( ctx , u . UserID , false )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channels" , err )
}
res = langext . ArrMap ( channels , func ( v models . Channel ) models . ChannelJSON { return v . JSON ( false ) } )
} else if sel == "all_any" {
channels , err := h . database . ListChannelsByAccess ( ctx , u . UserID , false )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channels" , err )
}
res = langext . ArrMap ( channels , func ( v models . Channel ) models . ChannelJSON { return v . JSON ( false ) } )
} else if sel == "subscribed" {
channels , err := h . database . ListChannelsBySubscriber ( ctx , u . UserID , true )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channels" , err )
}
res = langext . ArrMap ( channels , func ( v models . Channel ) models . ChannelJSON { return v . JSON ( false ) } )
} else if sel == "all" {
channels , err := h . database . ListChannelsByAccess ( ctx , u . UserID , true )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channels" , err )
}
res = langext . ArrMap ( channels , func ( v models . Channel ) models . ChannelJSON { return v . JSON ( false ) } )
} else {
return ginresp . APIError ( g , 400 , apierr . INVALID_ENUM_VALUE , "Invalid value for the [selector] parameter" , nil )
}
2022-11-19 17:07:30 +01:00
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , response { Channels : res } ) )
2022-11-18 21:25:40 +01:00
}
2022-11-19 17:07:30 +01:00
// GetChannel swaggerdoc
//
// @Summary List all channels of a user
// @ID api-channels-get
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-19 17:07:30 +01:00
//
// @Param uid path int true "UserID"
// @Param cid path int true "ChannelID"
//
// @Success 200 {object} models.ChannelJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid}/channels/{cid} [GET]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) GetChannel ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-19 17:07:30 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
ChannelID models . ChannelID ` uri:"cid" `
2022-11-19 17:07:30 +01:00
}
var u uri
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , nil , nil , nil )
2022-11-19 17:07:30 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionUserRead ( u . UserID ) ; permResp != nil {
return * permResp
}
channel , err := h . database . GetChannel ( ctx , u . UserID , u . ChannelID )
if err == sql . ErrNoRows {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 404 , apierr . CHANNEL_NOT_FOUND , "Channel not found" , err )
2022-11-19 17:07:30 +01:00
}
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channel" , err )
2022-11-19 17:07:30 +01:00
}
2022-11-20 21:15:06 +01:00
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , channel . JSON ( true ) ) )
2022-11-18 21:25:40 +01:00
}
2022-11-20 21:35:08 +01:00
// UpdateChannel swaggerdoc
//
// @Summary (Partially) update a channel
// @ID api-channels-update
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-20 21:35:08 +01:00
//
// @Param uid path int true "UserID"
// @Param cid path int true "ChannelID"
//
// @Param subscribe_key body string false "Send `true` to create a new subscribe_key"
// @Param send_key body string false "Send `true` to create a new send_key"
//
// @Success 200 {object} models.ChannelJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid}/channels/{cid} [PATCH]
2022-11-20 21:35:08 +01:00
func ( h APIHandler ) UpdateChannel ( g * gin . Context ) ginresp . HTTPResponse {
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
ChannelID models . ChannelID ` uri:"cid" `
2022-11-20 21:35:08 +01:00
}
type body struct {
RefreshSubscribeKey * bool ` json:"subscribe_key" `
RefreshSendKey * bool ` json:"send_key" `
}
var u uri
var b body
ctx , errResp := h . app . StartRequest ( g , & u , nil , & b , nil )
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionUserAdmin ( u . UserID ) ; permResp != nil {
return * permResp
}
_ , err := h . database . GetChannel ( ctx , u . UserID , u . ChannelID )
if err == sql . ErrNoRows {
return ginresp . APIError ( g , 404 , apierr . CHANNEL_NOT_FOUND , "Channel not found" , err )
}
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channel" , err )
}
if langext . Coalesce ( b . RefreshSendKey , false ) {
newkey := h . app . GenerateRandomAuthKey ( )
err := h . database . UpdateChannelSendKey ( ctx , u . ChannelID , newkey )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to update user" , err )
}
}
if langext . Coalesce ( b . RefreshSubscribeKey , false ) {
newkey := h . app . GenerateRandomAuthKey ( )
err := h . database . UpdateChannelSubscribeKey ( ctx , u . ChannelID , newkey )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to update user" , err )
}
}
user , err := h . database . GetChannel ( ctx , u . UserID , u . ChannelID )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query (updated) user" , err )
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , user . JSON ( true ) ) )
}
2022-11-20 00:30:30 +01:00
// ListChannelMessages swaggerdoc
//
// @Summary List messages of a channel
// @Description The next_page_token is an opaque token, the special value "@start" (or empty-string) is the beginning and "@end" is the end
// @Description Simply start the pagination without a next_page_token and get the next page by calling this endpoint with the returned next_page_token of the last query
// @Description If there are no more entries the token "@end" will be returned
// @Description By default we return long messages with a trimmed body, if trimmed=false is supplied we return full messages (this reduces the max page_size)
// @ID api-channel-messages
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-20 00:30:30 +01:00
//
// @Param query_data query handler.ListChannelMessages.query false " "
2022-11-25 22:42:21 +01:00
// @Param uid path int true "UserID"
// @Param cid path int true "ChannelID"
2022-11-20 00:30:30 +01:00
//
// @Success 200 {object} handler.ListChannelMessages.response
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid}/channels/{cid}/messages [GET]
2022-11-20 00:30:30 +01:00
func ( h APIHandler ) ListChannelMessages ( g * gin . Context ) ginresp . HTTPResponse {
type uri struct {
2022-11-20 22:18:24 +01:00
ChannelUserID models . UserID ` uri:"uid" `
ChannelID models . ChannelID ` uri:"cid" `
2022-11-20 00:30:30 +01:00
}
type query struct {
PageSize * int ` form:"page_size" `
NextPageToken * string ` form:"next_page_token" `
Filter * string ` form:"filter" `
Trimmed * bool ` form:"trimmed" `
}
type response struct {
Messages [ ] models . MessageJSON ` json:"messages" `
NextPageToken string ` json:"next_page_token" `
PageSize int ` json:"page_size" `
}
var u uri
var q query
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , & q , nil , nil )
2022-11-20 00:30:30 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
trimmed := langext . Coalesce ( q . Trimmed , true )
maxPageSize := langext . Conditional ( trimmed , 16 , 256 )
pageSize := mathext . Clamp ( langext . Coalesce ( q . PageSize , 64 ) , 1 , maxPageSize )
if permResp := ctx . CheckPermissionRead ( ) ; permResp != nil {
return * permResp
}
channel , err := h . database . GetChannel ( ctx , u . ChannelUserID , u . ChannelID )
if err == sql . ErrNoRows {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 404 , apierr . CHANNEL_NOT_FOUND , "Channel not found" , err )
2022-11-20 00:30:30 +01:00
}
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channel" , err )
2022-11-20 00:30:30 +01:00
}
userid := * ctx . GetPermissionUserID ( )
sub , err := h . database . GetSubscriptionBySubscriber ( ctx , userid , channel . ChannelID )
if err == sql . ErrNoRows {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 401 , apierr . USER_AUTH_FAILED , "You are not authorized for this action" , nil )
2022-11-20 00:30:30 +01:00
}
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query subscription" , err )
2022-11-20 00:30:30 +01:00
}
if ! sub . Confirmed {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 401 , apierr . USER_AUTH_FAILED , "You are not authorized for this action" , nil )
2022-11-20 00:30:30 +01:00
}
tok , err := cursortoken . Decode ( langext . Coalesce ( q . NextPageToken , "" ) )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . PAGETOKEN_ERROR , "Failed to decode next_page_token" , err )
2022-11-20 00:30:30 +01:00
}
messages , npt , err := h . database . ListChannelMessages ( ctx , channel . ChannelID , pageSize , tok )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query messages" , err )
2022-11-20 00:30:30 +01:00
}
var res [ ] models . MessageJSON
if trimmed {
res = langext . ArrMap ( messages , func ( v models . Message ) models . MessageJSON { return v . TrimmedJSON ( ) } )
} else {
res = langext . ArrMap ( messages , func ( v models . Message ) models . MessageJSON { return v . FullJSON ( ) } )
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , response { Messages : res , NextPageToken : npt . Token ( ) , PageSize : pageSize } ) )
2022-11-18 21:25:40 +01:00
}
2022-11-19 17:07:30 +01:00
// ListUserSubscriptions swaggerdoc
//
// @Summary List all channels of a user
// @ID api-user-subscriptions-list
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-19 17:07:30 +01:00
//
// @Param uid path int true "UserID"
//
// @Success 200 {object} handler.ListUserSubscriptions.response
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid}/subscriptions [GET]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) ListUserSubscriptions ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-19 17:07:30 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
2022-11-19 17:07:30 +01:00
}
type response struct {
Subscriptions [ ] models . SubscriptionJSON ` json:"subscriptions" `
}
var u uri
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , nil , nil , nil )
2022-11-19 17:07:30 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionUserRead ( u . UserID ) ; permResp != nil {
return * permResp
}
clients , err := h . database . ListSubscriptionsByOwner ( ctx , u . UserID )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channels" , err )
2022-11-19 17:07:30 +01:00
}
res := langext . ArrMap ( clients , func ( v models . Subscription ) models . SubscriptionJSON { return v . JSON ( ) } )
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , response { Subscriptions : res } ) )
2022-11-18 21:25:40 +01:00
}
2022-11-19 17:07:30 +01:00
// ListChannelSubscriptions swaggerdoc
//
// @Summary List all subscriptions of a channel
// @ID api-chan-subscriptions-list
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-19 17:07:30 +01:00
//
// @Param uid path int true "UserID"
// @Param cid path int true "ChannelID"
//
// @Success 200 {object} handler.ListChannelSubscriptions.response
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid}/channels/{cid}/subscriptions [GET]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) ListChannelSubscriptions ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-19 17:07:30 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
ChannelID models . ChannelID ` uri:"cid" `
2022-11-19 17:07:30 +01:00
}
type response struct {
Subscriptions [ ] models . SubscriptionJSON ` json:"subscriptions" `
}
var u uri
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , nil , nil , nil )
2022-11-19 17:07:30 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionUserRead ( u . UserID ) ; permResp != nil {
return * permResp
}
_ , err := h . database . GetChannel ( ctx , u . UserID , u . ChannelID )
if err == sql . ErrNoRows {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 404 , apierr . CHANNEL_NOT_FOUND , "Channel not found" , err )
2022-11-19 17:07:30 +01:00
}
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channels" , err )
2022-11-19 17:07:30 +01:00
}
clients , err := h . database . ListSubscriptionsByChannel ( ctx , u . ChannelID )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channels" , err )
2022-11-19 17:07:30 +01:00
}
res := langext . ArrMap ( clients , func ( v models . Subscription ) models . SubscriptionJSON { return v . JSON ( ) } )
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , response { Subscriptions : res } ) )
2022-11-18 21:25:40 +01:00
}
2022-11-19 17:07:30 +01:00
// GetSubscription swaggerdoc
//
// @Summary Get a single subscription
// @ID api-subscriptions-get
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-19 17:07:30 +01:00
//
// @Param uid path int true "UserID"
// @Param sid path int true "SubscriptionID"
//
// @Success 200 {object} models.SubscriptionJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid}/subscriptions/{sid} [GET]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) GetSubscription ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-19 17:07:30 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
SubscriptionID models . SubscriptionID ` uri:"sid" `
2022-11-19 17:07:30 +01:00
}
var u uri
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , nil , nil , nil )
2022-11-19 17:07:30 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionUserRead ( u . UserID ) ; permResp != nil {
return * permResp
}
subscription , err := h . database . GetSubscription ( ctx , u . SubscriptionID )
if err == sql . ErrNoRows {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 404 , apierr . SUBSCRIPTION_NOT_FOUND , "Subscription not found" , err )
2022-11-19 17:07:30 +01:00
}
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query subscription" , err )
2022-11-19 17:07:30 +01:00
}
if subscription . SubscriberUserID != u . UserID {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 401 , apierr . USER_AUTH_FAILED , "You are not authorized for this action" , nil )
2022-11-19 17:07:30 +01:00
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , subscription . JSON ( ) ) )
2022-11-18 21:25:40 +01:00
}
2022-11-19 17:07:30 +01:00
// CancelSubscription swaggerdoc
//
// @Summary Cancel (delete) subscription
// @ID api-subscriptions-delete
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-19 17:07:30 +01:00
//
// @Param uid path int true "UserID"
// @Param sid path int true "SubscriptionID"
//
// @Success 200 {object} models.SubscriptionJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid}/subscriptions/{sid} [DELETE]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) CancelSubscription ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-19 17:07:30 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
SubscriptionID models . SubscriptionID ` uri:"sid" `
2022-11-19 17:07:30 +01:00
}
var u uri
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , nil , nil , nil )
2022-11-19 17:07:30 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
2022-11-19 23:16:54 +01:00
if permResp := ctx . CheckPermissionUserAdmin ( u . UserID ) ; permResp != nil {
2022-11-19 17:07:30 +01:00
return * permResp
}
subscription , err := h . database . GetSubscription ( ctx , u . SubscriptionID )
if err == sql . ErrNoRows {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 404 , apierr . SUBSCRIPTION_NOT_FOUND , "Subscription not found" , err )
2022-11-19 17:07:30 +01:00
}
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query subscription" , err )
2022-11-19 17:07:30 +01:00
}
2022-11-19 23:16:54 +01:00
if subscription . SubscriberUserID != u . UserID && subscription . ChannelOwnerUserID != u . UserID {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 401 , apierr . USER_AUTH_FAILED , "You are not authorized for this action" , nil )
2022-11-19 17:07:30 +01:00
}
err = h . database . DeleteSubscription ( ctx , u . SubscriptionID )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to delete subscription" , err )
2022-11-19 17:07:30 +01:00
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , subscription . JSON ( ) ) )
2022-11-18 21:25:40 +01:00
}
2022-11-19 23:16:54 +01:00
// CreateSubscription swaggerdoc
//
// @Summary Creare/Request a subscription
// @ID api-subscriptions-create
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-19 23:16:54 +01:00
//
2022-11-20 00:19:41 +01:00
// @Param uid path int true "UserID"
// @Param query_data query handler.CreateSubscription.query false " "
// @Param post_data body handler.CreateSubscription.body false " "
2022-11-19 23:16:54 +01:00
//
2022-11-20 00:19:41 +01:00
// @Success 200 {object} models.SubscriptionJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
2022-11-19 23:16:54 +01:00
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid}/subscriptions [POST]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) CreateSubscription ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-19 23:16:54 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
2022-11-19 23:16:54 +01:00
}
type body struct {
2022-11-20 22:18:24 +01:00
ChannelOwnerUserID models . UserID ` form:"channel_owner_user_id" binding:"required" `
Channel string ` form:"channel_name" binding:"required" `
2022-11-19 23:16:54 +01:00
}
type query struct {
ChanSubscribeKey * string ` form:"chan_subscribe_key" `
}
var u uri
var q query
var b body
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , & q , & b , nil )
2022-11-19 23:16:54 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionUserAdmin ( u . UserID ) ; permResp != nil {
return * permResp
}
channel , err := h . database . GetChannelByName ( ctx , b . ChannelOwnerUserID , h . app . NormalizeChannelName ( b . Channel ) )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channel" , err )
2022-11-19 23:16:54 +01:00
}
if channel == nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 400 , apierr . CHANNEL_NOT_FOUND , "Channel not found" , err )
2022-11-19 23:16:54 +01:00
}
2022-11-20 03:06:08 +01:00
if channel . OwnerUserID != u . UserID && ( q . ChanSubscribeKey == nil || * q . ChanSubscribeKey != channel . SubscribeKey ) {
2022-11-20 20:34:18 +01:00
ginresp . APIError ( g , 401 , apierr . USER_AUTH_FAILED , "You are not authorized for this action" , nil )
2022-11-20 03:06:08 +01:00
}
2022-11-19 23:16:54 +01:00
sub , err := h . database . CreateSubscription ( ctx , u . UserID , * channel , channel . OwnerUserID == u . UserID )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to create subscription" , err )
2022-11-19 23:16:54 +01:00
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , sub . JSON ( ) ) )
2022-11-18 21:25:40 +01:00
}
2022-11-19 23:16:54 +01:00
// UpdateSubscription swaggerdoc
//
// @Summary Update a subscription (e.g. confirm)
// @ID api-subscriptions-update
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-19 23:16:54 +01:00
//
// @Param uid path int true "UserID"
// @Param sid path int true "SubscriptionID"
//
// @Success 200 {object} models.SubscriptionJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 19:32:23 +01:00
// @Router /api/users/{uid}/subscriptions/{sid} [PATCH]
2022-11-19 17:07:30 +01:00
func ( h APIHandler ) UpdateSubscription ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-19 23:16:54 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
UserID models . UserID ` uri:"uid" `
SubscriptionID models . SubscriptionID ` uri:"sid" `
2022-11-19 23:16:54 +01:00
}
type body struct {
Confirmed * bool ` form:"confirmed" `
}
var u uri
var b body
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , nil , & b , nil )
2022-11-19 23:16:54 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionUserAdmin ( u . UserID ) ; permResp != nil {
return * permResp
}
subscription , err := h . database . GetSubscription ( ctx , u . SubscriptionID )
if err == sql . ErrNoRows {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 404 , apierr . SUBSCRIPTION_NOT_FOUND , "Subscription not found" , err )
2022-11-19 23:16:54 +01:00
}
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query subscription" , err )
2022-11-19 23:16:54 +01:00
}
if subscription . ChannelOwnerUserID != u . UserID {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 401 , apierr . USER_AUTH_FAILED , "You are not authorized for this action" , nil )
2022-11-19 23:16:54 +01:00
}
if b . Confirmed != nil {
err = h . database . UpdateSubscriptionConfirmed ( ctx , u . SubscriptionID , * b . Confirmed )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to update subscription" , err )
2022-11-19 23:16:54 +01:00
}
}
subscription , err = h . database . GetSubscription ( ctx , u . SubscriptionID )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query subscription" , err )
2022-11-19 23:16:54 +01:00
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , subscription . JSON ( ) ) )
2022-11-19 17:07:30 +01:00
}
2022-11-20 00:19:41 +01:00
// ListMessages swaggerdoc
//
// @Summary List all (subscribed) messages
// @Description The next_page_token is an opaque token, the special value "@start" (or empty-string) is the beginning and "@end" is the end
// @Description Simply start the pagination without a next_page_token and get the next page by calling this endpoint with the returned next_page_token of the last query
// @Description If there are no more entries the token "@end" will be returned
// @Description By default we return long messages with a trimmed body, if trimmed=false is supplied we return full messages (this reduces the max page_size)
// @ID api-messages-list
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-20 00:19:41 +01:00
//
// @Param query_data query handler.ListMessages.query false " "
//
// @Success 200 {object} handler.ListMessages.response
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
//
2022-11-23 19:32:23 +01:00
// @Router /api/messages [GET]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) ListMessages ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-20 00:19:41 +01:00
type query struct {
PageSize * int ` form:"page_size" `
NextPageToken * string ` form:"next_page_token" `
Filter * string ` form:"filter" `
2022-11-29 11:07:15 +01:00
Trimmed * bool ` form:"trimmed" ` //TODO more filter (sender-name, channel, timestamps, prio, )
2022-11-20 00:19:41 +01:00
}
type response struct {
Messages [ ] models . MessageJSON ` json:"messages" `
NextPageToken string ` json:"next_page_token" `
PageSize int ` json:"page_size" `
}
var q query
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , nil , & q , nil , nil )
2022-11-20 00:19:41 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
trimmed := langext . Coalesce ( q . Trimmed , true )
maxPageSize := langext . Conditional ( trimmed , 16 , 256 )
pageSize := mathext . Clamp ( langext . Coalesce ( q . PageSize , 64 ) , 1 , maxPageSize )
if permResp := ctx . CheckPermissionRead ( ) ; permResp != nil {
return * permResp
}
userid := * ctx . GetPermissionUserID ( )
tok , err := cursortoken . Decode ( langext . Coalesce ( q . NextPageToken , "" ) )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . PAGETOKEN_ERROR , "Failed to decode next_page_token" , err )
2022-11-20 00:19:41 +01:00
}
err = h . database . UpdateUserLastRead ( ctx , userid )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to update last-read" , err )
2022-11-20 00:19:41 +01:00
}
messages , npt , err := h . database . ListMessages ( ctx , userid , pageSize , tok )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query messages" , err )
2022-11-20 00:19:41 +01:00
}
var res [ ] models . MessageJSON
if trimmed {
res = langext . ArrMap ( messages , func ( v models . Message ) models . MessageJSON { return v . TrimmedJSON ( ) } )
} else {
res = langext . ArrMap ( messages , func ( v models . Message ) models . MessageJSON { return v . FullJSON ( ) } )
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , response { Messages : res , NextPageToken : npt . Token ( ) , PageSize : pageSize } ) )
2022-11-18 21:25:40 +01:00
}
2022-11-19 23:16:54 +01:00
// GetMessage swaggerdoc
//
2022-11-20 00:19:41 +01:00
// @Summary Get a single message (untrimmed)
2022-11-19 23:16:54 +01:00
// @Description The user must either own the message and request the resource with the READ or ADMIN Key
// @Description Or the user must subscribe to the corresponding channel (and be confirmed) and request the resource with the READ or ADMIN Key
// @Description The returned message is never trimmed
2022-11-20 00:19:41 +01:00
// @ID api-messages-get
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-19 23:16:54 +01:00
//
2022-11-20 00:19:41 +01:00
// @Param mid path int true "SCNMessageID"
2022-11-19 23:16:54 +01:00
//
2022-11-20 00:19:41 +01:00
// @Success 200 {object} models.MessageJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
2022-11-19 23:16:54 +01:00
//
2022-11-23 19:32:23 +01:00
// @Router /api/messages/{mid} [PATCH]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) GetMessage ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-19 23:16:54 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
MessageID models . SCNMessageID ` uri:"mid" `
2022-11-19 23:16:54 +01:00
}
var u uri
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , nil , nil , nil )
2022-11-19 23:16:54 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionAny ( ) ; permResp != nil {
return * permResp
}
msg , err := h . database . GetMessage ( ctx , u . MessageID )
if err == sql . ErrNoRows {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 404 , apierr . MESSAGE_NOT_FOUND , "message not found" , err )
2022-11-19 23:16:54 +01:00
}
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query message" , err )
2022-11-19 23:16:54 +01:00
}
if ! ctx . CheckPermissionMessageReadDirect ( msg ) {
// either we have direct read permissions (it is our message + read/admin key)
// or we subscribe (+confirmed) to the channel and have read/admin key
if uid := ctx . GetPermissionUserID ( ) ; uid != nil && ctx . IsPermissionUserRead ( ) {
sub , err := h . database . GetSubscriptionBySubscriber ( ctx , * uid , msg . ChannelID )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query subscription" , err )
2022-11-19 23:16:54 +01:00
}
if sub == nil {
// not subbed
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 401 , apierr . USER_AUTH_FAILED , "You are not authorized for this action" , nil )
2022-11-19 23:16:54 +01:00
}
if ! sub . Confirmed {
// sub not confirmed
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 401 , apierr . USER_AUTH_FAILED , "You are not authorized for this action" , nil )
2022-11-19 23:16:54 +01:00
}
// => perm okay
} else {
// auth-key is not set or not a user:x variant
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 401 , apierr . USER_AUTH_FAILED , "You are not authorized for this action" , nil )
2022-11-19 23:16:54 +01:00
}
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , msg . FullJSON ( ) ) )
2022-11-18 21:25:40 +01:00
}
2022-11-19 23:16:54 +01:00
// DeleteMessage swaggerdoc
//
2022-11-20 00:19:41 +01:00
// @Summary Delete a single message
2022-11-19 23:16:54 +01:00
// @Description The user must own the message and request the resource with the ADMIN Key
2022-11-20 00:19:41 +01:00
// @ID api-messages-delete
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-19 23:16:54 +01:00
//
2022-11-20 00:19:41 +01:00
// @Param mid path int true "SCNMessageID"
2022-11-19 23:16:54 +01:00
//
2022-11-20 00:19:41 +01:00
// @Success 200 {object} models.MessageJSON
// @Failure 400 {object} ginresp.apiError
// @Failure 401 {object} ginresp.apiError
// @Failure 404 {object} ginresp.apiError
// @Failure 500 {object} ginresp.apiError
2022-11-19 23:16:54 +01:00
//
2022-11-23 22:12:47 +01:00
// @Router /api/messages/{mid} [DELETE]
2022-11-18 21:25:40 +01:00
func ( h APIHandler ) DeleteMessage ( g * gin . Context ) ginresp . HTTPResponse {
2022-11-19 23:16:54 +01:00
type uri struct {
2022-11-20 22:18:24 +01:00
MessageID models . SCNMessageID ` uri:"mid" `
2022-11-19 23:16:54 +01:00
}
var u uri
2022-11-20 03:06:08 +01:00
ctx , errResp := h . app . StartRequest ( g , & u , nil , nil , nil )
2022-11-19 23:16:54 +01:00
if errResp != nil {
return * errResp
}
defer ctx . Cancel ( )
if permResp := ctx . CheckPermissionAny ( ) ; permResp != nil {
return * permResp
}
msg , err := h . database . GetMessage ( ctx , u . MessageID )
if err == sql . ErrNoRows {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 404 , apierr . MESSAGE_NOT_FOUND , "message not found" , err )
2022-11-19 23:16:54 +01:00
}
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query message" , err )
2022-11-19 23:16:54 +01:00
}
if ! ctx . CheckPermissionMessageReadDirect ( msg ) {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 401 , apierr . USER_AUTH_FAILED , "You are not authorized for this action" , nil )
2022-11-19 23:16:54 +01:00
}
err = h . database . DeleteMessage ( ctx , msg . SCNMessageID )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to delete message" , err )
2022-11-19 23:16:54 +01:00
}
2022-11-20 15:40:19 +01:00
err = h . database . CancelPendingDeliveries ( ctx , msg . SCNMessageID )
if err != nil {
2022-11-20 20:34:18 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to cancel deliveries" , err )
2022-11-20 15:40:19 +01:00
}
2022-11-19 23:16:54 +01:00
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , msg . FullJSON ( ) ) )
2022-11-18 21:25:40 +01:00
}