/
post_handler.go
158 lines (133 loc) · 4.7 KB
/
post_handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package kycstatus
import (
"context"
"fmt"
"net/http"
"regexp"
"strings"
"github.com/TosinShada/monorepo/services/regulated-assets-approval-server/internal/serve/httperror"
"github.com/TosinShada/monorepo/support/errors"
"github.com/TosinShada/monorepo/support/http/httpdecode"
"github.com/TosinShada/monorepo/support/log"
"github.com/TosinShada/monorepo/support/render/httpjson"
"github.com/jmoiron/sqlx"
)
type kycPostRequest struct {
CallbackID string `path:"callback_id"`
EmailAddress string `json:"email_address"`
}
type kycPostResponse struct {
Result string `json:"result"`
StatusCode int `json:"-"`
}
func (k *kycPostResponse) Render(w http.ResponseWriter) {
httpjson.RenderStatus(w, k.StatusCode, k, httpjson.JSON)
}
func NewKYCStatusPostResponse() *kycPostResponse {
return &kycPostResponse{
Result: "no_further_action_required",
StatusCode: http.StatusOK,
}
}
type PostHandler struct {
DB *sqlx.DB
}
func (h PostHandler) validate() error {
if h.DB == nil {
return errors.New("database cannot be nil")
}
return nil
}
func (h PostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
err := h.validate()
if err != nil {
log.Ctx(ctx).Error(errors.Wrap(err, "validating kyc-status PostHandler"))
httperror.InternalServer.Render(w)
return
}
in := kycPostRequest{}
err = httpdecode.Decode(r, &in)
if err != nil {
log.Ctx(ctx).Error(errors.Wrap(err, "decoding kyc-status POST Request"))
httperror.BadRequest.Render(w)
return
}
kycResponse, err := h.handle(ctx, in)
if err != nil {
log.Ctx(ctx).Error(errors.Wrap(err, "validating the input POST request for kyc-status"))
httpErr, ok := err.(*httperror.Error)
if !ok {
httpErr = httperror.InternalServer
}
httpErr.Render(w)
return
}
kycResponse.Render(w)
}
func (h PostHandler) handle(ctx context.Context, in kycPostRequest) (*kycPostResponse, error) {
// Check if kycPostRequest values are present or not malformed.
if in.CallbackID == "" {
return nil, httperror.NewHTTPError(http.StatusBadRequest, "Missing callbackID.")
}
if in.EmailAddress == "" {
return nil, httperror.NewHTTPError(http.StatusBadRequest, "Missing email_address.")
}
if !RxEmail.MatchString(in.EmailAddress) {
return nil, httperror.NewHTTPError(http.StatusBadRequest, "The provided email_address is invalid.")
}
var exists bool
query, args := in.buildUpdateKYCQuery()
err := h.DB.QueryRowContext(ctx, query, args...).Scan(&exists)
if err != nil {
return nil, errors.Wrap(err, "querying the database")
}
if !exists {
return nil, httperror.NewHTTPError(http.StatusNotFound, "Not found.")
}
return NewKYCStatusPostResponse(), nil
}
// isKYCRuleRespected validates if KYC data is rejected. As an arbitrary rule,
// emails starting with "x" are rejected.
func (in kycPostRequest) isKYCRejected() bool {
return strings.HasPrefix(strings.ToLower(in.EmailAddress), "x")
}
// isKYCRuleRespected validates if KYC data is pending. As an arbitrary rule,
// emails starting with "y" are marked as pending.
func (in kycPostRequest) isKYCPending() bool {
return strings.HasPrefix(strings.ToLower(in.EmailAddress), "y")
}
// buildUpdateKYCQuery builds a query that will approve or reject stellar account from accounts_kyc_status table.
// Afterwards the query should return an exists boolean if present.
func (in kycPostRequest) buildUpdateKYCQuery() (string, []interface{}) {
var (
query strings.Builder
args []interface{}
)
query.WriteString("WITH updated_row AS (")
query.WriteString("UPDATE accounts_kyc_status ")
query.WriteString("SET kyc_submitted_at = NOW(), ")
args = append(args, in.EmailAddress)
query.WriteString(fmt.Sprintf("email_address = $%d, ", len(args)))
// update KYC status to rejected, pending or approved
if in.isKYCRejected() {
query.WriteString("rejected_at = NOW(), pending_at = NULL, approved_at = NULL ")
} else if in.isKYCPending() {
query.WriteString("rejected_at = NULL, pending_at = NOW(), approved_at = NULL ")
} else {
query.WriteString("rejected_at = NULL, pending_at = NULL, approved_at = NOW() ")
}
args = append(args, in.CallbackID)
query.WriteString(fmt.Sprintf("WHERE callback_id = $%d ", len(args)))
query.WriteString("RETURNING * ")
query.WriteString(")")
query.WriteString(`
SELECT EXISTS(
SELECT * FROM updated_row
)
`)
return query.String(), args
}
// RxEmail is a regex used to validate e-mail addresses, according with the reference https://www.alexedwards.net/blog/validation-snippets-for-go#email-validation.
// It's free to use under the [MIT Licence](https://opensource.org/licenses/MIT)
var RxEmail = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")