2020-04-19 05:01:09 +05:30
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
2024-02-13 06:38:42 +05:30
|
|
|
"context"
|
2020-04-19 05:01:09 +05:30
|
|
|
"errors"
|
2024-02-07 18:54:41 +05:30
|
|
|
"fmt"
|
2020-04-19 05:01:09 +05:30
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"github.com/gorilla/mux"
|
2024-06-03 08:04:35 +05:30
|
|
|
"github.com/huandu/xstrings"
|
2024-03-13 05:59:26 +05:30
|
|
|
"go.opentelemetry.io/otel/metric"
|
|
|
|
"go.uber.org/multierr"
|
2020-04-19 05:01:09 +05:30
|
|
|
|
2024-02-01 12:42:34 +05:30
|
|
|
"ely.by/chrly/internal/db"
|
2024-03-13 05:59:26 +05:30
|
|
|
"ely.by/chrly/internal/otel"
|
2024-02-01 12:42:34 +05:30
|
|
|
"ely.by/chrly/internal/profiles"
|
2020-04-19 05:01:09 +05:30
|
|
|
)
|
|
|
|
|
2024-01-30 13:35:04 +05:30
|
|
|
type ProfilesManager interface {
|
2024-02-13 06:38:42 +05:30
|
|
|
PersistProfile(ctx context.Context, profile *db.Profile) error
|
|
|
|
RemoveProfileByUuid(ctx context.Context, uuid string) error
|
2020-04-19 05:01:09 +05:30
|
|
|
}
|
|
|
|
|
2024-03-13 05:59:26 +05:30
|
|
|
func NewProfilesApi(profilesManager ProfilesManager) (*ProfilesApi, error) {
|
|
|
|
metrics, err := newProfilesApiMetrics(otel.GetMeter())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &ProfilesApi{
|
|
|
|
ProfilesManager: profilesManager,
|
|
|
|
metrics: metrics,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2024-03-05 17:37:54 +05:30
|
|
|
type ProfilesApi struct {
|
2024-01-30 13:35:04 +05:30
|
|
|
ProfilesManager
|
2024-03-13 05:59:26 +05:30
|
|
|
|
|
|
|
metrics *profilesApiMetrics
|
2020-04-19 05:01:09 +05:30
|
|
|
}
|
|
|
|
|
2024-03-13 05:59:26 +05:30
|
|
|
func (p *ProfilesApi) Handler() *mux.Router {
|
2020-04-19 05:01:09 +05:30
|
|
|
router := mux.NewRouter().StrictSlash(true)
|
2024-03-13 05:59:26 +05:30
|
|
|
router.HandleFunc("/", p.postProfileHandler).Methods(http.MethodPost)
|
|
|
|
router.HandleFunc("/{uuid}", p.deleteProfileByUuidHandler).Methods(http.MethodDelete)
|
2020-04-19 05:01:09 +05:30
|
|
|
|
|
|
|
return router
|
|
|
|
}
|
|
|
|
|
2024-03-13 05:59:26 +05:30
|
|
|
func (p *ProfilesApi) postProfileHandler(resp http.ResponseWriter, req *http.Request) {
|
|
|
|
p.metrics.UploadProfileRequest.Add(req.Context(), 1)
|
|
|
|
|
2024-01-30 13:35:04 +05:30
|
|
|
err := req.ParseForm()
|
|
|
|
if err != nil {
|
|
|
|
apiBadRequest(resp, map[string][]string{
|
|
|
|
"body": {"The body of the request must be a valid url-encoded string"},
|
|
|
|
})
|
2020-04-19 05:01:09 +05:30
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-01-30 13:35:04 +05:30
|
|
|
profile := &db.Profile{
|
|
|
|
Uuid: req.Form.Get("uuid"),
|
|
|
|
Username: req.Form.Get("username"),
|
|
|
|
SkinUrl: req.Form.Get("skinUrl"),
|
|
|
|
SkinModel: req.Form.Get("skinModel"),
|
|
|
|
CapeUrl: req.Form.Get("capeUrl"),
|
|
|
|
MojangTextures: req.Form.Get("mojangTextures"),
|
|
|
|
MojangSignature: req.Form.Get("mojangSignature"),
|
2020-04-19 05:01:09 +05:30
|
|
|
}
|
|
|
|
|
2024-03-13 05:59:26 +05:30
|
|
|
err = p.PersistProfile(req.Context(), profile)
|
2024-01-30 13:35:04 +05:30
|
|
|
if err != nil {
|
|
|
|
var v *profiles.ValidationError
|
|
|
|
if errors.As(err, &v) {
|
2024-06-03 08:04:35 +05:30
|
|
|
// Manager returns ValidationError according to the struct fields names.
|
|
|
|
// They are uppercased, but otherwise the same as the names in the API.
|
|
|
|
// So to make them consistent it's enough just to make the first lowercased.
|
2024-06-11 00:22:45 +05:30
|
|
|
newErrors := make(map[string][]string, len(v.Errors))
|
2024-06-03 08:04:35 +05:30
|
|
|
for field, errors := range v.Errors {
|
2024-06-11 00:22:45 +05:30
|
|
|
newErrors[xstrings.FirstRuneToLower(field)] = errors
|
2024-06-03 08:04:35 +05:30
|
|
|
}
|
|
|
|
|
2024-06-11 00:22:45 +05:30
|
|
|
apiBadRequest(resp, newErrors)
|
2024-06-03 08:04:35 +05:30
|
|
|
|
2024-01-30 13:35:04 +05:30
|
|
|
return
|
2020-04-20 17:46:15 +05:30
|
|
|
}
|
2020-04-19 05:01:09 +05:30
|
|
|
|
2024-03-13 05:59:26 +05:30
|
|
|
apiServerError(resp, req, fmt.Errorf("unable to save profile to db: %w", err))
|
2024-01-30 13:35:04 +05:30
|
|
|
return
|
2020-04-19 05:01:09 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
resp.WriteHeader(http.StatusCreated)
|
|
|
|
}
|
|
|
|
|
2024-03-13 05:59:26 +05:30
|
|
|
func (p *ProfilesApi) deleteProfileByUuidHandler(resp http.ResponseWriter, req *http.Request) {
|
|
|
|
p.metrics.DeleteProfileRequest.Add(req.Context(), 1)
|
|
|
|
|
2024-01-30 13:35:04 +05:30
|
|
|
uuid := mux.Vars(req)["uuid"]
|
2024-03-13 05:59:26 +05:30
|
|
|
err := p.ProfilesManager.RemoveProfileByUuid(req.Context(), uuid)
|
2020-04-19 05:01:09 +05:30
|
|
|
if err != nil {
|
2024-03-13 05:59:26 +05:30
|
|
|
apiServerError(resp, req, fmt.Errorf("unable to delete profile from db: %w", err))
|
2020-04-19 05:01:09 +05:30
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
resp.WriteHeader(http.StatusNoContent)
|
|
|
|
}
|
2024-03-13 05:59:26 +05:30
|
|
|
|
|
|
|
func newProfilesApiMetrics(meter metric.Meter) (*profilesApiMetrics, error) {
|
|
|
|
m := &profilesApiMetrics{}
|
|
|
|
var errors, err error
|
|
|
|
|
|
|
|
m.UploadProfileRequest, err = meter.Int64Counter("chrly.app.profiles.upload.request", metric.WithUnit("{request}"))
|
|
|
|
errors = multierr.Append(errors, err)
|
|
|
|
|
|
|
|
m.DeleteProfileRequest, err = meter.Int64Counter("chrly.app.profiles.delete.request", metric.WithUnit("{request}"))
|
|
|
|
errors = multierr.Append(errors, err)
|
|
|
|
|
|
|
|
return m, errors
|
|
|
|
}
|
|
|
|
|
|
|
|
type profilesApiMetrics struct {
|
|
|
|
UploadProfileRequest metric.Int64Counter
|
|
|
|
DeleteProfileRequest metric.Int64Counter
|
|
|
|
}
|