Deletion not sent to mobile

I have this sync formula:

  if (oldDoc && oldDoc._deleted){
    // Server doc. deleted -> just propagate this on the public channel!
    channel('!');
    return;
  }
  // Document type is mandatory
  if (!doc.type){
    throw({forbidden: "Document type is required."});
  }
  if ((doc.type == 'EnvLake' || doc.type == 'EnvMeasurement' || doc.type == 'Feedback' || doc.type == 'ActivityLog') && oldDoc){
    throw({forbidden: "Document type not allowed to sync to mobile..."});
  }
  // All public docs are available in the app
  if (doc.ispublic) {
    channel('!');
    //  return;
  }
  // All non-club fishing trips and catches are available (for stats)
  if ((doc.type == 'FishingTrip' || doc.type == 'Catch') && doc.clubonlykey == undefined) {
    channel('!');
  }
  // All non-specific user info is available (for stats)
  if (doc.type == 'User') {
    channel('!');
  }
  // Only docs "owned" by user can be updated
  var key;
  if(doc.type == 'User'){
    key = doc.key;
  } else if(doc.type == 'FishingTrip' || doc.type == 'Catch' || doc.type == 'Photo' || doc.type == 'Private' || doc.type == 'Image'){
    key = doc.userkey;
  }
  if(key){
    requireUser(key);
    channel('channel.' + key);
    access(key,'channel.' + key);
  }

  // Creation of new Feedback docs (perhaps without userkey)?
  // Creation of new docs?
  // Updates to existing docs?
  // Readonly for all non-user specific docs...

When I create a new document, say with type='Bait' and ispublic=true it is sync’ed to my test-app right away. If I then delete the new document from the server side again - then it is NOT sync’ed to the mobile app.??

I have tried a couple of things. First I just “returned” from the code. But following a couple of other posts I have added a channel to the doc - but still it doesn’t get deleted on the mobile.

Server: Couchbase Comunity Edition 6.0 - application on server using Java SDK 2.7.4
Sync. gateway: 2.5
C# on mobile using: Couchbase Lite 2.5

Before I can answer your question, could you please clarify where the “new document, with type=‘Bait’ and ispublic=true” was created? on the mobile app? or on sync gateway or on couchbase server?

And it seems that you delete the newly created document from couchbase server, is that right? If so, and the deleted document didn’t get reflected to the client, you need to check if on sync gateway config, if the following 2 settings are specified:

"enable_shared_bucket_access": true
"import_docs": "continuous"

These configuration settings allow sync gateway continuously import docs from couchbase server, therefore, deleted document can be recognized by sync gateway, and if you mobile client has proper replication enabled, then the document will get deleted on mobile client.

Yes, the scenario I was testing was exactly as you guessed - I created a document on the server. That was sent to the mobile almost instantly :slight_smile: However, when I then deleted the document also on the server then the deletion is not sent to the mobile…

The output of .....:4985/data/_config is:

{
    "server": "http://ds9:8091",
    "pool": "default",
    "bucket": "data",
    "username": "xxxxxxxxx",
    "password": "yyyyyyyyyyyy",
    "name": "data",
    "sync": "function (doc, oldDoc) {\n  // Document type is mandatory\n  if (oldDoc && oldDoc._deleted){\n    // Server doc. deleted -> just propagate this on the public channel!\n    channel('!');\n    return;\n  }\n  // Document type is mandatory\n  if (!doc.type){\n    throw({forbidden: \"Document type is required.\"});\n  }\n  if ((doc.type == 'EnvLake' || doc.type == 'EnvMeasurement' || doc.type == 'Feedback' || doc.type == 'ActivityLog') && oldDoc){\n    throw({forbidden: \"Document type not allowed to sync to mobile...\"});\n  }\n  // All public docs are available in the app\n  if (doc.ispublic) {\n    channel('!');\n    //  return;\n  }\n  // All non-club fishing trips and catches are available (for stats)\n  if ((doc.type == 'FishingTrip' || doc.type == 'Catch') && doc.clubonlykey == undefined) {\n    channel('!');\n  }\n  // All non-specific user info is available (for stats)\n  if (doc.type == 'User') {\n    channel('!');\n  }\n  // Only docs \"owned\" by user can be updated\n  var key;\n  if(doc.type == 'User'){\n    key = doc.key;\n  } else if(doc.type == 'FishingTrip' || doc.type == 'Catch' || doc.type == 'Photo' || doc.type == 'Private' || doc.type == 'Image'){\n    key = doc.userkey;\n  }\n  if(key){\n    requireUser(key);\n    channel('channel.' + key);\n    access(key,'channel.' + key);\n  }\n\n  // Creation of new Feedback docs (perhaps without userkey)?\n  // Creation of new docs?\n  // Updates to existing docs?\n  // Readonly for all non-user specific docs...\n}",
    "users": {
        "GUEST": {
            "name": "",
            "admin_channels": [
                "!"
            ],
            "all_channels": null,
            "disabled": true
        }
    },
    "revs_limit": 20,
    "import_docs": true,
    "import_backup_old_rev": false,
    "unsupported": {
        "user_views": {},
        "oidc_test_provider": {},
        "api_endpoints": {},
        "warning_thresholds": {
            "xattr_size_bytes": 943718,
            "channels_per_doc": 50,
            "access_and_role_grants_per_doc": 50
        }
    },
    "deprecated": {},
    "enable_shared_bucket_access": true,
    "session_cookie_name": "",
    "allow_conflicts": false,
    "num_index_replicas": 0,
    "use_views": false
}

