diff --git a/cmd/party/api/api.go b/cmd/party/api/api.go
index c64da75..2b66e67 100644
--- a/cmd/party/api/api.go
+++ b/cmd/party/api/api.go
@@ -27,14 +27,14 @@ func (api *Api) Authenticate(next http.Handler) http.Handler {
headerParts := strings.Split(authorizationHeader, " ")
if len(headerParts) != 2 || headerParts[0] != "Bearer" {
- api.InvalidAuthenticationTokenResponse(w, r)
+ api.errorResponse(w, r, data.ErrInvalidAuthToken)
return
}
token := headerParts[1]
v := validator.New()
if data.ValidateTokenPlaintext(v, token); !v.Valid() {
- api.InvalidAuthenticationTokenResponse(w, r)
+ api.errorResponse(w, r, data.ErrInvalidAuthToken)
return
}
@@ -42,7 +42,7 @@ func (api *Api) Authenticate(next http.Handler) http.Handler {
if err != nil {
switch {
case errors.Is(err, data.ErrRecordNotFound):
- api.InvalidAuthenticationTokenResponse(w, r)
+ api.errorResponse(w, r, data.ErrInvalidAuthToken)
default:
api.ServerErrorResponse(w, r, err)
}
@@ -53,7 +53,7 @@ func (api *Api) Authenticate(next http.Handler) http.Handler {
if err != nil {
switch {
case errors.Is(err, data.ErrRecordNotFound):
- api.InvalidCredentialsResponse(w, r)
+ api.errorResponse(w, r, data.ErrInvalidCredentials)
default:
api.ServerErrorResponse(w, r, err)
}
diff --git a/cmd/party/api/device_tokens.go b/cmd/party/api/device_tokens.go
index 5ec821f..b553f94 100644
--- a/cmd/party/api/device_tokens.go
+++ b/cmd/party/api/device_tokens.go
@@ -1,11 +1,11 @@
package api
import (
- "errors"
"fmt"
"net/http"
"party.at/party/cmd/party/common"
+ "party.at/party/internal/data"
)
func (api *Api) RegisterDeviceToken(w http.ResponseWriter, r *http.Request) {
@@ -23,14 +23,14 @@ func (api *Api) RegisterDeviceToken(w http.ResponseWriter, r *http.Request) {
"user_id": fmt.Sprint(user.ID),
"error": err.Error(),
})
- api.BadRequestResponse(w, r, err)
+ api.errorResponse(w, r, data.ErrBadRequest)
return
}
if input.Token == "" {
api.App.Logger.PrintInfo("register device token: empty token", map[string]string{
"user_id": fmt.Sprint(user.ID),
})
- api.BadRequestResponse(w, r, errors.New("token must be provided"))
+ api.errorResponse(w, r, data.ErrNoToken)
return
}
@@ -55,11 +55,11 @@ func (api *Api) DeleteDeviceToken(w http.ResponseWriter, r *http.Request) {
Token string `json:"token"`
}
if err := common.ReadJSON(w, r, &input); err != nil {
- api.BadRequestResponse(w, r, err)
+ api.errorResponse(w, r, data.ErrBadRequest)
return
}
if input.Token == "" {
- api.BadRequestResponse(w, r, errors.New("token must be provided"))
+ api.errorResponse(w, r, data.ErrNoToken)
return
}
@@ -76,7 +76,7 @@ func (api *Api) DeleteDeviceToken(w http.ResponseWriter, r *http.Request) {
}
}
if !owns {
- api.NotFoundResponse(w, r)
+ api.errorResponse(w, r, data.ErrRecordNotFound)
return
}
diff --git a/cmd/party/api/errors.go b/cmd/party/api/errors.go
index b7f2a4d..adbac8f 100644
--- a/cmd/party/api/errors.go
+++ b/cmd/party/api/errors.go
@@ -1,20 +1,13 @@
package api
import(
- "fmt"
"net/http"
+ "errors"
"party.at/party/cmd/party/common"
+ "party.at/party/internal/data"
)
-// ── Error responses ──────────────────────────────────────────────────────────
-
-type apiError struct {
- Code common.ErrorCode `json:"code,omitempty"`
- Message string `json:"message"`
- Details map[string]string `json:"details,omitempty"`
-}
-
func (api *Api) LogError(r *http.Request, err error) {
api.App.Logger.PrintError(err, map[string]string{
"request_method": r.Method,
@@ -22,124 +15,26 @@ func (api *Api) LogError(r *http.Request, err error) {
})
}
-func (api *Api) errorResponse(w http.ResponseWriter, r *http.Request, status int, ae apiError) {
- if err := common.WriteJSON(w, status, common.Envelope{"error": ae}, nil); err != nil {
+func (api *Api) errorResponse(w http.ResponseWriter, r *http.Request, err error) {
+ apiErr := &data.Error{
+ HttpCode: http.StatusInternalServerError,
+ Message: "the server encountered a problem",
+ }
+
+ // Try to "unbox" the error to see if it's our rich *Error type
+ var customErr *data.Error
+ if errors.As(err, &customErr) {
+ apiErr = customErr
+ }
+
+ if err := common.WriteJSON(w, apiErr.HttpCode, common.Envelope{"error": apiErr}, nil); err != nil {
api.App.LogError(r, err)
w.WriteHeader(500)
}
}
func (api *Api) ServerErrorResponse(w http.ResponseWriter, r *http.Request, err error) {
- api.App.LogError(r, err)
- api.errorResponse(w, r, http.StatusInternalServerError, apiError{
- Message: "the server encountered a problem and could not process your request",
- })
-}
+ api.App.LogError(r, err)
-func (api *Api) NotFoundResponse(w http.ResponseWriter, r *http.Request) {
- api.errorResponse(w, r, http.StatusNotFound, apiError{
- Message: "the requested resource could not be found",
- })
-}
-
-func (api *Api) MethodNotAllowedResponse(w http.ResponseWriter, r *http.Request) {
- api.errorResponse(w, r, http.StatusMethodNotAllowed, apiError{
- Message: fmt.Sprintf("the %s method is not supported for this resource", r.Method),
- })
-}
-
-func (api *Api) BadRequestResponse(w http.ResponseWriter, r *http.Request, err error) {
- api.errorResponse(w, r, http.StatusBadRequest, apiError{Message: err.Error()})
-}
-
-func (api *Api) FailedValidationResponse(w http.ResponseWriter, r *http.Request, errors map[string]string) {
- api.errorResponse(w, r, http.StatusUnprocessableEntity, apiError{
- Code: common.ErrCodeValidationFailed,
- Message: "validation failed",
- Details: errors,
- })
-}
-
-func (api *Api) EditConflictResponse(w http.ResponseWriter, r *http.Request) {
- api.errorResponse(w, r, http.StatusConflict, apiError{
- Code: common.ErrCodeEditConflict,
- Message: "unable to update the record due to an edit conflict, please try again",
- })
-}
-
-func (api *Api) InvalidCredentialsResponse(w http.ResponseWriter, r *http.Request) {
- api.errorResponse(w, r, http.StatusUnauthorized, apiError{
- Code: common.ErrCodeInvalidCredentials,
- Message: "invalid authentication credentials",
- })
-}
-
-func (api *Api) InvalidAuthenticationTokenResponse(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("WWW-Authenticate", "Bearer")
- api.errorResponse(w, r, http.StatusUnauthorized, apiError{
- Code: common.ErrCodeInvalidAuthToken,
- Message: "invalid or missing authentication token",
- })
-}
-
-func (api *Api) RateLimitExceededResponse(w http.ResponseWriter, r *http.Request) {
- api.errorResponse(w, r, http.StatusTooManyRequests, apiError{
- Message: "rate limit exceeded",
- })
-}
-
-func (api *Api) AuthenticationRequiredResponse(w http.ResponseWriter, r *http.Request) {
- api.errorResponse(w, r, http.StatusUnauthorized, apiError{
- Code: common.ErrCodeAuthRequired,
- Message: "you must be authenticated to access this resource",
- })
-}
-
-func (api *Api) InactiveAccountResponse(w http.ResponseWriter, r *http.Request) {
- api.errorResponse(w, r, http.StatusForbidden, apiError{
- Code: common.ErrCodeInactiveAccount,
- Message: "your user account must be activated to access this resource",
- })
-}
-
-func (api *Api) NotPermittedResponse(w http.ResponseWriter, r *http.Request) {
- api.errorResponse(w, r, http.StatusForbidden, apiError{
- Code: common.ErrCodeNotPermitted,
- Message: "your user account doesn't have the necessary permissions to access this resource",
- })
-}
-
-func (api *Api) AlreadyVotedResponse(w http.ResponseWriter, r *http.Request) {
- api.errorResponse(w, r, http.StatusConflict, apiError{
- Code: common.ErrCodeAlreadyVoted,
- Message: "your user account already voted for this issue",
- })
-}
-
-func (api *Api) AlreadyBlindSignedResponse(w http.ResponseWriter, r *http.Request) {
- api.errorResponse(w, r, http.StatusConflict, apiError{
- Code: common.ErrCodeAlreadyBlindSigned,
- Message: "already requested a blind signature for this issue",
- })
-}
-
-func (api *Api) BlindedVoteOutOfRangeResponse(w http.ResponseWriter, r *http.Request) {
- api.errorResponse(w, r, http.StatusUnprocessableEntity, apiError{
- Code: common.ErrCodeBlindedVoteRange,
- Message: "blinded_vote is out of valid range",
- })
-}
-
-func (api *Api) InvalidSignatureResponse(w http.ResponseWriter, r *http.Request) {
- api.errorResponse(w, r, http.StatusUnprocessableEntity, apiError{
- Code: common.ErrCodeInvalidSignature,
- Message: "invalid signature",
- })
-}
-
-func (api *Api) VoteAlreadyCastResponse(w http.ResponseWriter, r *http.Request) {
- api.errorResponse(w, r, http.StatusConflict, apiError{
- Code: common.ErrCodeVoteAlreadyCast,
- Message: "this vote has already been cast",
- })
+ api.errorResponse(w, r, err)
}
diff --git a/cmd/party/api/issues.go b/cmd/party/api/issues.go
index 991cc34..c6b9022 100644
--- a/cmd/party/api/issues.go
+++ b/cmd/party/api/issues.go
@@ -27,7 +27,7 @@ func (api *Api) ListIssues(w http.ResponseWriter, r *http.Request) {
input.Filters.SortSafelist = []string{"id", "-id", "title", "-title", "description", "-description"}
if data.ValidateFilters(v, input.Filters); !v.Valid() {
- api.FailedValidationResponse(w, r, v.Errors)
+ api.errorResponse(w, r, data.ErrValidationFailed.(*data.Error).WithDetails(v.Errors))
return
}
@@ -52,18 +52,13 @@ func (api *Api) CreateIssue(w http.ResponseWriter, r *http.Request) {
}
if err := common.ReadJSON(w, r, &input); err != nil {
- api.BadRequestResponse(w, r, err)
+ api.errorResponse(w, r, data.ErrBadRequest)
return
}
issue, options, err := api.App.CreateIssue(input.Title, input.Description, input.StartTime, input.EndTime, input.Options)
if err != nil {
- var ve *common.ValidationError
- if errors.As(err, &ve) {
- api.FailedValidationResponse(w, r, ve.Errors)
- } else {
- api.ServerErrorResponse(w, r, err)
- }
+ api.errorResponse(w, r, err)
return
}
@@ -77,14 +72,14 @@ func (api *Api) CreateIssue(w http.ResponseWriter, r *http.Request) {
func (api *Api) ReadIssue(w http.ResponseWriter, r *http.Request) {
id, err := common.ReadIDParam(r)
if err != nil {
- api.NotFoundResponse(w, r)
+ api.errorResponse(w, r, data.ErrRecordNotFound)
return
}
result, err := api.App.GetIssue(id, common.GetUser(r))
if err != nil {
if errors.Is(err, data.ErrRecordNotFound) {
- api.NotFoundResponse(w, r)
+ api.errorResponse(w, r, data.ErrRecordNotFound)
} else {
api.ServerErrorResponse(w, r, err)
}
@@ -99,7 +94,7 @@ func (api *Api) ReadIssue(w http.ResponseWriter, r *http.Request) {
func (api *Api) UpdateIssue(w http.ResponseWriter, r *http.Request) {
id, err := common.ReadIDParam(r)
if err != nil {
- api.NotFoundResponse(w, r)
+ api.errorResponse(w, r, data.ErrRecordNotFound)
return
}
@@ -111,23 +106,13 @@ func (api *Api) UpdateIssue(w http.ResponseWriter, r *http.Request) {
}
if err = common.ReadJSON(w, r, &input); err != nil {
- api.BadRequestResponse(w, r, err)
+ api.errorResponse(w, r, data.ErrBadRequest)
return
}
issue, err := api.App.UpdateIssue(id, input.Title, input.Description, input.StartTime, input.EndTime)
if err != nil {
- var ve *common.ValidationError
- switch {
- case errors.As(err, &ve):
- api.FailedValidationResponse(w, r, ve.Errors)
- case errors.Is(err, data.ErrRecordNotFound):
- api.NotFoundResponse(w, r)
- case errors.Is(err, data.ErrEditConflict):
- api.EditConflictResponse(w, r)
- default:
- api.ServerErrorResponse(w, r, err)
- }
+ api.errorResponse(w, r, err)
return
}
@@ -139,13 +124,13 @@ func (api *Api) UpdateIssue(w http.ResponseWriter, r *http.Request) {
func (api *Api) DeleteIssue(w http.ResponseWriter, r *http.Request) {
id, err := common.ReadIDParam(r)
if err != nil {
- api.NotFoundResponse(w, r)
+ api.errorResponse(w, r, data.ErrRecordNotFound)
return
}
if err = api.App.DeleteIssue(id); err != nil {
if errors.Is(err, data.ErrRecordNotFound) {
- api.NotFoundResponse(w, r)
+ api.errorResponse(w, r, data.ErrRecordNotFound)
} else {
api.ServerErrorResponse(w, r, err)
}
@@ -160,14 +145,14 @@ func (api *Api) DeleteIssue(w http.ResponseWriter, r *http.Request) {
func (api *Api) ReadIssuePubKey(w http.ResponseWriter, r *http.Request) {
id, err := common.ReadIDParam(r)
if err != nil {
- api.NotFoundResponse(w, r)
+ api.errorResponse(w, r, data.ErrRecordNotFound)
return
}
pubKey, err := api.App.GetIssuePublicKey(id)
if err != nil {
if errors.Is(err, data.ErrRecordNotFound) {
- api.NotFoundResponse(w, r)
+ api.errorResponse(w, r, data.ErrRecordNotFound)
} else {
api.ServerErrorResponse(w, r, err)
}
@@ -182,7 +167,7 @@ func (api *Api) ReadIssuePubKey(w http.ResponseWriter, r *http.Request) {
func (api *Api) BlindSignIssueVote(w http.ResponseWriter, r *http.Request) {
id, err := common.ReadIDParam(r)
if err != nil {
- api.NotFoundResponse(w, r)
+ api.errorResponse(w, r, data.ErrRecordNotFound)
return
}
@@ -190,22 +175,13 @@ func (api *Api) BlindSignIssueVote(w http.ResponseWriter, r *http.Request) {
BlindedVote []byte `json:"blinded_vote"`
}
if err = common.ReadJSON(w, r, &input); err != nil {
- api.BadRequestResponse(w, r, err)
+ api.errorResponse(w, r, data.ErrBadRequest)
return
}
signed, err := api.App.BlindSign(id, input.BlindedVote, common.GetUser(r))
if err != nil {
- switch {
- case errors.Is(err, data.ErrRecordNotFound):
- api.NotFoundResponse(w, r)
- case errors.Is(err, data.ErrDuplicateBlindSign):
- api.AlreadyBlindSignedResponse(w, r)
- case errors.Is(err, data.ErrInvalidBlindedVote):
- api.BlindedVoteOutOfRangeResponse(w, r)
- default:
- api.ServerErrorResponse(w, r, err)
- }
+ api.errorResponse(w, r, err)
return
}
diff --git a/cmd/party/api/middleware.go b/cmd/party/api/middleware.go
index 7b961af..4263d8f 100644
--- a/cmd/party/api/middleware.go
+++ b/cmd/party/api/middleware.go
@@ -1,13 +1,15 @@
package api
import (
- "net/http"
"fmt"
- "party.at/party/cmd/party/common"
- "time"
- "sync"
- "golang.org/x/time/rate"
"net"
+ "net/http"
+ "sync"
+ "time"
+
+ "golang.org/x/time/rate"
+ "party.at/party/cmd/party/common"
+ "party.at/party/internal/data"
)
func (api *Api) RecoverPanic(next http.Handler) http.Handler {
@@ -25,7 +27,7 @@ func (api *Api) RecoverPanic(next http.Handler) http.Handler {
func (api *Api) RequireAuthenticatedUser(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if common.GetUser(r).IsAnonymous() {
- api.AuthenticationRequiredResponse(w, r)
+ api.errorResponse(w, r, data.ErrAuthRequired)
return
}
next.ServeHTTP(w, r)
@@ -35,7 +37,7 @@ func (api *Api) RequireAuthenticatedUser(next http.HandlerFunc) http.HandlerFunc
func (api *Api) RequireActivatedUser(next http.HandlerFunc) http.HandlerFunc {
fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !common.GetUser(r).Activated {
- api.InactiveAccountResponse(w, r)
+ api.errorResponse(w, r, data.ErrInactiveAccount)
return
}
next.ServeHTTP(w, r)
@@ -51,7 +53,7 @@ func (api *Api) RequirePermission(code string, next http.HandlerFunc) http.Handl
return
}
if !permissions.Include(code) {
- api.NotPermittedResponse(w, r)
+ api.errorResponse(w, r, data.ErrNotPermitted)
return
}
next.ServeHTTP(w, r)
@@ -96,7 +98,7 @@ func (api *Api) RateLimit(next http.Handler) http.Handler {
clients[ip].lastSeen = time.Now()
if !clients[ip].limiter.Allow() {
mu.Unlock()
- api.RateLimitExceededResponse(w, r)
+ api.errorResponse(w, r, data.ErrRateLimitExceeded)
return
}
mu.Unlock()
diff --git a/cmd/party/api/parlvotedetail.go b/cmd/party/api/parlvotedetail.go
index 5c9683b..06c1a9a 100644
--- a/cmd/party/api/parlvotedetail.go
+++ b/cmd/party/api/parlvotedetail.go
@@ -1,18 +1,19 @@
package api
import (
- "errors"
"net/http"
"strings"
"party.at/party/cmd/party/common"
+ "party.at/party/internal/data"
)
func (api *Api) GetParlVoteDetail(w http.ResponseWriter, r *http.Request) {
routePath := r.PathValue("path")
routePath = strings.TrimPrefix(routePath, "/")
if routePath == "" {
- api.BadRequestResponse(w, r, errors.New("path is required"))
+ api.errorResponse(w, r, data.ErrBadRequest)
+
return
}
@@ -24,7 +25,7 @@ func (api *Api) GetParlVoteDetail(w http.ResponseWriter, r *http.Request) {
return
}
if detail == nil {
- api.NotFoundResponse(w, r)
+ api.errorResponse(w, r, data.ErrRecordNotFound)
return
}
diff --git a/cmd/party/api/tokens.go b/cmd/party/api/tokens.go
index e4e6ea8..ad45871 100644
--- a/cmd/party/api/tokens.go
+++ b/cmd/party/api/tokens.go
@@ -17,7 +17,7 @@ func (api *Api) CreateAuthenticationToken(w http.ResponseWriter, r *http.Request
}
if err := common.ReadJSON(w, r, &input); err != nil {
- api.BadRequestResponse(w, r, err)
+ api.errorResponse(w, r, data.ErrBadRequest)
return
}
@@ -25,7 +25,7 @@ func (api *Api) CreateAuthenticationToken(w http.ResponseWriter, r *http.Request
v.Check(input.Email != "", "email", "must be provided")
v.Check(input.Password != "", "password", "must be provided")
if !v.Valid() {
- api.FailedValidationResponse(w, r, v.Errors)
+ api.errorResponse(w, r, data.ErrValidationFailed.(*data.Error).WithDetails(v.Errors))
return
}
@@ -33,7 +33,7 @@ func (api *Api) CreateAuthenticationToken(w http.ResponseWriter, r *http.Request
if err != nil {
switch {
case errors.Is(err, data.ErrInvalidCredentials):
- api.InvalidCredentialsResponse(w, r)
+ api.errorResponse(w, r, data.ErrInvalidCredentials)
default:
api.ServerErrorResponse(w, r, err)
}
@@ -49,14 +49,14 @@ func (api *Api) DeleteAuthenticationToken(w http.ResponseWriter, r *http.Request
authHeader := r.Header.Get("Authorization")
parts := strings.Split(authHeader, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
- api.InvalidAuthenticationTokenResponse(w, r)
+ api.errorResponse(w, r, data.ErrInvalidAuthToken)
return
}
token := parts[1]
v := validator.New()
if data.ValidateTokenPlaintext(v, token); !v.Valid() {
- api.InvalidAuthenticationTokenResponse(w, r)
+ api.errorResponse(w, r, data.ErrInvalidAuthToken)
return
}
diff --git a/cmd/party/api/users.go b/cmd/party/api/users.go
index 3283901..5ea9437 100644
--- a/cmd/party/api/users.go
+++ b/cmd/party/api/users.go
@@ -25,7 +25,7 @@ func (api *Api) ListUsers(w http.ResponseWriter, r *http.Request) {
input.Filters.SortSafelist = []string{"id", "-id", "name", "-name", "email", "-email", "created", "-created"}
if data.ValidateFilters(v, input.Filters); !v.Valid() {
- api.FailedValidationResponse(w, r, v.Errors)
+ api.errorResponse(w, r, data.ErrValidationFailed.(*data.Error).WithDetails(v.Errors))
return
}
@@ -55,10 +55,12 @@ func (api *Api) CreateUser(w http.ResponseWriter, r *http.Request) {
}
if err := common.ReadJSON(w, r, &input); err != nil {
- api.BadRequestResponse(w, r, err)
+ api.errorResponse(w, r, data.ErrBadRequest)
return
}
+
+
user, authToken, err := api.App.RegisterUser(common.RegisterUserInput{
ProviderID: input.ProviderId,
Username: input.Username,
@@ -72,21 +74,7 @@ func (api *Api) CreateUser(w http.ResponseWriter, r *http.Request) {
Address: input.Address,
})
if err != nil {
- var ve *common.ValidationError
- switch {
- case errors.As(err, &ve):
- api.FailedValidationResponse(w, r, ve.Errors)
- case errors.Is(err, data.ErrDuplicateEmail):
- v := validator.New()
- v.AddError("email", "a user with this email address already exists")
- api.FailedValidationResponse(w, r, v.Errors)
- case errors.Is(err, data.ErrDuplicateUser):
- v := validator.New()
- v.AddError("username", "a user with this username already exists")
- api.FailedValidationResponse(w, r, v.Errors)
- default:
- api.ServerErrorResponse(w, r, err)
- }
+ api.errorResponse(w, r, err)
return
}
@@ -105,23 +93,20 @@ func (api *Api) ReadUser(w http.ResponseWriter, r *http.Request) {
var err error
id, err = strconv.ParseInt(param, 10, 64)
if err != nil || id < 1 {
- api.NotFoundResponse(w, r)
+ api.errorResponse(w, r, data.ErrRecordNotFound)
return
}
}
if common.GetUser(r).ID != id {
- api.errorResponse(w, r, http.StatusForbidden, apiError{
- Code: common.ErrCodeNotPermitted,
- Message: "your user account doesn't have the necessary permissions to access this resource",
- })
+ api.errorResponse(w, r, data.ErrNotPermitted)
return
}
user, err := api.App.GetUser(id)
if err != nil {
if errors.Is(err, data.ErrRecordNotFound) {
- api.NotFoundResponse(w, r)
+ api.errorResponse(w, r, data.ErrRecordNotFound)
} else {
api.ServerErrorResponse(w, r, err)
}
@@ -137,7 +122,7 @@ func (api *Api) ReadUser(w http.ResponseWriter, r *http.Request) {
// user := common.GetUser(r).ID
// // if err != nil {
// // if errors.Is(err, data.ErrRecordNotFound) {
-// // api.NotFoundResponse(w, r)
+// // api.errorResponse(w, r, data.ErrRecordNotFound)
// // } else {
// // api.ServerErrorResponse(w, r, err)
// // }
@@ -152,13 +137,13 @@ func (api *Api) ReadUser(w http.ResponseWriter, r *http.Request) {
func (api *Api) DeleteUser(w http.ResponseWriter, r *http.Request) {
id, err := common.ReadIDParam(r)
if err != nil {
- api.NotFoundResponse(w, r)
+ api.errorResponse(w, r, data.ErrRecordNotFound)
return
}
if err = api.App.DeleteUser(id); err != nil {
if errors.Is(err, data.ErrRecordNotFound) {
- api.NotFoundResponse(w, r)
+ api.errorResponse(w, r, data.ErrRecordNotFound)
} else {
api.ServerErrorResponse(w, r, err)
}
@@ -176,27 +161,19 @@ func (api *Api) ActivateUser(w http.ResponseWriter, r *http.Request) {
}
if err := common.ReadJSON(w, r, &input); err != nil {
- api.BadRequestResponse(w, r, err)
+ api.errorResponse(w, r, data.ErrBadRequest)
return
}
v := validator.New()
if data.ValidateTokenPlaintext(v, input.TokenPlaintext); !v.Valid() {
- api.FailedValidationResponse(w, r, v.Errors)
+ api.errorResponse(w, r, data.ErrValidationFailed.(*data.Error).WithDetails(v.Errors))
return
}
user, err := api.App.ActivateUser(input.TokenPlaintext)
if err != nil {
- switch {
- case errors.Is(err, data.ErrRecordNotFound):
- v.AddError("token", "invalid or expired activation token")
- api.FailedValidationResponse(w, r, v.Errors)
- case errors.Is(err, data.ErrEditConflict):
- api.EditConflictResponse(w, r)
- default:
- api.ServerErrorResponse(w, r, err)
- }
+ api.errorResponse(w, r, err)
return
}
diff --git a/cmd/party/api/votes.go b/cmd/party/api/votes.go
index 97d85ff..5c87896 100644
--- a/cmd/party/api/votes.go
+++ b/cmd/party/api/votes.go
@@ -1,7 +1,6 @@
package api
import (
- "errors"
"net/http"
"party.at/party/internal/data"
@@ -17,21 +16,13 @@ func (api *Api) Vote(w http.ResponseWriter, r *http.Request) {
}
if err := common.ReadJSON(w, r, &input); err != nil {
- api.BadRequestResponse(w, r, err)
+ api.errorResponse(w, r, data.ErrBadRequest)
return
}
if err := api.App.CastVote(input.IssueID, input.OptionID, input.Nonce, input.Signature); err != nil {
- switch {
- case errors.Is(err, data.ErrRecordNotFound):
- api.NotFoundResponse(w, r)
- case errors.Is(err, data.ErrInvalidSignature):
- api.InvalidSignatureResponse(w, r)
- case errors.Is(err, data.ErrDuplicateVote):
- api.AlreadyVotedResponse(w, r)
- default:
- api.ServerErrorResponse(w, r, err)
- }
+ api.ServerErrorResponse(w, r, err)
+
return
}
diff --git a/cmd/party/common/application.go b/cmd/party/common/application.go
index 8ea742a..89cc7a5 100644
--- a/cmd/party/common/application.go
+++ b/cmd/party/common/application.go
@@ -64,14 +64,6 @@ type Application struct {
CORSTrustedOrigins []string
}
-type ValidationError struct {
- Errors map[string]string
-}
-
-func (e *ValidationError) Error() string {
- return "validation failed"
-}
-
func (app *Application) background(fn func()) {
go func() {
defer func() {
diff --git a/cmd/party/common/errors.go b/cmd/party/common/errors.go
deleted file mode 100644
index 7282713..0000000
--- a/cmd/party/common/errors.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package common
-
-type ErrorCode int
-
-const (
- // 401 variants
- ErrCodeInvalidCredentials ErrorCode = 4011
- ErrCodeInvalidAuthToken ErrorCode = 4012
- ErrCodeAuthRequired ErrorCode = 4013
-
- // 403 variants
- ErrCodeInactiveAccount ErrorCode = 4031
- ErrCodeNotPermitted ErrorCode = 4032
-
- // 409 variants
- ErrCodeEditConflict ErrorCode = 4091
- ErrCodeAlreadyVoted ErrorCode = 4092
- ErrCodeAlreadyBlindSigned ErrorCode = 4093
- ErrCodeVoteAlreadyCast ErrorCode = 4094
-
- // 422 variants
- ErrCodeValidationFailed ErrorCode = 4221
- ErrCodeBlindedVoteRange ErrorCode = 4222
- ErrCodeInvalidSignature ErrorCode = 4223
-)
diff --git a/cmd/party/common/helpers.go b/cmd/party/common/helpers.go
index 04caaad..0da0d20 100644
--- a/cmd/party/common/helpers.go
+++ b/cmd/party/common/helpers.go
@@ -15,6 +15,7 @@ import (
"strings"
"party.at/party/internal/validator"
+ "party.at/party/internal/data"
)
type Envelope map[string]interface{}
@@ -49,14 +50,14 @@ func ReadJSON(w http.ResponseWriter, r *http.Request, dst interface{}) error {
case errors.As(err, &syntaxError):
return fmt.Errorf("body contains badly-formed JSON (at character %d)", syntaxError.Offset)
case errors.Is(err, io.ErrUnexpectedEOF):
- return errors.New("body contains badly-formed JSON")
+ return data.ErrBadlyFormedJSON
case errors.As(err, &unmarshalTypeError):
if unmarshalTypeError.Field != "" {
return fmt.Errorf("body contains incorrect JSON type for field %q", unmarshalTypeError.Field)
}
return fmt.Errorf("body contains incorrect JSON type (at character %d)", unmarshalTypeError.Offset)
case errors.Is(err, io.EOF):
- return errors.New("body must not be empty")
+ return data.ErrBodyEmpty
case strings.HasPrefix(err.Error(), "json: unknown field "):
fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ")
return fmt.Errorf("body contains unknown key %s", fieldName)
@@ -70,7 +71,7 @@ func ReadJSON(w http.ResponseWriter, r *http.Request, dst interface{}) error {
}
err = dec.Decode(&struct{}{})
if err != io.EOF {
- return errors.New("body must only contain a single JSON value")
+ return data.ErrSingleValue
}
return nil
}
@@ -91,7 +92,7 @@ func GenerateIssueKey() ([]byte, int, []byte, error) {
func ReadIDParam(r *http.Request) (int64, error) {
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
if err != nil || id < 1 {
- return 0, errors.New("invalid id parameter")
+ return 0, data.ErrInvalidID
}
return id, nil
}
diff --git a/cmd/party/common/issues.go b/cmd/party/common/issues.go
index 4c077ab..7e5f482 100644
--- a/cmd/party/common/issues.go
+++ b/cmd/party/common/issues.go
@@ -77,7 +77,7 @@ func (app *Application) CreateIssue(title, description string, startTime, endTim
data.ValidateOption(v, option)
}
if data.ValidateIssue(v, issue); !v.Valid() {
- return nil, nil, &ValidationError{Errors: v.Errors}
+ return nil, nil, data.ErrValidationFailed.(*data.Error).WithDetails(v.Errors)
}
n, e, privatePEM, err := generateIssueKey()
@@ -145,7 +145,7 @@ func (app *Application) UpdateIssue(id int64, title, description *string, startT
v := validator.New()
if data.ValidateIssue(v, issue); !v.Valid() {
- return nil, &ValidationError{Errors: v.Errors}
+ return nil, data.ErrValidationFailed.(*data.Error).WithDetails(v.Errors)
}
if err = app.Models.Issues.Update(issue); err != nil {
@@ -167,12 +167,16 @@ func (app *Application) GetIssuePublicKey(id int64) (*PublicKey, error) {
return &PublicKey{N: hex.EncodeToString(issue.N), E: issue.E}, nil
}
-func (app *Application) BlindSign(issueID int64, blindedVote []byte, user *data.User) ([]byte, error) {
+func (app *Application) BlindSign(issueID int64, blindedVote []byte, user *data.User) ([]byte, error) {
issue, err := app.Models.Issues.Get(issueID)
if err != nil {
return nil, err
}
+ if issue.StartTime.After(time.Now()) {
+ return nil, data.ErrHasNotStarted
+ }
+
blindSign := &data.BlindSign{UserID: user.ID, IssueID: issue.ID}
if err = app.Models.BlindSigns.Insert(blindSign); err != nil {
return nil, err
diff --git a/cmd/party/common/users.go b/cmd/party/common/users.go
index d503e68..143e0c3 100644
--- a/cmd/party/common/users.go
+++ b/cmd/party/common/users.go
@@ -45,7 +45,7 @@ func (app *Application) RegisterUser(input RegisterUserInput) (*data.User, *data
data.ValidateUser(v, user)
data.ValidateUserIdentity(v, userIdentity)
if !v.Valid() {
- return nil, nil, &ValidationError{Errors: v.Errors}
+ return nil, nil, data.ErrValidationFailed.(*data.Error).WithDetails(v.Errors)
}
if err := app.Models.Users.ExecuteRegistrationTx(user, userIdentity); err != nil {
@@ -54,7 +54,7 @@ func (app *Application) RegisterUser(input RegisterUserInput) (*data.User, *data
role := "viewer"
if app.Config.Env == "development" {
- role = ""
+ role = "admin"
}
if err := app.Models.Roles.AssignToUser(user.ID, role); err != nil {
diff --git a/cmd/party/web/errors.go b/cmd/party/web/errors.go
index 204896e..c2a0c9a 100644
--- a/cmd/party/web/errors.go
+++ b/cmd/party/web/errors.go
@@ -1,8 +1,8 @@
package web
import(
- "fmt"
"net/http"
+ "errors"
"party.at/party/internal/data"
"party.at/party/cmd/party/common"
@@ -10,12 +10,6 @@ import(
// ── Error responses ──────────────────────────────────────────────────────────
-type webError struct {
- Code common.ErrorCode `json:"code,omitempty"`
- Message string `json:"message"`
- Details map[string]string `json:"details,omitempty"`
-}
-
func (web *Web) LogError(r *http.Request, err error) {
web.App.Logger.PrintError(err, map[string]string{
"request_method": r.Method,
@@ -23,131 +17,32 @@ func (web *Web) LogError(r *http.Request, err error) {
})
}
-func (web *Web) errorResponse(w http.ResponseWriter, r *http.Request, status int, we webError) {
- // if err := common.WriteJSON(w, status, common.Envelope{"error": we}, nil); err != nil {
- // web.LogError(r, err)
- // w.WriteHeader(500)
- // }
+func (web *Web) errorResponse(w http.ResponseWriter, r *http.Request, err error) {
+ displayError := data.Error{
+ HttpCode: http.StatusInternalServerError,
+ Message: "the server encountered a problem and could not process your request",
+ }
+
+ var customErr *data.Error
+ if errors.As(err, &customErr) {
+ displayError = *customErr
+ }
user := common.GetUser(r)
-
- web.render(w, r, http.StatusOK, "error", struct {
+
+ web.render(w, r, displayError.HttpCode, "error", struct {
AuthenticatedUser *data.User
- FormErrors []string
IsDevelopment bool
- Status int
- We webError
+ Error data.Error
}{
AuthenticatedUser: user,
- IsDevelopment: web.App.Config.Env == "development",
- Status: status,
- We: we,
+ IsDevelopment: web.App.Config.Env == "development",
+ Error: displayError,
})
}
func (web *Web) ServerErrorResponse(w http.ResponseWriter, r *http.Request, err error) {
web.LogError(r, err)
- web.errorResponse(w, r, http.StatusInternalServerError, webError{
- Message: "the server encountered a problem and could not process your request",
- })
-}
-func (web *Web) NotFoundResponse(w http.ResponseWriter, r *http.Request) {
- web.errorResponse(w, r, http.StatusNotFound, webError{
- Message: "the requested resource could not be found",
- })
-}
-
-func (web *Web) MethodNotAllowedResponse(w http.ResponseWriter, r *http.Request) {
- web.errorResponse(w, r, http.StatusMethodNotAllowed, webError{
- Message: fmt.Sprintf("the %s method is not supported for this resource", r.Method),
- })
-}
-
-func (web *Web) BadRequestResponse(w http.ResponseWriter, r *http.Request, err error) {
- web.errorResponse(w, r, http.StatusBadRequest, webError{Message: err.Error()})
-}
-
-func (web *Web) FailedValidationResponse(w http.ResponseWriter, r *http.Request, errors map[string]string) {
- web.errorResponse(w, r, http.StatusUnprocessableEntity, webError{
- Code: common.ErrCodeValidationFailed,
- Message: "validation failed",
- Details: errors,
- })
-}
-
-func (web *Web) EditConflictResponse(w http.ResponseWriter, r *http.Request) {
- web.errorResponse(w, r, http.StatusConflict, webError{
- Code: common.ErrCodeEditConflict,
- Message: "unable to update the record due to an edit conflict, please try again",
- })
-}
-
-func (web *Web) InvalidCredentialsResponse(w http.ResponseWriter, r *http.Request) {
- web.errorResponse(w, r, http.StatusUnauthorized, webError{
- Code: common.ErrCodeInvalidCredentials,
- Message: "invalid authentication credentials",
- })
-}
-
-func (web *Web) RateLimitExceededResponse(w http.ResponseWriter, r *http.Request) {
- web.errorResponse(w, r, http.StatusTooManyRequests, webError{
- Message: "rate limit exceeded",
- })
-}
-
-func (web *Web) AuthenticationRequiredResponse(w http.ResponseWriter, r *http.Request) {
- web.errorResponse(w, r, http.StatusUnauthorized, webError{
- Code: common.ErrCodeAuthRequired,
- Message: "you must be authenticated to access this resource",
- })
-}
-
-func (web *Web) InactiveAccountResponse(w http.ResponseWriter, r *http.Request) {
- web.errorResponse(w, r, http.StatusForbidden, webError{
- Code: common.ErrCodeInactiveAccount,
- Message: "your user account must be activated to access this resource",
- })
-}
-
-func (web *Web) NotPermittedResponse(w http.ResponseWriter, r *http.Request) {
- web.errorResponse(w, r, http.StatusForbidden, webError{
- Code: common.ErrCodeNotPermitted,
- Message: "your user account doesn't have the necessary permissions to access this resource",
- })
-}
-
-func (web *Web) AlreadyVotedResponse(w http.ResponseWriter, r *http.Request) {
- web.errorResponse(w, r, http.StatusConflict, webError{
- Code: common.ErrCodeAlreadyVoted,
- Message: "your user account already voted for this issue",
- })
-}
-
-func (web *Web) AlreadyBlindSignedResponse(w http.ResponseWriter, r *http.Request) {
- web.errorResponse(w, r, http.StatusConflict, webError{
- Code: common.ErrCodeAlreadyBlindSigned,
- Message: "already requested a blind signature for this issue",
- })
-}
-
-func (web *Web) BlindedVoteOutOfRangeResponse(w http.ResponseWriter, r *http.Request) {
- web.errorResponse(w, r, http.StatusUnprocessableEntity, webError{
- Code: common.ErrCodeBlindedVoteRange,
- Message: "blinded_vote is out of valid range",
- })
-}
-
-func (web *Web) InvalidSignatureResponse(w http.ResponseWriter, r *http.Request) {
- web.errorResponse(w, r, http.StatusUnprocessableEntity, webError{
- Code: common.ErrCodeInvalidSignature,
- Message: "invalid signature",
- })
-}
-
-func (web *Web) VoteAlreadyCastResponse(w http.ResponseWriter, r *http.Request) {
- web.errorResponse(w, r, http.StatusConflict, webError{
- Code: common.ErrCodeVoteAlreadyCast,
- Message: "this vote has already been cast",
- })
+ web.errorResponse(w, r, err)
}
diff --git a/cmd/party/web/issues.go b/cmd/party/web/issues.go
index 8b9480e..ec2db60 100644
--- a/cmd/party/web/issues.go
+++ b/cmd/party/web/issues.go
@@ -93,6 +93,7 @@ func (web *Web) IssuePage(w http.ResponseWriter, r *http.Request) {
id, err := common.ReadIDParam(r)
if err != nil {
http.NotFound(w, r)
+ web.errorResponse(w, r, data.ErrRecordNotFound)
return
}
@@ -239,9 +240,11 @@ func (web *Web) BlindSignIssueVote(w http.ResponseWriter, r *http.Request) {
case errors.Is(err, data.ErrRecordNotFound):
http.NotFound(w, r)
case errors.Is(err, data.ErrDuplicateBlindSign):
- common.WriteJSON(w, http.StatusConflict, common.Envelope{"error": map[string]string{"message": "bereits eine Blindsignatur für diese Abstimmung angefordert"}}, nil)
+ common.WriteJSON(w, http.StatusConflict, common.Envelope{"error": map[string]string{"message": err.Error()}}, nil)
case errors.Is(err, data.ErrInvalidBlindedVote):
common.WriteJSON(w, http.StatusUnprocessableEntity, common.Envelope{"error": map[string]string{"message": err.Error()}}, nil)
+ case errors.Is(err, data.ErrHasNotStarted):
+ common.WriteJSON(w, http.StatusUnprocessableEntity, common.Envelope{"error": map[string]string{"message": err.Error()}}, nil)
default:
web.App.LogError(r, err)
common.WriteJSON(w, http.StatusInternalServerError, common.Envelope{"error": map[string]string{"message": "internal server error"}}, nil)
@@ -250,6 +253,6 @@ func (web *Web) BlindSignIssueVote(w http.ResponseWriter, r *http.Request) {
}
if err = common.WriteJSON(w, http.StatusOK, common.Envelope{"signed": signed}, nil); err != nil {
- web.App.LogError(r, err)
+ web.ServerErrorResponse(w, r, err)
}
}
diff --git a/cmd/party/web/middleware.go b/cmd/party/web/middleware.go
index a2827be..4be2c25 100644
--- a/cmd/party/web/middleware.go
+++ b/cmd/party/web/middleware.go
@@ -91,7 +91,7 @@ func (web *Web) RequireActivatedUser(next http.HandlerFunc) http.HandlerFunc {
func (web *Web) RequirePermission(code string, next http.HandlerFunc) http.HandlerFunc {
return web.RequireActivatedUser(func(w http.ResponseWriter, r *http.Request) {
if !common.GetPermissions(r).Include(code) {
- web.NotPermittedResponse(w, r)
+ web.errorResponse(w, r, data.ErrNotPermitted)
return
}
next.ServeHTTP(w, r)
diff --git a/cmd/party/web/users.go b/cmd/party/web/users.go
index 370781f..a23fa96 100644
--- a/cmd/party/web/users.go
+++ b/cmd/party/web/users.go
@@ -48,20 +48,12 @@ func (web *Web) RegisterUserPage(w http.ResponseWriter, r *http.Request) {
})
if err != nil {
var formErrors []string
- var ve *common.ValidationError
- switch {
- case errors.As(err, &ve):
- for _, msg := range ve.Errors {
- formErrors = append(formErrors, msg)
- }
- case errors.Is(err, data.ErrDuplicateEmail):
- formErrors = []string{"Diese E-Mail-Adresse wird bereits verwendet."}
- case errors.Is(err, data.ErrDuplicateUser):
- formErrors = []string{"Dieser Benutzername wird bereits verwendet."}
- default:
- web.App.LogError(r, err)
- formErrors = []string{"Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut."}
+ var customErr *data.Error
+
+ if errors.As(err, &customErr) && len(customErr.Details) > 0 {
+ formErrors = customErr.DetailMessages()
}
+
web.render(w, r, http.StatusUnprocessableEntity, "register", struct {
AuthenticatedUser *data.User
FormErrors []string
diff --git a/internal/data/errors.go b/internal/data/errors.go
new file mode 100644
index 0000000..99527cb
--- /dev/null
+++ b/internal/data/errors.go
@@ -0,0 +1,100 @@
+package data
+
+import (
+ "fmt"
+)
+
+type ErrorCode int
+
+const (
+ // 401 variants
+ ErrCodeInvalidCredentials ErrorCode = 4011
+ ErrCodeInvalidAuthToken ErrorCode = 4012
+ ErrCodeAuthRequired ErrorCode = 4013
+
+ // 403 variants
+ ErrCodeInactiveAccount ErrorCode = 4031
+ ErrCodeNotPermitted ErrorCode = 4032
+
+ // 409 variants
+ ErrCodeEditConflict ErrorCode = 4091
+ ErrCodeAlreadyVoted ErrorCode = 4092
+ ErrCodeAlreadyBlindSigned ErrorCode = 4093
+ ErrCodeVoteAlreadyCast ErrorCode = 4094
+
+ // 422 variants
+ ErrCodeValidationFailed ErrorCode = 4221
+ ErrCodeBlindedVoteRange ErrorCode = 4222
+ ErrCodeInvalidSignature ErrorCode = 4223
+ ErrCodeHasNotStarted ErrorCode = 4224
+)
+
+type Error struct {
+ HttpCode int `json:"http_code,omitempty"`
+ Code ErrorCode `json:"code,omitempty"`
+ Message string `json:"message"`
+ Details map[string]string `json:"details,omitempty"`
+}
+
+func (e *Error) Error() string {
+ return e.Message
+}
+
+func (e *Error) WithDetails(details map[string]string) error {
+ e.Details = details
+ return e
+}
+
+
+func (e *Error) DetailMessages() []string {
+ msgs := make([]string, 0, len(e.Details))
+ for key, msg := range e.Details {
+ msgs = append(msgs, fmt.Sprintf("%s: %s", key, msg))
+ }
+ return msgs
+}
+
+func New(httpCode int, code ErrorCode, text string) error {
+ return &Error{HttpCode: httpCode, Code: code, Message: text}
+}
+
+var (
+ // 400 Bad Request
+ ErrFailedPEM = New(400, 0, "failed to decode PEM block")
+ ErrBadlyFormedJSON = New(400, 0, "body contains badly-formed JSON")
+ ErrBodyEmpty = New(400, 0, "body must not be empty")
+ ErrSingleValue = New(400, 0, "body must only contain a single JSON value")
+ ErrInvalidID = New(400, 0, "invalid id parameter")
+ ErrBadRequest = New(400, 0, "the server cannot process the request due to a client error")
+
+ // 401 Unauthorized
+ ErrInvalidCredentials = New(401, 4011, "invalid credentials")
+ ErrInvalidAuthToken = New(401, 4012, "invalid or missing authentication token")
+ ErrNoToken = New(401, 4012, "token must be provided")
+ ErrAuthRequired = New(401, 4013, "you must be authenticated to access this resource")
+ ErrHasNotStarted = New(401, 4224, "the vote has not yet started")
+
+ // 403 Forbidden
+ ErrInactiveAccount = New(403, 4031, "your user account must be activated to access this resource")
+ ErrNotPermitted = New(403, 4032, "your user account doesn't have the necessary permissions to access this resource")
+
+ // 404 Not Found
+ ErrRecordNotFound = New(404, 0, "record not found")
+ ErrNoPath = New(404, 0, "path is required")
+
+ // 409 Conflict
+ ErrEditConflict = New(409, 4091, "edit conflict")
+ ErrDuplicateVote = New(409, 4092, "this signature has already been used to cast a vote")
+ ErrDuplicateBlindSign = New(409, 4093, "user has already requested a blind signature for this issue")
+ ErrDuplicateSignature = New(409, 4094, "this signature has already been used to cast a vote")
+ ErrDuplicateEmail = New(409, 0, "duplicate email")
+ ErrDuplicateUser = New(409, 0, "duplicate username")
+
+ // 422 Unprocessable Entity
+ ErrValidationFailed = New(422, 4221, "validation failed")
+ ErrInvalidBlindedVote = New(422, 4222, "blinded_vote is out of valid range [1, n-1]")
+ ErrInvalidSignature = New(422, 4223, "signature verification failed")
+
+ // 429 Too Many Requests
+ ErrRateLimitExceeded = New(429, 0, "rate limit exceeded")
+)
diff --git a/internal/data/helpers.go b/internal/data/helpers.go
index c3245d1..dd8d2f9 100644
--- a/internal/data/helpers.go
+++ b/internal/data/helpers.go
@@ -3,14 +3,13 @@ package data
import(
"crypto/rsa"
"encoding/pem"
- "errors"
"crypto/x509"
)
func parsePrivateKey(pemBytes []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
- return nil, errors.New("failed to decode PEM block")
+ return nil, ErrFailedPEM
}
return x509.ParsePKCS1PrivateKey(block.Bytes)
}
diff --git a/internal/data/models.go b/internal/data/models.go
index 1babb04..47ee955 100644
--- a/internal/data/models.go
+++ b/internal/data/models.go
@@ -2,12 +2,6 @@ package data
import (
"database/sql"
- "errors"
-)
-
-var (
- ErrRecordNotFound = errors.New("record not found")
- ErrEditConflict = errors.New("edit conflict")
)
type Models struct {
diff --git a/internal/data/users.go b/internal/data/users.go
index 017bd47..ab88a43 100644
--- a/internal/data/users.go
+++ b/internal/data/users.go
@@ -12,12 +12,6 @@ import (
"party.at/party/internal/validator"
)
-var (
- ErrDuplicateEmail = errors.New("duplicate email")
- ErrDuplicateUser = errors.New("duplicate username")
- ErrInvalidCredentials = errors.New("invalid credentials")
-)
-
var AnonymousUser = &User{}
type User struct {
diff --git a/internal/data/votes.go b/internal/data/votes.go
index df9ce33..8d9aac3 100644
--- a/internal/data/votes.go
+++ b/internal/data/votes.go
@@ -1,7 +1,6 @@
package data
import (
- "errors"
"time"
"database/sql"
"math/big"
@@ -12,14 +11,6 @@ import (
"github.com/lib/pq"
)
-var (
- ErrDuplicateBlindSign = errors.New("user has already requested a blind signature for this issue")
- ErrInvalidBlindedVote = errors.New("blinded_vote is out of valid range [1, n-1]")
- ErrInvalidSignature = errors.New("signature verification failed")
- ErrDuplicateVote = errors.New("this signature has already been used to cast a vote")
-)
-
-
type Vote struct {
ID int64 `json:"id"`
OptionID int64 `json:"option_id"`
diff --git a/web/html/base.layout.tmpl b/web/html/base.layout.tmpl
index d2adf19..4f7bbdf 100644
--- a/web/html/base.layout.tmpl
+++ b/web/html/base.layout.tmpl
@@ -27,10 +27,6 @@
{{end}}
-
-

-
-
{{template "body" .}}
diff --git a/web/html/home.page.tmpl b/web/html/home.page.tmpl
index f8ff5e8..3440bdf 100644
--- a/web/html/home.page.tmpl
+++ b/web/html/home.page.tmpl
@@ -3,6 +3,10 @@
{{define "title"}}Übersicht{{end}}
{{define "body"}}
+
+

+
+