Add self-hosted PocketBase stack with auto-migrations
- docker-compose.yml: app + pocketbase services (PB internal to the network) - pocketbase/: pinned PocketBase image, superuser bootstrap via env vars - pb_migrations: production schema (collections, rules, public_users view) applied automatically on first boot - pocketbase/pb_schema.json: schema export kept as reference - README: full-stack deployment docs
This commit is contained in:
@@ -36,11 +36,34 @@ against different PocketBase instances. For local dev, copy `.env.example` to
|
|||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
|
Single container (external PocketBase):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker build -t pokeshare .
|
docker build -t pokeshare .
|
||||||
docker run -p 4321:4321 -e POCKETBASE_URL=https://pb.example.com pokeshare
|
docker run -p 4321:4321 -e POCKETBASE_URL=https://pb.example.com pokeshare
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Full stack (app + PocketBase)
|
||||||
|
|
||||||
|
`docker-compose.yml` runs the app together with a PocketBase container. On
|
||||||
|
first boot, PocketBase applies `pocketbase/pb_migrations/`, which creates the
|
||||||
|
`users` profile fields, the `public_users` view, and the `mycards` and
|
||||||
|
`friendships` collections with their API rules — no manual setup needed.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
PB_SUPERUSER_EMAIL=admin@example.com PB_SUPERUSER_PASSWORD=changeme123 \
|
||||||
|
docker compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
- The app listens on `4321`; PocketBase listens on `8090` (internal network,
|
||||||
|
admin UI at `/_/` if you expose it).
|
||||||
|
- In Coolify, deploy as a **Docker Compose** resource and expose the `app`
|
||||||
|
service on your domain. Set `PB_SUPERUSER_*` in the environment variables.
|
||||||
|
- Accounts are created from the PocketBase admin UI (public registration is
|
||||||
|
disabled by the migration).
|
||||||
|
- To move existing data, use the PocketBase admin UI backups (Settings →
|
||||||
|
Backups) on the old instance and restore into `pb_data`.
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
- `src/pages/` — routes (`/`, `/search`, `/mycards`, `/card/[id]`,
|
- `src/pages/` — routes (`/`, `/search`, `/mycards`, `/card/[id]`,
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Full PokeShare stack: the Astro app and its PocketBase backend.
|
||||||
|
# In Coolify, deploy this file as a "Docker Compose" resource.
|
||||||
|
# The `app` service is the one to expose on your public domain.
|
||||||
|
# `pocketbase` stays on the internal network — expose it on a subdomain
|
||||||
|
# only if you want to reach the admin UI (/_/) from outside.
|
||||||
|
services:
|
||||||
|
pocketbase:
|
||||||
|
build: ./pocketbase
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
# Optional: create/update the admin account on boot (min 8 chars)
|
||||||
|
PB_SUPERUSER_EMAIL: ${PB_SUPERUSER_EMAIL:-}
|
||||||
|
PB_SUPERUSER_PASSWORD: ${PB_SUPERUSER_PASSWORD:-}
|
||||||
|
volumes:
|
||||||
|
- pb_data:/pb/pb_data
|
||||||
|
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
# Internal service URL — the browser never talks to PocketBase
|
||||||
|
# directly, everything goes through the Astro server.
|
||||||
|
POCKETBASE_URL: http://pocketbase:8090
|
||||||
|
depends_on:
|
||||||
|
- pocketbase
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pb_data:
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# PocketBase backend with auto-applied migrations (collections + rules).
|
||||||
|
# Migrations in ./pb_migrations run automatically when the server starts.
|
||||||
|
FROM alpine:3.21
|
||||||
|
|
||||||
|
ARG PB_VERSION=0.39.9
|
||||||
|
|
||||||
|
RUN apk add --no-cache ca-certificates curl unzip
|
||||||
|
|
||||||
|
WORKDIR /pb
|
||||||
|
RUN curl -fsSL "https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_linux_amd64.zip" -o pb.zip \
|
||||||
|
&& unzip pb.zip \
|
||||||
|
&& rm pb.zip
|
||||||
|
|
||||||
|
COPY pb_migrations ./pb_migrations
|
||||||
|
COPY entrypoint.sh ./
|
||||||
|
RUN chmod +x entrypoint.sh pocketbase
|
||||||
|
|
||||||
|
EXPOSE 8090
|
||||||
|
CMD ["./entrypoint.sh"]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Optionally create/update the admin (superuser) account on boot.
|
||||||
|
# Set PB_SUPERUSER_EMAIL and PB_SUPERUSER_PASSWORD as env vars to enable.
|
||||||
|
# The password must be at least 8 characters.
|
||||||
|
if [ -n "$PB_SUPERUSER_EMAIL" ] && [ -n "$PB_SUPERUSER_PASSWORD" ]; then
|
||||||
|
./pocketbase superuser upsert "$PB_SUPERUSER_EMAIL" "$PB_SUPERUSER_PASSWORD" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# `serve` automatically applies pending migrations from ./pb_migrations
|
||||||
|
exec ./pocketbase serve --http=0.0.0.0:8090
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/**
|
||||||
|
* Initial PokeShare schema: extends the default `users` auth collection and
|
||||||
|
* creates `public_users`, `mycards` and `friendships` with their API rules.
|
||||||
|
* Mirrors the production schema (pb_schema.json export).
|
||||||
|
* Applied automatically by `pocketbase serve` on first boot.
|
||||||
|
*/
|
||||||
|
migrate((app) => {
|
||||||
|
// --- users (auth collection): extra profile fields, self-only access ---
|
||||||
|
const users = app.findCollectionByNameOrId("users");
|
||||||
|
users.fields.add(
|
||||||
|
new Text({ name: "username", required: true, min: 3, max: 20 }),
|
||||||
|
new Select({
|
||||||
|
name: "theme_preference",
|
||||||
|
values: ["System", "Light", "Dark"],
|
||||||
|
maxSelect: 1,
|
||||||
|
}),
|
||||||
|
new Select({
|
||||||
|
name: "visibility",
|
||||||
|
values: ["Public", "Friends", "Private"],
|
||||||
|
maxSelect: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
users.indexes.push(
|
||||||
|
"CREATE UNIQUE INDEX idx_users_username ON users (username COLLATE NOCASE)",
|
||||||
|
);
|
||||||
|
users.listRule = "id = @request.auth.id";
|
||||||
|
users.viewRule = "id = @request.auth.id";
|
||||||
|
users.updateRule = "id = @request.auth.id";
|
||||||
|
// No public registration — accounts are created from the admin UI (/_/)
|
||||||
|
users.createRule = null;
|
||||||
|
app.save(users);
|
||||||
|
|
||||||
|
// --- public_users: read-only view used by the friend search ---
|
||||||
|
const publicUsers = new Collection({
|
||||||
|
name: "public_users",
|
||||||
|
type: "view",
|
||||||
|
viewQuery: `SELECT id, username, name, visibility FROM users WHERE visibility != 'Private'`,
|
||||||
|
listRule: '@request.auth.id != ""',
|
||||||
|
viewRule: null,
|
||||||
|
});
|
||||||
|
app.save(publicUsers);
|
||||||
|
|
||||||
|
// --- mycards: one row per owned card ---
|
||||||
|
// listRule enforces collection visibility at the database level:
|
||||||
|
// owner, or anyone if the owner is Public, or accepted friends if Friends.
|
||||||
|
const mycards = new Collection({
|
||||||
|
name: "mycards",
|
||||||
|
type: "base",
|
||||||
|
fields: [
|
||||||
|
{ name: "card_id", type: "text" },
|
||||||
|
{ name: "card_name", type: "text" },
|
||||||
|
{
|
||||||
|
name: "user",
|
||||||
|
type: "relation",
|
||||||
|
collectionId: "_pb_users_auth_",
|
||||||
|
maxSelect: 1,
|
||||||
|
cascadeDelete: true,
|
||||||
|
},
|
||||||
|
{ name: "card_image", type: "url" },
|
||||||
|
{ name: "variant", type: "text" },
|
||||||
|
{ name: "quantity", type: "number" },
|
||||||
|
{ name: "set_name", type: "text" },
|
||||||
|
{ name: "notes", type: "text" },
|
||||||
|
{ name: "condition", type: "text" },
|
||||||
|
],
|
||||||
|
listRule:
|
||||||
|
"@request.auth.id = user || (user.visibility = 'Public') || (user.visibility = 'Friends' && ((@collection.friendships.requester = user && @collection.friendships.addressee = @request.auth.id && @collection.friendships.status = 'accepted') || (@collection.friendships.addressee = user && @collection.friendships.requester = @request.auth.id && @collection.friendships.status = 'accepted')))",
|
||||||
|
viewRule:
|
||||||
|
"@request.auth.id = user || (@collection.friendships.requester = user && @collection.friendships.addressee = @request.auth.id && @collection.friendships.status = 'accepted') || (@collection.friendships.addressee = user && @collection.friendships.requester = @request.auth.id && @collection.friendships.status = 'accepted')",
|
||||||
|
createRule: '@request.auth.id != "" && user = @request.auth.id',
|
||||||
|
updateRule: '@request.auth.id != "" && user = @request.auth.id',
|
||||||
|
deleteRule: '@request.auth.id != "" && user = @request.auth.id',
|
||||||
|
});
|
||||||
|
app.save(mycards);
|
||||||
|
|
||||||
|
// --- friendships ---
|
||||||
|
const friendships = new Collection({
|
||||||
|
name: "friendships",
|
||||||
|
type: "base",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "requester",
|
||||||
|
type: "relation",
|
||||||
|
collectionId: "_pb_users_auth_",
|
||||||
|
maxSelect: 1,
|
||||||
|
cascadeDelete: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "addressee",
|
||||||
|
type: "relation",
|
||||||
|
collectionId: "_pb_users_auth_",
|
||||||
|
maxSelect: 1,
|
||||||
|
cascadeDelete: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "status",
|
||||||
|
type: "select",
|
||||||
|
values: ["accepted", "pending", "declined"],
|
||||||
|
maxSelect: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
listRule: "@request.auth.id = requester || @request.auth.id = addressee",
|
||||||
|
viewRule: "@request.auth.id = requester || @request.auth.id = addressee",
|
||||||
|
createRule: "@request.auth.id = requester && addressee != requester",
|
||||||
|
updateRule: "@request.auth.id = addressee || @request.auth.id = requester",
|
||||||
|
deleteRule: "@request.auth.id = addressee || @request.auth.id = requester",
|
||||||
|
});
|
||||||
|
app.save(friendships);
|
||||||
|
}, (app) => {
|
||||||
|
// Down: drop the custom collections. The added `users` fields are left in
|
||||||
|
// place — removing them would destroy data and they are harmless.
|
||||||
|
for (const name of ["friendships", "mycards", "public_users"]) {
|
||||||
|
try {
|
||||||
|
app.delete(app.findCollectionByNameOrId(name));
|
||||||
|
} catch {
|
||||||
|
// already missing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "pbc_274306238",
|
||||||
|
"listRule": "@request.auth.id = requester || @request.auth.id = addressee",
|
||||||
|
"viewRule": "@request.auth.id = requester || @request.auth.id = addressee",
|
||||||
|
"createRule": "@request.auth.id = requester && addressee != requester",
|
||||||
|
"updateRule": "@request.auth.id = addressee || @request.auth.id = requester",
|
||||||
|
"deleteRule": "@request.auth.id = addressee || @request.auth.id = requester",
|
||||||
|
"name": "friendships",
|
||||||
|
"type": "base",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "[a-z0-9]{15}",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text3208210256",
|
||||||
|
"max": 15,
|
||||||
|
"min": 15,
|
||||||
|
"name": "id",
|
||||||
|
"pattern": "^[a-z0-9]+$",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": true,
|
||||||
|
"required": true,
|
||||||
|
"system": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"collectionId": "_pb_users_auth_",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "relation1820765950",
|
||||||
|
"maxSelect": 0,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "requester",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"collectionId": "_pb_users_auth_",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "relation2602483783",
|
||||||
|
"maxSelect": 0,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "addressee",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "select2063623452",
|
||||||
|
"maxSelect": 0,
|
||||||
|
"name": "status",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "select",
|
||||||
|
"values": [
|
||||||
|
"accepted",
|
||||||
|
"pending",
|
||||||
|
"declined"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "autodate2990389176",
|
||||||
|
"name": "created",
|
||||||
|
"onCreate": true,
|
||||||
|
"onUpdate": false,
|
||||||
|
"presentable": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "autodate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "autodate3332085495",
|
||||||
|
"name": "updated",
|
||||||
|
"onCreate": true,
|
||||||
|
"onUpdate": true,
|
||||||
|
"presentable": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "autodate"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [],
|
||||||
|
"system": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pbc_143883864",
|
||||||
|
"listRule": "@request.auth.id = user || (user.visibility = 'Public') || (user.visibility = 'Friends' && ((@collection.friendships.requester = user && @collection.friendships.addressee = @request.auth.id && @collection.friendships.status = 'accepted') || (@collection.friendships.addressee = user && @collection.friendships.requester = @request.auth.id && @collection.friendships.status = 'accepted')))",
|
||||||
|
"viewRule": "@request.auth.id = user || (@collection.friendships.requester = user && @collection.friendships.addressee = @request.auth.id && @collection.friendships.status = 'accepted') || (@collection.friendships.addressee = user && @collection.friendships.requester = @request.auth.id && @collection.friendships.status = 'accepted')",
|
||||||
|
"createRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||||
|
"updateRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||||
|
"deleteRule": "@request.auth.id != \"\" && user = @request.auth.id",
|
||||||
|
"name": "mycards",
|
||||||
|
"type": "base",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "[a-z0-9]{15}",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text3208210256",
|
||||||
|
"max": 15,
|
||||||
|
"min": 15,
|
||||||
|
"name": "id",
|
||||||
|
"pattern": "^[a-z0-9]+$",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": true,
|
||||||
|
"required": true,
|
||||||
|
"system": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text1254922784",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "card_id",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text2987424932",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "card_name",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"collectionId": "_pb_users_auth_",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "relation1653163849",
|
||||||
|
"maxSelect": 0,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "user",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"exceptDomains": null,
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "url4245288345",
|
||||||
|
"name": "card_image",
|
||||||
|
"onlyDomains": null,
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "url"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text4047749037",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "variant",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "number2683508278",
|
||||||
|
"max": null,
|
||||||
|
"min": null,
|
||||||
|
"name": "quantity",
|
||||||
|
"onlyInt": false,
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text3420757135",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "set_name",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text18589324",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "notes",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text849699619",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "condition",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "autodate2990389176",
|
||||||
|
"name": "created",
|
||||||
|
"onCreate": true,
|
||||||
|
"onUpdate": false,
|
||||||
|
"presentable": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "autodate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "autodate3332085495",
|
||||||
|
"name": "updated",
|
||||||
|
"onCreate": true,
|
||||||
|
"onUpdate": true,
|
||||||
|
"presentable": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "autodate"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [],
|
||||||
|
"system": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pbc_83919930",
|
||||||
|
"listRule": "@request.auth.id != \"\"",
|
||||||
|
"viewRule": null,
|
||||||
|
"createRule": null,
|
||||||
|
"updateRule": null,
|
||||||
|
"deleteRule": null,
|
||||||
|
"name": "public_users",
|
||||||
|
"type": "view",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text3208210256",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "id",
|
||||||
|
"pattern": "^[a-z0-9]+$",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": true,
|
||||||
|
"required": true,
|
||||||
|
"system": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "_clone_EOl9",
|
||||||
|
"max": 20,
|
||||||
|
"min": 3,
|
||||||
|
"name": "username",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "_clone_0z4L",
|
||||||
|
"max": 255,
|
||||||
|
"min": 0,
|
||||||
|
"name": "name",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"help": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "_clone_3D0a",
|
||||||
|
"maxSelect": 0,
|
||||||
|
"name": "visibility",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "select",
|
||||||
|
"values": [
|
||||||
|
"Public",
|
||||||
|
"Friends",
|
||||||
|
"Private"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [],
|
||||||
|
"system": false,
|
||||||
|
"viewQuery": "SELECT id, username, name, visibility FROM users WHERE visibility != 'Private'"
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user