Please note that I have changed the “import_docs” to true following the documentation for sync_gateway that specifies this as either true/false. Only an older example shows “continuous” - but it is not part of the description of the variable (for 2.5)… I also noted that in another post: Issue with sync after upgrading to 2.5

Perhaps I’m misunderstanding the concept of doc and oldDoc… I thought that the “oldDoc” was representing the server version of the document - and that could to some extent be correct - but not always…

So I guess I am trying to do things based on the direction of the sync - and I’m not sure how (and if) to do this.

This is my scenario:

  1. I have some “public” or “meta data” documents added to the “!” channel that GUEST has access to. All documents with ispublic:true are added to this channel and should be replicated to the mobile app.
  2. Users on the mobile app are not allowed to delete the public documents. Any request to delete a public document on the mobile should be rejected by the sync gateway.
  3. The application on the server maintains and updates the public documents and these changes (including deletions) should be replicated to all mobile app users
  4. The user has “own documents” (like a profile, fishing trips, catches, photos, etc.) that all have a userkey field on them (key on the user profile). Creation, updates and deletion of these documents should be replicated - no matter whether they come from the mobile app or the server web app.
  5. Finally, a user can create a “feedback” document that really shouldn’t exist on the app after it has been replicated to the server - but it’s not a big deal if it stays
  6. Some of the information is available as public data - and owned by the user (through key/userkey). So everybody can read - but only the actual user can modify/delete

I guess the above leads to a couple of questions (after having read the documentation about the sync. formula etc.):

  1. How can I debug the sync. formula? I need to see what is going on to make sure that I’m doing the right thing - or rather find out why what I’m doing isn’t rigth :wink:
  2. How can I delete documents on the server and sync them to mobile - but not allow the other way? (my public docs)
  3. Ditto for updates… - unless the user is the owner of a public doc?

This is all working as a black box right now - but not the way I want it to do it… That bothers me as it can spoil many things if it is not working correctly… :grimacing:

You can call console.log() from the sync function, and the message will be shown in SG logs.

The first part of that should be true already, if it’s not we should work on that separately.

To disallow public docs from being deleted/modified, you can call requireAdmin() and/or requireUser(oldDoc.owners)

Well, the main topic here is that I did not get the deletions sent to the mobile app.

How could requireAdmin() help me not sending deletions from the mobile app to the server - but allow the opposite direction?? I really don’t understand this…?!

Is oldDoc.owners a builtin field - or just used as an example for me to understand it? But why would that work in one direction but not the other???

SDK writes (shared bucket imports) are performed as an administrator.

Just an example. oldDoc is a reference to the parent revision, so when a mobile user updates a doc, the parent revision (which resides on the server-side) knows who owns it.

In the reverse case, as above, if it’s an SDK write/import, the action is performed as an administrator.

I really need to understand this better to be able to build a correct formula.

Are there any ressources (outside of the sync. gateway docs.) to explain this better for a dummy like me? - like a tutorial that I have overlooked?

Is oldDoc always the server side document? - and doc always the mobile side (aka the SDK?)… - or?

