21 changed files with 745 additions and 121 deletions
-
2api/v1/tokens/tokens.go
-
2api/v1/users/route.go
-
129api/v1/users/users.go
-
48api/v1/users/users_test.go
-
2cmd/add/admin/admin.go
-
28cmd/server/server.go
-
3go.mod
-
92go.sum
-
62indexes/indexes.go
-
168lib/apperrors/apperrors.go
-
41lib/apperrors/constants.go
-
5lib/authentication/authentication.go
-
45lib/response/response.go
-
13lib/response/response_test.go
-
3main.go
-
37repositories/token_test.go
-
46repositories/user.go
-
41repositories/user_test.go
-
35services/token_test.go
-
27services/user.go
-
37services/user_test.go
@ -0,0 +1,48 @@ |
|||
package users |
|||
|
|||
import ( |
|||
"encoding/json" |
|||
"net/http" |
|||
"net/http/httptest" |
|||
"testing" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
func usersMock(t *testing.T) *gin.Engine { |
|||
gin.SetMode(gin.TestMode) |
|||
app := gin.Default() |
|||
// create token
|
|||
return app |
|||
} |
|||
|
|||
func TestUsersEndpoint(t *testing.T) { |
|||
app := usersMock(t) |
|||
api := app.Group("/api/v1") |
|||
ApplyRoutes(api) |
|||
request, err := http.NewRequest("GET", "/api/v1/users", nil) |
|||
if err != nil { |
|||
t.Error(err) |
|||
} |
|||
// Unauthorized
|
|||
response := httptest.NewRecorder() |
|||
app.ServeHTTP(response, request) |
|||
if response.Result().StatusCode != http.StatusUnauthorized { |
|||
t.Errorf("api users - http status code must be \"%d\" (but is \"%d\") because of unauthorized request", http.StatusUnauthorized, response.Result().StatusCode) |
|||
} |
|||
body := response.Body.String() |
|||
var j map[string]interface{} |
|||
if err := json.Unmarshal([]byte(body), &j); err != nil { |
|||
t.Error(err) |
|||
} |
|||
if _, exists := j["success"]; !exists { |
|||
t.Error("api users - success flag needs always be given") |
|||
} else if success, ok := j["success"].(bool); !ok { |
|||
t.Error("api users - success flag needs to be boolean") |
|||
} else if success { |
|||
t.Error("api users - success flag needs to be false because of unauthorized request") |
|||
} |
|||
// Create user
|
|||
// request.Header.Set("Authorization", "Bearer "+token)
|
|||
// t.Error(response)
|
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
package indexes |
|||
|
|||
import ( |
|||
"fmt" |
|||
|
|||
"git.devices.local/mawas/golang-api-skeleton/lib/common" |
|||
"git.devices.local/mawas/golang-api-skeleton/repositories" |
|||
"git.devices.local/mawas/golang-api-skeleton/services" |
|||
"github.com/blevesearch/bleve" |
|||
"gorm.io/gorm" |
|||
) |
|||
|
|||
func Init(db *gorm.DB) (bleve.Index, error) { |
|||
tokenMapping := bleve.NewDocumentMapping() |
|||
tokenMapping.AddFieldMappingsAt("Token", bleve.NewTextFieldMapping()) |
|||
userMapping := bleve.NewDocumentMapping() |
|||
userMapping.AddSubDocumentMapping("Tokens", tokenMapping) |
|||
userMapping.AddFieldMappingsAt("Username", bleve.NewTextFieldMapping()) |
|||
userMapping.AddFieldMappingsAt("Firstname", bleve.NewTextFieldMapping()) |
|||
userMapping.AddFieldMappingsAt("Lastname", bleve.NewTextFieldMapping()) |
|||
userMapping.AddFieldMappingsAt("Email", bleve.NewTextFieldMapping()) |
|||
userMapping.AddFieldMappingsAt("Active", bleve.NewTextFieldMapping()) |
|||
|
|||
// indexName := "users.bleve"
|
|||
mapping := bleve.NewIndexMapping() |
|||
mapping.AddDocumentMapping("Thread", userMapping) |
|||
|
|||
// ???? mapping.TypeField = "Username"
|
|||
|
|||
// index, err := bleve.New(indexName, mapping)
|
|||
index, err := bleve.NewMemOnly(mapping) |
|||
if err != nil { |
|||
return index, err |
|||
} |
|||
userRepo := repositories.NewUserRepository(db, common.CLIUserID, common.CLIUsername, nil) |
|||
userService := services.NewUserService(userRepo) |
|||
users, _ := userService.ReadAll() |
|||
for i := range users { |
|||
id := users[i].ID |
|||
// doc, err := json.Marshal(users[i])
|
|||
// if err != nil {
|
|||
// return index, err
|
|||
// }
|
|||
// fmt.Println(id, string(doc))
|
|||
err = index.Index(id.String(), users[i]) |
|||
if err != nil { |
|||
return index, err |
|||
} |
|||
// err = index.SetInternal([]byte(id), doc)
|
|||
// if err != nil {
|
|||
// return index, err
|
|||
// }
|
|||
} |
|||
query := bleve.NewMatchQuery("Mustermann") |
|||
search := bleve.NewSearchRequest(query) |
|||
searchResults, err := index.Search(search) |
|||
if err != nil { |
|||
fmt.Println("error", err.Error()) |
|||
} |
|||
fmt.Println(searchResults) |
|||
return index, nil |
|||
} |
|||
@ -0,0 +1,168 @@ |
|||
package apperrors |
|||
|
|||
import ( |
|||
"net/http" |
|||
"strings" |
|||
) |
|||
|
|||
type APPError interface { |
|||
GetType() ErrorKey |
|||
GetHTTPStatus() int |
|||
GetDetail() string |
|||
SetDetail(string) |
|||
Error() string |
|||
} |
|||
|
|||
// Error response structure
|
|||
type Error struct { |
|||
Code int `json:"code"` |
|||
Type ErrorKey `json:"-"` |
|||
Message string `json:"message"` |
|||
Detail string `json:"detail"` |
|||
HTTPStatus int `json:"-"` |
|||
ObjectID uint64 `json:"-"` |
|||
ObjectName string `json:"-"` |
|||
} |
|||
|
|||
func (err Error) GetType() ErrorKey { |
|||
return err.Type |
|||
} |
|||
|
|||
func (err Error) GetDetail() string { |
|||
return err.Detail |
|||
} |
|||
|
|||
func (err Error) SetDetail(detail string) { |
|||
err.Detail = detail |
|||
} |
|||
|
|||
func (err Error) GetHTTPStatus() int { |
|||
return customErrors[err.Type].HTTPStatus |
|||
} |
|||
|
|||
func (err Error) Error() string { |
|||
return err.Detail |
|||
} |
|||
|
|||
func NewError(errType ErrorKey, detail string) (err APPError) { |
|||
switch errType { |
|||
case DBError, InvalidDBQuery: |
|||
detail = humanReadableDatabaseError(detail) |
|||
case ValidationFailed: |
|||
detail = humanReadableMarshalError(detail) |
|||
} |
|||
return Error{ |
|||
Type: errType, |
|||
Code: customErrors[errType].Code, |
|||
Message: customErrors[errType].Message, |
|||
Detail: detail, |
|||
HTTPStatus: customErrors[errType].HTTPStatus, |
|||
} |
|||
} |
|||
|
|||
// customErrors object contains self defined error codes and messages
|
|||
var customErrors = map[ErrorKey]Error{ |
|||
ItemNotFound: { |
|||
Code: 1000, |
|||
Message: MessageItemNotFound, |
|||
HTTPStatus: http.StatusNotFound, |
|||
}, |
|||
InvalidID: { |
|||
Code: 1001, |
|||
Message: MessageInvalidID, |
|||
HTTPStatus: http.StatusBadRequest, |
|||
}, |
|||
ValidationFailed: { |
|||
Code: 1002, |
|||
Message: MessageValidationFailed, |
|||
HTTPStatus: http.StatusBadRequest, |
|||
}, |
|||
PathNotFound: { |
|||
Code: 4000, |
|||
Message: MessagePathNotFound, |
|||
HTTPStatus: http.StatusNotFound, |
|||
}, |
|||
MalformedQueryParameter: { |
|||
Code: 4001, |
|||
Message: MessageMalformedQueryParameter, |
|||
HTTPStatus: http.StatusBadRequest, |
|||
}, |
|||
MalformedFilterString: { |
|||
Code: 4002, |
|||
Message: MessageMalformedFilterString, |
|||
HTTPStatus: http.StatusBadRequest, |
|||
}, |
|||
InvalidPagination: { |
|||
Code: 4003, |
|||
Message: MessageInvalidPagination, |
|||
HTTPStatus: http.StatusBadRequest, |
|||
}, |
|||
UnknownError: { |
|||
Code: 5000, |
|||
Message: MessageUnknownError, |
|||
HTTPStatus: http.StatusInternalServerError, |
|||
}, |
|||
DBError: { |
|||
Code: 5002, |
|||
Message: MessageDBError, |
|||
HTTPStatus: http.StatusInternalServerError, |
|||
}, |
|||
InvalidDBQuery: { |
|||
Code: 5003, |
|||
Message: MessageInvalidDBQuery, |
|||
HTTPStatus: http.StatusInternalServerError, |
|||
}, |
|||
EmptyDBResponse: { |
|||
Code: 5004, |
|||
Message: MessageEmptyDBResponse, |
|||
HTTPStatus: http.StatusNotFound, |
|||
}, |
|||
Unauthorized: { |
|||
Code: 6000, |
|||
Message: MessageUnauthorized, |
|||
HTTPStatus: http.StatusUnauthorized, |
|||
}, |
|||
Forbidden: { |
|||
Code: 6001, |
|||
Message: MessageForbidden, |
|||
HTTPStatus: http.StatusForbidden, |
|||
}, |
|||
ReportBug: { |
|||
Code: 9999, |
|||
Message: MessageReportBug, |
|||
HTTPStatus: http.StatusInternalServerError, |
|||
}, |
|||
} |
|||
|
|||
// humanReadableMarshalError translates some cryptic error messages
|
|||
func humanReadableMarshalError(detail string) string { |
|||
switch { |
|||
case strings.Contains(detail, "decode slice: expect [ or n, but found {"): |
|||
return "payload must be a list" |
|||
case strings.Contains(detail, "json: cannot unmarshal object into Go value of type ["): |
|||
return "payload must be a list" |
|||
case strings.Contains(detail, "strconv.ParseUint: parsing "): |
|||
return "id must be numeric" |
|||
case strings.Contains(detail, "json: cannot unmarshal array into Go value of type"), |
|||
strings.HasPrefix(detail, "readObjectStart: expect { or n, but found ["): |
|||
return "payload must be an object" |
|||
default: |
|||
return detail |
|||
} |
|||
} |
|||
|
|||
// humanReadableDatabaseError translates some cryptic error messages
|
|||
func humanReadableDatabaseError(detail string) string { |
|||
switch { |
|||
case strings.Contains(detail, "Cannot insert duplicate key row in object 'dbo.users' with unique index 'username'."): |
|||
return "username already in use, no duplicate usernames allowed" |
|||
case strings.Contains(detail, "Cannot insert duplicate key row in object 'dbo.roles' with unique index 'rolename'."): |
|||
return "rolename already in use, no duplicate rolenames allowed" |
|||
case (strings.Contains(detail, "Duplicate entry") && strings.HasSuffix(detail, "for key 'idx_property_name_tenant_sid'")): |
|||
return "propertyname already in use, no duplicate propertynames allowed: " + detail |
|||
case strings.Contains(detail, "Cannot insert duplicate key in object 'dbo.tenants'."): |
|||
return "Tenant name or id in use, no duplicates allowed" |
|||
default: |
|||
return detail |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
package apperrors |
|||
|
|||
type ErrorKey uint16 |
|||
|
|||
const ( |
|||
NoError ErrorKey = iota |
|||
PathNotFound |
|||
CacheError |
|||
DBError |
|||
InvalidDBQuery |
|||
EmptyDBResponse |
|||
ValidationFailed |
|||
UnknownError |
|||
InvalidID |
|||
MalformedFilterString |
|||
MalformedQueryParameter |
|||
ItemNotFound |
|||
InvalidPagination |
|||
Unauthorized |
|||
Forbidden |
|||
ReportBug |
|||
) |
|||
|
|||
// Custom error response messages
|
|||
const ( |
|||
MessagePathNotFound = "Path Not Found" |
|||
MessageCacheError = "Cache Error" |
|||
MessageDBError = "Database Error" |
|||
MessageInvalidDBQuery = "Invalid Database Query" |
|||
MessageEmptyDBResponse = "Empty DB Response" |
|||
MessageValidationFailed = "JSON Body Validation Failed" |
|||
MessageUnknownError = "Unknown Error" |
|||
MessageInvalidID = "Invalid ID" |
|||
MessageMalformedFilterString = "Malformed Filter String" |
|||
MessageMalformedQueryParameter = "Malformed Query Parameter" |
|||
MessageInvalidPagination = "Invalid Pagination Parameter" |
|||
MessageItemNotFound = "Item Not Found" |
|||
MessageUnauthorized = "Unauthorized" |
|||
MessageForbidden = "Forbidden" |
|||
MessageReportBug = "Bug found, please report" |
|||
) |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue