Extract PrivacyAccepted fields into separate calls (#41)

This commit is contained in:
itsscb 2023-09-27 20:49:05 +02:00 committed by GitHub
parent 34889a5fcf
commit d1cdce72ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 398 additions and 96 deletions

View File

@ -120,20 +120,41 @@ func (server *Server) listAccounts(ctx *gin.Context) {
ctx.JSON(http.StatusOK, accounts)
}
type updateAccountPrivacyRequest struct {
ID int64 `binding:"required" json:"ID"`
Changer string `binding:"required" json:"changer"`
PrivacyAccepted bool `json:"privacy_accepted"`
}
func (server *Server) updateAccountPrivacy(ctx *gin.Context) {
var req updateAccountPrivacyRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err))
return
}
account, err := server.store.UpdateAccountPrivacyTx(ctx, db.UpdateAccountPrivacyTxParams(req))
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
return
}
ctx.JSON(http.StatusOK, account)
}
type updateAccountRequest struct {
ID int64 `binding:"required" json:"ID"`
Changer string `binding:"required" json:"changer"`
PrivacyAccepted bool `json:"privacy_accepted"`
Passwordhash string `json:"passwordhash"`
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
Birthday time.Time `json:"birthday"`
Email string `json:"email"`
Phone string `json:"phone"`
City string `json:"city"`
Zip string `json:"zip"`
Street string `json:"street"`
Country string `json:"country"`
ID int64 `binding:"required" json:"ID"`
Changer string `binding:"required" json:"changer"`
Passwordhash string `json:"passwordhash"`
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
Birthday time.Time `json:"birthday"`
Email string `json:"email"`
Phone string `json:"phone"`
City string `json:"city"`
Zip string `json:"zip"`
Street string `json:"street"`
Country string `json:"country"`
}
func (server *Server) updateAccount(ctx *gin.Context) {
@ -143,12 +164,6 @@ func (server *Server) updateAccount(ctx *gin.Context) {
return
}
account, err := server.store.GetAccount(ctx, req.ID)
if err != nil {
ctx.JSON(http.StatusNotFound, errorResponse(err))
return
}
arg := db.UpdateAccountTxParams{
ID: req.ID,
Changer: req.Changer,
@ -192,13 +207,9 @@ func (server *Server) updateAccount(ctx *gin.Context) {
String: req.Phone,
Valid: req.Phone != "",
},
PrivacyAccepted: sql.NullBool{
Valid: account.PrivacyAccepted.Bool != req.PrivacyAccepted,
Bool: req.PrivacyAccepted,
},
}
account, err = server.store.UpdateAccountTx(ctx, arg)
account, err := server.store.UpdateAccountTx(ctx, arg)
if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err))
return

View File

