CHANNELS: Calls the JS sync function to assign the doc to channels, grant users access to channels, and reject invalid documents.
(ctx context.Context, doc *Document, body Body, metaMap map[string]interface{}, revID string)
| 3267 | // Calls the JS sync function to assign the doc to channels, grant users |
| 3268 | // access to channels, and reject invalid documents. |
| 3269 | func (col *DatabaseCollectionWithUser) getChannelsAndAccess(ctx context.Context, doc *Document, body Body, metaMap map[string]interface{}, revID string) ( |
| 3270 | result base.Set, |
| 3271 | access channels.AccessMap, |
| 3272 | roles channels.AccessMap, |
| 3273 | expiry *uint32, |
| 3274 | oldJson string, |
| 3275 | err error) { |
| 3276 | base.DebugfCtx(ctx, base.KeyCRUD, "Invoking sync on doc %q rev %s", base.UD(doc.ID), body[BodyRev]) |
| 3277 | |
| 3278 | // Low-level protection against writes for read-only guest. Handles write pathways that don't fail-fast |
| 3279 | if col.user != nil && col.user.Name() == "" && col.isGuestReadOnly() { |
| 3280 | return result, access, roles, expiry, oldJson, base.HTTPErrorf(403, auth.GuestUserReadOnly) |
| 3281 | } |
| 3282 | |
| 3283 | // Get the parent revision, to pass to the sync function: |
| 3284 | var oldJsonBytes []byte |
| 3285 | if oldJsonBytes, err = col.getAncestorJSON(ctx, doc, revID); err != nil { |
| 3286 | return |
| 3287 | } |
| 3288 | oldJson = string(oldJsonBytes) |
| 3289 | |
| 3290 | if col.ChannelMapper != nil { |
| 3291 | // Call the ChannelMapper: |
| 3292 | col.dbStats().Database().SyncFunctionCount.Add(1) |
| 3293 | col.collectionStats.SyncFunctionCount.Add(1) |
| 3294 | |
| 3295 | var output *channels.ChannelMapperOutput |
| 3296 | |
| 3297 | startTime := time.Now() |
| 3298 | var syncOptions map[string]any |
| 3299 | syncOptions, err = MakeUserCtx(col.user, col.ScopeName, col.Name) |
| 3300 | if err != nil { |
| 3301 | return result, access, roles, expiry, oldJson, err |
| 3302 | } |
| 3303 | output, err = col.ChannelMapper.MapToChannelsAndAccess(ctx, body, oldJson, metaMap, syncOptions) |
| 3304 | syncFunctionTimeNano := time.Since(startTime).Nanoseconds() |
| 3305 | |
| 3306 | col.dbStats().Database().SyncFunctionTime.Add(syncFunctionTimeNano) |
| 3307 | col.collectionStats.SyncFunctionTime.Add(syncFunctionTimeNano) |
| 3308 | |
| 3309 | if err == nil { |
| 3310 | result = output.Channels |
| 3311 | access = output.Access |
| 3312 | roles = output.Roles |
| 3313 | expiry = output.Expiry |
| 3314 | err = output.Rejection |
| 3315 | if err != nil { |
| 3316 | base.InfofCtx(ctx, base.KeyAll, "Sync fn rejected doc %q / %q --> %s", base.UD(doc.ID), base.UD(doc.NewestRev), err) |
| 3317 | base.DebugfCtx(ctx, base.KeyAll, " rejected doc %q / %q : new=%+v old=%s", base.UD(doc.ID), base.UD(doc.NewestRev), base.UD(body), base.UD(oldJson)) |
| 3318 | col.dbStats().Security().NumDocsRejected.Add(1) |
| 3319 | col.collectionStats.SyncFunctionRejectCount.Add(1) |
| 3320 | if isAccessError(err) { |
| 3321 | col.dbStats().Security().NumAccessErrors.Add(1) |
| 3322 | col.collectionStats.SyncFunctionRejectAccessCount.Add(1) |
| 3323 | } |
| 3324 | } else if !validateAccessMap(ctx, access) || !validateRoleAccessMap(ctx, roles) { |
| 3325 | err = base.HTTPErrorf(500, "Error in JS sync function") |
| 3326 | } |