Added PUT /account/{id}/fields

This commit is contained in:
Lilleman auf Larv 2021-06-24 01:55:47 +02:00
parent 227132413a
commit 880a384f35
8 changed files with 376 additions and 8 deletions

View File

@ -34,12 +34,12 @@ func (d Db) AccountCreate(input AccountCreateInput) (CreatedAccount, error) {
_, err := d.DbPool.Exec(context.Background(), accountFieldsSQL, newFieldID, input.ID, field.Name, field.Values) _, err := d.DbPool.Exec(context.Background(), accountFieldsSQL, newFieldID, input.ID, field.Name, field.Values)
if err != nil { if err != nil {
if strings.HasPrefix(err.Error(), "ERROR: duplicate key") { //if strings.HasPrefix(err.Error(), "ERROR: duplicate key") {
d.Log.Error("Database error when trying to account field", "err", err.Error()) d.Log.Error("Database error when trying to add account field", "err", err.Error(), "accountID", input.ID, "fieldName", field.Name, "fieldvalues", field.Values)
} // }
} }
d.Log.Debug("Added account field", "accountId", input.ID, "fieldName", field.Name, "fieldValues", field.Values) d.Log.Debug("Added account field", "accountID", input.ID, "fieldName", field.Name, "fieldValues", field.Values)
} }
return CreatedAccount{ return CreatedAccount{
@ -129,3 +129,51 @@ func (d Db) AccountGet(accountID string, APIKey string, Name string) (Account, e
return account, nil return account, nil
} }
func (d Db) AccountUpdateFields(accountID string, fields []AccountCreateInputFields) (Account, error) {
// Begin database transaction
conn, err := d.DbPool.Acquire(context.Background())
if err != nil {
d.Log.Error("Could not acquire database connection", "err", err.Error(), "accountID", accountID)
return Account{}, err
}
tx, err := conn.Begin(context.Background())
if err != nil {
d.Log.Error("Could not begin database transaction", "err", err.Error(), "accountID", accountID)
return Account{}, err
}
// Rollback is safe to call even if the tx is already closed, so if
// the tx commits successfully, this is a no-op
defer tx.Rollback(context.Background())
_, err = tx.Exec(context.Background(), "DELETE FROM \"accountsFields\" WHERE \"accountId\" = $1;", accountID)
if err != nil {
d.Log.Error("Could not delete previous fields", "err", err.Error(), "accountID", accountID)
return Account{}, err
}
accountFieldsSQL := "INSERT INTO \"accountsFields\" (id, \"accountId\", name, value) VALUES($1,$2,$3,$4);"
for _, field := range fields {
newFieldID, err := uuid.NewRandom()
if err != nil {
d.Log.Fatal("Could not create new Uuid", "err", err.Error())
}
_, err = tx.Exec(context.Background(), accountFieldsSQL, newFieldID, accountID, field.Name, field.Values)
if err != nil {
d.Log.Error("Database error when trying to add account field", "err", err.Error(), "accountID", accountID, "fieldName", field.Name, "fieldvalues", field.Values)
}
d.Log.Debug("Added account field", "accountID", accountID, "fieldName", field.Name, "fieldValues", field.Values)
}
err = tx.Commit(context.Background())
if err != nil {
d.Log.Error("Database error when tying to commit", "err", err.Error())
return Account{}, err
}
return d.AccountGet(accountID, "", "")
}

View File

@ -119,7 +119,7 @@ var doc = `{
}, },
"/account/:id": { "/account/:id": {
"delete": { "delete": {
"description": "Requires Authorization-header with role \"admin\".\nExample: Authorization: bearer xxx\nWhere \"xxx\" is a valid JWT token", "description": "Requires Authorization-header with role \"admin\" or a matching account id\nExample: Authorization: bearer xxx\nWhere \"xxx\" is a valid JWT token",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -171,6 +171,15 @@ var doc = `{
} }
} }
}, },
"404": {
"description": "Not Found",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ResJSONError"
}
}
},
"415": { "415": {
"description": "Unsupported Media Type", "description": "Unsupported Media Type",
"schema": { "schema": {
@ -258,6 +267,86 @@ var doc = `{
} }
} }
}, },
"/account/{id}/fields": {
"put": {
"description": "Requires Authorization-header with role \"admin\".\nExample: Authorization: bearer xxx\nWhere \"xxx\" is a valid JWT token",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Update account fields",
"operationId": "account-update-fields",
"parameters": [
{
"description": "Fields array with objects to be written to database",
"name": "body",
"in": "body",
"required": true,
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/db.AccountCreateInputFields"
}
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/db.Account"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ResJSONError"
}
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ResJSONError"
}
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ResJSONError"
}
}
},
"415": {
"description": "Unsupported Media Type",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ResJSONError"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ResJSONError"
}
}
}
}
}
},
"/auth/api-key": { "/auth/api-key": {
"post": { "post": {
"description": "Authenticate account by API Key", "description": "Authenticate account by API Key",

View File

@ -103,7 +103,7 @@
}, },
"/account/:id": { "/account/:id": {
"delete": { "delete": {
"description": "Requires Authorization-header with role \"admin\".\nExample: Authorization: bearer xxx\nWhere \"xxx\" is a valid JWT token", "description": "Requires Authorization-header with role \"admin\" or a matching account id\nExample: Authorization: bearer xxx\nWhere \"xxx\" is a valid JWT token",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@ -155,6 +155,15 @@
} }
} }
}, },
"404": {
"description": "Not Found",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ResJSONError"
}
}
},
"415": { "415": {
"description": "Unsupported Media Type", "description": "Unsupported Media Type",
"schema": { "schema": {
@ -242,6 +251,86 @@
} }
} }
}, },
"/account/{id}/fields": {
"put": {
"description": "Requires Authorization-header with role \"admin\".\nExample: Authorization: bearer xxx\nWhere \"xxx\" is a valid JWT token",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"summary": "Update account fields",
"operationId": "account-update-fields",
"parameters": [
{
"description": "Fields array with objects to be written to database",
"name": "body",
"in": "body",
"required": true,
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/db.AccountCreateInputFields"
}
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/db.Account"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ResJSONError"
}
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ResJSONError"
}
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ResJSONError"
}
}
},
"415": {
"description": "Unsupported Media Type",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ResJSONError"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/handlers.ResJSONError"
}
}
}
}
}
},
"/auth/api-key": { "/auth/api-key": {
"post": { "post": {
"description": "Authenticate account by API Key", "description": "Authenticate account by API Key",

View File

@ -142,7 +142,7 @@ paths:
consumes: consumes:
- application/json - application/json
description: |- description: |-
Requires Authorization-header with role "admin". Requires Authorization-header with role "admin" or a matching account id
Example: Authorization: bearer xxx Example: Authorization: bearer xxx
Where "xxx" is a valid JWT token Where "xxx" is a valid JWT token
operationId: account-del operationId: account-del
@ -177,6 +177,12 @@ paths:
items: items:
$ref: '#/definitions/handlers.ResJSONError' $ref: '#/definitions/handlers.ResJSONError'
type: array type: array
"404":
description: Not Found
schema:
items:
$ref: '#/definitions/handlers.ResJSONError'
type: array
"415": "415":
description: Unsupported Media Type description: Unsupported Media Type
schema: schema:
@ -237,6 +243,62 @@ paths:
$ref: '#/definitions/handlers.ResJSONError' $ref: '#/definitions/handlers.ResJSONError'
type: array type: array
summary: Get account by id summary: Get account by id
/account/{id}/fields:
put:
consumes:
- application/json
description: |-
Requires Authorization-header with role "admin".
Example: Authorization: bearer xxx
Where "xxx" is a valid JWT token
operationId: account-update-fields
parameters:
- description: Fields array with objects to be written to database
in: body
name: body
required: true
schema:
items:
$ref: '#/definitions/db.AccountCreateInputFields'
type: array
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/db.Account'
"400":
description: Bad Request
schema:
items:
$ref: '#/definitions/handlers.ResJSONError'
type: array
"401":
description: Unauthorized
schema:
items:
$ref: '#/definitions/handlers.ResJSONError'
type: array
"403":
description: Forbidden
schema:
items:
$ref: '#/definitions/handlers.ResJSONError'
type: array
"415":
description: Unsupported Media Type
schema:
items:
$ref: '#/definitions/handlers.ResJSONError'
type: array
"500":
description: Internal Server Error
schema:
items:
$ref: '#/definitions/handlers.ResJSONError'
type: array
summary: Update account fields
/auth/api-key: /auth/api-key:
post: post:
consumes: consumes:

View File

@ -7,7 +7,7 @@ import (
// AccountDel godoc // AccountDel godoc
// @Summary Delete an account // @Summary Delete an account
// @Description Requires Authorization-header with role "admin". // @Description Requires Authorization-header with role "admin" or a matching account id
// @Description Example: Authorization: bearer xxx // @Description Example: Authorization: bearer xxx
// @Description Where "xxx" is a valid JWT token // @Description Where "xxx" is a valid JWT token
// @ID account-del // @ID account-del

52
src/handlers/put.go Normal file
View File

@ -0,0 +1,52 @@
package handlers
import (
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"gitlab.larvit.se/power-plan/auth/src/db"
)
// AccountUpdateFields godoc
// @Summary Update account fields
// @Description Requires Authorization-header with role "admin".
// @Description Example: Authorization: bearer xxx
// @Description Where "xxx" is a valid JWT token
// @ID account-update-fields
// @Accept json
// @Produce json
// @Param body body []db.AccountCreateInputFields true "Fields array with objects to be written to database"
// @Success 200 {object} db.Account
// @Failure 400 {object} []ResJSONError
// @Failure 401 {object} []ResJSONError
// @Failure 403 {object} []ResJSONError
// @Failure 415 {object} []ResJSONError
// @Failure 500 {object} []ResJSONError
// @Router /account/{id}/fields [put]
func (h Handlers) AccountUpdateFields(c *fiber.Ctx) error {
accountID := c.Params("accountID")
_, uuidErr := uuid.Parse(accountID)
if uuidErr != nil {
return c.Status(400).JSON([]ResJSONError{{Error: "Invalid uuid format"}})
}
authErr := h.RequireAdminRole(c)
if authErr != nil {
return c.Status(403).JSON([]ResJSONError{{Error: authErr.Error()}})
}
fieldsInput := new([]db.AccountCreateInputFields)
if err := c.BodyParser(fieldsInput); err != nil {
return c.Status(400).JSON([]ResJSONError{
{Error: err.Error()},
})
}
updatedAccount, err := h.Db.AccountUpdateFields(accountID, *fieldsInput)
if err != nil {
return c.Status(500).JSON([]ResJSONError{{Error: "Internal server error"}})
}
return c.Status(200).JSON(updatedAccount)
}

View File

@ -97,6 +97,8 @@ func main() {
app.Post("/auth/api-key", handlers.AccountAuthAPIKey) app.Post("/auth/api-key", handlers.AccountAuthAPIKey)
app.Post("/auth/password", handlers.AccountAuthPassword) app.Post("/auth/password", handlers.AccountAuthPassword)
app.Post("/renew-token", handlers.RenewToken) app.Post("/renew-token", handlers.RenewToken)
app.Put("/account/:accountID/fields", handlers.AccountUpdateFields)
// app.Put("")
log.Info("Trying to start web server", "WEB_BIND_HOST", os.Getenv("WEB_BIND_HOST")) log.Info("Trying to start web server", "WEB_BIND_HOST", os.Getenv("WEB_BIND_HOST"))

View File

@ -110,6 +110,32 @@ test('test-cases/01basic.js: Auth by username and password', async t => {
t.equal(userJWT.accountName, userName, 'The verified account name should match the created user'); t.equal(userJWT.accountName, userName, 'The verified account name should match the created user');
}); });
test('test-cases/01basic.js: PUT /account/{id}/fields', async t => {
const res = await got.put(`${process.env.AUTH_URL}/account/${user.id}/fields`, {
headers: { 'Authorization': `bearer ${adminJWTString}`},
json: [
{
name: 'foo',
values: ['bar'],
},
{
name: 'role',
values: ['tomte'],
}
],
responseType: 'json',
});
t.equal(user.id, res.body.id, 'The responded account id should be the same as the old one');
t.equal(Object.keys(res.body.fields).length, 2, 'There should only be two fields in total');
t.equal(JSON.stringify(res.body.fields.foo), '["bar"]', 'The foo field should have values ["bar"]');
t.equal(JSON.stringify(res.body.fields.role), '["tomte"]', 'The role field should have values ["tomte"]');
// Overload the previous user
user.fields = res.body.fields;
user.name = res.body.name;
});
test('test-cases/01basic.js: Remove an account', async t => { test('test-cases/01basic.js: Remove an account', async t => {
try { try {
// Random uuid that should not exist in the db. The chance of this existing is... small // Random uuid that should not exist in the db. The chance of this existing is... small