@ -303,10 +303,6 @@ func TestUpdateAccountTxAPI(t *testing.T) {
Changer: changer,
}
store.EXPECT().
GetAccount(gomock.Any(), gomock.Eq(account.ID)).
Times(1)
store.EXPECT().
UpdateAccountTx(gomock.Any(), gomock.Eq(arg)).
Times(1).
@ -339,10 +335,6 @@ func TestUpdateAccountTxAPI(t *testing.T) {
Changer: changer,
}
store.EXPECT().
GetAccount(gomock.Any(), gomock.Eq(account.ID)).
Times(1)
store.EXPECT().
UpdateAccountTx(gomock.Any(), gomock.Eq(arg)).
Times(1).
@ -573,6 +565,139 @@ func TestListAccountsAPI(t *testing.T) {
}
}
func TestUpdateAccountPrivacyTxAPI(t *testing.T) {
account := randomAccount()
changer := util.RandomName()
testCases := []struct {
name string
body gin.H
buildStubs func(store *mockdb.MockStore)
checkResponse func(recoder *httptest.ResponseRecorder)
}{
{
name: "OK",
body: gin.H{
"id": account.ID,
"changer": changer,
"privacy_accepted": true,
},
buildStubs: func(store *mockdb.MockStore) {
arg := db.UpdateAccountPrivacyTxParams{
ID: account.ID,
PrivacyAccepted: true,
Changer: changer,
}
account2 := account
account2.PrivacyAccepted.Valid = true
account2.PrivacyAccepted.Bool = true
account2.Changer = changer
store.EXPECT().
UpdateAccountPrivacyTx(gomock.Any(), gomock.Eq(arg)).
Times(1).
Return(account2, nil)
},
checkResponse: func(recoder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusOK, recoder.Code)
data, err := io.ReadAll(recoder.Body)
require.NoError(t, err)
var getAccount db.Account
err = json.Unmarshal(data, &getAccount)
require.NoError(t, err)
require.Equal(t, account.ID, getAccount.ID)
require.Equal(t, true, getAccount.PrivacyAccepted.Bool)
require.Equal(t, true, getAccount.PrivacyAccepted.Valid)
require.WithinDuration(t, timestamp, getAccount.PrivacyAcceptedDate.Time, time.Second)
},
},
{
name: "Revoked",
body: gin.H{
"id": account.ID,
"changer": changer,
"privacy_accepted": false,
},
buildStubs: func(store *mockdb.MockStore) {
arg := db.UpdateAccountPrivacyTxParams{
ID: account.ID,
PrivacyAccepted: false,
Changer: changer,
}
account2 := account
account2.PrivacyAccepted.Valid = true
account2.PrivacyAccepted.Bool = false
account2.PrivacyAcceptedDate.Valid = true
account2.PrivacyAcceptedDate.Time = time.Time{}
account2.Changer = changer
store.EXPECT().
UpdateAccountPrivacyTx(gomock.Any(), gomock.Eq(arg)).
Times(1).
Return(account2, nil)
},
checkResponse: func(recoder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusOK, recoder.Code)
data, err := io.ReadAll(recoder.Body)
require.NoError(t, err)
var getAccount db.Account
err = json.Unmarshal(data, &getAccount)
require.NoError(t, err)
require.Equal(t, account.ID, getAccount.ID)
require.Equal(t, false, getAccount.PrivacyAccepted.Bool)
require.Equal(t, true, getAccount.PrivacyAccepted.Valid)
require.Equal(t, time.Time{}, getAccount.PrivacyAcceptedDate.Time)
},
}, {
name: "OK",
body: gin.H{
"id": account.ID,
},
buildStubs: func(store *mockdb.MockStore) {
store.EXPECT().
UpdateAccountPrivacyTx(gomock.Any(), gomock.Any()).
Times(0)
},
checkResponse: func(recoder *httptest.ResponseRecorder) {
require.Equal(t, http.StatusBadRequest, recoder.Code)
},
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
store := mockdb.NewMockStore(ctrl)
tc.buildStubs(store)
server := NewServer(config, store)
recorder := httptest.NewRecorder()
// Marshal body data to JSON
data, err := json.Marshal(tc.body)
require.NoError(t, err)
url := "/accounts/privacy"
request, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
require.NoError(t, err)
server.router.ServeHTTP(recorder, request)
tc.checkResponse(recorder)
})
}
}
func randomAccount() db.Account {
acc := db.Account{
ID: util.RandomInt(1, 1000),

View File

@ -47,6 +47,7 @@ func NewServer(config util.Config, store db.Store) *Server {
router.POST("/accounts", server.createAccount)
router.PUT("/accounts", server.updateAccount)
router.PUT("/accounts/privacy", server.updateAccountPrivacy)
router.GET("/accounts/:id", server.getAccount)
router.GET("/accounts", server.listAccounts)

View File

@ -586,6 +586,36 @@ func (mr *MockStoreMockRecorder) UpdateAccount(arg0, arg1 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAccount", reflect.TypeOf((*MockStore)(nil).UpdateAccount), arg0, arg1)
}
// UpdateAccountPrivacy mocks base method.
func (m *MockStore) UpdateAccountPrivacy(arg0 context.Context, arg1 db.UpdateAccountPrivacyParams) (db.Account, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateAccountPrivacy", arg0, arg1)
ret0, _ := ret[0].(db.Account)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateAccountPrivacy indicates an expected call of UpdateAccountPrivacy.
func (mr *MockStoreMockRecorder) UpdateAccountPrivacy(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAccountPrivacy", reflect.TypeOf((*MockStore)(nil).UpdateAccountPrivacy), arg0, arg1)
}
// UpdateAccountPrivacyTx mocks base method.
func (m *MockStore) UpdateAccountPrivacyTx(arg0 context.Context, arg1 db.UpdateAccountPrivacyTxParams) (db.Account, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateAccountPrivacyTx", arg0, arg1)
ret0, _ := ret[0].(db.Account)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateAccountPrivacyTx indicates an expected call of UpdateAccountPrivacyTx.
func (mr *MockStoreMockRecorder) UpdateAccountPrivacyTx(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAccountPrivacyTx", reflect.TypeOf((*MockStore)(nil).UpdateAccountPrivacyTx), arg0, arg1)
}
// UpdateAccountTx mocks base method.
func (m *MockStore) UpdateAccountTx(arg0 context.Context, arg1 db.UpdateAccountTxParams) (db.Account, error) {
m.ctrl.T.Helper()

View File

@ -50,8 +50,6 @@ OFFSET $2;
UPDATE accounts
SET
"passwordhash" = COALESCE(sqlc.narg(passwordhash), "passwordhash"),
"privacy_accepted" = COALESCE(sqlc.narg(privacy_accepted), "privacy_accepted"),
"privacy_accepted_date" = COALESCE(sqlc.narg(privacy_accepted_date), "privacy_accepted_date"),
"firstname" = COALESCE(sqlc.narg(firstname), "firstname"),
"lastname" = COALESCE(sqlc.narg(lastname), "lastname"),
"birthday" = COALESCE(sqlc.narg(birthday), "birthday"),
@ -66,6 +64,16 @@ SET
WHERE "id" = $1
RETURNING *;
-- name: UpdateAccountPrivacy :one
UPDATE accounts
SET
"privacy_accepted" = sqlc.arg(privacy_accepted),
"privacy_accepted_date" = sqlc.arg(privacy_accepted_date),
"changer" = sqlc.arg(changer),
"changed" = now()
WHERE "id" = sqlc.arg(id)
RETURNING *;
-- name: DeleteAccount :exec
DELETE FROM accounts
WHERE "id" = $1;

View File

@ -240,17 +240,15 @@ const updateAccount = `-- name: UpdateAccount :one
UPDATE accounts
SET
"passwordhash" = COALESCE($3, "passwordhash"),
"privacy_accepted" = COALESCE($4, "privacy_accepted"),
"privacy_accepted_date" = COALESCE($5, "privacy_accepted_date"),
"firstname" = COALESCE($6, "firstname"),
"lastname" = COALESCE($7, "lastname"),
"birthday" = COALESCE($8, "birthday"),
"email" = COALESCE($9, "email"),
"phone" = COALESCE($10, "phone"),
"city" = COALESCE($11, "city"),
"zip" = COALESCE($12, "zip"),
"street" = COALESCE($13, "street"),
"country" = COALESCE($14, "country"),
"firstname" = COALESCE($4, "firstname"),
"lastname" = COALESCE($5, "lastname"),
"birthday" = COALESCE($6, "birthday"),
"email" = COALESCE($7, "email"),
"phone" = COALESCE($8, "phone"),
"city" = COALESCE($9, "city"),
"zip" = COALESCE($10, "zip"),
"street" = COALESCE($11, "street"),
"country" = COALESCE($12, "country"),
"changer" = $2,
"changed" = now()
WHERE "id" = $1
@ -258,20 +256,18 @@ RETURNING id, passwordhash, firstname, lastname, birthday, privacy_accepted, pri
`
type UpdateAccountParams struct {
ID int64 `json:"id"`
Changer string `json:"changer"`
Passwordhash sql.NullString `json:"passwordhash"`
PrivacyAccepted sql.NullBool `json:"privacy_accepted"`
PrivacyAcceptedDate sql.NullTime `json:"privacy_accepted_date"`
Firstname sql.NullString `json:"firstname"`
Lastname sql.NullString `json:"lastname"`
Birthday sql.NullTime `json:"birthday"`
Email sql.NullString `json:"email"`
Phone sql.NullString `json:"phone"`
City sql.NullString `json:"city"`
Zip sql.NullString `json:"zip"`
Street sql.NullString `json:"street"`
Country sql.NullString `json:"country"`
ID int64 `json:"id"`
Changer string `json:"changer"`
Passwordhash sql.NullString `json:"passwordhash"`
Firstname sql.NullString `json:"firstname"`
Lastname sql.NullString `json:"lastname"`
Birthday sql.NullTime `json:"birthday"`
Email sql.NullString `json:"email"`
Phone sql.NullString `json:"phone"`
City sql.NullString `json:"city"`
Zip sql.NullString `json:"zip"`
Street sql.NullString `json:"street"`
Country sql.NullString `json:"country"`
}
func (q *Queries) UpdateAccount(ctx context.Context, arg UpdateAccountParams) (Account, error) {
@ -279,8 +275,6 @@ func (q *Queries) UpdateAccount(ctx context.Context, arg UpdateAccountParams) (A
arg.ID,
arg.Changer,
arg.Passwordhash,
arg.PrivacyAccepted,
arg.PrivacyAcceptedDate,
arg.Firstname,
arg.Lastname,
arg.Birthday,
@ -316,3 +310,54 @@ func (q *Queries) UpdateAccount(ctx context.Context, arg UpdateAccountParams) (A
)
return i, err
}
const updateAccountPrivacy = `-- name: UpdateAccountPrivacy :one
UPDATE accounts
SET
"privacy_accepted" = $1,
"privacy_accepted_date" = $2,
"changer" = $3,
"changed" = now()
WHERE "id" = $4
RETURNING id, passwordhash, firstname, lastname, birthday, privacy_accepted, privacy_accepted_date, email, phone, city, zip, street, country, token, token_valid, token_expiration, creator, created, changer, changed
`
type UpdateAccountPrivacyParams struct {
PrivacyAccepted sql.NullBool `json:"privacy_accepted"`
PrivacyAcceptedDate sql.NullTime `json:"privacy_accepted_date"`
Changer string `json:"changer"`
ID int64 `json:"id"`
}
func (q *Queries) UpdateAccountPrivacy(ctx context.Context, arg UpdateAccountPrivacyParams) (Account, error) {
row := q.db.QueryRowContext(ctx, updateAccountPrivacy,
arg.PrivacyAccepted,
arg.PrivacyAcceptedDate,
arg.Changer,
arg.ID,
)
var i Account
err := row.Scan(
&i.ID,
&i.Passwordhash,
&i.Firstname,
&i.Lastname,
&i.Birthday,
&i.PrivacyAccepted,
&i.PrivacyAcceptedDate,
&i.Email,
&i.Phone,
&i.City,
&i.Zip,
&i.Street,
&i.Country,
&i.Token,
&i.TokenValid,
&i.TokenExpiration,
&i.Creator,
&i.Created,
&i.Changer,
&i.Changed,
)
return i, err
}

View File

@ -10,6 +10,8 @@ import (
"github.com/stretchr/testify/require"
)
var timestamp = time.Now()
func createRandomAccount(t *testing.T) Account {
creator := util.RandomName()
@ -28,6 +30,10 @@ func createRandomAccount(t *testing.T) Account {
Valid: true,
Bool: true,
},
PrivacyAcceptedDate: sql.NullTime{
Valid: true,
Time: timestamp,
},
City: util.RandomString(15),
Zip: util.RandomString(5),
Street: util.RandomString(20),
@ -119,6 +125,56 @@ func TestUpdateAccount(t *testing.T) {
require.NotEqual(t, account1.Changer, account2.Changer)
}
func TestUpdateAccountPrivacy(t *testing.T) {
account1 := createRandomAccount(t)
require.NotEmpty(t, account1)
changer1 := util.RandomName()
arg := UpdateAccountPrivacyParams{
ID: account1.ID,
PrivacyAccepted: sql.NullBool{
Valid: true,
Bool: false,
},
PrivacyAcceptedDate: sql.NullTime{
Valid: true,
Time: time.Time{},
},
Changer: changer1,
}
account2, err := testQueries.UpdateAccountPrivacy(context.Background(), arg)
require.NoError(t, err)
require.NotEmpty(t, account2)
require.Equal(t, account1.ID, account2.ID)
require.Equal(t, account1.Lastname, account2.Lastname)
require.WithinDuration(t, time.Time{}, account2.PrivacyAcceptedDate.Time, time.Second)
require.NotEqual(t, account1.PrivacyAccepted.Bool, account2.PrivacyAccepted.Bool)
require.NotEqual(t, account1.PrivacyAcceptedDate.Time, account2.PrivacyAcceptedDate.Time)
arg.PrivacyAccepted = sql.NullBool{
Valid: true,
Bool: true,
}
arg.PrivacyAcceptedDate = sql.NullTime{
Valid: true,
Time: timestamp.UTC(),
}
account1, err = testQueries.UpdateAccountPrivacy(context.Background(), arg)
require.NoError(t, err)
require.NotEmpty(t, account2)
require.Equal(t, account1.ID, account2.ID)
require.Equal(t, account1.Lastname, account2.Lastname)
require.WithinDuration(t, timestamp.UTC(), account1.PrivacyAcceptedDate.Time, time.Second)
require.NotEqual(t, account1.PrivacyAccepted.Bool, account2.PrivacyAccepted.Bool)
require.NotEqual(t, account1.PrivacyAcceptedDate.Time, account2.PrivacyAcceptedDate.Time)
}
func TestListAccounts(t *testing.T) {
for i := 0; i < 10; i++ {
createRandomAccount(t)

View File

@ -58,6 +58,7 @@ type Querier interface {
ListReturns(ctx context.Context, arg ListReturnsParams) ([]Return, error)
ListReturnsLogs(ctx context.Context, arg ListReturnsLogsParams) ([]ReturnsLog, error)
UpdateAccount(ctx context.Context, arg UpdateAccountParams) (Account, error)
UpdateAccountPrivacy(ctx context.Context, arg UpdateAccountPrivacyParams) (Account, error)
UpdateDocument(ctx context.Context, arg UpdateDocumentParams) (Document, error)
UpdatePayment(ctx context.Context, arg UpdatePaymentParams) (Payment, error)
UpdatePerson(ctx context.Context, arg UpdatePersonParams) (Person, error)

View File

@ -10,6 +10,7 @@ type Store interface {
Querier
CreateAccountTx(ctx context.Context, arg CreateAccountTxParams) (Account, error)
UpdateAccountTx(ctx context.Context, arg UpdateAccountTxParams) (Account, error)
UpdateAccountPrivacyTx(ctx context.Context, arg UpdateAccountPrivacyTxParams) (Account, error)
}
// Store provides all functions to execute db queries and transactions

View File

@ -3,24 +3,21 @@ package db
import (
"context"
"database/sql"
"time"
)
type UpdateAccountTxParams struct {
ID int64 `json:"ID"`
Changer string `json:"changer"`
Passwordhash sql.NullString `json:"passwordhash"`
PrivacyAccepted sql.NullBool `json:"privacy_accepted"`
PrivacyAcceptedDate sql.NullTime `json:"privacy_accepted_date"`
Firstname sql.NullString `json:"firstname"`
Lastname sql.NullString `json:"lastname"`
Birthday sql.NullTime `json:"birthday"`
Email sql.NullString `json:"email"`
Phone sql.NullString `json:"phone"`
City sql.NullString `json:"city"`
Zip sql.NullString `json:"zip"`
Street sql.NullString `json:"street"`
Country sql.NullString `json:"country"`
ID int64 `json:"ID"`
Changer string `json:"changer"`
Passwordhash sql.NullString `json:"passwordhash"`
Firstname sql.NullString `json:"firstname"`
Lastname sql.NullString `json:"lastname"`
Birthday sql.NullTime `json:"birthday"`
Email sql.NullString `json:"email"`
Phone sql.NullString `json:"phone"`
City sql.NullString `json:"city"`
Zip sql.NullString `json:"zip"`
Street sql.NullString `json:"street"`
Country sql.NullString `json:"country"`
}
type UpdateAccountTxResult struct {
@ -30,26 +27,7 @@ type UpdateAccountTxResult struct {
func (store *SQLStore) UpdateAccountTx(ctx context.Context, arg UpdateAccountTxParams) (Account, error) {
var result UpdateAccountTxResult
account, err := store.GetAccount(ctx, arg.ID)
if err != nil {
return Account{}, err
}
if arg.PrivacyAccepted.Bool && arg.PrivacyAccepted.Bool != account.PrivacyAccepted.Bool {
arg.PrivacyAcceptedDate = sql.NullTime{
Valid: true,
Time: time.Now(),
}
}
if !account.PrivacyAccepted.Bool && !arg.PrivacyAccepted.Bool {
arg.PrivacyAcceptedDate = sql.NullTime{
Valid: true,
Time: time.Time{},
}
}
err = store.execTx(ctx, func(q *Queries) error {
err := store.execTx(ctx, func(q *Queries) error {
var err error
result.Account, err = q.UpdateAccount(ctx, UpdateAccountParams(arg))
return err

View File

@ -0,0 +1,46 @@
package db
import (
"context"
"database/sql"
"time"
)
type UpdateAccountPrivacyTxParams struct {
ID int64 `json:"ID"`
Changer string `json:"changer"`
PrivacyAccepted bool `json:"privacy_accepted"`
}
type UpdateAccountPrivacyTxResult struct {
Account Account `json:"account"`
}
func (store *SQLStore) UpdateAccountPrivacyTx(ctx context.Context, arg UpdateAccountPrivacyTxParams) (Account, error) {
var result UpdateAccountPrivacyTxResult
param := UpdateAccountPrivacyParams{
ID: arg.ID,
Changer: arg.Changer,
}
if arg.PrivacyAccepted {
param.PrivacyAcceptedDate = sql.NullTime{
Valid: true,
Time: time.Now(),
}
} else {
param.PrivacyAcceptedDate = sql.NullTime{
Valid: true,
Time: time.Time{},
}
}
err := store.execTx(ctx, func(q *Queries) error {
var err error
result.Account, err = q.UpdateAccountPrivacy(ctx, param)
return err
})
return result.Account, err
}