diff --git a/server/README.md b/server/README.md index 7751bd3..eb9b396 100644 --- a/server/README.md +++ b/server/README.md @@ -3,9 +3,8 @@ //TODO - - List subscriptions on all owned channels (RESTful?) - - route to re-create keys - ack/read deliveries && return ack-count + - go ID types - full-text-search: https://www.sqlite.org/fts5.html#contentless_tables diff --git a/server/api/handler/api.go b/server/api/handler/api.go index 71930f5..605c20e 100644 --- a/server/api/handler/api.go +++ b/server/api/handler/api.go @@ -160,7 +160,13 @@ func (h APIHandler) GetUser(g *gin.Context) ginresp.HTTPResponse { // @Description The body-values are optional, only send the ones you want to update // @ID api-user-update // -// @Param post_body body handler.UpdateUser.body false " " +// @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" // // @Success 200 {object} models.UserJSON // @Failure 400 {object} ginresp.apiError @@ -174,8 +180,11 @@ func (h APIHandler) UpdateUser(g *gin.Context) ginresp.HTTPResponse { UserID int64 `uri:"uid"` } type body struct { - Username *string `json:"username"` - ProToken *string `json:"pro_token"` + Username *string `json:"username"` + ProToken *string `json:"pro_token"` + RefreshReadKey *bool `json:"read_key"` + RefreshSendKey *bool `json:"send_key"` + RefreshAdminKey *bool `json:"admin_key"` } var u uri @@ -223,6 +232,33 @@ func (h APIHandler) UpdateUser(g *gin.Context) ginresp.HTTPResponse { } } + 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) + } + } + user, err := h.database.GetUser(ctx, u.UserID) if err != nil { return ginresp.APIError(g, 500, apierr.DATABASE_ERROR, "Failed to query (updated) user", err) @@ -549,6 +585,80 @@ func (h APIHandler) GetChannel(g *gin.Context) ginresp.HTTPResponse { return ctx.FinishSuccess(ginresp.JSON(http.StatusOK, channel.JSON(true))) } +// UpdateChannel swaggerdoc +// +// @Summary (Partially) update a channel +// @ID api-channels-update +// +// @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 +// +// @Router /api-v2/users/{uid}/channels/{cid} [PATCH] +func (h APIHandler) UpdateChannel(g *gin.Context) ginresp.HTTPResponse { + type uri struct { + UserID int64 `uri:"uid"` + ChannelID int64 `uri:"cid"` + } + 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))) +} + // ListChannelMessages swaggerdoc // // @Summary List messages of a channel diff --git a/server/api/router.go b/server/api/router.go index 4f9b2b6..e77a5fe 100644 --- a/server/api/router.go +++ b/server/api/router.go @@ -105,6 +105,7 @@ func (r *Router) Init(e *gin.Engine) { apiv2.GET("/users/:uid/channels", ginresp.Wrap(r.apiHandler.ListChannels)) apiv2.GET("/users/:uid/channels/:cid", ginresp.Wrap(r.apiHandler.GetChannel)) + apiv2.PATCH("/users/:uid/channels/:cid", ginresp.Wrap(r.apiHandler.UpdateChannel)) apiv2.GET("/users/:uid/channels/:cid/messages", ginresp.Wrap(r.apiHandler.ListChannelMessages)) apiv2.GET("/users/:uid/channels/:cid/subscriptions", ginresp.Wrap(r.apiHandler.ListChannelSubscriptions)) diff --git a/server/db/channels.go b/server/db/channels.go index 4d10eee..53a6075 100644 --- a/server/db/channels.go +++ b/server/db/channels.go @@ -167,3 +167,35 @@ func (db *Database) IncChannelMessageCounter(ctx TxContext, channel models.Chann return nil } + +func (db *Database) UpdateChannelSendKey(ctx TxContext, channelid int64, newkey string) error { + tx, err := ctx.GetOrCreateTransaction(db) + if err != nil { + return err + } + + _, err = tx.ExecContext(ctx, "UPDATE channels SET send_key = ? WHERE channel_id = ?", + newkey, + channelid) + if err != nil { + return err + } + + return nil +} + +func (db *Database) UpdateChannelSubscribeKey(ctx TxContext, channelid int64, newkey string) error { + tx, err := ctx.GetOrCreateTransaction(db) + if err != nil { + return err + } + + _, err = tx.ExecContext(ctx, "UPDATE channels SET subscribe_key = ? WHERE channel_id = ?", + newkey, + channelid) + if err != nil { + return err + } + + return nil +} diff --git a/server/db/users.go b/server/db/users.go index 6fc5413..e5915d8 100644 --- a/server/db/users.go +++ b/server/db/users.go @@ -191,3 +191,51 @@ func (db *Database) UpdateUserKeys(ctx TxContext, userid int64, sendKey string, return nil } + +func (db *Database) UpdateUserSendKey(ctx TxContext, userid int64, newkey string) error { + tx, err := ctx.GetOrCreateTransaction(db) + if err != nil { + return err + } + + _, err = tx.ExecContext(ctx, "UPDATE users SET send_key = ? WHERE user_id = ?", + newkey, + userid) + if err != nil { + return err + } + + return nil +} + +func (db *Database) UpdateUserReadKey(ctx TxContext, userid int64, newkey string) error { + tx, err := ctx.GetOrCreateTransaction(db) + if err != nil { + return err + } + + _, err = tx.ExecContext(ctx, "UPDATE users SET read_key = ? WHERE user_id = ?", + newkey, + userid) + if err != nil { + return err + } + + return nil +} + +func (db *Database) UpdateUserAdminKey(ctx TxContext, userid int64, newkey string) error { + tx, err := ctx.GetOrCreateTransaction(db) + if err != nil { + return err + } + + _, err = tx.ExecContext(ctx, "UPDATE users SET admin_key = ? WHERE user_id = ?", + newkey, + userid) + if err != nil { + return err + } + + return nil +} diff --git a/server/swagger/swagger.json b/server/swagger/swagger.json index 71347fc..a4e0a12 100644 --- a/server/swagger/swagger.json +++ b/server/swagger/swagger.json @@ -424,11 +424,50 @@ "operationId": "api-user-update", "parameters": [ { - "description": " ", - "name": "post_body", + "type": "integer", + "description": "UserID", + "name": "uid", + "in": "path", + "required": true + }, + { + "description": "Change the username (send an empty string to clear it)", + "name": "username", "in": "body", "schema": { - "$ref": "#/definitions/handler.UpdateUser.body" + "type": "string" + } + }, + { + "description": "Send a verification of permium purchase", + "name": "pro_token", + "in": "body", + "schema": { + "type": "string" + } + }, + { + "description": "Send `true` to create a new read_key", + "name": "read_key", + "in": "body", + "schema": { + "type": "string" + } + }, + { + "description": "Send `true` to create a new send_key", + "name": "send_key", + "in": "body", + "schema": { + "type": "string" + } + }, + { + "description": "Send `true` to create a new admin_key", + "name": "admin_key", + "in": "body", + "schema": { + "type": "string" } } ], @@ -469,7 +508,7 @@ "/api-v2/users/{uid}/channels": { "get": { "description": "The possible values for 'selector' are:\n- \"owned\" Return all channels of the user\n- \"subscribed\" Return all channels that the user is subscribing to\n- \"all\" Return channels that the user owns or is subscribing\n- \"subscribed_any\" Return all channels that the user is subscribing to (even unconfirmed)\n- \"all_any\" Return channels that the user owns or is subscribing (even unconfirmed)", - "summary": "List all channels of a user", + "summary": "List channels of a user (subscribed/owned)", "operationId": "api-channels-list", "parameters": [ { @@ -580,6 +619,74 @@ } } } + }, + "patch": { + "summary": "(Partially) update a channel", + "operationId": "api-channels-update", + "parameters": [ + { + "type": "integer", + "description": "UserID", + "name": "uid", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "ChannelID", + "name": "cid", + "in": "path", + "required": true + }, + { + "description": "Send `true` to create a new subscribe_key", + "name": "subscribe_key", + "in": "body", + "schema": { + "type": "string" + } + }, + { + "description": "Send `true` to create a new send_key", + "name": "send_key", + "in": "body", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.ChannelJSON" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/ginresp.apiError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/ginresp.apiError" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/ginresp.apiError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/ginresp.apiError" + } + } + } } }, "/api-v2/users/{uid}/channels/{cid}/messages": { @@ -2283,17 +2390,6 @@ } } }, - "handler.UpdateUser.body": { - "type": "object", - "properties": { - "pro_token": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, "handler.Upgrade.response": { "type": "object", "properties": { @@ -2404,6 +2500,10 @@ "owner_user_id": { "type": "integer" }, + "send_key": { + "description": "can be nil, depending on endpoint", + "type": "string" + }, "subscribe_key": { "description": "can be nil, depending on endpoint", "type": "string" diff --git a/server/swagger/swagger.yaml b/server/swagger/swagger.yaml index a090e00..c90c6dd 100644 --- a/server/swagger/swagger.yaml +++ b/server/swagger/swagger.yaml @@ -245,13 +245,6 @@ definitions: user_key: type: string type: object - handler.UpdateUser.body: - properties: - pro_token: - type: string - username: - type: string - type: object handler.Upgrade.response: properties: is_pro: @@ -324,6 +317,9 @@ definitions: type: string owner_user_id: type: integer + send_key: + description: can be nil, depending on endpoint + type: string subscribe_key: description: can be nil, depending on endpoint type: string @@ -715,11 +711,36 @@ paths: description: The body-values are optional, only send the ones you want to update operationId: api-user-update parameters: - - description: ' ' + - description: UserID + in: path + name: uid + required: true + type: integer + - description: Change the username (send an empty string to clear it) in: body - name: post_body + name: username schema: - $ref: '#/definitions/handler.UpdateUser.body' + type: string + - description: Send a verification of permium purchase + in: body + name: pro_token + schema: + type: string + - description: Send `true` to create a new read_key + in: body + name: read_key + schema: + type: string + - description: Send `true` to create a new send_key + in: body + name: send_key + schema: + type: string + - description: Send `true` to create a new admin_key + in: body + name: admin_key + schema: + type: string responses: "200": description: OK @@ -790,7 +811,7 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/ginresp.apiError' - summary: List all channels of a user + summary: List channels of a user (subscribed/owned) /api-v2/users/{uid}/channels/{cid}: get: operationId: api-channels-get @@ -827,6 +848,51 @@ paths: schema: $ref: '#/definitions/ginresp.apiError' summary: List all channels of a user + patch: + operationId: api-channels-update + parameters: + - description: UserID + in: path + name: uid + required: true + type: integer + - description: ChannelID + in: path + name: cid + required: true + type: integer + - description: Send `true` to create a new subscribe_key + in: body + name: subscribe_key + schema: + type: string + - description: Send `true` to create a new send_key + in: body + name: send_key + schema: + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.ChannelJSON' + "400": + description: Bad Request + schema: + $ref: '#/definitions/ginresp.apiError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/ginresp.apiError' + "404": + description: Not Found + schema: + $ref: '#/definitions/ginresp.apiError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/ginresp.apiError' + summary: (Partially) update a channel /api-v2/users/{uid}/channels/{cid}/messages: get: description: |-