Пример #1
0
func (_ FilesResource) ApiCreate(res kit.Resource, obj kit.Model, r kit.Request) kit.Response {
	// Verify that tmp path is set either in metadata or on model.

	file := obj.(kit.File)
	if file.GetTmpPath() == "" {
		file.SetTmpPath(r.GetMeta().String("file"))
	}

	filePath := file.GetTmpPath()
	if filePath == "" {
		return kit.NewErrorResponse("no_tmp_path", "A tmp path must be set when creating a file", true)
	}

	tmpPath := getTmpPath(res)

	if !strings.HasPrefix(filePath, tmpPath) && filePath[0] != '/' {
		filePath = tmpPath + string(os.PathSeparator) + filePath
		file.SetTmpPath(filePath)
	}

	// Build the file, save it to backend and persist it to the db.
	err := res.Registry().FileService().BuildFile(file, r.GetUser(), true, true)
	if err != nil {
		kit.NewErrorResponse(err)
	}

	return &kit.AppResponse{
		Data: file,
	}
}
Пример #2
0
func (PageResource) BeforeDelete(res kit.Resource, obj kit.Model, user kit.User) apperror.Error {
	// Note: other relationship deletion handling happens in models BeforeDelete() hook.
	// Files have to be deleted here, since they require the fileService.

	// Delete files.
	fileService := res.Registry().FileService()

	m2m, err := res.Backend().M2M(obj, "Files")
	if err != nil {
		return err
	}

	files, err := m2m.All()
	if err != nil {
		return err
	}

	// Delete m2m relation.
	if err := m2m.Clear(); err != nil {
		return err
	}

	for _, file := range files {
		if err := fileService.Delete(file.(kit.File), user); err != nil {
			return err
		}
	}

	return nil
}
Пример #3
0
func (UserResource) AllowDelete(res kit.Resource, obj kit.Model, user kit.User) bool {
	if user == nil {
		return false
	}
	if obj.(kit.UserModel).GetUserId() == user.GetId() {
		return true
	}
	return user.HasRole("admin") || user.HasPermission(res.Collection()+".delete")
}
Пример #4
0
func (SessionResourceHooks) ApiDelete(res kit.Resource, id string, r kit.Request) kit.Response {
	if id != r.GetSession().GetStrId() {
		return kit.NewErrorResponse("permission_denied", "Permission denied", 403)
	}

	if err := res.Backend().Delete(r.GetSession()); err != nil {
		return kit.NewErrorResponse("db_delete_error", err, true)
	}

	return &kit.AppResponse{}
}
Пример #5
0
func getTmpPath(res kit.Resource) string {
	c := res.Registry().Config()
	tmpPath := c.UPath("tmpUploadDir")
	if tmpPath == "" {
		tmpPath = c.TmpDir()
		if tmpPath == "" {
			panic("config.TmpDir() empty")
		}
		tmpPath = path.Join(tmpPath, "uploads")
	}

	return tmpPath
}
Пример #6
0
func (s *Service) RegisterResource(res kit.Resource) {
	if res.Backend() == nil {
		if s.defaultBackend == nil {
			s.registry.Logger().Panic("Registering resource without backend, but no default backend set on resources.Service")
		}
		s.defaultBackend.RegisterModel(res.Model())
		res.SetBackend(s.defaultBackend)
	}

	if res.Collection() == "" {
		s.registry.Logger().Panic("Registering resource without a model type")
	}

	s.resources[res.Collection()] = res
}
Пример #7
0
func (hooks UserResourceHooks) ApiCreate(res kit.Resource, obj kit.Model, r kit.Request) kit.Response {
	meta := r.GetMeta()

	adaptor := meta.String("adaptor")
	if adaptor == "" {
		return kit.NewErrorResponse("adaptor_missing", "Expected 'adaptor' in metadata.", true)
	}

	rawData, ok := meta.Get("authData")
	if !ok {
		return kit.NewErrorResponse("auth_data_missing", "Expected 'authData' in metadata.", true)
	}

	data, ok := rawData.(map[string]interface{})
	if !ok {
		return kit.NewErrorResponse("invalid_auth_data", "Invalid auth data: expected dictionary", true)
	}

	user := obj.(kit.User)

	service := res.Registry().UserService()

	// If a profile model was registered, and profile data is in meta,
	// create the profile model.
	if profiles := service.ProfileResource(); profiles != nil {
		profile := profiles.CreateModel().(kit.UserProfile)

		if rawData, ok := meta.Get("profile"); ok {
			if data, ok := rawData.(map[string]interface{}); ok {
				// Profile data present in meta.
				// Update profile with data.
				if err := res.ModelInfo().UpdateModelFromData(profile, data); err != nil {
					return kit.NewErrorResponse("invalid_profile_data", "Invalid profile data.", err, true)
				}
			}
		}

		user.SetProfile(profile)
	}

	if err := service.CreateUser(user, adaptor, data); err != nil {
		return kit.NewErrorResponse(err)
	}

	return &kit.AppResponse{
		Data: user,
	}
}
Пример #8
0
func (PageResource) Methods(res kit.Resource) []kit.Method {

	publish := &methods.Method{
		Name:     "cms.page.publish",
		Blocking: true,
		Handler: func(registry kit.Registry, r kit.Request, unblock func()) kit.Response {
			user := r.GetUser()
			if user == nil || !user.HasRole("admin") {
				return kit.NewErrorResponse("permission_denied")
			}

			id := utils.GetMapStringKey(r.GetData(), "id")

			if id == "" {
				return kit.NewErrorResponse("no_id_in_data", "Expected 'id' key in data.")
			}

			rawPage, err := res.Backend().FindOne("pages", id)
			if err != nil {
				return kit.NewErrorResponse("db_error", err)
			} else if rawPage == nil {
				return kit.NewErrorResponse("not_found", "The specified page id does not exist.")
			}

			err = res.ModelInfo().UpdateModelFromData(rawPage, map[string]interface{}{
				"published":    true,
				"published_at": time.Now(),
			})

			if err != nil {
				return kit.NewErrorResponse("db_error", err)
			}

			return &kit.AppResponse{
				Data: map[string]interface{}{"success": true},
			}
		},
	}

	return []kit.Method{publish}
}
Пример #9
0
// ApiFindOne verifies the session and returns user and profile in meta if valid.
func (SessionResourceHooks) ApiFindOne(res kit.Resource, rawId string, r kit.Request) kit.Response {
	if rawId == "" {
		return kit.NewErrorResponse("empty_token", "Empty token")
	}

	userService := res.Registry().UserService()

	user, session, err := userService.VerifySession(rawId)
	if err != nil {
		return kit.NewErrorResponse(err)
	}

	meta := make(map[string]interface{})

	if user != nil {
		userData, err := res.Backend().ModelToMap(user, true, false)
		if err != nil {
			return kit.NewErrorResponse("marshal_error", err)
		}
		meta["user"] = userData

		if user.GetProfile() != nil {
			profileData, err := res.Backend().ModelToMap(user.GetProfile(), true, false)
			if err != nil {
				return kit.NewErrorResponse("marshal_error", err)
			}
			meta["profile"] = profileData
		}
	}

	return &kit.AppResponse{
		Data: session,
		Meta: meta,
	}
}
Пример #10
0
// Creating a session is equivalent to logging in.
func (hooks SessionResourceHooks) ApiCreate(res kit.Resource, obj kit.Model, r kit.Request) kit.Response {
	userService := res.Registry().UserService()

	meta := r.GetMeta()

	isAnonymous, _ := meta.Bool("anonymous")

	// Find user.
	userIdentifier := meta.String("user")
	adaptor := meta.String("adaptor")
	data, _ := meta.Map("authData")

	var user kit.User
	if !isAnonymous {
		if adaptor == "" {
			return kit.NewErrorResponse("adaptor_missing", "Expected 'adaptor' in metadata.", true)
		}

		if data == nil {
			kit.NewErrorResponse("no_or_invalid_auth_data", "Expected 'authData' dictionary in metadata.")
		}

		var err apperror.Error
		user, err = userService.AuthenticateUser(userIdentifier, adaptor, data)
		if err != nil {
			return kit.NewErrorResponse(err)
		}
	}

	session, err := userService.StartSession(user, r.GetFrontend())
	if err != nil {
		return kit.NewErrorResponse(err)
	}

	responseMeta := make(map[string]interface{})

	if !isAnonymous {
		userData, err := res.Backend().ModelToMap(user, true, false)
		if err != nil {
			return kit.NewErrorResponse("marshal_error", err)
		}
		responseMeta["user"] = userData

		if user.GetProfile() != nil {
			profileData, err := res.Backend().ModelToMap(user.GetProfile(), true, false)
			if err != nil {
				return kit.NewErrorResponse("marshal_error", err)
			}
			responseMeta["profile"] = profileData
		}
	}

	return &kit.AppResponse{
		Data: session,
		Meta: responseMeta,
	}
}
Пример #11
0
func (UserResourceHooks) Methods(res kit.Resource) []kit.Method {
	sendConfirmationEmail := &methods.Method{
		Name:     "users.send-confirmation-email",
		Blocking: false,
		Handler: func(registry kit.Registry, r kit.Request, unblock func()) kit.Response {
			user := r.GetUser()
			if user == nil {
				return kit.NewErrorResponse("not_authenticated", "")
			}

			if user.IsEmailConfirmed() {
				return kit.NewErrorResponse("email_already_confirmed", "The users email address is already confirmed")
			}

			err := registry.UserService().SendConfirmationEmail(user)
			if err != nil {
				return kit.NewErrorResponse("confirm_failed", "Could not confirm email")
			}

			return &kit.AppResponse{
				Data: map[string]interface{}{"success": true},
			}
		},
	}

	confirmEmail := &methods.Method{
		Name:     "users.confirm-email",
		Blocking: false,
		Handler: func(registry kit.Registry, r kit.Request, unblock func()) kit.Response {
			data, ok := r.GetData().(map[string]interface{})
			if !ok {
				return kit.NewErrorResponse("invalid_data", "Expected data dict with 'token' key")
			}
			token, ok := data["token"].(string)
			if !ok {
				return kit.NewErrorResponse("invalid_data", "Expected 'token' string key in data")
			}
			if token == "" {
				return kit.NewErrorResponse("empty_token", "")
			}

			_, err := registry.UserService().ConfirmEmail(token)
			if err != nil {
				return kit.NewErrorResponse("confirm_failed", "Could not confirm email")
			}

			return &kit.AppResponse{
				Data: map[string]interface{}{"success": true},
			}
		},
	}

	requestPwReset := &methods.Method{
		Name:     "users.request-password-reset",
		Blocking: false,
		Handler: func(registry kit.Registry, r kit.Request, unblock func()) kit.Response {
			data, ok := r.GetData().(map[string]interface{})
			if !ok {
				return kit.NewErrorResponse("invalid_data", "Expected data dict with 'user' key", true)
			}

			userIdentifier, ok := data["user"].(string)
			if !ok {
				return kit.NewErrorResponse("invalid_data", "Expected data dict with 'user' string key", true)
			}

			rawUser, err := res.Q().Filter("email", userIdentifier).Or("username", userIdentifier).First()
			if err != nil {
				return kit.NewErrorResponse(err)
			}
			if rawUser == nil {
				return kit.NewErrorResponse("unknown_user", fmt.Sprintf("The user %v does not exist", userIdentifier), true)
			}

			user := rawUser.(kit.User)

			err = registry.UserService().SendPasswordResetEmail(user)
			if err != nil {
				registry.Logger().Errorf("Could not send password reset email for user %v: %v", user, err)
				return kit.NewErrorResponse("reset_email_send_failed", "Could not send the reset password mail.", true)
			}

			return &kit.AppResponse{
				Data: map[string]interface{}{"success": true},
			}
		},
	}

	pwReset := &methods.Method{
		Name:     "users.password-reset",
		Blocking: false,
		Handler: func(registry kit.Registry, r kit.Request, unblock func()) kit.Response {
			// Verify that token is in data.
			data, ok := r.GetData().(map[string]interface{})
			if !ok {
				return kit.NewErrorResponse("invalid_data", "Expected 'token' key in data", true)
			}
			token, ok := data["token"].(string)
			if !ok {
				return kit.NewErrorResponse("invalid_data", "Expected 'token' string key in data", true)
			}
			if token == "" {
				return kit.NewErrorResponse("empty_token", "", true)
			}

			// Verify that password is in data.
			newPw, ok := data["password"].(string)
			if !ok {
				return kit.NewErrorResponse("invalid_passord", "Expected 'password' string key in data", true)
			}
			if newPw == "" {
				return kit.NewErrorResponse("empty_password", "Password may not be empty", true)
			}

			user, err := registry.UserService().ResetPassword(token, newPw)
			if err != nil {
				if err.IsPublic() {
					return kit.NewErrorResponse(err)
				} else {
					return kit.NewErrorResponse("password_reset_failed", "Could not reset the password.", true)
				}
			}

			return &kit.AppResponse{
				Data: map[string]interface{}{
					"success":   true,
					"userId":    user.GetId(),
					"userEmail": user.GetEmail(),
				},
			}
		},
	}

	changePassword := &methods.Method{
		Name:     "users.change-password",
		Blocking: false,
		Handler: func(registry kit.Registry, r kit.Request, unblock func()) kit.Response {
			// Get userId and password from request.
			userId := utils.GetMapStringKey(r.GetData(), "userId")
			if userId == "" {
				return kit.NewErrorResponse("no_userid", "Expected userId key in data", true)
			}
			password := utils.GetMapStringKey(r.GetData(), "password")
			if password == "" {
				return kit.NewErrorResponse("no_password", "Expected password key in data", true)
			}

			// Permission check.
			user := r.GetUser()
			if user == nil {
				return kit.NewErrorResponse("permission_denied", true)
			}

			// Users can only change their own password, unless they are admins.
			if userId != user.GetStrId() {
				if !(user.HasRole("admin") || user.HasPermission("users.change_passwords")) {
					return kit.NewErrorResponse("permission_denied", true)
				}
			}

			// User has the right permissions.
			userService := registry.UserService()

			// Find the user.
			rawUser, err := userService.UserResource().FindOne(userId)
			if err != nil {
				return kit.NewErrorResponse("db_error", true, err)
			}
			if rawUser == nil {
				return kit.NewErrorResponse("user_does_not_exist", true)
			}

			targetUser := rawUser.(kit.User)

			if err := userService.ChangePassword(targetUser, password); err != nil {
				return kit.NewErrorResponse(err)
			}

			// Everything worked fine.
			return &kit.AppResponse{
				Data: map[string]interface{}{"success": true},
			}
		},
	}

	return []kit.Method{
		AuthenticateMethod,
		ResumeSessionMethod,
		UnAuthenticateMethod,

		sendConfirmationEmail,
		confirmEmail,
		requestPwReset,
		pwReset, changePassword,
	}
}
Пример #12
0
func (hooks FilesResource) HttpRoutes(res kit.Resource) []kit.HttpRoute {
	maxRunning := res.Registry().Config().UInt("files.thumbGenerator.maxRunning", 10)
	maxPerIPPerMinute := res.Registry().Config().UInt("files.thumbGenerator.maxPerIPPerMinute", 100)
	maxQueueSize := res.Registry().Config().UInt("files.thumbGenerator.maxQueueSize", 100)
	hooks.thumbnailRateLimiter = newRateLimiter(maxRunning, maxPerIPPerMinute, maxQueueSize)

	routes := make([]kit.HttpRoute, 0)

	// Upload route.
	uploadOptionsHandler := func(registry kit.Registry, r kit.Request) (kit.Response, bool) {
		header := r.GetHttpResponseWriter().Header()

		allowedOrigins := registry.Config().UString("fileHandler.allowedOrigins", "*")
		header.Set("Access-Control-Allow-Origin", allowedOrigins)

		header.Set("Access-Control-Allow-Methods", "OPTIONS, POST")

		allowedHeaders := registry.Config().UString("accessControl.allowedHeaders")
		if allowedHeaders == "" {
			allowedHeaders = "Authentication, Content-Type, Content-Range, Content-Disposition"
		} else {
			allowedHeaders += ", Authentication, Content-Type, Content-Range, Content-Disposition"
		}
		header.Set("Access-Control-Allow-Headers", allowedHeaders)

		return &kit.AppResponse{
			HttpStatus: 200,
			RawData:    []byte{},
		}, true
	}

	uploadOptionsRoute := kit.NewHttpRoute("/api/file-upload", "OPTIONS", uploadOptionsHandler)
	routes = append(routes, uploadOptionsRoute)

	tmpPath := getTmpPath(res)
	if tmpPath == "" {
		panic("Empty tmp path")
	}

	uploadHandler := func(registry kit.Registry, r kit.Request) (kit.Response, bool) {
		if registry.Config().UBool("fileHandler.requiresAuth", false) {
			if r.GetUser() == nil {
				return kit.NewErrorResponse("permission_denied", ""), false
			}
		}

		var files []string
		var err apperror.Error

		if err == nil {
			files, err = handleUpload(registry, tmpPath, r.GetHttpRequest())
			if err != nil {
				return kit.NewErrorResponse(err), false
			}
		}

		data := map[string]interface{}{
			"data": files,
		}

		return &kit.AppResponse{Data: data}, false
	}
	uploadRoute := kit.NewHttpRoute("/api/file-upload", "POST", uploadHandler)
	routes = append(routes, uploadRoute)

	serveFileHandler := func(registry kit.Registry, r kit.Request) (kit.Response, bool) {
		file, err := registry.FileService().FindOne(r.GetContext().String("id"))

		if err != nil {
			return kit.NewErrorResponse(err), false
		}

		if file == nil {
			return &kit.AppResponse{
				HttpStatus: 404,
				RawData:    []byte("File not found"),
			}, false
		}

		reader, err := file.Reader()
		if err != nil {
			return kit.NewErrorResponse(err), false
		}
		defer reader.Close()

		w := r.GetHttpResponseWriter()

		err = serveFile(w, r.GetHttpRequest(), file.GetMime(), file.GetSize(), reader)
		reader.Close()

		if err != nil {
			registry.Logger().Errorf("Error while serving file %v(%v): %v", file.GetId(), file.GetBackendId(), err)
		}

		return nil, true
	}
	serveFileRoute := kit.NewHttpRoute("/files/:id/*rest", "GET", serveFileHandler)
	routes = append(routes, serveFileRoute)

	serveImageHandler := func(registry kit.Registry, r kit.Request) (kit.Response, bool) {
		file, err := registry.FileService().FindOne(r.GetContext().String("id"))
		if err != nil {
			return kit.NewErrorResponse(err), false
		}

		if file == nil {
			return &kit.AppResponse{
				HttpStatus: 404,
				RawData:    []byte("File not found"),
			}, false
		}

		if !file.GetIsImage() {
			return &kit.AppResponse{
				Error: &apperror.Err{
					Code:    "file_is_no_image",
					Message: "The requested file is not an image",
				},
			}, false
		}

		httpRequest := r.GetHttpRequest()

		query := httpRequest.URL.Query()
		rawWidth := query.Get("width")
		rawHeight := query.Get("height")

		var width, height int64

		if rawWidth != "" {
			width, _ = strconv.ParseInt(rawWidth, 10, 64)
		}
		if rawHeight != "" {
			height, _ = strconv.ParseInt(rawHeight, 10, 64)
		}

		rawFilters := query.Get("filters")
		filters := strings.Split(rawFilters, ",")

		thumbDir := registry.Config().UString("thumbnailDir")
		if thumbDir == "" {
			thumbDir = tmpPath + string(os.PathSeparator) + "thumbnails"
		}

		ip := strings.Split(httpRequest.RemoteAddr, ":")[0]
		if ip == "" {
			ip = httpRequest.Header.Get("X-Forwarded-For")
		}

		reader, size, err := hooks.getImageReader(registry, thumbDir, file, width, height, filters, ip)
		if err != nil {
			return kit.NewErrorResponse(err), false
		}

		w := r.GetHttpResponseWriter()

		err = serveFile(w, httpRequest, file.GetMime(), size, reader)
		reader.Close()

		if err != nil {
			registry.Logger().Errorf("Error while serving image %v(%v): %v", file.GetId(), file.GetBackendId(), err)
		}

		return nil, true
	}
	serveImageRoute := kit.NewHttpRoute("/images/:id/*rest", "GET", serveImageHandler)
	routes = append(routes, serveImageRoute)

	return routes
}
Пример #13
0
func (AdminResource) AllowDelete(res kit.Resource, obj kit.Model, user kit.User) bool {
	return user != nil && (user.HasRole("admin") || user.HasPermission(res.Collection()+".delete"))
}
Пример #14
0
func (a *App) RegisterResource(res kit.Resource) {
	if res.Backend() == nil {
		if a.registry.DefaultBackend() == nil {
			a.Logger().Panic("Registering resource without backend, but no default backend set.")
		}

		// Set backend.
		res.SetBackend(a.registry.DefaultBackend())
	}

	if res.Registry() == nil {
		res.SetRegistry(a.registry)
	}

	res.SetDebug(a.Debug())

	// Allow a resource to register custom http routes and methods.
	if res.Hooks() != nil {

		// Handle http routes.
		if resRoutes, ok := res.Hooks().(resources.ApiHttpRoutes); ok {
			for _, route := range resRoutes.HttpRoutes(res) {
				a.RegisterHttpHandler(route.Method(), route.Route(), route.Handler())
			}
		}

		// Handle methods.
		if resMethods, ok := res.Hooks().(resources.MethodsHook); ok {
			for _, method := range resMethods.Methods(res) {
				a.RegisterMethod(method)
			}
		}
	}

	a.registry.AddResource(res)
}
Пример #15
0
func (d *Registry) AddResource(res kit.Resource) {
	d.resources[res.Collection()] = res
}
Пример #16
0
func Find(res kit.Resource, request kit.Request) (kit.Response, apperror.Error) {
	collection := res.Collection()

	info := res.Backend().ModelInfo(collection)

	var query *db.Query

	jsonQuery := request.GetContext().String("query")
	if jsonQuery != "" {
		var rawQuery map[string]interface{}
		if err := json.Unmarshal([]byte(jsonQuery), &rawQuery); err != nil {
			return nil, apperror.Wrap(err, "invalid_query_json")
		}

		rawQuery["collection"] = collection

		// A custom query was supplied.
		// Try to parse the query.
		var err apperror.Error
		query, err = db.ParseQuery(res.Backend(), rawQuery)
		if err != nil {
			return nil, apperror.Wrap(err, "invalid_query", "", false)
		}
	}

	if query == nil {
		query = res.Backend().Q(collection)
	}

	// Check paging parameters.
	var limit, offset int

	context := request.GetContext()

	if context.Has("limit") {
		val, err := context.Int("limit")
		if err != nil {
			return nil, &apperror.Err{
				Public:  true,
				Code:    "non_numeric_limit_parameter",
				Message: "The get query contains a non-numeric ?limit",
			}
		}
		limit = val
	}

	if context.Has("offset") {
		val, err := context.Int("offset")
		if err != nil {
			return nil, &apperror.Err{
				Public:  true,
				Code:    "non_numeric_offset_parameter",
				Message: "The get query contains a non-numeric ?offset",
			}
		}
		offset = val
	}

	var page, perPage int

	if context.Has("page") {
		val, err := context.Int("page")
		if err != nil {
			return nil, &apperror.Err{
				Public:  true,
				Code:    "non_numeric_page_parameter",
				Message: "The get query contains a non-numeric ?page",
			}
		}
		page = val
	}

	if context.Has("per_page") {
		val, err := context.Int("per_page")
		if err != nil {
			return nil, &apperror.Err{
				Public:  true,
				Code:    "non_numeric_per_page_parameter",
				Message: "The get query contains a non-numeric ?per_page",
			}
		}
		perPage = val
	}

	if perPage > 0 {
		limit = perPage
	}

	if page > 1 {
		offset = (page - 1) * limit
	}

	if limit > 0 {
		query.Limit(int(limit)).Offset(int(offset))
	}

	// Add joins.
	if context.Has("joins") {
		parts := strings.Split(context.String("joins"), ",")
		for _, name := range parts {
			relation := info.FindRelation(name)

			if relation == nil {
				return nil, &apperror.Err{
					Code:    "invalid_join",
					Message: fmt.Sprintf("Tried to join a NON-existant relationship %v", name),
					Public:  true,
				}
			}

			query.Join(relation.Name())
		}
	}

	// Add filters.
	if context.Has("filters") {
		parts := strings.Split(context.String("filters"), ",")
		for _, filter := range parts {
			filterParts := strings.Split(filter, ":")

			if len(filterParts) != 2 {
				return nil, &apperror.Err{
					Public:  true,
					Code:    "invalid_filter",
					Message: fmt.Sprintf("Invalid filter: %v", filter),
				}
			}

			fieldName := filterParts[0]
			// COnvert id query to pk field.
			if fieldName == "id" {
				fieldName = info.PkAttribute().BackendName()
			}

			var typ reflect.Type

			if attr := info.FindAttribute(fieldName); attr != nil {
				fieldName = attr.BackendName()
				typ = attr.Type()
			} else if rel := info.FindRelation(fieldName); rel != nil {
				if rel.RelationType() == db.RELATION_TYPE_HAS_ONE {
					fieldName = rel.LocalField()
					typ = info.Attribute(rel.LocalField()).Type()
				} else {
					return nil, &apperror.Err{
						Public:  true,
						Code:    "cant_filter_on_relation",
						Message: fmt.Sprintf("Tried to filter on relationship field %v (only possible for has-one relations)", fieldName),
					}
				}
			} else {
				return nil, &apperror.Err{
					Public:  true,
					Code:    "filter_for_inexistant_field",
					Message: fmt.Sprintf("Tried to filter with inexistant field %v", fieldName),
				}
			}

			converted, err := reflector.R(filterParts[1]).ConvertTo(typ)
			if err != nil {
				return nil, &apperror.Err{
					Public: true,
					Code:   "inconvertible_filter_value",
					Message: fmt.Sprintf("Coult not convert filter value %v for field %v (should be %v)",
						filterParts[1], fieldName, typ),
				}
			}
			query.Filter(fieldName, converted)
		}
	}

	return res.ApiFind(query, request), nil
}