plentico / plenti

Static Site Generator with Go backend and Svelte frontend
https://plenti.co
Apache License 2.0
1.01k stars 49 forks source link

Public / Private Data #213

Closed jimafisk closed 2 years ago

jimafisk commented 2 years ago

There are scenarios where you might want to save some public data to JSON (delivered clientside) but also save some private data to a third-party backend (like https://pocketbase.io/). The reason being you still want specific endpoints with HTML fallbacks for faster page loads and SEO, but you also have protected info that should only be shared with those who have the appropriate access.

For example: You have events on your site that you display publicly, but you want to save additional information for the event such as a virtual meeting link that should remain private unless a user registers and pays for the event.

Currently you have to make a choice between SSR (all public via Plenti) or full CSR (blank page that loads in data from your backend via JS). It would be nice to allow a hybrid approach, but it's challenging to do this in a nonprescriptive way that allows for developer choice of backend setup.

To start, I think we'd need a persistent ID to tie the frontend to the backend. We could create a new id type for _schema.json that generates a UUID if it's blank (i.e. on initial creation) and doesn't allow you to edit it through the visual editor (input would be disabled with maybe a button to copy the ID). This would save publicly like any other Plenti field so it persists and can be used to load backend data for the private fields.

jamestagal commented 2 years ago

Hi @jimafisk This sound good. Could you clarify the hybrid approach here? Is it like an opt in arrangement? So all data by default is public via Plenti but once the _schema_json is initialised (i.e. for private data ) you can use it to specify the data you want to be private in this way? Ben

jimafisk commented 2 years ago

Great question @jamestagal. Everything in Plenti gets sent to the browser of the site visitor. As a Plenti developer you can obscure information, or choose not to display it, but you should not treat this information as secure. A savvy visitor could inspect this clientside code to reveal information, even if it appears hidden by looking at the display of the website. This is by design, Plenti aims at being good at building dynamic frontend experiences with as little complication as possible. If you want additional functionality, like webforms, e-commerce, user authentication, etc, we expect that you will use a third party solution as a "microservice" to your Plenti site.

In order to have truly have private data for example, you'd want to save information to a backend database that is managed serverside. There are tons of Backend as a Service (BaaS) solutions that allow you to do this and connect to a frontend application like Plenti:

So you could write a template in Plenti to save data to and display information from one of these solutions and that would allow you to have protected data that can only be created / viewed by a user who is logged into your app and has the appropriate level of access. For aspects of an app that will never be public, like a user dashboard for example, that's all you'd need to do.

However, this issue is trying to answer the question of what you should do if you have information that is largely public, that you want Google to crawl and index for search results, and you want Plenti to optimize with initial HTML rendering before loading interactive JavaScript, but you also have certain aspects related to that data that need to be private. You don't want to fully manage the information in Plenti because that wouldn't be secure, but you don't want to fully manage the information in the BaaS because you lose all the optimizations that Plenti does for public information. I'm trying to figure out a "hybrid" approach where Plenti will optimize the public stuff and the BaaS secures the private stuff. Essentially I just need a way to tie this information together and provide good ergonomics for the developer so they don't feel like they're reinventing the wheel each time they want to do this - while also being careful not to make the solution too rigid. Does that make sense?

jimafisk commented 2 years ago

Just want to make sure I'm answering your actual questions:

Is it like an opt in arrangement?

Yes, you don't need to do any of this unless you want people to log into your site and only get access to certain information based on their roles / permissions. It's actually one step farther than that even, it's only information that also has both a public and private component to it.

So all data by default is public via Plenti but once the _schema_json is initialised (i.e. for private data ) you can use it to specify the data you want to be private in this way?

Yes, all data in Plenti is public, but _schema.json doesn't make anything more private. You can use _schema.json to define the editor experience (e.g. you want certain field widgets, like checkboxes, for editing specific data), but the underlying data will still be public. The only thing that gives you truly private data is using a third-party "microservice" like one of the BaaS solutions mentioned above.

Hope that helps!

jamestagal commented 2 years ago

Hi @jimafisk Great replies! I see. Thanks for your comprehensive explanation of the issue you want to solve and answering to my questions too. :) I can see this sort of functionality is certainly something developers would want to implement for instance authentication and payment type services are very common an may be required for some sites so having an avenue to this is really important. And the how you lie these competing aspects of optimised public data against the private stuff looks challenging.

BTW Doing a quick search I pulled up HashIds for Javascript which is a small open-source library that generates short, unique, non-sequential ids from numbers. I watched this Youtube video - GUIDs and UUIDs are cool, but this is cooler of someone talking through how to implement Hashids but is C# but at least he covers the process. It looks cool! I like the way they demonstrate it by adding Hashids to page links on hover!
Update: I also see a Go version if you wanted to do it at that layer! Hope that helps. Ben

jimafisk commented 2 years ago

Thanks for the resources @jamestagal!

jimafisk commented 2 years ago

Been playing around with the API for this using the example from the original posting of this issue (events content type with private zoom_link field to be handled by PocketBase)

One approach (that I don't like) is to just say this is the developer's problem and force them to insert fields into the sidenav themselves in side the component

Example of inserting private fields manually ```svelte
```

I think it would be nicer if you could add some simple instructions to your _schema.json file for the content type to add least place the field in the sidenav. The advantages would be:

I started thinking about how to determine placement on the actual form. I played with the idea of an order key that you could set to an integer (e.g. order: 5) but what about if you're adding multiple private fields, do they count in the order calculation or just the original fields? Also is it a 0 based order, or should we start at 1? Seemed to hard to reason about and would probably require looking up the docs or trial and error. So I thought using a before key that you could point to an existing field might work better. Going with that, we'd also want an after key so you could get the full range of placement on the form.

I originally thought we would add a private: true key to determine that we're using this kind of field in _schema.json, but if we're going to add before / after keys this would be redundant because we could just queue off of them. We would only need to use the before / after keys for these non-JSON-tracked fields because you could easily set order for normal fields using _blueprint.json (soon to be _defaults.json).

Example _schema.json API ```json "zoom_link": { "type": "wysiwyg", "options": [ "all" ], "before": "location" }, ```
jimafisk commented 2 years ago

The before / after schema keys allow you to place an untracked field in your editor, but you still need to rig up a fair amount of scaffolding to watch for it and extract the value in your component.

```js ```

It would be nice if some of this work was done for you by Plenti behind the scenes. We could potentially create a new magic prop called shadowContent that we'd use to save values that aren't tracked in the regular content object.

We'd also need a way to listen for the Plenti publish event (saving content). I'll need to think through how this would work a little more, but just throwing a rough example out there for now.

```js ```
jimafisk commented 2 years ago

Here's a working example of loading a shadowContent value from a database into the editor and then saving the value as a record back to the database:

```js ```

note: this snippet does not check if a record with the same ID exists, it just keeps creating new records. You'd want to do that and run an update instead of a create action to avoid duplicates.

jimafisk commented 2 years ago

Full example with create / update / delete:

```js ```
jimafisk commented 2 years ago

You can use shadowContent using the v0.5.15 release.