2022-11-18 21:25:40 +01:00
package handler
import (
"blackforestbytes.com/simplecloudnotifier/api/apierr"
2022-12-20 13:55:09 +01:00
"blackforestbytes.com/simplecloudnotifier/api/ginresp"
2022-11-20 00:19:41 +01:00
"blackforestbytes.com/simplecloudnotifier/db/cursortoken"
2023-01-06 00:39:21 +01:00
primarydb "blackforestbytes.com/simplecloudnotifier/db/impl/primary"
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-12-14 16:57:08 +01:00
"fmt"
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
2023-01-06 00:39:21 +01:00
database * primarydb . Database
2022-11-19 15:13:47 +01:00
}
func NewAPIHandler ( app * logic . Application ) APIHandler {
return APIHandler {
app : app ,
2023-01-06 00:39:21 +01:00
database : app . Database . Primary ,
2022-11-19 15:13:47 +01:00
}
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-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 500 {object} ginresp.apiError "internal server error"
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 {
2023-01-17 22:03:27 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to clear existing pro 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 {
2022-11-30 12:40:03 +01:00
err := h . database . DeleteClientsByFCM ( ctx , b . FCMToken )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to delete existing clients in db" , err )
}
2022-11-24 12:53:27 +01:00
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
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "user not found"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-18 23:12:37 +01:00
//
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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
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
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "user not found"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-18 23:28:37 +01:00
//
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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
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-30 13:57:55 +01:00
err := h . database . UpdateUserUsername ( ctx , u . UserID , 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 {
2023-01-17 22:56:04 +01:00
if * b . ProToken == "" {
err := h . database . UpdateUserProToken ( ctx , u . UserID , nil )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to update user" , err )
}
} else {
ptok , err := h . app . VerifyProToken ( ctx , * b . ProToken )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . FAILED_VERIFY_PRO_TOKEN , "Failed to query purchase status" , err )
}
if ! ptok {
return ginresp . APIError ( g , 400 , apierr . INVALID_PRO_TOKEN , "Purchase token could not be verified" , nil )
}
err = h . database . ClearProTokens ( ctx , * b . ProToken )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to clear existing fcm tokens" , err )
}
err = h . database . UpdateUserProToken ( ctx , u . UserID , b . ProToken )
if err != nil {
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-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-19 12:47:23 +01:00
//
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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
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-30 12:40:03 +01:00
// @Summary Get a single client
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
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "client not found"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-19 12:50:41 +01:00
//
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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
ClientID models . ClientID ` uri:"cid" binding:"entityid" `
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
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 500 {object} ginresp.apiError "internal server error"
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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
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-30 12:40:03 +01:00
err := h . database . DeleteClientsByFCM ( ctx , b . FCMToken )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to delete existing clients in db" , err )
}
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
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "client not found"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-19 12:59:25 +01:00
//
2022-11-30 12:40:03 +01:00
// @Router /api/users/{uid}/clients/{cid} [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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
ClientID models . ClientID ` uri:"cid" binding:"entityid" `
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-12-21 18:14:13 +01:00
// @Summary List channels of a user (subscribed/owned/all)
2022-11-20 21:15:06 +01:00
// @Description The possible values for 'selector' are:
2022-12-21 18:14:13 +01:00
// @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
2022-11-20 21:15:06 +01:00
// @Description - "subscribed_any" Return all channels that the user is subscribing to (even unconfirmed)
2022-12-21 18:14:13 +01:00
// @Description - "all_any" Return channels that the user owns or is subscribing (even unconfirmed)
//
2022-11-20 21:15:06 +01:00
// @ID api-channels-list
2022-11-23 19:32:23 +01:00
// @Tags API-v2
2022-11-20 21:15:06 +01:00
//
2022-12-22 12:43:40 +01:00
// @Param uid path int true "UserID"
// @Param selector query string false "Filter channels (default: owned)" Enums(owned, subscribed, all, subscribed_any, all_any)
2022-11-20 21:15:06 +01:00
//
// @Success 200 {object} handler.ListChannels.response
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-20 21:15:06 +01:00
//
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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
2022-11-19 17:07:30 +01:00
}
2022-11-20 21:15:06 +01:00
type query struct {
2022-12-14 18:38:30 +01:00
Selector * string ` json:"selector" form:"selector" enums:"owned,subscribed_any,all_any,subscribed,all" `
2022-11-20 21:15:06 +01:00
}
2022-11-19 17:07:30 +01:00
type response struct {
2022-12-21 18:14:13 +01:00
Channels [ ] models . ChannelWithSubscriptionJSON ` json:"channels" `
2022-11-19 17:07:30 +01:00
}
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-12-21 18:14:13 +01:00
var res [ ] models . ChannelWithSubscriptionJSON
2022-11-20 21:15:06 +01:00
if sel == "owned" {
2022-12-21 18:14:13 +01:00
channels , err := h . database . ListChannelsByOwner ( ctx , u . UserID , u . UserID )
2022-11-20 21:15:06 +01:00
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channels" , err )
}
2022-12-21 18:14:13 +01:00
res = langext . ArrMap ( channels , func ( v models . ChannelWithSubscription ) models . ChannelWithSubscriptionJSON { return v . JSON ( true ) } )
2022-11-20 21:15:06 +01:00
} else if sel == "subscribed_any" {
2022-12-21 18:14:13 +01:00
channels , err := h . database . ListChannelsBySubscriber ( ctx , u . UserID , nil )
2022-11-20 21:15:06 +01:00
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channels" , err )
}
2022-12-21 18:14:13 +01:00
res = langext . ArrMap ( channels , func ( v models . ChannelWithSubscription ) models . ChannelWithSubscriptionJSON { return v . JSON ( false ) } )
2022-11-20 21:15:06 +01:00
} else if sel == "all_any" {
2022-12-21 18:14:13 +01:00
channels , err := h . database . ListChannelsByAccess ( ctx , u . UserID , nil )
2022-11-20 21:15:06 +01:00
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channels" , err )
}
2022-12-21 18:14:13 +01:00
res = langext . ArrMap ( channels , func ( v models . ChannelWithSubscription ) models . ChannelWithSubscriptionJSON { return v . JSON ( false ) } )
2022-11-20 21:15:06 +01:00
} else if sel == "subscribed" {
2022-12-21 18:14:13 +01:00
channels , err := h . database . ListChannelsBySubscriber ( ctx , u . UserID , langext . Ptr ( true ) )
2022-11-20 21:15:06 +01:00
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channels" , err )
}
2022-12-21 18:14:13 +01:00
res = langext . ArrMap ( channels , func ( v models . ChannelWithSubscription ) models . ChannelWithSubscriptionJSON { return v . JSON ( false ) } )
2022-11-20 21:15:06 +01:00
} else if sel == "all" {
2022-12-21 18:14:13 +01:00
channels , err := h . database . ListChannelsByAccess ( ctx , u . UserID , langext . Ptr ( true ) )
2022-11-20 21:15:06 +01:00
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channels" , err )
}
2022-12-21 18:14:13 +01:00
res = langext . ArrMap ( channels , func ( v models . ChannelWithSubscription ) models . ChannelWithSubscriptionJSON { return v . JSON ( false ) } )
2022-11-20 21:15:06 +01:00
} else {
2022-12-21 18:14:13 +01:00
2022-11-20 21:15:06 +01:00
return ginresp . APIError ( g , 400 , apierr . INVALID_ENUM_VALUE , "Invalid value for the [selector] parameter" , nil )
2022-12-21 18:14:13 +01:00
2022-11-20 21:15:06 +01:00
}
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
//
2022-12-21 18:14:13 +01:00
// @Summary Get a single channel
2022-11-19 17:07:30 +01:00
// @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"
//
2022-12-21 18:14:13 +01:00
// @Success 200 {object} models.ChannelWithSubscriptionJSON
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "channel not found"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-19 17:07:30 +01:00
//
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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
ChannelID models . ChannelID ` uri:"cid" binding:"entityid" `
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-12-14 14:29:59 +01:00
// CreateChannel swaggerdoc
//
// @Summary Create a new (empty) channel
// @ID api-channels-create
// @Tags API-v2
//
// @Param uid path int true "UserID"
// @Param post_body body handler.CreateChannel.body false " "
//
2022-12-21 18:14:13 +01:00
// @Success 200 {object} models.ChannelWithSubscriptionJSON
2022-12-14 14:29:59 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 409 {object} ginresp.apiError "channel already exists"
// @Failure 500 {object} ginresp.apiError "internal server error"
//
2022-12-21 18:14:13 +01:00
// @Router /api/users/{uid}/channels [POST]
2022-12-14 14:29:59 +01:00
func ( h APIHandler ) CreateChannel ( g * gin . Context ) ginresp . HTTPResponse {
type uri struct {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
2022-12-14 14:29:59 +01:00
}
type body struct {
2022-12-21 18:14:13 +01:00
Name string ` json:"name" `
Subscribe * bool ` json:"subscribe" `
2022-12-14 14:29:59 +01:00
}
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
}
2022-12-22 12:43:40 +01:00
if b . Name == "" {
return ginresp . APIError ( g , 400 , apierr . INVALID_BODY_PARAM , "Missing parameter: name" , nil )
}
2022-12-22 11:22:36 +01:00
channelDisplayName := h . app . NormalizeChannelDisplayName ( b . Name )
channelInternalName := h . app . NormalizeChannelInternalName ( b . Name )
2022-12-14 14:29:59 +01:00
2022-12-22 11:22:36 +01:00
channelExisting , err := h . database . GetChannelByName ( ctx , u . UserID , channelInternalName )
2022-12-14 14:29:59 +01:00
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channel" , err )
}
2022-12-14 16:57:08 +01:00
user , err := h . database . GetUser ( ctx , u . UserID )
if err == sql . ErrNoRows {
2022-12-22 12:43:40 +01:00
return ginresp . APIError ( g , 400 , apierr . USER_NOT_FOUND , "User not found" , nil )
2022-12-14 16:57:08 +01:00
}
if err != nil {
2022-12-22 12:43:40 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query user" , err )
2022-12-14 16:57:08 +01:00
}
2022-12-22 11:22:36 +01:00
if len ( channelDisplayName ) > user . MaxChannelNameLength ( ) {
2022-12-22 12:43:40 +01:00
return ginresp . APIError ( g , 400 , apierr . CHANNEL_TOO_LONG , fmt . Sprintf ( "Channel too long (max %d characters)" , user . MaxChannelNameLength ( ) ) , nil )
2022-12-22 11:22:36 +01:00
}
if len ( channelInternalName ) > user . MaxChannelNameLength ( ) {
2022-12-22 12:43:40 +01:00
return ginresp . APIError ( g , 400 , apierr . CHANNEL_TOO_LONG , fmt . Sprintf ( "Channel too long (max %d characters)" , user . MaxChannelNameLength ( ) ) , nil )
2022-12-14 16:57:08 +01:00
}
2022-12-14 14:29:59 +01:00
if channelExisting != nil {
return ginresp . APIError ( g , 409 , apierr . CHANNEL_ALREADY_EXISTS , "Channel with this name already exists" , nil )
}
subscribeKey := h . app . GenerateRandomAuthKey ( )
sendKey := h . app . GenerateRandomAuthKey ( )
2022-12-22 11:22:36 +01:00
channel , err := h . database . CreateChannel ( ctx , u . UserID , channelDisplayName , channelInternalName , subscribeKey , sendKey )
2022-12-14 14:29:59 +01:00
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to create channel" , err )
}
2022-12-21 18:14:13 +01:00
if langext . Coalesce ( b . Subscribe , true ) {
sub , err := h . database . CreateSubscription ( ctx , u . UserID , channel , true )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to create subscription" , err )
}
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , channel . WithSubscription ( langext . Ptr ( sub ) ) . JSON ( true ) ) )
} else {
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , channel . WithSubscription ( nil ) . JSON ( true ) ) )
}
2022-12-14 14:29:59 +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"
2022-12-22 11:22:36 +01:00
// @Param display_name body string false "Change the cahnnel display-name (only chnages to lowercase/uppercase are allowed - internal_name must stay the same)"
2022-11-20 21:35:08 +01:00
//
2022-12-21 18:14:13 +01:00
// @Success 200 {object} models.ChannelWithSubscriptionJSON
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "channel not found"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-20 21:35:08 +01:00
//
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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
ChannelID models . ChannelID ` uri:"cid" binding:"entityid" `
2022-11-20 21:35:08 +01:00
}
type body struct {
2022-12-22 11:22:36 +01:00
RefreshSubscribeKey * bool ` json:"subscribe_key" `
RefreshSendKey * bool ` json:"send_key" `
DisplayName * string ` json:"display_name" `
2023-01-13 12:43:20 +01:00
DescriptionName * string ` json:"description_name" `
2022-11-20 21:35:08 +01:00
}
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
}
2022-12-22 11:22:36 +01:00
oldChannel , err := h . database . GetChannel ( ctx , u . UserID , u . ChannelID )
2022-11-20 21:35:08 +01:00
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 )
}
2023-01-13 12:43:20 +01:00
user , err := h . database . GetUser ( ctx , u . UserID )
if err == sql . ErrNoRows {
return ginresp . APIError ( g , 400 , apierr . USER_NOT_FOUND , "User not found" , nil )
}
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query user" , err )
}
2022-11-20 21:35:08 +01:00
if langext . Coalesce ( b . RefreshSendKey , false ) {
newkey := h . app . GenerateRandomAuthKey ( )
err := h . database . UpdateChannelSendKey ( ctx , u . ChannelID , newkey )
if err != nil {
2022-12-22 11:22:36 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to update channel" , err )
2022-11-20 21:35:08 +01:00
}
}
if langext . Coalesce ( b . RefreshSubscribeKey , false ) {
newkey := h . app . GenerateRandomAuthKey ( )
err := h . database . UpdateChannelSubscribeKey ( ctx , u . ChannelID , newkey )
if err != nil {
2022-12-22 11:22:36 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to update channel" , err )
}
}
if b . DisplayName != nil {
newDisplayName := h . app . NormalizeChannelDisplayName ( * b . DisplayName )
newInternalName := h . app . NormalizeChannelInternalName ( * b . DisplayName )
if newInternalName != oldChannel . InternalName {
return ginresp . APIError ( g , 400 , apierr . CHANNEL_NAME_WOULD_CHANGE , "Cannot substantially change the channel name" , err )
}
2023-01-13 12:43:20 +01:00
if len ( newDisplayName ) > user . MaxChannelNameLength ( ) {
return ginresp . APIError ( g , 400 , apierr . CHANNEL_TOO_LONG , fmt . Sprintf ( "Channel too long (max %d characters)" , user . MaxChannelNameLength ( ) ) , nil )
}
2022-12-22 11:22:36 +01:00
err := h . database . UpdateChannelDisplayName ( ctx , u . ChannelID , newDisplayName )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to update channel" , err )
2022-11-20 21:35:08 +01:00
}
2022-12-22 11:22:36 +01:00
2022-11-20 21:35:08 +01:00
}
2023-01-13 12:43:20 +01:00
if b . DescriptionName != nil {
var descName * string = nil
if strings . TrimSpace ( * b . DescriptionName ) != "" {
descName = langext . Ptr ( strings . TrimSpace ( * b . DescriptionName ) )
}
if descName != nil && len ( * descName ) > user . MaxChannelDescriptionNameLength ( ) {
return ginresp . APIError ( g , 400 , apierr . CHANNEL_DESCRIPTION_TOO_LONG , fmt . Sprintf ( "Channel-Description too long (max %d characters)" , user . MaxChannelNameLength ( ) ) , nil )
}
err := h . database . UpdateChannelDescriptionName ( ctx , u . ChannelID , descName )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to update channel" , err )
}
}
2022-12-21 18:14:13 +01:00
channel , err := h . database . GetChannel ( ctx , u . UserID , u . ChannelID )
2022-11-20 21:35:08 +01:00
if err != nil {
2022-12-22 11:22:36 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query (updated) channel" , err )
2022-11-20 21:35:08 +01:00
}
2022-12-21 18:14:13 +01:00
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , channel . JSON ( true ) ) )
2022-11-20 21:35:08 +01:00
}
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
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "channel not found"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-20 00:30:30 +01:00
//
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 {
2023-01-14 00:48:51 +01:00
ChannelUserID models . UserID ` uri:"uid" binding:"entityid" `
ChannelID models . ChannelID ` uri:"cid" binding:"entityid" `
2022-11-20 00:30:30 +01:00
}
type query struct {
2022-11-30 16:46:14 +01:00
PageSize * int ` json:"page_size" form:"page_size" `
NextPageToken * string ` json:"next_page_token" form:"next_page_token" `
Filter * string ` json:"filter" form:"filter" `
Trimmed * bool ` json:"trimmed" form:"trimmed" `
2022-11-20 00:30:30 +01:00
}
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
}
2022-12-10 03:38:48 +01:00
filter := models . MessageFilter {
ChannelID : langext . Ptr ( [ ] models . ChannelID { channel . ChannelID } ) ,
}
messages , npt , err := h . database . ListMessages ( ctx , filter , pageSize , tok )
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 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
//
2022-12-22 12:43:40 +01:00
// @Summary List all subscriptions of a user (incoming/owned)
// @Description The possible values for 'selector' are:
// @Description - "outgoing_all" All subscriptions (confirmed/unconfirmed) with the user as subscriber (= subscriptions he can use to read channels)
// @Description - "outgoing_confirmed" Confirmed subscriptions with the user as subscriber
// @Description - "outgoing_unconfirmed" Unconfirmed (Pending) subscriptions with the user as subscriber
// @Description - "incoming_all" All subscriptions (confirmed/unconfirmed) from other users to channels of this user (= incoming subscriptions and subscription requests)
// @Description - "incoming_confirmed" Confirmed subscriptions from other users to channels of this user
// @Description - "incoming_unconfirmed" Unconfirmed subscriptions from other users to channels of this user (= requests)
//
// @ID api-user-subscriptions-list
// @Tags API-v2
2022-11-19 17:07:30 +01:00
//
2022-12-22 12:43:40 +01:00
// @Param uid path int true "UserID"
// @Param selector query string true "Filter subscriptions (default: owner_all)" Enums(outgoing_all, outgoing_confirmed, outgoing_unconfirmed, incoming_all, incoming_confirmed, incoming_unconfirmed)
2022-11-19 17:07:30 +01:00
//
2022-12-22 12:43:40 +01:00
// @Success 200 {object} handler.ListUserSubscriptions.response
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-19 17:07:30 +01:00
//
2022-12-22 12:43:40 +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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
2022-11-19 17:07:30 +01:00
}
2022-12-21 18:14:13 +01:00
type query struct {
Selector * string ` json:"selector" form:"selector" enums:"owner_all,owner_confirmed,owner_unconfirmed,incoming_all,incoming_confirmed,incoming_unconfirmed" `
}
2022-11-19 17:07:30 +01:00
type response struct {
Subscriptions [ ] models . SubscriptionJSON ` json:"subscriptions" `
}
var u uri
2022-12-21 18:14:13 +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-12-21 18:14:13 +01:00
sel := strings . ToLower ( langext . Coalesce ( q . Selector , "owner_all" ) )
var res [ ] models . Subscription
var err error
2022-12-22 12:43:40 +01:00
if sel == "outgoing_all" {
2022-12-21 18:14:13 +01:00
2022-12-22 12:43:40 +01:00
res , err = h . database . ListSubscriptionsBySubscriber ( ctx , u . UserID , nil )
2022-12-21 18:14:13 +01:00
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query subscriptions" , err )
}
2022-12-22 12:43:40 +01:00
} else if sel == "outgoing_confirmed" {
2022-12-21 18:14:13 +01:00
2022-12-22 12:43:40 +01:00
res , err = h . database . ListSubscriptionsBySubscriber ( ctx , u . UserID , langext . Ptr ( true ) )
2022-12-21 18:14:13 +01:00
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query subscriptions" , err )
}
2022-12-22 12:43:40 +01:00
} else if sel == "outgoing_unconfirmed" {
2022-12-21 18:14:13 +01:00
2022-12-22 12:43:40 +01:00
res , err = h . database . ListSubscriptionsBySubscriber ( ctx , u . UserID , langext . Ptr ( false ) )
2022-12-21 18:14:13 +01:00
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query subscriptions" , err )
}
} else if sel == "incoming_all" {
2022-12-22 12:43:40 +01:00
res , err = h . database . ListSubscriptionsByChannelOwner ( ctx , u . UserID , nil )
2022-12-21 18:14:13 +01:00
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query subscriptions" , err )
}
} else if sel == "incoming_confirmed" {
2022-12-22 12:43:40 +01:00
res , err = h . database . ListSubscriptionsByChannelOwner ( ctx , u . UserID , langext . Ptr ( true ) )
2022-12-21 18:14:13 +01:00
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query subscriptions" , err )
}
} else if sel == "incoming_unconfirmed" {
2022-12-22 12:43:40 +01:00
res , err = h . database . ListSubscriptionsByChannelOwner ( ctx , u . UserID , langext . Ptr ( false ) )
2022-12-21 18:14:13 +01:00
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query subscriptions" , err )
}
} 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
}
2022-12-21 18:14:13 +01:00
jsonres := langext . ArrMap ( res , func ( v models . Subscription ) models . SubscriptionJSON { return v . JSON ( ) } )
2022-11-19 17:07:30 +01:00
2022-12-21 18:14:13 +01:00
return ctx . FinishSuccess ( ginresp . JSON ( http . StatusOK , response { Subscriptions : jsonres } ) )
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
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "channel not found"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-19 17:07:30 +01:00
//
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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
ChannelID models . ChannelID ` uri:"cid" binding:"entityid" `
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 {
2023-01-16 20:29:49 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channel" , err )
2022-11-19 17:07:30 +01:00
}
clients , err := h . database . ListSubscriptionsByChannel ( ctx , u . ChannelID )
if err != nil {
2023-01-16 20:29:49 +01:00
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query subscriptions" , 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
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "subscription not found"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-19 17:07:30 +01:00
//
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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
SubscriptionID models . SubscriptionID ` uri:"sid" binding:"entityid" `
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
}
2022-12-22 12:43:40 +01:00
if subscription . SubscriberUserID != u . UserID && subscription . ChannelOwnerUserID != u . UserID {
return ginresp . APIError ( g , 404 , apierr . SUBSCRIPTION_USER_MISMATCH , "Subscription not found" , 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
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "subscription not found"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-19 17:07:30 +01:00
//
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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
SubscriptionID models . SubscriptionID ` uri:"sid" binding:"entityid" `
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-12-22 12:43:40 +01:00
return ginresp . APIError ( g , 404 , apierr . SUBSCRIPTION_USER_MISMATCH , "Subscription not found" , 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
//
2022-12-22 12:43:40 +01:00
// @Summary Create/Request a subscription
// @Description Either [channel_owner_user_id, channel_internal_name] or [channel_id] must be supplied in the request body
// @ID api-subscriptions-create
// @Tags API-v2
2022-11-19 23:16:54 +01:00
//
2022-12-22 12:43:40 +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-12-22 12:43:40 +01:00
// @Success 200 {object} models.SubscriptionJSON
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-19 23:16:54 +01:00
//
2022-12-22 12:43:40 +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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
2022-11-19 23:16:54 +01:00
}
type body struct {
2023-01-14 00:48:51 +01:00
ChannelOwnerUserID * models . UserID ` json:"channel_owner_user_id" binding:"entityid" `
2022-12-22 12:43:40 +01:00
ChannelInternalName * string ` json:"channel_internal_name" `
2023-01-14 00:48:51 +01:00
ChannelID * models . ChannelID ` json:"channel_id" binding:"entityid" `
2022-11-19 23:16:54 +01:00
}
type query struct {
2022-11-30 16:46:14 +01:00
ChanSubscribeKey * string ` json:"chan_subscribe_key" form:"chan_subscribe_key" `
2022-11-19 23:16:54 +01:00
}
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
}
2022-12-22 12:43:40 +01:00
var channel models . Channel
if b . ChannelOwnerUserID != nil && b . ChannelInternalName != nil && b . ChannelID == nil {
channelInternalName := h . app . NormalizeChannelInternalName ( * b . ChannelInternalName )
outchannel , err := h . database . GetChannelByName ( ctx , * b . ChannelOwnerUserID , channelInternalName )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channel" , err )
}
if outchannel == nil {
return ginresp . APIError ( g , 400 , apierr . CHANNEL_NOT_FOUND , "Channel not found" , err )
}
channel = * outchannel
} else if b . ChannelOwnerUserID == nil && b . ChannelInternalName == nil && b . ChannelID != nil {
outchannel , err := h . database . GetChannelByID ( ctx , * b . ChannelID )
if err != nil {
return ginresp . APIError ( g , 500 , apierr . DATABASE_ERROR , "Failed to query channel" , err )
}
if outchannel == nil {
return ginresp . APIError ( g , 400 , apierr . CHANNEL_NOT_FOUND , "Channel not found" , err )
}
channel = * outchannel
} else {
return ginresp . APIError ( g , 400 , apierr . INVALID_BODY_PARAM , "Must either supply [channel_owner_user_id, channel_internal_name] or [channel_id]" , nil )
2022-12-22 11:22:36 +01:00
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-12-22 12:43:40 +01:00
return 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-12-22 12:43:40 +01:00
sub , err := h . database . CreateSubscription ( ctx , u . UserID , channel , channel . OwnerUserID == u . UserID )
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 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
//
2022-12-22 17:29:59 +01:00
// @Param uid path int true "UserID"
// @Param sid path int true "SubscriptionID"
// @Param post_data body handler.UpdateSubscription.body false " "
2022-11-19 23:16:54 +01:00
//
2022-12-22 17:29:59 +01:00
// @Success 200 {object} models.SubscriptionJSON
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "subscription not found"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-19 23:16:54 +01:00
//
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 {
2023-01-14 00:48:51 +01:00
UserID models . UserID ` uri:"uid" binding:"entityid" `
SubscriptionID models . SubscriptionID ` uri:"sid" binding:"entityid" `
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
}
2022-12-22 17:29:59 +01:00
userid := * ctx . GetPermissionUserID ( )
2022-11-19 23:16:54 +01:00
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
}
2022-12-22 17:29:59 +01:00
if subscription . SubscriberUserID != u . UserID && subscription . ChannelOwnerUserID != u . UserID {
2022-12-22 12:43:40 +01:00
return ginresp . APIError ( g , 404 , apierr . SUBSCRIPTION_USER_MISMATCH , "Subscription not found" , nil )
2022-11-19 23:16:54 +01:00
}
if b . Confirmed != nil {
2022-12-22 17:29:59 +01:00
if subscription . ChannelOwnerUserID != userid {
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 . 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
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 500 {object} ginresp.apiError "internal server error"
2022-11-20 00:19:41 +01:00
//
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 {
2022-11-30 16:46:14 +01:00
PageSize * int ` json:"page_size" form:"page_size" `
NextPageToken * string ` json:"next_page_token" form:"next_page_token" `
Filter * string ` json:"filter" form:"filter" `
Trimmed * bool ` json:"trimmed" 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
}
2022-12-10 03:38:48 +01:00
filter := models . MessageFilter {
ConfirmedSubscriptionBy : langext . Ptr ( userid ) ,
}
2022-12-11 02:47:23 +01:00
if q . Filter != nil && strings . TrimSpace ( * q . Filter ) != "" {
filter . SearchString = langext . Ptr ( [ ] string { strings . TrimSpace ( * q . Filter ) } )
}
2022-12-10 03:38:48 +01:00
messages , npt , err := h . database . ListMessages ( ctx , filter , pageSize , tok )
2022-11-20 00:19: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 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
//
2023-01-14 00:48:51 +01:00
// @Param mid path int true "MessageID"
2022-11-19 23:16:54 +01:00
//
2022-11-20 00:19:41 +01:00
// @Success 200 {object} models.MessageJSON
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "message not found"
// @Failure 500 {object} ginresp.apiError "internal server error"
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 {
2023-01-14 00:48:51 +01:00
MessageID models . MessageID ` uri:"mid" binding:"entityid" `
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
}
2022-12-14 12:29:55 +01:00
msg , err := h . database . GetMessage ( ctx , u . MessageID , false )
2022-11-19 23:16:54 +01:00
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
//
2023-01-14 00:48:51 +01:00
// @Param mid path int true "MessageID"
2022-11-19 23:16:54 +01:00
//
2022-11-20 00:19:41 +01:00
// @Success 200 {object} models.MessageJSON
2022-12-14 14:27:41 +01:00
// @Failure 400 {object} ginresp.apiError "supplied values/parameters cannot be parsed / are invalid"
// @Failure 401 {object} ginresp.apiError "user is not authorized / has missing permissions"
// @Failure 404 {object} ginresp.apiError "message not found"
// @Failure 500 {object} ginresp.apiError "internal server error"
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 {
2023-01-14 00:48:51 +01:00
MessageID models . MessageID ` uri:"mid" binding:"entityid" `
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
}
2022-12-14 12:29:55 +01:00
msg , err := h . database . GetMessage ( ctx , u . MessageID , false )
2022-11-19 23:16:54 +01:00
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
}
2023-01-14 00:48:51 +01:00
err = h . database . DeleteMessage ( ctx , msg . MessageID )
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 delete message" , err )
2022-11-19 23:16:54 +01:00
}
2023-01-14 00:48:51 +01:00
err = h . database . CancelPendingDeliveries ( ctx , msg . MessageID )
2022-11-20 15:40:19 +01:00
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
}