Actually, I know exactly what I want to obtain - but I have too many unknown factors in this concept to build it - otherwise it would talk one hour to get right… So I really need to see some examples with the explanation of what is oldDoc and what is doc in what situations…

I’m trying to investigate this more.

When I start the sync gateway I see this line in sg_warn.log:

Sync fn exception: (anonymous): Line 46:116 Unexpected identifier (and 1 more errors); doc = map[_deleted:true _rev:2-8ff6df25364ab385e030eb7835a110f3 _id:Bait:17] -- db.(*Database).getChannelsAndAccess() at crud.go:1486

… and these related to same doc in sg_info.log:

2019-05-07T12:16:42.235+02:00 [INF] DCP: Backfill in progress: 0% (1 / 34151)
2019-05-07T12:16:42.419+02:00 [INF] Import: Created new rev ID for doc "Bait:17" / "2-8ff6df25364ab385e030eb7835a110f3"
2019-05-07T12:16:42.431+02:00 [WRN] Sync fn exception: (anonymous): Line 46:116 Unexpected identifier (and 1 more errors); doc = map[_deleted:true _rev:2-8ff6df25364ab385e030eb7835a110f3 _id:Bait:17] -- db.(*Database).getChannelsAndAccess() at crud.go:1486
2019-05-07T12:16:42.431+02:00 [INF] Import: Error importing doc "Bait:17": 500 Exception in JS sync function

This document I created on the server - and it replicated to the CBL database. Then I deleted it on the server - and the deletion did not make it to the CBL db. I guess the above “explains” that - but I don’t understand the message nor what I need to allow the change to be replicated to the CBL database…

  1. Should I add it to a channel and give the user access to that channel? If I add it to the “!” channel would the deletion then replicate?
  2. If so, how do I control that if the same document was deleted in the CBL database (by error - or malicious code) that the deletion is not sent back to the server?

Thanks!

@bbrks , I have added this line to the very beginning of the sync function:

console.log("XX: sync...");

I can’t see “XX:” showing up in any of the sg_... log files in the logs subdirectory of the sync_gateway. Where should I look for these messages?

Edit:
I have investigated the log settings and changed the default in my config from:

	"log": ["HTTP+"],

To:

	"log": ["*"],

… but my own log messages do not turn up in any of the logs.

I also tried to manually create the file sync_gateway_access.log that should contain these messages (or was this before Sync Gateway 2.5? - not sure…) - but it stays empty.

After changing the log settings (or adding consol.log(…) to the sync function) I have stopped and started the sync gateway service. Doing a “restart” does not seem to put any changes in effect…

@jda Hi!
When using console.log() from the _sync function this defaults to printing to stdout and currently this is the only place it outputs to; it does not include the Sync Gateway formatting and does not get propagated to the Sync Gateway log files. This has since been implemented and is currently due to be in the next version of Sync Gateway. Currently the only output is from stdout.

The reason your document isn’t being deleted on the Couchbase Lite side appears to be due to a _sync function exception as is shown in the logs. The Line 46:116 Unexpected identifier suggests you may have incorrect javascript syntax in your sync function. The line on which the issue occurs has been provided so I suggest you check that.
Thanks!

Thanks for your response.

I apologise if the comments in this thread gets a little “messy”.

First, thanks for pinpointing the error - it turned out to be an “invisible” character that wasn’t directly visible to the eye. But corrected now. However, I am not sure that was the source of the problem of the doc not being deleted… After I added:

  if (doc && doc._deleted){
    // Server doc. deleted -> just propagate this on the public channel!
    _log('doc deleted, id: ' + (doc._id || 'no id!') + ', ' + (oldDoc ? ('old key=' + oldDoc.key + ', userkey=' + oldDoc.userkey) : 'no oldDoc'));
    channel('!');
    return;
  }

to my sync function then the document was deleted. I have tried to remove that snippet again after fixing the “wrong” character - and then docs. don’t get deleted…

I still haven’t got my head around how to make sure that all deletions are sent to the app - and only deletions made by the user to docs. “owned” by the user. I’ll need to test more following @bbrks’s suggestions. And that is where I needed to “see” what was going on (i.e. log from the sync function).

Apparently, I misunderstand this:

I’m running Couchbase on a CentOS 7.6 - but as far as I know I can’t see the output from the service when running. Do you know a way I could work around that? At least just for development :wink: