3
0

Add records limit count per namespace

This commit is contained in:
Mumbi Francis
2024-10-31 14:32:09 +03:00
committed by Jože Fortun
parent 05152a9ef7
commit ca5604121a
19 changed files with 287 additions and 204 deletions
@@ -22,3 +22,4 @@ errors:
staleData: stale data
unknownBulkOperation: unknown bulk operation {bulkOperation}
valueInput: invalid record value input
maxRecordsReached: maximum number of records per namespace reached
+50 -44
View File
@@ -33,7 +33,7 @@
###############################################################################
# Allow insecure (invalid, expired TLS/SSL certificates) connections.
#
#
# [IMPORTANT]
# ====
# We strongly recommend keeping this value set to false except for local development or demos.
@@ -203,7 +203,7 @@
###############################################################################
# Password for the web console endpoint. When running in dev environment, password is not required.
#
#
# Corteza intentionally sets default password to random chars to prevent security incidents.
# Type: string
# Default: <no value>
@@ -290,7 +290,7 @@
# Email sending
#
# Configure your local SMTP server or use one of the available providers.
#
#
# These values are copied to settings when the server starts and can be managed from the administration console.
# We recommend you remove these values after they are copied to settings.
# If server detects difference between these options and settings, it shows a warning in the log on server start.
@@ -430,7 +430,7 @@
###############################################################################
# Password security allows you to disable constraints to which passwords must conform to.
#
#
# [CAUTION]
# ====
# Disabling password security can be useful for development environments as it removes the need for complex passwords.
@@ -442,12 +442,12 @@
###############################################################################
# Algoritm to be use for JWT signature.
#
#
# Supported valus:
# - HS256, HS384, HS512
# - PS256, PS384, PS512,
# - RS256, RS384, RS512
#
#
# Provide shared secret string for HS256, HS384, HS512 and full private key or path to the file PS* and RS* algorithms.
# Type: string
# Default: HS512
@@ -456,7 +456,7 @@
###############################################################################
# Secret used for signing JWT tokens.
# Value is used only when HS256, HS384 or HS512 algorithm is used.
#
#
# [IMPORTANT]
# ====
# If secret is not set, system auto-generates one from DB_DSN and HOSTNAME environment variables.
@@ -480,7 +480,7 @@
###############################################################################
# Lifetime of the refresh token. Should be much longer than lifetime of the access token.
#
#
# Refresh tokens are used to exchange expired access tokens with new ones.
# Type: time.Duration
# Default: 72h
@@ -488,7 +488,7 @@
###############################################################################
# Redirect URL to be sent with OAuth2 authentication request to provider
#
#
# `provider` placeholder is replaced with the actual value when used.
# Type: string
# Default: <no value>
@@ -496,7 +496,7 @@
###############################################################################
# Secret used for securing cookies
#
#
# [IMPORTANT]
# ====
# If secret is not set, system auto-generates one from DB_DSN and HOSTNAME environment variables.
@@ -539,21 +539,21 @@
###############################################################################
# Maximum time user is allowed to stay idle when logged in without "remember-me" option and before session is expired.
#
#
# Recomended value is between an hour and a day.
#
#
# [IMPORTANT]
# ====
# This affects only profile (/auth) pages. Using applications (admin, compose, ...) does not prolong the session.
# ====
#
#
# Type: time.Duration
# Default: 24h
# AUTH_SESSION_LIFETIME=24h
###############################################################################
# Duration of the session in /auth lasts when user logs-in with "remember-me" option.
#
#
# If set to 0, "remember-me" option is removed.
# Type: time.Duration
# Default: 8640h
@@ -580,7 +580,7 @@
###############################################################################
# Secret used for securing CSRF protection
#
#
# [IMPORTANT]
# ====
# If secret is not set, system auto-generates one from DB_DSN and HOSTNAME environment variables.
@@ -610,19 +610,19 @@
###############################################################################
# Handle for OAuth2 client used for automatic redirect from /auth/oauth2/go endpoint.
#
#
# This simplifies configuration for OAuth2 flow for Corteza Web applications as it removes
# the need to suply redirection URL and client ID (oauth2/go endpoint does that internally)
#
#
# Type: string
# Default: corteza-webapp
# AUTH_DEFAULT_CLIENT=corteza-webapp
###############################################################################
# Path to js, css, images and template source files
#
#
# When corteza starts, if path exists it tries to load template files from it.
#
#
# When empty path is set (default value), embedded files are used.
# Type: string
# Default: <no value>
@@ -631,7 +631,7 @@
###############################################################################
# When enabled, corteza reloads template before every execution.
# Enable this for debugging or when developing auth templates.
#
#
# Should be disabled in production where templates do not change between server restarts.
# Type: bool
# Default: <no value>
@@ -640,7 +640,7 @@
###############################################################################
# When set, Corteza creates one or more users with the configured values using provided email as a password.
# It skips existing (email, handle). All new users are assigned to all bypass roles.
#
#
# When set in production, Corteza stops and reports an error
# Type: string
# Default: <no value>
@@ -810,6 +810,12 @@
# Default: <no value>
# LIMIT_SYSTEM_USERS=<no value>
###############################################################################
# Maximum number of records per namespace
# Type: int
# Default: <no value>
# LIMIT_RECORD_COUNT_PER_NAMESPACE=<no value>
###############################################################################
###############################################################################
# locale
@@ -818,16 +824,16 @@
###############################################################################
# List of compa delimited languages (language tags) to enable.
# In case when an enabled language can not be loaded, error is logged.
#
#
# When loading language configurations (config.xml) from the configured path(s).
#
#
# Type: string
# Default: en
# LOCALE_LANGUAGES=en
###############################################################################
# One or more paths to locale config and translation files, separated by colon
#
#
# When with LOCALE_DEVELOPMENT_MODE=true, default value for path is ../../locale
# Type: string
# Default: <no value>
@@ -837,7 +843,7 @@
# Name of the query string parameter used to pass the language tag (it overrides Accept-Language header).
# Set it to empty string to disable detection from the query string.
# This parameter is ignored if only one language is enabled
#
#
# Type: string
# Default: lng
# LOCALE_QUERY_STRING_PARAM=lng
@@ -868,9 +874,9 @@
###############################################################################
# Disables json format for logging and enables more human-readable output with colors.
#
#
# Disable for production.
#
#
# Type: bool
# Default: <no value>
# LOG_DEBUG=<no value>
@@ -878,11 +884,11 @@
###############################################################################
# Minimum logging level. If set to "warn",
# Levels warn, error, dpanic panic and fatal will be logged.
#
#
# Recommended value for production: warn
#
#
# Possible values: debug, info, warn, error, dpanic, panic, fatal
#
#
# Type: string
# Default: warn
# LOG_LEVEL=warn
@@ -890,25 +896,25 @@
###############################################################################
# Log filtering rules by level and name (log-level:log-namespace).
# Please note that level (LOG_LEVEL) is applied before filter and it affects the final output!
#
#
# Leave unset for production.
#
#
# Example:
# `warn+:* *:auth,workflow.*`
# Log warnings, errors, panic, fatals. Everything from auth and workflow is logged.
#
#
#
#
# See more examples and documentation here: https://github.com/moul/zapfilter
#
#
# Type: string
# Default: <no value>
# LOG_FILTER=<no value>
###############################################################################
# Set to true to see where the logging was called from.
#
#
# Disable for production.
#
#
# Type: bool
# Default: <no value>
# LOG_INCLUDE_CALLER=<no value>
@@ -916,9 +922,9 @@
###############################################################################
# Include stack-trace when logging at a specified level or below.
# Disable for production.
#
#
# Possible values: debug, info, warn, error, dpanic, panic, fatal
#
#
# Type: string
# Default: dpanic
# LOG_STACKTRACE_LEVEL=dpanic
@@ -1013,11 +1019,11 @@
#
# Provisioning allows you to configure a {PRODUCT_NAME} instance when deployed.
# It occurs automatically after the {PRODUCT_NAME} server starts.
#
#
# [IMPORTANT]
# ====
# We recommend you to keep provisioning enabled as it simplifies version updates by updating the database and updating settings.
#
#
# If you're doing local development or some debugging, you can disable this.
# ====
#
@@ -1043,7 +1049,7 @@
# ====
# These parameters help in the development and testing process.
# When you are deploying to production, these should be disabled to improve performance and reduce storage usage.
#
#
# You should configure external services such as Sentry or ELK to keep track of logs and error reports.
# ====
#
@@ -1149,7 +1155,7 @@
# Delay system startup
#
# You can configure these options to defer API execution until another external (HTTP) service is up and running.
#
#
# [ TIP ]
# ====
# Delaying API execution can come in handy in complex setups where execution order is important.
@@ -1173,7 +1179,7 @@
###############################################################################
# Space delimited list of hosts and/or URLs to probe.
# Host format: `host` or `host:443` (port will default to 80).
#
#
# [NOTE]
# ====
# Services are probed in parallel.
+49 -48
View File
@@ -1,53 +1,53 @@
package app
import (
"context"
"crypto/tls"
"fmt"
"net/url"
"os"
"regexp"
"strings"
"time"
"context"
"crypto/tls"
"fmt"
"net/url"
"os"
"regexp"
"strings"
"time"
authService "github.com/cortezaproject/corteza/server/auth"
"github.com/cortezaproject/corteza/server/auth/saml"
authSettings "github.com/cortezaproject/corteza/server/auth/settings"
autService "github.com/cortezaproject/corteza/server/automation/service"
cmpService "github.com/cortezaproject/corteza/server/compose/service"
cmpEvent "github.com/cortezaproject/corteza/server/compose/service/event"
discoveryService "github.com/cortezaproject/corteza/server/discovery/service"
fedService "github.com/cortezaproject/corteza/server/federation/service"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/apigw"
apigwTypes "github.com/cortezaproject/corteza/server/pkg/apigw/types"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/corredor"
"github.com/cortezaproject/corteza/server/pkg/eventbus"
"github.com/cortezaproject/corteza/server/pkg/healthcheck"
"github.com/cortezaproject/corteza/server/pkg/http"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/pkg/locale"
"github.com/cortezaproject/corteza/server/pkg/logger"
"github.com/cortezaproject/corteza/server/pkg/mail"
"github.com/cortezaproject/corteza/server/pkg/messagebus"
"github.com/cortezaproject/corteza/server/pkg/monitor"
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/cortezaproject/corteza/server/pkg/provision"
"github.com/cortezaproject/corteza/server/pkg/rbac"
"github.com/cortezaproject/corteza/server/pkg/scheduler"
"github.com/cortezaproject/corteza/server/pkg/sentry"
"github.com/cortezaproject/corteza/server/pkg/valuestore"
"github.com/cortezaproject/corteza/server/pkg/version"
"github.com/cortezaproject/corteza/server/pkg/websocket"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
sysService "github.com/cortezaproject/corteza/server/system/service"
sysEvent "github.com/cortezaproject/corteza/server/system/service/event"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/lestrrat-go/jwx/jwt"
"go.uber.org/zap"
gomail "gopkg.in/mail.v2"
authService "github.com/cortezaproject/corteza/server/auth"
"github.com/cortezaproject/corteza/server/auth/saml"
authSettings "github.com/cortezaproject/corteza/server/auth/settings"
autService "github.com/cortezaproject/corteza/server/automation/service"
cmpService "github.com/cortezaproject/corteza/server/compose/service"
cmpEvent "github.com/cortezaproject/corteza/server/compose/service/event"
discoveryService "github.com/cortezaproject/corteza/server/discovery/service"
fedService "github.com/cortezaproject/corteza/server/federation/service"
"github.com/cortezaproject/corteza/server/pkg/actionlog"
"github.com/cortezaproject/corteza/server/pkg/apigw"
apigwTypes "github.com/cortezaproject/corteza/server/pkg/apigw/types"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/corredor"
"github.com/cortezaproject/corteza/server/pkg/eventbus"
"github.com/cortezaproject/corteza/server/pkg/healthcheck"
"github.com/cortezaproject/corteza/server/pkg/http"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/pkg/locale"
"github.com/cortezaproject/corteza/server/pkg/logger"
"github.com/cortezaproject/corteza/server/pkg/mail"
"github.com/cortezaproject/corteza/server/pkg/messagebus"
"github.com/cortezaproject/corteza/server/pkg/monitor"
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/cortezaproject/corteza/server/pkg/provision"
"github.com/cortezaproject/corteza/server/pkg/rbac"
"github.com/cortezaproject/corteza/server/pkg/scheduler"
"github.com/cortezaproject/corteza/server/pkg/sentry"
"github.com/cortezaproject/corteza/server/pkg/valuestore"
"github.com/cortezaproject/corteza/server/pkg/version"
"github.com/cortezaproject/corteza/server/pkg/websocket"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
sysService "github.com/cortezaproject/corteza/server/system/service"
sysEvent "github.com/cortezaproject/corteza/server/system/service/event"
"github.com/cortezaproject/corteza/server/system/types"
"github.com/lestrrat-go/jwx/jwt"
"go.uber.org/zap"
gomail "gopkg.in/mail.v2"
)
const (
@@ -416,6 +416,7 @@ func (app *CortezaApp) InitServices(ctx context.Context) (err error) {
ActionLog: app.Opt.ActionLog,
Discovery: app.Opt.Discovery,
Storage: app.Opt.ObjStore,
Limit: app.Opt.Limit,
UserFinder: sysService.DefaultUser,
SchemaAltManager: sysService.DefaultDalSchemaAlteration,
})
@@ -562,9 +563,9 @@ func (app *CortezaApp) Activate(ctx context.Context) (err error) {
app.AuthService.Watch(ctx)
updateSassInstallSettings(ctx, sysService.DefaultStylesheet.SassInstalled(), app.Log)
updateSassInstallSettings(ctx, sysService.DefaultStylesheet.SassInstalled(), app.Log)
//Generate CSS for webapps
if err = sysService.DefaultStylesheet.GenerateCSS(sysService.CurrentSettings, app.Opt.Webapp.ScssDirPath, app.Log); err != nil {
if err = sysService.DefaultStylesheet.GenerateCSS(sysService.CurrentSettings, app.Opt.Webapp.ScssDirPath, app.Log); err != nil {
return fmt.Errorf("could not generate css for webapps: %w", err)
}
+4
View File
@@ -11,6 +11,10 @@ limit: schema.#optionsGroup & {
type: "int"
description: "Maximum number of valid (not deleted, not suspended) users"
}
record_count_per_namespace: {
type: "int"
description: "Maximum number of records per namespace"
}
}
title: "Limits"
}
+1 -1
View File
@@ -94,7 +94,7 @@ func (app *CortezaApp) mountHttpRoutes(r chi.Router) {
}
if app.Opt.Federation.Enabled {
r.Route("/federation", federationRest.MountRoutes())
r.Route("/federation", federationRest.MountRoutes(app.Opt.Limit))
}
var fullpathDocs = options.CleanBase(ho.BaseUrl, ho.ApiBaseUrl, "docs")
+18 -1
View File
@@ -3,7 +3,6 @@ package dalutils
import (
"context"
"fmt"
"github.com/cortezaproject/corteza/server/compose/types"
"github.com/cortezaproject/corteza/server/pkg/dal"
"github.com/cortezaproject/corteza/server/pkg/filter"
@@ -29,6 +28,10 @@ type (
deleter interface {
Delete(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, pkv ...dal.ValueGetter) (err error)
}
counter interface {
Count(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, f filter.Filter) (uint, error)
}
)
// ComposeRecordsList iterates over results and collects all available records
@@ -65,6 +68,20 @@ func ComposeRecordsFind(ctx context.Context, l lookuper, mod *types.Module, reco
return
}
func ComposeRecordsCount(ctx context.Context, c counter, mod *types.Module, filter types.RecordFilter) (cnt uint, err error) {
constraints := map[string][]interface{}{
"namespaceID": {mod.NamespaceID},
}
if filter.ModuleID != 0 {
constraints["moduleID"] = []interface{}{filter.ModuleID}
}
dalFilter := filter.ToConstraintedFilter(constraints)
return c.Count(ctx, mod.ModelRef(), recLookupOperations(mod), dalFilter)
}
func ComposeRecordCreate(ctx context.Context, c creator, mod *types.Module, records ...*types.Record) (err error) {
return c.Create(ctx, mod.ModelRef(), recCreateOperations(mod), recToGetters(records...)...)
}
+1
View File
@@ -12,6 +12,7 @@ type (
Create(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, vv ...dal.ValueGetter) error
Update(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, rr ...dal.ValueGetter) (err error)
Search(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, f filter.Filter) (dal.Iterator, error)
Count(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, f filter.Filter) (uint, error)
Run(ctx context.Context, pp dal.Pipeline) (dal.Iterator, error)
Lookup(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, lookup dal.ValueGetter, dst dal.ValueSetter) (err error)
Delete(ctx context.Context, m dal.ModelRef, operations dal.OperationSet, pkv ...dal.ValueGetter) (err error)
+35 -1
View File
@@ -52,6 +52,8 @@ type (
namespace namespaceFinder
module moduleFinder
opt RecordOptions
revisions *recordRevisions
formatter recordValuesFormatter
@@ -154,6 +156,10 @@ type (
Country() string
}
RecordOptions struct {
LimitRecords int
}
RecordImportSession struct {
Name string `json:"-"`
SessionID uint64 `json:"sessionID,string"`
@@ -198,7 +204,7 @@ type (
recordReportEntry map[string]any
)
func Record() *record {
func Record(opts RecordOptions) *record {
svc := &record{
actionlog: DefaultActionlog,
ac: DefaultAccessControl,
@@ -209,6 +215,8 @@ func Record() *record {
namespace: DefaultNamespace,
module: DefaultModule,
opt: opts,
revisions: &recordRevisions{revisions.Service(dal.Service())},
formatter: values.Formatter(),
@@ -817,6 +825,11 @@ func (svc record) create(ctx context.Context, new *types.Record) (rec *types.Rec
aProps.setNamespace(ns)
aProps.setModule(m)
// check the records limit per namespace
if err = svc.checkLimit(ctx, m); err != nil {
return nil, nil, err
}
if !svc.ac.CanCreateRecordOnModule(ctx, m) {
return nil, dd, RecordErrNotAllowedToCreate()
}
@@ -887,6 +900,27 @@ func (svc record) create(ctx context.Context, new *types.Record) (rec *types.Rec
return
}
func (svc record) checkLimit(ctx context.Context, m *types.Module) (err error) {
if svc.opt.LimitRecords == 0 {
return nil
}
// get the number of records count for the namespace
recordsCount, err := dalutils.ComposeRecordsCount(ctx, svc.dal, m, types.RecordFilter{
NamespaceID: m.NamespaceID,
ModuleID: 0,
})
if err != nil {
return err
}
if recordsCount >= uint(svc.opt.LimitRecords) {
return RecordErrMaxRecordsReached()
}
return nil
}
// RecordValueSanitization does basic field and format validation
//
// Received values must fit the data model: on unknown fields
+34 -99
View File
@@ -62,7 +62,6 @@ var (
// setRecord updates recordActionProps's record
//
// This function is auto-generated.
//
func (p *recordActionProps) setRecord(record *types.Record) *recordActionProps {
p.record = record
return p
@@ -71,7 +70,6 @@ func (p *recordActionProps) setRecord(record *types.Record) *recordActionProps {
// setChanged updates recordActionProps's changed
//
// This function is auto-generated.
//
func (p *recordActionProps) setChanged(changed *types.Record) *recordActionProps {
p.changed = changed
return p
@@ -80,7 +78,6 @@ func (p *recordActionProps) setChanged(changed *types.Record) *recordActionProps
// setFilter updates recordActionProps's filter
//
// This function is auto-generated.
//
func (p *recordActionProps) setFilter(filter *types.RecordFilter) *recordActionProps {
p.filter = filter
return p
@@ -89,7 +86,6 @@ func (p *recordActionProps) setFilter(filter *types.RecordFilter) *recordActionP
// setNamespace updates recordActionProps's namespace
//
// This function is auto-generated.
//
func (p *recordActionProps) setNamespace(namespace *types.Namespace) *recordActionProps {
p.namespace = namespace
return p
@@ -98,7 +94,6 @@ func (p *recordActionProps) setNamespace(namespace *types.Namespace) *recordActi
// setModule updates recordActionProps's module
//
// This function is auto-generated.
//
func (p *recordActionProps) setModule(module *types.Module) *recordActionProps {
p.module = module
return p
@@ -107,7 +102,6 @@ func (p *recordActionProps) setModule(module *types.Module) *recordActionProps {
// setBulkOperation updates recordActionProps's bulkOperation
//
// This function is auto-generated.
//
func (p *recordActionProps) setBulkOperation(bulkOperation string) *recordActionProps {
p.bulkOperation = bulkOperation
return p
@@ -116,7 +110,6 @@ func (p *recordActionProps) setBulkOperation(bulkOperation string) *recordAction
// setField updates recordActionProps's field
//
// This function is auto-generated.
//
func (p *recordActionProps) setField(field string) *recordActionProps {
p.field = field
return p
@@ -125,7 +118,6 @@ func (p *recordActionProps) setField(field string) *recordActionProps {
// setPositionField updates recordActionProps's positionField
//
// This function is auto-generated.
//
func (p *recordActionProps) setPositionField(positionField *types.ModuleField) *recordActionProps {
p.positionField = positionField
return p
@@ -134,7 +126,6 @@ func (p *recordActionProps) setPositionField(positionField *types.ModuleField) *
// setGroupField updates recordActionProps's groupField
//
// This function is auto-generated.
//
func (p *recordActionProps) setGroupField(groupField *types.ModuleField) *recordActionProps {
p.groupField = groupField
return p
@@ -143,7 +134,6 @@ func (p *recordActionProps) setGroupField(groupField *types.ModuleField) *record
// setValue updates recordActionProps's value
//
// This function is auto-generated.
//
func (p *recordActionProps) setValue(value string) *recordActionProps {
p.value = value
return p
@@ -152,7 +142,6 @@ func (p *recordActionProps) setValue(value string) *recordActionProps {
// setValueErrors updates recordActionProps's valueErrors
//
// This function is auto-generated.
//
func (p *recordActionProps) setValueErrors(valueErrors *types.RecordValueErrorSet) *recordActionProps {
p.valueErrors = valueErrors
return p
@@ -161,7 +150,6 @@ func (p *recordActionProps) setValueErrors(valueErrors *types.RecordValueErrorSe
// Serialize converts recordActionProps to actionlog.Meta
//
// This function is auto-generated.
//
func (p recordActionProps) Serialize() actionlog.Meta {
var (
m = make(actionlog.Meta)
@@ -219,7 +207,6 @@ func (p recordActionProps) Serialize() actionlog.Meta {
// tr translates string and replaces meta value placeholder with values
//
// This function is auto-generated.
//
func (p recordActionProps) Format(in string, err error) string {
var (
pairs = []string{"{{err}}"}
@@ -385,7 +372,6 @@ func (p recordActionProps) Format(in string, err error) string {
// String returns loggable description as string
//
// This function is auto-generated.
//
func (a *recordAction) String() string {
var props = &recordActionProps{}
@@ -413,7 +399,6 @@ func (e *recordAction) ToAction() *actionlog.Action {
// RecordActionSearch returns "compose:record.search" action
//
// This function is auto-generated.
//
func RecordActionSearch(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -433,7 +418,6 @@ func RecordActionSearch(props ...*recordActionProps) *recordAction {
// RecordActionSearchSensitive returns "compose:record.searchSensitive" action
//
// This function is auto-generated.
//
func RecordActionSearchSensitive(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -453,7 +437,6 @@ func RecordActionSearchSensitive(props ...*recordActionProps) *recordAction {
// RecordActionLookup returns "compose:record.lookup" action
//
// This function is auto-generated.
//
func RecordActionLookup(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -473,7 +456,6 @@ func RecordActionLookup(props ...*recordActionProps) *recordAction {
// RecordActionReport returns "compose:record.report" action
//
// This function is auto-generated.
//
func RecordActionReport(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -493,7 +475,6 @@ func RecordActionReport(props ...*recordActionProps) *recordAction {
// RecordActionBulk returns "compose:record.bulk" action
//
// This function is auto-generated.
//
func RecordActionBulk(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -513,7 +494,6 @@ func RecordActionBulk(props ...*recordActionProps) *recordAction {
// RecordActionCreate returns "compose:record.create" action
//
// This function is auto-generated.
//
func RecordActionCreate(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -533,7 +513,6 @@ func RecordActionCreate(props ...*recordActionProps) *recordAction {
// RecordActionUpdate returns "compose:record.update" action
//
// This function is auto-generated.
//
func RecordActionUpdate(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -553,7 +532,6 @@ func RecordActionUpdate(props ...*recordActionProps) *recordAction {
// RecordActionDelete returns "compose:record.delete" action
//
// This function is auto-generated.
//
func RecordActionDelete(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -573,7 +551,6 @@ func RecordActionDelete(props ...*recordActionProps) *recordAction {
// RecordActionPatch returns "compose:record.patch" action
//
// This function is auto-generated.
//
func RecordActionPatch(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -593,7 +570,6 @@ func RecordActionPatch(props ...*recordActionProps) *recordAction {
// RecordActionUndelete returns "compose:record.undelete" action
//
// This function is auto-generated.
//
func RecordActionUndelete(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -613,7 +589,6 @@ func RecordActionUndelete(props ...*recordActionProps) *recordAction {
// RecordActionImport returns "compose:record.import" action
//
// This function is auto-generated.
//
func RecordActionImport(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -633,7 +608,6 @@ func RecordActionImport(props ...*recordActionProps) *recordAction {
// RecordActionSearchRevisions returns "compose:record.searchRevisions" action
//
// This function is auto-generated.
//
func RecordActionSearchRevisions(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -653,7 +627,6 @@ func RecordActionSearchRevisions(props ...*recordActionProps) *recordAction {
// RecordActionExport returns "compose:record.export" action
//
// This function is auto-generated.
//
func RecordActionExport(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -673,7 +646,6 @@ func RecordActionExport(props ...*recordActionProps) *recordAction {
// RecordActionOrganize returns "compose:record.organize" action
//
// This function is auto-generated.
//
func RecordActionOrganize(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -693,7 +665,6 @@ func RecordActionOrganize(props ...*recordActionProps) *recordAction {
// RecordActionIteratorInvoked returns "compose:record.iteratorInvoked" action
//
// This function is auto-generated.
//
func RecordActionIteratorInvoked(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -713,7 +684,6 @@ func RecordActionIteratorInvoked(props ...*recordActionProps) *recordAction {
// RecordActionIteratorIteration returns "compose:record.iteratorIteration" action
//
// This function is auto-generated.
//
func RecordActionIteratorIteration(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -733,7 +703,6 @@ func RecordActionIteratorIteration(props ...*recordActionProps) *recordAction {
// RecordActionIteratorClone returns "compose:record.iteratorClone" action
//
// This function is auto-generated.
//
func RecordActionIteratorClone(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -753,7 +722,6 @@ func RecordActionIteratorClone(props ...*recordActionProps) *recordAction {
// RecordActionIteratorUpdate returns "compose:record.iteratorUpdate" action
//
// This function is auto-generated.
//
func RecordActionIteratorUpdate(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -773,7 +741,6 @@ func RecordActionIteratorUpdate(props ...*recordActionProps) *recordAction {
// RecordActionIteratorDelete returns "compose:record.iteratorDelete" action
//
// This function is auto-generated.
//
func RecordActionIteratorDelete(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -793,7 +760,6 @@ func RecordActionIteratorDelete(props ...*recordActionProps) *recordAction {
// RecordActionIteratorUndelete returns "compose:record.iteratorUndelete" action
//
// This function is auto-generated.
//
func RecordActionIteratorUndelete(props ...*recordActionProps) *recordAction {
a := &recordAction{
timestamp: time.Now(),
@@ -816,9 +782,7 @@ func RecordActionIteratorUndelete(props ...*recordActionProps) *recordAction {
// RecordErrGeneric returns "compose:record.generic" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrGeneric(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -852,9 +816,7 @@ func RecordErrGeneric(mm ...*recordActionProps) *errors.Error {
// RecordErrNotFound returns "compose:record.notFound" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrNotFound(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -886,9 +848,7 @@ func RecordErrNotFound(mm ...*recordActionProps) *errors.Error {
// RecordErrNamespaceNotFound returns "compose:record.namespaceNotFound" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrNamespaceNotFound(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -920,9 +880,7 @@ func RecordErrNamespaceNotFound(mm ...*recordActionProps) *errors.Error {
// RecordErrModuleNotFoundModule returns "compose:record.moduleNotFoundModule" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrModuleNotFoundModule(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -954,9 +912,7 @@ func RecordErrModuleNotFoundModule(mm ...*recordActionProps) *errors.Error {
// RecordErrInvalidID returns "compose:record.invalidID" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrInvalidID(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -988,9 +944,7 @@ func RecordErrInvalidID(mm ...*recordActionProps) *errors.Error {
// RecordErrInvalidNamespaceID returns "compose:record.invalidNamespaceID" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrInvalidNamespaceID(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1022,9 +976,7 @@ func RecordErrInvalidNamespaceID(mm ...*recordActionProps) *errors.Error {
// RecordErrInvalidModuleID returns "compose:record.invalidModuleID" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrInvalidModuleID(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1056,9 +1008,7 @@ func RecordErrInvalidModuleID(mm ...*recordActionProps) *errors.Error {
// RecordErrStaleData returns "compose:record.staleData" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrStaleData(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1090,9 +1040,7 @@ func RecordErrStaleData(mm ...*recordActionProps) *errors.Error {
// RecordErrNotAllowedToRead returns "compose:record.notAllowedToRead" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrNotAllowedToRead(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1126,9 +1074,7 @@ func RecordErrNotAllowedToRead(mm ...*recordActionProps) *errors.Error {
// RecordErrNotAllowedToSearch returns "compose:record.notAllowedToSearch" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrNotAllowedToSearch(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1162,9 +1108,7 @@ func RecordErrNotAllowedToSearch(mm ...*recordActionProps) *errors.Error {
// RecordErrNotAllowedToSearchRevisions returns "compose:record.notAllowedToSearchRevisions" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrNotAllowedToSearchRevisions(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1198,9 +1142,7 @@ func RecordErrNotAllowedToSearchRevisions(mm ...*recordActionProps) *errors.Erro
// RecordErrRevisionsDisabledOnModule returns "compose:record.revisionsDisabledOnModule" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrRevisionsDisabledOnModule(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1234,9 +1176,7 @@ func RecordErrRevisionsDisabledOnModule(mm ...*recordActionProps) *errors.Error
// RecordErrNotAllowedToReadNamespace returns "compose:record.notAllowedToReadNamespace" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrNotAllowedToReadNamespace(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1270,9 +1210,7 @@ func RecordErrNotAllowedToReadNamespace(mm ...*recordActionProps) *errors.Error
// RecordErrNotAllowedToReadModule returns "compose:record.notAllowedToReadModule" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrNotAllowedToReadModule(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1306,9 +1244,7 @@ func RecordErrNotAllowedToReadModule(mm ...*recordActionProps) *errors.Error {
// RecordErrNotAllowedToListRecords returns "compose:record.notAllowedToListRecords" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrNotAllowedToListRecords(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1342,9 +1278,7 @@ func RecordErrNotAllowedToListRecords(mm ...*recordActionProps) *errors.Error {
// RecordErrNotAllowedToCreate returns "compose:record.notAllowedToCreate" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrNotAllowedToCreate(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1378,9 +1312,7 @@ func RecordErrNotAllowedToCreate(mm ...*recordActionProps) *errors.Error {
// RecordErrNotAllowedToUpdate returns "compose:record.notAllowedToUpdate" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrNotAllowedToUpdate(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1414,9 +1346,7 @@ func RecordErrNotAllowedToUpdate(mm ...*recordActionProps) *errors.Error {
// RecordErrNotAllowedToDelete returns "compose:record.notAllowedToDelete" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrNotAllowedToDelete(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1450,9 +1380,7 @@ func RecordErrNotAllowedToDelete(mm ...*recordActionProps) *errors.Error {
// RecordErrNotAllowedToUndelete returns "compose:record.notAllowedToUndelete" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrNotAllowedToUndelete(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1486,9 +1414,7 @@ func RecordErrNotAllowedToUndelete(mm ...*recordActionProps) *errors.Error {
// RecordErrNotAllowedToChangeFieldValue returns "compose:record.notAllowedToChangeFieldValue" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrNotAllowedToChangeFieldValue(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1520,11 +1446,43 @@ func RecordErrNotAllowedToChangeFieldValue(mm ...*recordActionProps) *errors.Err
return e
}
// RecordErrMissingPositionField returns "compose:record.missingPositionField" as *errors.Error
//
// RecordErrMaxRecordsReached returns "compose:record.maxRecordsReached" as *errors.Error
//
// This function is auto-generated.
func RecordErrMaxRecordsReached(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
p = mm[0]
}
var e = errors.New(
errors.KindInternal,
p.Format("maximum number of records per namespace reached", nil),
errors.Meta("type", "maxRecordsReached"),
errors.Meta("resource", "compose:record"),
// action log entry; no formatting, it will be applied inside recordAction fn.
errors.Meta(recordLogMetaKey{}, "maximum number of records per namespace reached"),
errors.Meta(recordPropsMetaKey{}, p),
// translation namespace & key
errors.Meta(locale.ErrorMetaNamespace{}, "compose"),
errors.Meta(locale.ErrorMetaKey{}, "record.errors.maxRecordsReached"),
errors.StackSkip(1),
)
if len(mm) > 0 {
}
return e
}
// RecordErrMissingPositionField returns "compose:record.missingPositionField" as *errors.Error
//
// This function is auto-generated.
func RecordErrMissingPositionField(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1558,9 +1516,7 @@ func RecordErrMissingPositionField(mm ...*recordActionProps) *errors.Error {
// RecordErrInvalidPositionFieldKind returns "compose:record.invalidPositionFieldKind" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrInvalidPositionFieldKind(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1594,9 +1550,7 @@ func RecordErrInvalidPositionFieldKind(mm ...*recordActionProps) *errors.Error {
// RecordErrInvalidPositionFieldConfigMultiValue returns "compose:record.invalidPositionFieldConfigMultiValue" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrInvalidPositionFieldConfigMultiValue(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1630,9 +1584,7 @@ func RecordErrInvalidPositionFieldConfigMultiValue(mm ...*recordActionProps) *er
// RecordErrInvalidPositionValueType returns "compose:record.invalidPositionValueType" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrInvalidPositionValueType(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1666,9 +1618,7 @@ func RecordErrInvalidPositionValueType(mm ...*recordActionProps) *errors.Error {
// RecordErrMissingGroupField returns "compose:record.missingGroupField" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrMissingGroupField(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1702,9 +1652,7 @@ func RecordErrMissingGroupField(mm ...*recordActionProps) *errors.Error {
// RecordErrInvalidGroupFieldConfigMultiValue returns "compose:record.invalidGroupFieldConfigMultiValue" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrInvalidGroupFieldConfigMultiValue(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1738,9 +1686,7 @@ func RecordErrInvalidGroupFieldConfigMultiValue(mm ...*recordActionProps) *error
// RecordErrImportSessionAlreadActive returns "compose:record.importSessionAlreadActive" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrImportSessionAlreadActive(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1774,9 +1720,7 @@ func RecordErrImportSessionAlreadActive(mm ...*recordActionProps) *errors.Error
// RecordErrFieldNotFound returns "compose:record.fieldNotFound" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrFieldNotFound(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1808,9 +1752,7 @@ func RecordErrFieldNotFound(mm ...*recordActionProps) *errors.Error {
// RecordErrInvalidValueStructure returns "compose:record.invalidValueStructure" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrInvalidValueStructure(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1842,9 +1784,7 @@ func RecordErrInvalidValueStructure(mm ...*recordActionProps) *errors.Error {
// RecordErrUnknownBulkOperation returns "compose:record.unknownBulkOperation" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrUnknownBulkOperation(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1876,9 +1816,7 @@ func RecordErrUnknownBulkOperation(mm ...*recordActionProps) *errors.Error {
// RecordErrInvalidReferenceFormat returns "compose:record.invalidReferenceFormat" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrInvalidReferenceFormat(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1910,9 +1848,7 @@ func RecordErrInvalidReferenceFormat(mm ...*recordActionProps) *errors.Error {
// RecordErrValueInput returns "compose:record.valueInput" as *errors.Error
//
//
// This function is auto-generated.
//
func RecordErrValueInput(mm ...*recordActionProps) *errors.Error {
var p = &recordActionProps{}
if len(mm) > 0 {
@@ -1950,7 +1886,6 @@ func RecordErrValueInput(mm ...*recordActionProps) *errors.Error {
// It will wrap unrecognized/internal errors with generic errors.
//
// This function is auto-generated.
//
func (svc record) recordAction(ctx context.Context, props *recordActionProps, actionFn func(...*recordActionProps) *recordAction, err error) error {
if svc.actionlog == nil || actionFn == nil {
// action log disabled or no action fn passed, return error as-is
@@ -183,6 +183,10 @@ errors:
message: "not allowed to change value of field {{field}}"
log: "failed to change value of field {{field}}; insufficient permissions"
- error: maxRecordsReached
message: "maximum number of records per namespace reached"
log: "maximum number of records per namespace reached"
# Organizer
- error: missingPositionField
message: "position module field not found"
+2 -1
View File
@@ -42,6 +42,7 @@ type (
ActionLog options.ActionLogOpt
Discovery options.DiscoveryOpt
Storage options.ObjectStoreOpt
Limit options.LimitOpt
UserFinder userFinder
SchemaAltManager schemaAltManager
}
@@ -185,7 +186,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, c Config)
DefaultModule = Module(c.SchemaAltManager)
DefaultImportSession = ImportSession()
DefaultRecord = Record()
DefaultRecord = Record(RecordOptions{LimitRecords: c.Limit.RecordCountPerNamespace})
DefaultPage = Page()
DefaultPageLayout = PageLayout()
DefaultChart = Chart()
+3 -2
View File
@@ -1,13 +1,14 @@
package rest
import (
"github.com/cortezaproject/corteza/server/pkg/options"
"github.com/go-chi/chi/v5"
"github.com/cortezaproject/corteza/server/federation/rest/handlers"
"github.com/cortezaproject/corteza/server/pkg/auth"
)
func MountRoutes() func(r chi.Router) {
func MountRoutes(opts options.LimitOpt) func(r chi.Router) {
return func(r chi.Router) {
r.Group(func(r chi.Router) {
handlers.NewNodeHandshake(NodeHandshake{}.New()).MountRoutes(r)
@@ -22,7 +23,7 @@ func MountRoutes() func(r chi.Router) {
handlers.NewNode(Node{}.New()).MountRoutes(r)
handlers.NewManageStructure((ManageStructure{}.New())).MountRoutes(r)
handlers.NewSyncData((SyncData{}.New())).MountRoutes(r)
handlers.NewSyncData((SyncData{}.New(opts))).MountRoutes(r)
handlers.NewSyncStructure((SyncStructure{}.New())).MountRoutes(r)
})
}
+10 -5
View File
@@ -3,6 +3,7 @@ package rest
import (
"context"
"fmt"
"github.com/cortezaproject/corteza/server/pkg/options"
"net/http"
"strings"
"time"
@@ -21,7 +22,9 @@ import (
)
type (
SyncData struct{}
SyncData struct {
opts options.LimitOpt
}
recordPayload struct {
*ct.Record
@@ -50,8 +53,10 @@ type (
}
)
func (SyncData) New() *SyncData {
return &SyncData{}
func (SyncData) New(opts options.LimitOpt) *SyncData {
return &SyncData{
opts: opts,
}
}
func (ctrl SyncData) ReadExposedAll(ctx context.Context, r *request.SyncDataReadExposedAll) (interface{}, error) {
@@ -95,7 +100,7 @@ func (ctrl SyncData) ReadExposedAll(ctx context.Context, r *request.SyncDataRead
// todo - handle error properly
// @todo !!!
if list, _, err := (cs.Record()).Find(ctx, rf); err != nil || len(list) == 0 {
if list, _, err := (cs.Record(cs.RecordOptions{LimitRecords: ctrl.opts.RecordCountPerNamespace})).Find(ctx, rf); err != nil || len(list) == 0 {
continue
}
@@ -223,7 +228,7 @@ func (ctrl SyncData) readExposed(ctx context.Context, r *request.SyncDataReadExp
}
// @todo !!!
list, f, err := (cs.Record()).Find(ctx, f)
list, f, err := (cs.Record(cs.RecordOptions{LimitRecords: ctrl.opts.RecordCountPerNamespace})).Find(ctx, f)
if err != nil {
return nil, err
+3
View File
@@ -49,6 +49,9 @@ type (
// Search returns an iterator which can be used to access all if the bits
Search(context.Context, *Model, filter.Filter) (Iterator, error)
// Count returns the total number of rows matching the filter
Count(context.Context, *Model, filter.Filter) (uint, error)
// Analyze returns the operation analysis the connection can perform for the model
Analyze(ctx context.Context, m *Model) (map[string]OpAnalysis, error)
+16
View File
@@ -53,6 +53,7 @@ type (
Update(ctx context.Context, mf ModelRef, operations OperationSet, rr ...ValueGetter) (err error)
Search(ctx context.Context, mf ModelRef, operations OperationSet, f filter.Filter) (iter Iterator, err error)
Lookup(ctx context.Context, mf ModelRef, operations OperationSet, lookup ValueGetter, dst ValueSetter) (err error)
Count(ctx context.Context, mf ModelRef, operations OperationSet, f filter.Filter) (uint, error)
Delete(ctx context.Context, mf ModelRef, operations OperationSet, vv ...ValueGetter) (err error)
Truncate(ctx context.Context, mf ModelRef, operations OperationSet) (err error)
@@ -488,6 +489,21 @@ func (svc *service) Search(ctx context.Context, mf ModelRef, operations Operatio
return cw.connection.Search(ctx, model, f)
}
func (svc *service) Count(ctx context.Context, mf ModelRef, operations OperationSet, f filter.Filter) (cnt uint, err error) {
if err = svc.canOpData(mf); err != nil {
err = fmt.Errorf("cannot count data entry: %w", err)
return
}
model, cw, err := svc.storeOpPrep(ctx, mf, operations)
if err != nil {
err = fmt.Errorf("cannot count data entry: %w", err)
return
}
return cw.connection.Count(ctx, model, f)
}
// Run returns an iterator based on the provided Pipeline
// @todo consider moving the Search method to utilize this also
func (svc *service) Run(ctx context.Context, pp Pipeline) (iter Iterator, err error) {
+2 -1
View File
@@ -162,7 +162,8 @@ type (
}
LimitOpt struct {
SystemUsers int `env:"LIMIT_SYSTEM_USERS"`
SystemUsers int `env:"LIMIT_SYSTEM_USERS"`
RecordCountPerNamespace int `env:"LIMIT_RECORD_COUNT_PER_NAMESPACE"`
}
LocaleOpt struct {
@@ -109,6 +109,13 @@ func (c *connection) Search(ctx context.Context, m *dal.Model, f filter.Filter)
})
}
func (c *connection) Count(ctx context.Context, m *dal.Model, f filter.Filter) (i uint, _ error) {
return i, c.withModel(m, func(m *model) (err error) {
i, err = m.Count(ctx, f)
return
})
}
func (c *connection) Analyze(ctx context.Context, m *dal.Model) (a map[string]dal.OpAnalysis, err error) {
// @todo somehow (probably operations) bring in the info what can be done
// for now, since we're quite rigid on the drivers, this will do.
+46
View File
@@ -268,6 +268,52 @@ func (d *model) Search(f filter.Filter) (i *iterator, err error) {
return
}
func (d *model) Count(ctx context.Context, f filter.Filter) (c uint, err error) {
var (
aux = struct {
Count uint `db:"count"`
}{}
rows *sql.Rows
exprs []exp.Expression
q = d.dialect.GOQU().
From(d.table.Ident()).Select(goqu.COUNT(goqu.Star()).As("count"))
)
for ident, val := range f.Constraints() {
attrExpr, err := d.table.AttributeExpression(ident)
if err != nil {
return 0, err
}
exprs = append(exprs, exp.NewBooleanExpression(exp.EqOp, attrExpr, val))
}
query, args, err := q.Where(exprs...).Limit(1).ToSQL()
if err != nil {
return
}
rows, err = d.conn.QueryContext(ctx, query, args...)
if err != nil {
return
}
defer rows.Close()
if !rows.Next() {
return 0, errors.NotFound("not found")
}
if err = rows.Scan(&aux.Count); err != nil {
return
}
c = aux.Count
return c, nil
}
// Aggregate constructs SELECT sql with group-by and an optional having CLAUSE
//
// All group-by attributes are prepended to aggregation
+1 -1
View File
@@ -64,7 +64,7 @@ func InitTestApp() {
r = chi.NewRouter()
r.Use(server.BaseMiddleware(false, logger.Default())...)
helpers.BindAuthMiddleware(r)
r.Group(rest.MountRoutes())
r.Group(rest.MountRoutes(testApp.Opt.Limit))
}
}