Compare commits
62 Commits
claude/rea
...
feat/ai-pi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40eff976b4 | ||
|
|
f838755991 | ||
|
|
8a8745fad2 | ||
| a5c18ccd0f | |||
|
|
b85785a0cb | ||
|
|
4a8dbee7df | ||
| d07d15b2a7 | |||
|
|
db5bb854c3 | ||
|
|
6fe66bbd93 | ||
|
|
2752fb95d9 | ||
|
|
caaf2b9eba | ||
|
|
d6c1813f75 | ||
|
|
d5655d2232 | ||
|
|
08aaed591f | ||
|
|
8529def748 | ||
|
|
d23b0b6b16 | ||
|
|
fa5dcaeb01 | ||
|
|
190d1a6e0b | ||
|
|
d2b067735c | ||
|
|
00541f4a08 | ||
|
|
3555048308 | ||
|
|
590912ac04 | ||
|
|
d71caa41f6 | ||
|
|
f68d76e3d2 | ||
|
|
7b84545dc5 | ||
|
|
f35550f270 | ||
|
|
9f3e1113a6 | ||
|
|
07a41938f1 | ||
|
|
3afef7785e | ||
|
|
d68164880f | ||
|
|
08f5b1fe18 | ||
|
|
228d0d7a54 | ||
|
|
06eb974825 | ||
|
|
5659ff6726 | ||
|
|
5b37c04588 | ||
|
|
98e32d8ac0 | ||
|
|
43d01dff58 | ||
|
|
b28956bbe7 | ||
|
|
e2de7f0729 | ||
|
|
2374413282 | ||
|
|
77d411b77e | ||
|
|
775c3030d5 | ||
|
|
1bc51b3cb6 | ||
|
|
1ba647fecd | ||
|
|
271baba860 | ||
|
|
8b57e10754 | ||
|
|
e68335e159 | ||
|
|
bb39a5fa71 | ||
|
|
267fa340a8 | ||
|
|
6e25cdbf25 | ||
|
|
4b7562749d | ||
|
|
7234074c21 | ||
|
|
0a73ab3466 | ||
|
|
aaafe413f6 | ||
|
|
e223836d7d | ||
|
|
0673122536 | ||
|
|
bb0aef92cf | ||
|
|
74ba5d3dc0 | ||
|
|
42d7209773 | ||
|
|
59e746a8a8 | ||
|
|
e783c5f1e7 | ||
|
|
3a4f6d7c22 |
31
.github/workflows/deploy-dev.yml
vendored
Normal file
31
.github/workflows/deploy-dev.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Deploy to Dev
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Ansible and collections
|
||||
run: |
|
||||
pip install --break-system-packages ansible
|
||||
ansible-galaxy collection install ansible.posix community.docker
|
||||
|
||||
- name: Set up SSH key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SRV_SSH_KEY }}" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
|
||||
- name: Run Ansible playbook
|
||||
run: |
|
||||
ansible-playbook \
|
||||
-i infra/development/hosts.ini \
|
||||
-e "ansible_ssh_private_key_file=~/.ssh/deploy_key" \
|
||||
-e "anthropic_api_key=${{ secrets.ANTHROPIC_API_KEY }}" \
|
||||
infra/development/site/deploy-playbook.yml
|
||||
33
.github/workflows/deploy-prod.yml
vendored
Normal file
33
.github/workflows/deploy-prod.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Deploy to Production
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Install Ansible and collections
|
||||
run: |
|
||||
pip install --break-system-packages ansible
|
||||
ansible-galaxy collection install ansible.posix community.docker
|
||||
|
||||
- name: Set up SSH key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.SRV_SSH_KEY }}" > ~/.ssh/deploy_key
|
||||
chmod 600 ~/.ssh/deploy_key
|
||||
|
||||
- name: Run Ansible playbook
|
||||
run: |
|
||||
ansible-playbook \
|
||||
-i infra/production/hosts.ini \
|
||||
-e "ansible_ssh_private_key_file=~/.ssh/deploy_key" \
|
||||
-e "anthropic_api_key=${{ secrets.ANTHROPIC_API_KEY }}" \
|
||||
infra/production/site/deploy-playbook.yml
|
||||
41
.github/workflows/on-create-tag.yml
vendored
Normal file
41
.github/workflows/on-create-tag.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: On Create Tag
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
uses: ./.github/workflows/publish-image.yml
|
||||
with:
|
||||
build_mode: production
|
||||
secrets: inherit
|
||||
|
||||
update-compose:
|
||||
needs: publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
token: ${{ github.token }}
|
||||
|
||||
- name: Update image tag in production compose.yml
|
||||
run: |
|
||||
VERSION=${GITHUB_REF_NAME#v}
|
||||
sed -i "s|\(image:.*:\)[^[:space:]]*|\1${VERSION}|" infra/production/site/compose.yml
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
git config user.name "respellion-bot"
|
||||
git config user.email "no-reply.git@respellion.nl"
|
||||
git add infra/production/site/compose.yml
|
||||
git commit -m "chore: update prod image to ${GITHUB_REF_NAME#v}"
|
||||
git push origin main
|
||||
|
||||
deploy-prod:
|
||||
needs: update-compose
|
||||
uses: ./.github/workflows/deploy-prod.yml
|
||||
secrets: inherit
|
||||
20
.github/workflows/on-pull-request-to-main.yml
vendored
Normal file
20
.github/workflows/on-pull-request-to-main.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: On Pull Request to Main
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
uses: ./.github/workflows/test.yml
|
||||
|
||||
publish:
|
||||
needs: test
|
||||
uses: ./.github/workflows/publish-image.yml
|
||||
secrets: inherit
|
||||
|
||||
deploy-dev:
|
||||
needs: publish
|
||||
uses: ./.github/workflows/deploy-dev.yml
|
||||
secrets: inherit
|
||||
21
.github/workflows/on-push-main.yml
vendored
Normal file
21
.github/workflows/on-push-main.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: On Push to Main
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
uses: ./.github/workflows/test.yml
|
||||
|
||||
publish:
|
||||
needs: test
|
||||
uses: ./.github/workflows/publish-image.yml
|
||||
with:
|
||||
build_mode: development
|
||||
secrets: inherit
|
||||
|
||||
deploy-dev:
|
||||
needs: publish
|
||||
uses: ./.github/workflows/deploy-dev.yml
|
||||
secrets: inherit
|
||||
56
.github/workflows/publish-image.yml
vendored
Normal file
56
.github/workflows/publish-image.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Publish Image
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
build_mode:
|
||||
description: "Build mode (development or production)"
|
||||
type: string
|
||||
default: production
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build_mode:
|
||||
description: "Build mode (development or production)"
|
||||
type: string
|
||||
default: production
|
||||
|
||||
env:
|
||||
REGISTRY: git.labs.respellion.tech
|
||||
IMAGE: respellion/learning-platform/learning-platform
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: respellion-bot
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE }}
|
||||
tags: |
|
||||
type=sha,format=short,prefix=
|
||||
type=raw,value=latest
|
||||
type=semver,pattern={{version}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
BUILD_MODE=${{ inputs.build_mode }}
|
||||
48
.github/workflows/test.yml
vendored
Normal file
48
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build Docker image
|
||||
run: docker build -t learning-platform .
|
||||
|
||||
- name: Build test image
|
||||
run: |
|
||||
cat > Dockerfile.test <<'EOF'
|
||||
FROM learning-platform
|
||||
COPY Caddyfile.test /etc/caddy/Caddyfile
|
||||
EOF
|
||||
docker build -t learning-platform-test -f Dockerfile.test .
|
||||
|
||||
- name: Start container
|
||||
run: |
|
||||
docker run -d --network gitea --name test-container learning-platform-test
|
||||
sleep 5
|
||||
|
||||
- name: Test homepage
|
||||
run: |
|
||||
status=$(curl -s -o /dev/null -w "%{http_code}" http://test-container)
|
||||
if [ "$status" != "200" ]; then
|
||||
echo "Homepage failed with status $status"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Homepage OK"
|
||||
|
||||
- name: Test security headers
|
||||
run: |
|
||||
headers=$(curl -sI http://test-container)
|
||||
echo "$headers" | grep -q "X-Frame-Options" || (echo "Missing X-Frame-Options" && exit 1)
|
||||
echo "$headers" | grep -q "X-Content-Type-Options" || (echo "Missing X-Content-Type-Options" && exit 1)
|
||||
echo "✓ Security headers OK"
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: docker stop test-container && docker rm test-container
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -22,3 +22,16 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Secrets
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# PocketBase
|
||||
pb_data/
|
||||
pocketbase.exe
|
||||
pocketbase.zip
|
||||
|
||||
# Claude Code local agent state (settings.local.json holds per-user
|
||||
# command allowlists; never share across teammates)
|
||||
.claude/
|
||||
|
||||
147
AI_AGENT.md
147
AI_AGENT.md
@@ -2,57 +2,146 @@
|
||||
|
||||
Welcome, fellow AI agent! If you are reading this, you are tasked with maintaining or extending the Respellion Learning Platform. This document provides the critical context, architectural patterns, and design standards you need to successfully work on this codebase.
|
||||
|
||||
> **Last updated:** 2026-05-18 — Adds 52-week annual curriculum system (§12). Reflects selective content generation (3 types), ISO week alignment, GitHub sync folder change, and AI extraction token limits.
|
||||
|
||||
## 1. Architectural Overview
|
||||
This is a single-page React application built with **Vite**. It is completely self-contained and currently runs without a dedicated backend database.
|
||||
* **Frontend:** React, React Router, Tailwind CSS.
|
||||
This is a single-page React application built with **Vite**, backed by **PocketBase** as the database and auth layer.
|
||||
* **Frontend:** React, React Router, Vanilla CSS (via CSS variables) + Tailwind utility classes mapped to those variables.
|
||||
* **Backend:** PocketBase (self-hosted). All data is stored in PocketBase collections, not localStorage.
|
||||
* **Animations:** Framer Motion (used extensively for page transitions, podium effects, and gamification feedback).
|
||||
* **Icons:** Lucide React.
|
||||
* **Visualizations:** D3.js (used strictly for the Admin Knowledge Graph).
|
||||
|
||||
## 2. State Management & Storage (Critical)
|
||||
There is **no backend database**. All data is persisted locally in the browser using a custom wrapper around `window.localStorage` located at `src/lib/storage.js`.
|
||||
All persistent data lives in **PocketBase**. The data access layer is in `src/lib/db.js`, which wraps the PocketBase SDK client from `src/lib/pb.js`.
|
||||
|
||||
**Namespacing Convention:**
|
||||
You must strictly adhere to these prefixes when reading/writing to `storage.js`:
|
||||
* `kb:*` - Knowledge base data (e.g., `kb:topics`, `kb:relations`, `kb:content:{topicId}`).
|
||||
* `admin:*` - Admin settings (e.g., `admin:anthropic_key`, `admin:sources`, `admin:use_simulation`).
|
||||
* `user:{id}:*` - Specific user progress (e.g., `user:{id}:week:{n}:learn_done`).
|
||||
* `quiz:bank:{topicId}` - Cached banks of AI-generated multiple-choice questions.
|
||||
* `quiz:result:{userId}:week:{n}` - The exact score and payload of a user's weekly test.
|
||||
* `leaderboard:current` - The active points ledger. Array of objects: `{ userId, name, points, testsCompleted, perfectScores }`.
|
||||
* `users:registry` - Array of registered users, including admins.
|
||||
**PocketBase Collections:**
|
||||
* `topics` — Knowledge graph nodes (`id`, `label`, `type`, `description`).
|
||||
* `relations` — Knowledge graph edges (`source`, `target`, `type`).
|
||||
* `content` — AI-generated learning modules per topic (`topic_id`, `data`). The `data` field is a **merged JSON object** containing only the content types that have been generated for that topic (e.g. `{ article: {...}, slides: [...] }`). New types are shallow-merged into the existing object by `learningService.js`; nothing is ever overwritten.
|
||||
* `quiz_banks` — Banks of AI-generated questions per topic (`topic_id`, `questions[]`).
|
||||
* `quiz_results` — User test scores (`user_id`, `week_number`, `score`, `total`, `percentage`, etc.).
|
||||
* `quiz_cache` — Cached weekly quiz for a user (`user_id`, `week_number`, `questions[]`, `meta`).
|
||||
* `learn_progress` — Whether a user completed the weekly learning session (`user_id`, `week_number`, `done`).
|
||||
* `leaderboard` — Points ledger (`user_id`, `name`, `points`, `tests_completed`).
|
||||
* `team_members` — Registered users with PIN auth (`name`, `role`, `pin`).
|
||||
* `sources` — Uploaded source documents and their extraction status (`name`, `status`, `error`).
|
||||
* `curriculum` — Annual learning schedule (`year`, `week_number`, `topic_id`, `theme`, `quarter`, `is_review_week`, `sort_order`). One entry per week per year. Managed via the admin Curriculum tab.
|
||||
* `settings` — Key/value store for app-wide settings (`key`, `value`).
|
||||
|
||||
**localStorage** is only used for **admin browser settings** (not user data):
|
||||
* `respellion:admin:anthropic_key` — Anthropic API key.
|
||||
* `respellion:admin:model` — Model override.
|
||||
* `respellion:admin:use_simulation` — Simulation mode toggle.
|
||||
* `kb:suggestions` — Pending/approved/rejected graph deltas proposed by R42. Always mutated via `kbStore` (see §8).
|
||||
* `quiz:active:{userId}` — Boolean flag set while the user is mid-quiz. R42's FAB is hidden when this is true (quiz-integrity rule).
|
||||
* `chat:thread:{userId}` — Persisted R42 conversation, capped at 50 messages.
|
||||
|
||||
**Session:** User login is PIN-based. The logged-in user's ID is stored in `sessionStorage` under `respellion_session` and resolved against `team_members` on app load (see `src/store/AppContext.jsx`).
|
||||
|
||||
**Week Number:** The current ISO-8601 week number is calculated dynamically on app load via `getWeekNumber(new Date())` in `src/store/AppContext.jsx`. It is **not** stored in the database. The `ADVANCE_WEEK` action still exists for admin use, but initial state always reflects the real calendar week.
|
||||
|
||||
**Curriculum Year:** The curriculum year is derived from `new Date().getFullYear()` via `getCurriculumYear()` in `src/lib/curriculumService.js`. It is not stored — always computed.
|
||||
|
||||
**Important:** All `db.js` functions are `async`. Always `await` them — omitting `await` will silently pass a Promise where data is expected.
|
||||
|
||||
**Auto-Cancellation:** The PocketBase JS SDK has auto-cancellation enabled by default. This causes concurrent identical requests (like `db.getTopics()` during React StrictMode renders or concurrent Promise.all) to abort with `ClientResponseError 0`. This feature is **globally disabled** in `src/lib/pb.js` via `pb.autoCancellation(false)` to prevent UI crashes during concurrent fetching.
|
||||
|
||||
## 3. The AI Integration (Anthropic)
|
||||
The application acts as a proxy to the Anthropic API (`claude-sonnet-4`).
|
||||
The application calls the Anthropic API via a proxy to avoid CORS issues.
|
||||
* **Location:** `src/lib/api.js`.
|
||||
* **Proxy:** In Docker, `/api/anthropic` is proxied via Nginx to bypass CORS restrictions. If the user reports CORS errors during local dev, ensure the Vite proxy in `vite.config.js` matches the Nginx spec.
|
||||
* **JSON Enforcement:** Prompts *must* strictly enforce that Claude returns *only* raw JSON. Do not let the AI wrap the response in markdown blocks (`\`\`\``) unless you build a regex to strip them (which is currently implemented via `.match(/\{[\s\S]*\}/)`).
|
||||
* **Proxy:** In Docker, `/api/anthropic` is proxied via Caddy to the Anthropic API endpoint. In local dev, configure `vite.config.js` to proxy the same path. The API key is injected server-side by Caddy; there is **no client-side API key**.
|
||||
* **Token limit:** `generateContent` uses `max_tokens: 8192`. Do not lower this — large knowledge-base files need the headroom.
|
||||
* **JSON Enforcement:** Prompts *must* strictly enforce that Claude returns *only* raw JSON. Do not let the AI wrap the response in markdown blocks — a regex strip via `.match(/\{[\s\S]*\}/)` is already applied in all service files.
|
||||
|
||||
## 4. Design System & Aesthetics
|
||||
Respellion relies on a premium, modern aesthetic.
|
||||
Respellion relies on a premium, modern aesthetic.
|
||||
* **CSS Variables:** Rely heavily on the variables defined in `src/index.css`.
|
||||
* Colors: `var(--color-bg)`, `var(--color-paper)`, `var(--color-teal)`, `var(--color-accent)`.
|
||||
* Radii: `var(--r-sm)`, `var(--r-lg)`, `var(--r-org)` (an organic, pill-like shape used for badges and podiums).
|
||||
* **Tailwind:** Tailwind is mapped to these CSS variables. Avoid hardcoding random hex codes. Use the existing Tailwind classes like `bg-teal`, `text-fg-muted`, and `border-bg-warm`.
|
||||
* **Components:** Always reuse the UI primitives in `src/components/ui/` (`Card.jsx`, `Button.jsx`, `Tag.jsx`, `Input.jsx`).
|
||||
|
||||
## 5. Gamification Rules
|
||||
## 5. Learning Content Types
|
||||
`src/lib/learningService.js` supports **three** content types for selective generation:
|
||||
| Type | Schema key | Description |
|
||||
|---|---|---|
|
||||
| `article` | `content.article` | Title, intro, sections, key takeaways |
|
||||
| `slides` | `content.slides` | Array of slides with bullets and speaker notes |
|
||||
| `infographic` | `content.infographic` | Headline, tagline, stats, steps, quote |
|
||||
|
||||
**There is no podcast type.** It was removed. Do not re-add it.
|
||||
|
||||
`generateLearningContent(topic, force, selectedType)` accepts one of the three types above, or `'all'` (admin regeneration). Cache-hit logic checks `content[selectedType]` directly. On generation, the new data is shallow-merged into the existing cached object so other types are preserved.
|
||||
|
||||
The `LearningContentViewer` tab bar reflects exactly these three modes. The empty-state for an un-generated tab shows a "Generate [Type]" button that calls `onGenerate(activeMode)` passed from the parent.
|
||||
|
||||
## 6. Gamification Rules
|
||||
If you are extending the Gamification system (`src/pages/Leaderboard.jsx`):
|
||||
* Tests grant **+2 points** per correct answer.
|
||||
* A 100% score grants the **Perfectionist** badge.
|
||||
* Tests grant **+2 points** per correct answer (handled in `testService.js → saveTestResult`).
|
||||
* A 100% score grants the **Perfectionist** badge (computed at render time in `Leaderboard.jsx`).
|
||||
* Completing 1 test grants **First Steps**, completing 5 grants **Veteran**.
|
||||
* Do not reset points dynamically. Read from `leaderboard:current`, mutate, and save back immediately.
|
||||
* Points are accumulated in the `leaderboard` collection via `db.upsertLeaderboardEntry`.
|
||||
|
||||
## 6. Docker & Deployment
|
||||
The app is fully containerized.
|
||||
## 7. Docker & Deployment
|
||||
The app is fully containerized. PocketBase runs as a sidecar service.
|
||||
* **Build:** `docker build -t respellion-app:latest .`
|
||||
* **Run:** `docker run -d -p 8080:80 --name respellion respellion-app:latest`
|
||||
* **Nginx:** Ensure any new frontend routes are caught by the Nginx `try_files $uri $uri/ /index.html;` directive defined in `nginx.conf` to prevent 404s on page refresh.
|
||||
* **Run:** `docker compose up -d` (see `docker-compose.yml`).
|
||||
* **Caddy (reverse proxy):** Handles SPA fallback, injects the Anthropic API key via a `Authorization` header on `/api/anthropic/*` requests, and proxies `/pb/*` to the PocketBase service. Config lives in `Caddyfile`.
|
||||
* **PocketBase URL:** Resolved from `VITE_PB_URL` env var, or falls back to `window.location.origin + '/pb'` (see `src/lib/pb.js`).
|
||||
|
||||
## 7. How to Add New Features
|
||||
1. **Define State:** Decide where the data lives in `localStorage`.
|
||||
2. **Build UI:** Create components using `Card`, `Button`, and `framer-motion` for smooth entry (`animate-in fade-in slide-in-from-bottom-4`).
|
||||
3. **Wire Logic:** Connect to `src/store/AppContext.jsx` if it requires global user context, otherwise manage locally and persist to `storage.js`.
|
||||
4. **Test:** Run the Docker container to ensure no build errors exist.
|
||||
## 8. GitHub Knowledge-Base Sync
|
||||
The Admin upload panel (`src/components/admin/UploadZone.jsx`) can sync markdown/text files directly from a GitHub repository.
|
||||
|
||||
* **Default folder:** `docs/knowledge-base/` of the `respellion/employee-handbook` repo. This is persisted in the `settings` collection under the key `github:url` so admins can change it from the UI.
|
||||
* **Change detection:** Each file's SHA is stored as `github:sha:<filename>` in `settings`. Files whose SHA differs are marked *Updated*; new files are *New*. Up-to-date files are skipped.
|
||||
* **Extraction pipeline (`src/lib/extractionPipeline.js`):** Calls `anthropicApi.generateContent` with a strict JSON-only system prompt. To prevent truncated responses on large files, the prompt limits extraction to **max 15 topics** and their most important relations. If the AI returns non-JSON, the file is marked `failed` in the `sources` collection.
|
||||
* **Deduplication:** A file already in `sources` with `status: completed` will throw and not be re-processed. Delete the source record first to force a re-analysis.
|
||||
* **Do not increase topic cap beyond 15** without also verifying the `max_tokens: 8192` budget is sufficient for the expected file size.
|
||||
|
||||
## 9. R42 Chatbot
|
||||
The platform ships a global chatbot avatar called **R42**, rendered as the Respellion `{ r }` brand mark in three states (idle / typing / error).
|
||||
|
||||
* **Mark component:** `src/components/ui/Mark.jsx`. Pure SVG; renders the brand mark with `state`, `size`, `theme`, `showFrame`, `brace`, `letter` props. Requires the `BallPill` font (loaded via `@font-face` in `src/index.css`, served from `public/fonts/BallPill-light.otf`). Falls back to JetBrains Mono (already loaded).
|
||||
* **Chat module:** `src/components/chat/`.
|
||||
* `ChatLauncher.jsx` — global FAB; auto-hides when `quiz:active:{userId}` is set. Listens to the `respellion:quiz-state` window event for fast updates.
|
||||
* `ChatWindow.jsx` — 380×480 chat panel; Esc closes; renders messages from `useChat`.
|
||||
* `useChat.js` — owns the message list, persists to `chat:thread:{userId}`, calls `anthropicApi.chat()`. `buildKbContext` is async (PocketBase), so `send()` is fully async.
|
||||
* `prompts.js` — system prompt, greeting, and the `propose_graph_delta` tool spec.
|
||||
* `rag.js` — fetches `kb:topics` + `kb:relations` from PocketBase; lazy-loads `kb:content:{id}` only when a topic is mentioned. Returns `{ context, topics }` so `validateDelta` can reuse the fetched topics without a second round-trip.
|
||||
* **Multi-turn API:** `anthropicApi.chat(systemPrompt, messages, { tools })` in `src/lib/api.js`. Returns the raw Anthropic response (`{ content: [...], stop_reason }`) so callers can read both text blocks and `tool_use` blocks. No API key header — Caddy proxy injects it server-side, matching the existing `generateContent` pattern.
|
||||
* **Quiz-integrity rule:** `src/pages/Testen.jsx` sets `quiz:active:{userId}=true` on start and clears it on every non-quiz phase + unmount, plus dispatches a `respellion:quiz-state` event. Never bypass this — letting users ask R42 mid-quiz would break scoring.
|
||||
* **Graph refinement:** when R42's `tool_use` block proposes a `propose_graph_delta`, `rag.js` validates (no duplicate ids, no orphan relations, caps 3 topics / 5 relations) and surfaces a confirmation chip inline.
|
||||
* **Admin user clicks Ja** — `kbStore.applyDelta` writes to PocketBase via `db.upsertTopic` / `db.addRelation` immediately.
|
||||
* **Non-admin clicks Ja** — `kbStore.appendSuggestion` queues an entry in `kb:suggestions` localStorage (status `pending`).
|
||||
* **Admin approval UI:** `src/components/admin/SuggestionsQueue.jsx`, mounted at the top of the Knowledge Graph admin panel. Approve calls `kbStore.approveSuggestion(id)` which runs the same `applyDelta` merge (async, PocketBase) and flips status to `approved`. Reject flips to `rejected` for audit.
|
||||
* **kbStore:** `src/lib/kbStore.js` is the single source of truth for KB mutations from the chatbot path. Topics/relations go to PocketBase; suggestions queue goes to localStorage. Dispatches `respellion:kb-updated` after any write so the D3 graph and the queue panel refresh without a reload.
|
||||
|
||||
## 10. How to Add New Features
|
||||
1. **Define Schema:** Add a new PocketBase collection via the Admin UI or the init script (`scripts/setup-pb-collections.mjs`).
|
||||
2. **Add DB Helpers:** Add async CRUD functions in `src/lib/db.js`.
|
||||
3. **Build UI:** Create components using `Card`, `Button`, and `framer-motion` for smooth entry (`animate-in fade-in slide-in-from-bottom-4`).
|
||||
4. **Wire Logic:** Connect to `src/store/AppContext.jsx` if it requires global user context, otherwise manage state locally.
|
||||
5. **Test:** Run the Docker stack (`docker compose up`) to ensure no build errors exist.
|
||||
|
||||
## 11. Known Gotchas & Decisions
|
||||
* **No podcast type.** The podcast learning type was deliberately removed. The three supported types are `article`, `slides`, and `infographic`.
|
||||
* **Week number is computed, not stored.** Do not add a `admin:current_week` setting — the ISO week is always derived from `new Date()`.
|
||||
* **Caddy, not Nginx.** Older docs/comments may reference Nginx. The reverse proxy is Caddy. Update any references you encounter.
|
||||
* **AI token budget.** If you see `[Pipeline] AI returned non-JSON response` in the logs, the response was truncated. Increase the topic cap prompt constraint before raising `max_tokens`.
|
||||
* **PocketBase auto-cancellation is OFF.** `pb.autoCancellation(false)` is set globally in `src/lib/pb.js`. Never re-enable it — it causes abort errors during concurrent fetches in React StrictMode.
|
||||
|
||||
## 12. Annual Curriculum System
|
||||
The platform uses a **52-week annual curriculum** so every employee covers all knowledge-base topics in one calendar year.
|
||||
|
||||
* **Service:** `src/lib/curriculumService.js` — curriculum engine with topic lookup, progress tracking, and auto-generation.
|
||||
* **DB functions:** `db.getCurriculum(year)`, `db.getCurriculumWeek(year, week)`, `db.setCurriculumWeek(...)`, `db.bulkSetCurriculum(year, weeks[])`.
|
||||
* **Admin UI:** `src/components/admin/CurriculumManager.jsx` — accessed via the "Curriculum" tab in the admin panel. Admins can auto-generate a schedule from KB topics or manually assign topics per week.
|
||||
* **Same topic for everyone:** All employees study the same topic each week — this is by design to enable team discussion and shared quizzes.
|
||||
* **Quarterly structure:** Weeks 1–13 (Q1), 14–26 (Q2), 27–39 (Q3), 40–52 (Q4). Review/recap weeks at 13, 26, 39, 52.
|
||||
* **Fallback:** If no curriculum exists for the current year, `getAssignedTopic()` falls back to the legacy hash-based assignment for backward compatibility.
|
||||
* **Progress tracking:** `getYearProgress(userId)` and `getQuarterProgress(userId, quarter)` compute completion from the `learn_progress` collection against the curriculum.
|
||||
* **Auto-generate:** `autoGenerateCurriculum(year)` distributes all non-fact topics across 48 content weeks + 4 review weeks. If there are fewer topics than weeks, they cycle. If more, excess topics remain in the self-service library.
|
||||
* **Do not remove the hash fallback** — it ensures the platform works even without a configured curriculum.
|
||||
|
||||
Good luck! You are building a platform that empowers continuous learning. Keep it fast, keep it beautiful, and keep the user engaged.
|
||||
|
||||
569
AI_PIPELINE_HARDENING_PLAN.md
Normal file
569
AI_PIPELINE_HARDENING_PLAN.md
Normal file
@@ -0,0 +1,569 @@
|
||||
# AI Pipeline Hardening — Implementation Plan
|
||||
|
||||
> **Audience:** an AI agent executing this plan against the Respellion Learning Platform.
|
||||
> **Owner before this work:** Raymond Verhoef (rve@respellion.nl).
|
||||
> **Source of truth for repo conventions:** [`AI_AGENT.md`](AI_AGENT.md). Read it before starting.
|
||||
|
||||
This plan upgrades the platform's interaction with the Anthropic API: how prompts are built, how responses are parsed, how the model is retried, and how outputs are validated. It is broken into six phases that can be implemented and shipped independently. Each phase ends with verifiable acceptance criteria.
|
||||
|
||||
---
|
||||
|
||||
## 0. Operating principles
|
||||
|
||||
These rules govern every phase. Re-read them before you commit.
|
||||
|
||||
1. **PocketBase is the source of truth.** No persistent state in localStorage (see [`AI_AGENT.md`](AI_AGENT.md) §2). The Anthropic API is proxied via Caddy; **never** add `x-api-key` headers in frontend code.
|
||||
2. **No behaviour regressions.** Existing UI flows (extraction, weekly learning, weekly quiz, R42 chat, handbook sync, analyze-graph) must keep working after every phase. Phases are additive.
|
||||
3. **Schema-first.** Where the model produces structured output, define a JSON Schema (Zod) and validate every response. Reject (don't paper over) malformed output.
|
||||
4. **Single LLM entry point.** After Phase 1 there is exactly one module that talks to `/api/anthropic/v1/messages`. All callers go through it.
|
||||
5. **No silent truncation.** If `stop_reason === 'max_tokens'` on a structured-output call, throw — never persist a partial parse.
|
||||
6. **Cache what is stable, vary what is dynamic.** Use prompt caching for system prompts and KB context. User messages are never cached.
|
||||
7. **Comments and docs:** follow the repo's terse style. Don't add explanatory comments unless the *why* is non-obvious.
|
||||
8. **Migrations:** when changing a PocketBase schema, add a migration in `pb_migrations/` following the existing timestamp prefix convention. Never edit shipped migrations.
|
||||
9. **Stop and ask** if you encounter a decision the plan doesn't cover (e.g. a model deprecation, a missing collection, a failing test that looks pre-existing).
|
||||
|
||||
### Files you will touch (or create) across all phases
|
||||
|
||||
| Path | Purpose |
|
||||
|---|---|
|
||||
| `src/lib/llm.js` *(new, Phase 1)* | Single Anthropic client wrapper |
|
||||
| `src/lib/llmSchemas.js` *(new, Phase 1)* | Zod schemas for every structured task |
|
||||
| `src/lib/llmRetry.js` *(new, Phase 1)* | Retry + backoff + abort policy |
|
||||
| `src/lib/random.js` *(new, Phase 4)* | Fisher–Yates shuffle + RNG helpers |
|
||||
| `src/lib/api.js` | Becomes a thin re-export from `llm.js` (back-compat) |
|
||||
| `src/lib/extractionPipeline.js` | Migrated to `llm.js` + tool use + overlap chunking |
|
||||
| `src/lib/learningService.js` | Migrated to `llm.js` + tool use + patch-refine |
|
||||
| `src/lib/testService.js` | Migrated to `llm.js` + tool use + dedup + shuffle fix |
|
||||
| `src/components/admin/KnowledgeGraph.jsx` | `analyzeGraph` → tool use + dry-run preview |
|
||||
| `src/components/chat/rag.js` | Retrieval (TF-IDF) + `lookup_topic` tool |
|
||||
| `src/components/chat/prompts.js` | Split system prompt into cacheable + dynamic |
|
||||
| `src/components/chat/useChat.js` | Wire retrieval + truncation |
|
||||
| `pb_migrations/*` | Schema additions for `llm_calls`, question `difficulty`, topic `relevance_locked` |
|
||||
| `evals/` *(new, Phase 6)* | Golden-set eval harness |
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Foundation (single LLM client + robust parsing)
|
||||
|
||||
**Goal:** every LLM call goes through one module that handles retry, timeout, abort, JSON extraction, and schema validation. No behaviour change visible to the user.
|
||||
|
||||
### 1.1 Create `src/lib/llmRetry.js`
|
||||
|
||||
Implements the retry policy used by `llm.js`.
|
||||
|
||||
**Behaviour:**
|
||||
- Exponential backoff with full jitter, base 1000ms, cap 16000ms.
|
||||
- Retries only on these HTTP statuses: `408, 425, 429, 500, 502, 503, 504, 529`.
|
||||
- Honours `Retry-After` header (seconds or HTTP date). If present and ≤ 60s, use it; if > 60s, fail fast.
|
||||
- Default `maxRetries = 4`.
|
||||
- Does **not** retry on `AbortError`.
|
||||
|
||||
**Exported interface:**
|
||||
|
||||
```js
|
||||
// withRetry: (fn: (attempt:number) => Promise<T>, opts?) => Promise<T>
|
||||
// RetryableError(status, retryAfterMs)
|
||||
export async function withRetry(fn, { maxRetries = 4, signal } = {}) { ... }
|
||||
export class RetryableError extends Error { constructor(status, retryAfterMs) { ... } }
|
||||
```
|
||||
|
||||
### 1.2 Create `src/lib/llmSchemas.js`
|
||||
|
||||
One Zod schema per structured task. Install Zod (`npm i zod`).
|
||||
|
||||
Required schemas (names + shape match what callers already produce — do not change field names):
|
||||
|
||||
- `extractionResultSchema` — `{ topics: Topic[], relations: Relation[] }` matching the existing `SYSTEM_PROMPT` in [`extractionPipeline.js`](src/lib/extractionPipeline.js).
|
||||
- `handbookResultSchema` — same shape, but `relation.type` enum unified to `related_to | depends_on | part_of | executed_by` (see Phase 3 task 3.5 — for now the schema accepts both `executes` and `executed_by`, normalize `executes → executed_by` post-validation).
|
||||
- `learningArticleSchema`, `learningSlidesSchema`, `learningInfographicSchema`, `learningAllSchema` matching [`learningService.js`](src/lib/learningService.js).
|
||||
- `quizQuestionsSchema` — `{ questions: Question[] }` with `options.length === 4` and `correctIndex ∈ [0,3]`.
|
||||
- `customTopicSchema` — `{ label, type: 'concept'|'role'|'process', description }`.
|
||||
- `graphActionsSchema` — `{ merges, deletions, newRelations, relevanceUpdates }` matching [`KnowledgeGraph.jsx:329`](src/components/admin/KnowledgeGraph.jsx).
|
||||
- `proposeGraphDeltaSchema` — matches `PROPOSE_GRAPH_DELTA_TOOL.input_schema` in [`prompts.js`](src/components/chat/prompts.js).
|
||||
|
||||
**Acceptance:** every schema has at least one happy-path Vitest test in `src/lib/__tests__/llmSchemas.test.js` (add `vitest` if not present).
|
||||
|
||||
### 1.3 Create `src/lib/llm.js`
|
||||
|
||||
The single Anthropic client. All other modules must call only this one.
|
||||
|
||||
**Public interface:**
|
||||
|
||||
```js
|
||||
// Task tier — used to pick a model from settings.
|
||||
// 'fast' → admin:model:fast (default: claude-haiku-4-5-20251001)
|
||||
// 'standard' → admin:model:standard (default: claude-sonnet-4-6)
|
||||
// 'reasoning' → admin:model:reasoning (default: claude-opus-4-7)
|
||||
export async function callLLM({
|
||||
task, // string, e.g. 'extract.source' — used for logging only
|
||||
tier = 'standard',
|
||||
system, // string OR Array<{ type:'text', text:string, cache_control?:{type:'ephemeral'} }>
|
||||
messages, // [{ role, content }] OR omitted (use `user`)
|
||||
user, // shorthand for [{role:'user', content: user}]
|
||||
tools, // optional Anthropic tool definitions
|
||||
toolChoice, // optional, e.g. { type:'tool', name:'emit_knowledge_graph' }
|
||||
schema, // optional Zod schema for text→JSON path (used only when no tool)
|
||||
maxTokens = 4096,
|
||||
temperature = 0,
|
||||
signal, // AbortSignal
|
||||
}): Promise<{
|
||||
text: string,
|
||||
toolUses: Array<{ name, input }>,
|
||||
stopReason: string,
|
||||
usage: { input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens },
|
||||
requestId: string | null,
|
||||
model: string,
|
||||
durationMs: number,
|
||||
}>
|
||||
```
|
||||
|
||||
**Key requirements:**
|
||||
|
||||
1. **Simulation mode** preserved: if `storage.get('admin:use_simulation') === true`, return a deterministic stub (use existing `simulateResponse` payload for backward compatibility — branch on `task` prefix to return a matching stub).
|
||||
2. **Fetch** with `AbortController`; default **60-second timeout** if caller didn't pass a signal.
|
||||
3. **Retry** through `withRetry` (Phase 1.1).
|
||||
4. **Auth-portal detection** preserved: if response is not `application/json`, throw `Your session has expired. Please refresh the page and log in again.` exactly as today.
|
||||
5. **No truncation acceptance**: if `stop_reason === 'max_tokens'` AND caller passed `schema` OR `toolChoice` requested a tool, throw `LLMTruncatedError`.
|
||||
6. **Robust JSON extraction** when caller passed `schema` (and no tool was used): use `parseStructuredText(text)` that
|
||||
- strips ```` ```json ```` and ```` ``` ```` fences,
|
||||
- finds the outermost balanced JSON value (object **or** array) via a tiny brace-matching scan, not regex,
|
||||
- throws `LLMOutputError` if no balanced JSON found,
|
||||
- runs Zod `schema.parse` on the result.
|
||||
7. **Tool path:** when `tools` is provided and the model emits `tool_use`, return them under `toolUses`. Validate each tool's input against the corresponding Zod schema if the caller wired one in (via `toolSchemas: { [toolName]: ZodSchema }`).
|
||||
8. **Logging:** after every call, append a row to a new PocketBase collection `llm_calls` (best-effort — never block on this; catch and console.debug failures). Fields: `task, model, tier, duration_ms, input_tokens, output_tokens, cache_read_tokens, cache_create_tokens, stop_reason, ok, error_msg`. See Phase 5 task 5.6 for the migration.
|
||||
9. **Custom errors**: `LLMHttpError`, `LLMTruncatedError`, `LLMOutputError`, `LLMValidationError`. All extend `Error` and set `name` for `instanceof` checks.
|
||||
|
||||
### 1.4 Make `src/lib/api.js` a thin shim
|
||||
|
||||
Replace the existing `anthropicApi.generateContent` and `anthropicApi.chat` implementations with calls into `llm.js`. Preserve the exact exported names and return shapes so no caller breaks.
|
||||
|
||||
```js
|
||||
// api.js after Phase 1
|
||||
export const anthropicApi = {
|
||||
async generateContent(systemPrompt, userMessage, maxRetries = 1) {
|
||||
const { text } = await callLLM({
|
||||
task: 'legacy.generateContent',
|
||||
tier: 'standard',
|
||||
system: systemPrompt,
|
||||
user: userMessage,
|
||||
maxTokens: 8192,
|
||||
temperature: 0,
|
||||
});
|
||||
return text;
|
||||
},
|
||||
async chat(systemPrompt, messages, opts = {}) {
|
||||
const r = await callLLM({
|
||||
task: 'legacy.chat',
|
||||
tier: 'standard',
|
||||
system: systemPrompt,
|
||||
messages,
|
||||
tools: opts.tools,
|
||||
maxTokens: 1024,
|
||||
temperature: 0.3, // chat default — see Phase 5
|
||||
});
|
||||
return {
|
||||
content: [
|
||||
...(r.text ? [{ type:'text', text: r.text }] : []),
|
||||
...r.toolUses.map(tu => ({ type:'tool_use', name: tu.name, input: tu.input })),
|
||||
],
|
||||
stop_reason: r.stopReason,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 1.5 Update default model + tiered settings
|
||||
|
||||
- Replace `DEFAULT_MODEL = 'claude-sonnet-4-20250514'` with the three tier defaults above.
|
||||
- In **Admin → Settings**, add three model selects (`fast`, `standard`, `reasoning`). Read existing `admin:model` as a legacy fallback for `standard` (so existing users don't lose their override).
|
||||
|
||||
### Phase 1 acceptance criteria
|
||||
|
||||
- [ ] `npm run lint` passes; `npm run test` passes (Vitest).
|
||||
- [ ] Every existing user flow (extraction, weekly content, weekly quiz, R42, handbook sync, analyze graph) still works against the live API.
|
||||
- [ ] `grep -r "fetch.*anthropic" src/` returns only `src/lib/llm.js`.
|
||||
- [ ] Simulation mode toggle still returns stubbed responses for all flows.
|
||||
- [ ] Manually verify: kill the network mid-call → request aborts within 60s and surfaces a clear error message.
|
||||
- [ ] Manually verify: rate-limit the proxy (429 + `Retry-After: 5`) → call retries once after ~5s and then succeeds.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Prompt caching & tool-based structured outputs
|
||||
|
||||
**Goal:** structured-output tasks no longer parse JSON out of prose. Large stable prompts are cached.
|
||||
|
||||
### 2.1 Migrate extraction to tool use
|
||||
|
||||
In [`extractionPipeline.js`](src/lib/extractionPipeline.js):
|
||||
|
||||
- Replace the "Return JSON only" instruction with a tool: `emit_knowledge_graph` whose `input_schema` mirrors `extractionResultSchema`.
|
||||
- Replace `anthropicApi.generateContent(...)` with `callLLM({ ..., tools:[emitKnowledgeGraphTool], toolChoice:{ type:'tool', name:'emit_knowledge_graph' } })`.
|
||||
- Read the validated object from `toolUses[0].input`.
|
||||
- Same migration for `analyzeHandbookDelta` (tool `emit_handbook_delta`).
|
||||
- Delete every `responseText.match(/\{[\s\S]*\}/)` site.
|
||||
|
||||
### 2.2 Migrate learning, quiz, custom-topic, graph-actions to tool use
|
||||
|
||||
Same pattern, in:
|
||||
|
||||
- [`learningService.js`](src/lib/learningService.js): tools `emit_learning_article`, `emit_learning_slides`, `emit_learning_infographic`, `emit_learning_all`, `emit_custom_topic`.
|
||||
- [`testService.js`](src/lib/testService.js): tool `emit_quiz_questions`.
|
||||
- [`KnowledgeGraph.jsx:297`](src/components/admin/KnowledgeGraph.jsx): tool `emit_graph_actions`.
|
||||
|
||||
### 2.3 Prompt caching
|
||||
|
||||
Pass `system` as an array of blocks so the stable parts can be cached:
|
||||
|
||||
```js
|
||||
system: [
|
||||
{ type:'text', text: STABLE_SYSTEM_HEADER, cache_control: { type:'ephemeral' } },
|
||||
{ type:'text', text: dynamicPart }, // not cached
|
||||
],
|
||||
```
|
||||
|
||||
Apply caching to:
|
||||
- Extraction `SYSTEM_PROMPT` and `HANDBOOK_SYSTEM_PROMPT` (both fully stable → cache the whole block).
|
||||
- R42 system prompt — split into three blocks: stable preamble (cached), KB context (cached *only* while the graph hasn't changed; bust by appending a short hash of the topic IDs+labels — Phase 5 details), and per-turn role line (not cached).
|
||||
|
||||
### 2.4 Patch-based learning refinement
|
||||
|
||||
Refactor `refineLearningContent` ([`learningService.js:147`](src/lib/learningService.js:147)) from "return the full updated JSON" to **patch operations** via tools:
|
||||
|
||||
- `set_section(heading: string, body: string)` — replace one section by heading match.
|
||||
- `add_section(heading: string, body: string, position: 'start'|'end')`.
|
||||
- `remove_section(heading: string)`.
|
||||
- `replace_takeaways(items: string[])`.
|
||||
- `set_intro(intro: string)`.
|
||||
|
||||
Apply patches client-side to the cached object. Re-validate against `learningArticleSchema` after patching; reject the whole turn if invalid.
|
||||
|
||||
### Phase 2 acceptance criteria
|
||||
|
||||
- [ ] No regex JSON extraction left in `src/`: `grep -rn "match(/\\\\{\\[\\\\s\\\\S\\]\\*\\\\}/)" src/` returns nothing.
|
||||
- [ ] Token usage telemetry shows `cache_read_input_tokens > 0` on the second extraction call within 5 minutes (cache hit).
|
||||
- [ ] Re-running extraction on a known source produces the same topic count ±10% as before this phase.
|
||||
- [ ] `refineLearningContent` round-trip ("make the intro shorter") produces only the changed section in the diff against the prior cached content.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Extraction quality
|
||||
|
||||
**Goal:** fewer near-duplicate topics, no silent truncation, adaptive throttling, unified vocabulary.
|
||||
|
||||
### 3.1 Sentence-aware chunking with overlap
|
||||
|
||||
Replace `chunkText` in [`extractionPipeline.js:87`](src/lib/extractionPipeline.js:87):
|
||||
|
||||
- Target **~2000 input tokens per chunk**. Approximate as `chars / 4`. Configurable via `MAX_CHUNK_CHARS = 8000`.
|
||||
- **200-token overlap** between chunks (`OVERLAP_CHARS = 800`).
|
||||
- Split on sentence boundaries (`/(?<=[.!?])\s+/`) first; fall back to paragraph boundary if a sentence is too long; never produce a chunk larger than `MAX_CHUNK_CHARS`.
|
||||
- Add a guard: if a single sentence exceeds `MAX_CHUNK_CHARS`, hard-split at character boundary and log a warning.
|
||||
|
||||
### 3.2 Stateful extraction
|
||||
|
||||
Before each chunk after the first, prepend to the user message:
|
||||
|
||||
```text
|
||||
Already-extracted topic IDs (do NOT create new IDs for these — reuse them if the same concept appears here):
|
||||
- software-engineer
|
||||
- onboarding-buddy
|
||||
...
|
||||
```
|
||||
|
||||
Cap the list at 200 IDs by recency to keep token cost bounded. The model will then reuse IDs instead of inventing variants like `software-developer`.
|
||||
|
||||
### 3.3 Adaptive throttling
|
||||
|
||||
Replace the hard `setTimeout(r, 12000)` in [`extractionPipeline.js:127`](src/lib/extractionPipeline.js:127) and the 15s sleep in [`KnowledgeGraph.jsx:274`](src/components/admin/KnowledgeGraph.jsx:274) with a shared token-bucket limiter in `src/lib/llmRetry.js`:
|
||||
|
||||
```js
|
||||
export const extractionLimiter = createLimiter({ rps: 5/60, burst: 1 }); // 5 req/min
|
||||
// usage: await extractionLimiter.acquire();
|
||||
```
|
||||
|
||||
`callLLM` accepts an optional `limiter` param that it `await`s before fetch. On 429 with `Retry-After`, the limiter is paused for that duration.
|
||||
|
||||
### 3.4 Preserve admin-edited relevance
|
||||
|
||||
Add a migration introducing `relevance_locked: bool` on `topics`. Set it to `true` whenever an admin edits `learning_relevance` via the UI ([`KnowledgeGraph.jsx`](src/components/admin/KnowledgeGraph.jsx) edit handler — locate by searching for `setLearningRelevance` or the relevance form field).
|
||||
|
||||
In `mergeKnowledgeGraph` ([`extractionPipeline.js:167`](src/lib/extractionPipeline.js:167)), when `relevance_locked`, never overwrite `learning_relevance`.
|
||||
|
||||
### 3.5 Unify relation vocabulary
|
||||
|
||||
Pick **one** set: `related_to | depends_on | part_of | executed_by`. Migrate:
|
||||
|
||||
- `HANDBOOK_SYSTEM_PROMPT` ([`extractionPipeline.js:42`](src/lib/extractionPipeline.js:42)) — change `executes` to `executed_by` and swap the source/target in the prompt example.
|
||||
- Write a one-shot migration script `pb_migrations/<timestamp>_normalize_relation_types.js` that rewrites any existing `executes` relation to `executed_by` and swaps `source ↔ target`.
|
||||
- Verify R42's `validateDelta` ([`rag.js:108`](src/components/chat/rag.js:108)) already enforces this set (it does) — no change needed there.
|
||||
|
||||
### 3.6 Cancellation
|
||||
|
||||
Add a "Cancel" button to the source-processing UI in `ContentManager.jsx` / `UploadZone.jsx` (locate the one that displays extraction progress). Wire it to abort the in-flight `callLLM` via the `signal` it receives. On cancel, set source status to `cancelled` (add to status enum migration).
|
||||
|
||||
### Phase 3 acceptance criteria
|
||||
|
||||
- [ ] Running extraction twice on the same `sources/ROLES.md` produces zero new topics on the second run (idempotency through reused IDs).
|
||||
- [ ] Locked-relevance topics survive re-extraction.
|
||||
- [ ] No fixed `setTimeout` ≥ 5s anywhere in `src/` (`grep -rn "setTimeout" src/`).
|
||||
- [ ] Cancelling an extraction mid-run leaves the source in `cancelled` state, not `processing`.
|
||||
- [ ] `pb_migrations` includes the relation-vocabulary normalization and the `relevance_locked` column.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Quiz & content quality
|
||||
|
||||
**Goal:** quiz questions are positionally unbiased, deduped, and difficulty-tagged. Random helpers are correct.
|
||||
|
||||
### 4.1 Random helpers
|
||||
|
||||
Create `src/lib/random.js`:
|
||||
|
||||
```js
|
||||
export function shuffle(arr) { /* Fisher–Yates, returns NEW array */ }
|
||||
export function sample(arr, n) { /* unbiased sample without replacement */ }
|
||||
export function pickInt(min, maxInclusive) { /* uniform integer */ }
|
||||
```
|
||||
|
||||
Replace every `.sort(() => 0.5 - Math.random())` with `shuffle(arr)`:
|
||||
|
||||
- [`testService.js:122`](src/lib/testService.js:122) and [`testService.js:163`](src/lib/testService.js:163).
|
||||
- Any other site found by `grep -rn "0.5 - Math.random()" src/`.
|
||||
|
||||
### 4.2 Debias `correctIndex` in quiz prompt
|
||||
|
||||
In [`testService.js:81`](src/lib/testService.js:81):
|
||||
|
||||
- Change the example in the prompt to use `"correctIndex": 2` (not 0).
|
||||
- Add to the prompt: *"Distribute correctIndex roughly evenly across 0, 1, 2, and 3. Do not place the correct answer at the same position more than 4 out of 10 times."*
|
||||
- After parsing, run a check: if more than 50% of the batch share the same `correctIndex`, log a warning and re-roll up to 2 times.
|
||||
|
||||
### 4.3 Difficulty field
|
||||
|
||||
- Add to `quizQuestionsSchema`: `difficulty: 'easy'|'medium'|'hard'`.
|
||||
- Update the prompt to require difficulty on every question (current prompt says "4 easy, 4 medium, 2 hard" but never tagged — now tag it).
|
||||
- Migration: add `difficulty` to the `quiz_banks.questions[]` element. PocketBase stores `questions` as JSON, so the migration is a no-op at the column level; older records get `difficulty: 'medium'` on read (add a normalizer in `db.js`).
|
||||
|
||||
### 4.4 Question dedup
|
||||
|
||||
In `forceGenerateTopicQuestions` ([`testService.js:65`](src/lib/testService.js:65)):
|
||||
|
||||
- Normalize question text (lowercase, strip punctuation, collapse whitespace) → `normKey`.
|
||||
- Before persisting, drop any new question whose `normKey` matches an existing bank question.
|
||||
- Log dropped duplicates with `console.debug('[quiz] dropped duplicate:', text)`.
|
||||
|
||||
### 4.5 Quality gate
|
||||
|
||||
In the same function, after schema validation:
|
||||
|
||||
- Reject the whole batch if any question has fewer than 4 distinct options.
|
||||
- Reject if any option contains `"all of the above"`, `"none of the above"`, `"both A and B"` (case-insensitive).
|
||||
- Reject if `explanation.trim().length < 20`.
|
||||
- Surface the rejection to the admin UI with a "Retry" button.
|
||||
|
||||
### 4.6 Custom topic ID hygiene
|
||||
|
||||
In `generateCustomTopic` ([`learningService.js:177`](src/lib/learningService.js:177)):
|
||||
|
||||
- Generate kebab-case ID from the polished label, not `Date.now()`.
|
||||
- Collision check against existing topics (append `-2`, `-3`, … if needed).
|
||||
- Default `learning_relevance: 'standard'` when the model omits it.
|
||||
|
||||
### Phase 4 acceptance criteria
|
||||
|
||||
- [ ] No `.sort(() => 0.5 - Math.random())` anywhere in `src/`.
|
||||
- [ ] Sample of 50 fresh quiz questions across 5 topics: no position holds >40% of correct answers.
|
||||
- [ ] Re-running quiz generation for the same topic does not grow the bank with semantic duplicates.
|
||||
- [ ] Custom topics created via R42 use kebab-case IDs and pass schema validation.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — R42 retrieval & telemetry
|
||||
|
||||
**Goal:** R42 stops shipping the entire KG every turn; conversations are bounded; every call is logged.
|
||||
|
||||
### 5.1 TF-IDF retrieval in the browser
|
||||
|
||||
Create `src/lib/retrieval.js`:
|
||||
|
||||
```js
|
||||
export function buildIndex(topics) { /* TF-IDF over label + description */ }
|
||||
export function retrieveTopK(index, query, k = 10) { /* returns Topic[] */ }
|
||||
```
|
||||
|
||||
Implementation: a small dependency-free TF-IDF — tokenize on `/[a-zA-Z0-9-]+/`, lowercase, drop stopwords (Dutch + English short list). Cache the index on the `topics` array reference. About 100 lines.
|
||||
|
||||
### 5.2 Rewrite `buildKbContext`
|
||||
|
||||
In [`rag.js:11`](src/components/chat/rag.js:11):
|
||||
|
||||
- Use `retrieveTopK(index, userMessage, 10)` to pick which topics go into the system prompt.
|
||||
- Always include any topic whose ID or label is mentioned verbatim (existing behaviour).
|
||||
- Drop the full "every topic" dump.
|
||||
- Return `{ context, retrievedTopics, allTopics }` — `validateDelta` continues to use `allTopics`.
|
||||
|
||||
### 5.3 `lookup_topic` tool
|
||||
|
||||
Add a second R42 tool in [`prompts.js`](src/components/chat/prompts.js):
|
||||
|
||||
```js
|
||||
export const LOOKUP_TOPIC_TOOL = {
|
||||
name: 'lookup_topic',
|
||||
description: 'Fetch the full description and any deeper learning content for a topic. Use when the retrieved context does not contain enough to answer.',
|
||||
input_schema: { type:'object', properties: { id:{type:'string'} }, required:['id'] },
|
||||
};
|
||||
```
|
||||
|
||||
In `useChat.js`, when the model emits a `lookup_topic`, fetch via `db.getTopics()` + `db.getContent(id)` and append a `tool_result` block, then call `callLLM` again with the extended messages. Cap to **3 lookup hops per turn** to avoid loops.
|
||||
|
||||
### 5.4 R42 prompt cache busting
|
||||
|
||||
KB-context block is cached as ephemeral. The cache key is automatic per text, so any change busts it. **Append a stable suffix** to the KB block: `"\n[kb_hash: <first-8-chars-of-sha256-of-sorted-topic-ids>]"`. This guarantees the block content changes the moment a topic is added/removed, even if the included topic list looks similar.
|
||||
|
||||
### 5.5 Conversation truncation
|
||||
|
||||
In `useChat.js`:
|
||||
|
||||
- Keep last **12 turns** verbatim in `apiMessages`.
|
||||
- Older messages: if more than 12 turns exist, summarize the older ones with a cheap (`tier: 'fast'`) call and prepend a single `{ role:'system'-equivalent inside the user history is not allowed by Anthropic — instead prepend a user/assistant pair }` block, OR simply drop older turns. **Default: drop older turns and prepend a one-line `assistant` message saying "(earlier conversation truncated)".** Summarization is an optional follow-up.
|
||||
|
||||
### 5.6 `llm_calls` collection
|
||||
|
||||
Add a migration `pb_migrations/<timestamp>_created_llm_calls.js` for collection `llm_calls`:
|
||||
|
||||
| Field | Type | Notes |
|
||||
|---|---|---|
|
||||
| `task` | text | e.g. `extract.source` |
|
||||
| `model` | text | resolved model ID |
|
||||
| `tier` | text | `fast` / `standard` / `reasoning` |
|
||||
| `duration_ms` | number | |
|
||||
| `input_tokens` | number | |
|
||||
| `output_tokens` | number | |
|
||||
| `cache_read_tokens` | number | |
|
||||
| `cache_create_tokens` | number | |
|
||||
| `stop_reason` | text | |
|
||||
| `ok` | bool | |
|
||||
| `error_msg` | text | nullable |
|
||||
|
||||
Wire `callLLM` to write to it (best-effort, never throws). Add a minimal `Admin → Diagnostics` view that shows the last 100 calls and aggregate cost (using public Anthropic prices in a constant; refresh manually).
|
||||
|
||||
### 5.7 R42 defaults
|
||||
|
||||
- `temperature: 0.3` for R42 chat in `callLLM`.
|
||||
- `maxTokens: 2048` for R42 (text + tool budget).
|
||||
|
||||
### Phase 5 acceptance criteria
|
||||
|
||||
- [ ] R42 system prompt is ≤ 4000 tokens regardless of KG size (verify on a graph with 200+ topics).
|
||||
- [ ] Adding a topic in the admin UI causes the **next** R42 call to show `cache_read_tokens === 0` for the KB block, then subsequent calls to show non-zero.
|
||||
- [ ] R42 successfully answers a question about a topic whose ID was not in the user's message by emitting `lookup_topic` (manual verification).
|
||||
- [ ] `Admin → Diagnostics` shows recent LLM calls with model, tokens, duration.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — Eval harness (optional, high-leverage)
|
||||
|
||||
**Goal:** prompt or model changes can be measured before they ship.
|
||||
|
||||
### 6.1 Golden sets
|
||||
|
||||
Create `evals/` at the repo root:
|
||||
|
||||
```
|
||||
evals/
|
||||
extraction/
|
||||
cases/
|
||||
roles-handbook.txt
|
||||
governance-handbook.txt
|
||||
expected/
|
||||
roles-handbook.json # { mustContain: ['software-engineer', ...], minTopics: 20 }
|
||||
governance-handbook.json
|
||||
quiz/
|
||||
cases/
|
||||
software-engineer.json # topic to generate quiz for
|
||||
rubric.md # human-readable quality rubric
|
||||
chat/
|
||||
scripts/
|
||||
ask-about-known-topic.json
|
||||
ask-about-unknown-topic.json
|
||||
propose-new-role.json
|
||||
```
|
||||
|
||||
10 extraction cases, 5 quiz topics, 10 chat scripts is enough to start.
|
||||
|
||||
### 6.2 Runner
|
||||
|
||||
`evals/run.mjs` — Node script that:
|
||||
|
||||
1. Loads each case.
|
||||
2. Invokes the same code path the app uses (import from `src/lib/*` directly; reuse the simulation toggle off).
|
||||
3. Compares against expectations:
|
||||
- **Extraction:** `mustContain` IDs present? `minTopics` met? No `stop_reason: 'max_tokens'`?
|
||||
- **Quiz:** distribution of `correctIndex`, mean explanation length, banned phrases.
|
||||
- **Chat:** for each script, did the expected tool fire? Did the reply contain expected anchors?
|
||||
4. Writes `evals/results/<ISO-timestamp>.json` and a Markdown diff against the previous baseline.
|
||||
|
||||
Add `npm run eval` to `package.json`.
|
||||
|
||||
### 6.3 Prompt versioning
|
||||
|
||||
In each prompt module, export `PROMPT_VERSION = '2026-05-20-001'`. Persist it on the artifact (`content.data.prompt_version`, `quiz_banks.questions[i].prompt_version`, `topics.metadata.prompt_version`). Add an admin button "Mark stale content for regeneration" that lists artifacts whose version is older than the current.
|
||||
|
||||
### Phase 6 acceptance criteria
|
||||
|
||||
- [ ] `npm run eval` runs end-to-end against the live API and produces a result file.
|
||||
- [ ] CI (or a manual check) runs evals on every change to `src/lib/llm.js`, prompts, or schemas.
|
||||
- [ ] Each AI-generated artifact in PocketBase carries a `prompt_version` and is filterable by it.
|
||||
|
||||
---
|
||||
|
||||
## Cross-phase verification checklist
|
||||
|
||||
After every phase, run this short checklist before merging:
|
||||
|
||||
1. **Build:** `npm run build` succeeds.
|
||||
2. **Lint:** `npm run lint` clean.
|
||||
3. **Tests:** `npm run test` green.
|
||||
4. **Smoke flows** in dev (simulation off, real API key):
|
||||
- Add a source via `Admin → Sources`, extract, verify topics appear.
|
||||
- Visit `Leren` for the current week, generate article. Then slides.
|
||||
- Visit `Testen`, generate weekly quiz. Submit. Score lands on leaderboard.
|
||||
- Open R42, ask a known and an unknown question; propose a new topic; admin accepts it.
|
||||
- Run `Admin → Knowledge Graph → Analyze & Optimize Graph`.
|
||||
- Run `Admin → Knowledge Graph → Sync Handbook` (small repo or mock).
|
||||
5. **Simulation toggle:** flip simulation mode on and confirm no real API calls happen (Network tab).
|
||||
|
||||
---
|
||||
|
||||
## Rollback strategy
|
||||
|
||||
Every phase is shippable on its own. If a phase introduces a regression:
|
||||
|
||||
- **Phase 1–2:** `git revert` the merge commit. `api.js` retains the legacy interface, so callers that haven't migrated still work.
|
||||
- **Phase 3:** revert + the relation-vocabulary migration is reversible (rerun the reverse swap).
|
||||
- **Phase 4:** revert; quiz schema additions are forward-compatible (older readers ignore `difficulty`).
|
||||
- **Phase 5:** revert + drop the `llm_calls` collection if undesired.
|
||||
- **Phase 6:** purely additive; remove the `evals/` folder if abandoned.
|
||||
|
||||
Never `git push --force` to `main`. PR-per-phase.
|
||||
|
||||
---
|
||||
|
||||
## Out of scope (do not do as part of this plan)
|
||||
|
||||
- Replacing PocketBase. Stays as-is.
|
||||
- Server-side embeddings or a vector store. Phase 5 deliberately uses in-browser TF-IDF.
|
||||
- Streaming responses. Mentioned as a future improvement; not in this plan.
|
||||
- Multi-tenant changes. The platform serves one company.
|
||||
- UI redesign of the Admin pages beyond what each phase requires.
|
||||
|
||||
---
|
||||
|
||||
## Glossary
|
||||
|
||||
- **Tier** — coarse model class (`fast`/`standard`/`reasoning`) mapped to a concrete Anthropic model ID via admin settings.
|
||||
- **Tool use** — Anthropic's structured-output mechanism. The model emits a `tool_use` content block whose `input` is schema-valid JSON.
|
||||
- **Prompt caching** — Anthropic feature where blocks marked `cache_control: { type:'ephemeral' }` are reused across requests at lower input cost. 5-minute TTL.
|
||||
- **TF-IDF** — Term-frequency / inverse-document-frequency. A classic IR scoring function used here as a cheap retrieval signal.
|
||||
- **KB context** — The block in R42's system prompt that lists topics and relations from the knowledge graph.
|
||||
- **Delta** — A proposed addition to the knowledge graph emitted by R42 via the `propose_graph_delta` tool.
|
||||
1016
CHANGELOG.md
Normal file
1016
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
57
Caddyfile
Normal file
57
Caddyfile
Normal file
@@ -0,0 +1,57 @@
|
||||
:80 {
|
||||
encode gzip zstd
|
||||
|
||||
header {
|
||||
X-Frame-Options "SAMEORIGIN"
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-XSS-Protection "1; mode=block"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
Content-Security-Policy "default-src 'self'; connect-src 'self' https://api.github.com https://raw.githubusercontent.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com"
|
||||
Permissions-Policy "geolocation=(), microphone=(), camera=()"
|
||||
}
|
||||
|
||||
handle /api/anthropic/* {
|
||||
uri strip_prefix /api/anthropic
|
||||
reverse_proxy https://api.anthropic.com {
|
||||
header_up Host api.anthropic.com
|
||||
header_up x-api-key "{$ANTHROPIC_API_KEY}"
|
||||
header_up anthropic-dangerous-direct-browser-access "true"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
handle /api/* {
|
||||
reverse_proxy pocketbase-learning:8090
|
||||
}
|
||||
|
||||
handle /_/* {
|
||||
reverse_proxy pocketbase-learning:8090
|
||||
}
|
||||
|
||||
handle {
|
||||
root * /srv
|
||||
|
||||
@static {
|
||||
path *.css *.js *.png *.jpg *.jpeg *.gif *.webp *.svg *.woff *.woff2 *.ttf
|
||||
}
|
||||
header @static Cache-Control "public, max-age=31536000, immutable"
|
||||
|
||||
@html {
|
||||
path *.html /
|
||||
}
|
||||
header @html Cache-Control "public, max-age=0, must-revalidate"
|
||||
|
||||
try_files {path} /index.html
|
||||
file_server
|
||||
}
|
||||
|
||||
handle_errors {
|
||||
rewrite * /index.html
|
||||
file_server
|
||||
}
|
||||
|
||||
log {
|
||||
output stdout
|
||||
format console
|
||||
}
|
||||
}
|
||||
25
Caddyfile.test
Normal file
25
Caddyfile.test
Normal file
@@ -0,0 +1,25 @@
|
||||
:80 {
|
||||
root * /srv
|
||||
file_server
|
||||
|
||||
encode gzip
|
||||
|
||||
header {
|
||||
X-Frame-Options "SAMEORIGIN"
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-XSS-Protection "1; mode=block"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
}
|
||||
|
||||
try_files {path} /index.html
|
||||
|
||||
handle_errors {
|
||||
rewrite * /index.html
|
||||
file_server
|
||||
}
|
||||
|
||||
log {
|
||||
output stdout
|
||||
format console
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
# 🎯 Overdrachtsdocument – Productie-deployment van **Respellion Learning Platform** met Portainer
|
||||
|
||||
> **Doel** – Dit document geeft een *complete* handleiding zodat een ontwikkelaar of AI-agent de Respellion Learning Platform applicatie kan bouwen, configureren en in productie draaien op een Hetzner-VPS via Portainer.
|
||||
|
||||
---
|
||||
|
||||
## 1. Project-structuur
|
||||
|
||||
```
|
||||
learning-platform/
|
||||
│
|
||||
├─ Dockerfile # Productie Dockerfile (Nginx + React/Vite)
|
||||
├─ nginx.conf # Nginx configuratie voor Anthropic API proxy
|
||||
├─ docker-compose.prod.yml # Productie-compose configuratie (Caddy SSL + Frontend)
|
||||
└─ src/ # React frontend broncode
|
||||
```
|
||||
|
||||
> **Opmerking**: Deze applicatie is een 100% frontend applicatie ("Single Page Application"). De interne state wordt opgeslagen in `localStorage`. Er is **geen externe backend of database (zoals Postgres)** nodig. De enige netwerk calls gaan via de interne Nginx proxy direct naar de Anthropic API.
|
||||
|
||||
---
|
||||
|
||||
## 2. Kernbestanden – inhoud & rationale
|
||||
|
||||
### 2.1 `docker-compose.prod.yml`
|
||||
|
||||
```yaml
|
||||
services:
|
||||
frontend:
|
||||
build: .
|
||||
restart: always
|
||||
expose:
|
||||
- "80"
|
||||
|
||||
caddy:
|
||||
image: caddy:2-alpine
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
environment:
|
||||
- DOMAIN_NAME=${DOMAIN_NAME}
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
# Build Caddyfile on container start
|
||||
CLEAN_DOMAIN=$(printf '%s' "$DOMAIN_NAME" | tr -d ';')
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
${CLEAN_DOMAIN} {
|
||||
reverse_proxy /* frontend:80
|
||||
}
|
||||
EOF
|
||||
# Run Caddy with the generated file
|
||||
caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
|
||||
volumes:
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
depends_on:
|
||||
- frontend
|
||||
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
```
|
||||
|
||||
**Belangrijkste punten**
|
||||
|
||||
| Item | Waarom |
|
||||
|------|--------|
|
||||
| **`frontend` (Nginx)** | Bevat de React applicatie en proxy-configuratie voor Anthropic. Exposeert intern poort 80. |
|
||||
| **`caddy` (Reverse Proxy)** | Ontvangt alle externe requests op poort 80 en 443 en haalt volautomatisch een SSL Let's Encrypt certificaat op voor `DOMAIN_NAME`. Stuurt verkeer door naar `frontend:80`. |
|
||||
| **Volumes `caddy_data/config`** | Slaan de SSL certificaten persistent op zodat Caddy niet bij elke herstart de rate-limits van Let's Encrypt overschrijdt. |
|
||||
|
||||
---
|
||||
|
||||
### 2.2 `Dockerfile` (Productie)
|
||||
|
||||
De applicatie wordt in 2 fasen ("multi-stage") gebouwd:
|
||||
1. **Node.js Build:** `npm install` en `npm run build` worden uitgevoerd om de statische Vite applicatie te compileren naar de `dist/` map.
|
||||
2. **Nginx Serve:** Een lightweight Nginx Alpine image wordt gestart. Het pakt de `dist/` bestanden en past `nginx.conf` toe om API-aanroepen (CORS blokkades) te omzeilen door als proxy te fungeren.
|
||||
|
||||
---
|
||||
|
||||
## 3. Omgevingsvariabelen (te definiëren in Portainer)
|
||||
|
||||
| Variabele | Voorbeeld | Toelichting |
|
||||
|-----------|-----------|-------------|
|
||||
| `DOMAIN_NAME` | `leren.mijndomein.nl` | De domeinnaam die Caddy zal beveiligen met HTTPS. Zorg dat je DNS A-record naar je VPS-IP wijst. |
|
||||
|
||||
*(De Anthropic API key wordt ingevuld via de instellingen in de applicatie (Settings menu) door een admin, en veilig opgeslagen in localStorage van de gebruiker. Hij hoeft dus niet in Portainer te staan!)*
|
||||
|
||||
---
|
||||
|
||||
## 4. Deploy-procedure (stap-voor-stap)
|
||||
|
||||
### Portainer – stack aanmaken / bijwerken
|
||||
|
||||
| Stap | Actie | Details |
|
||||
|------|-------|---------|
|
||||
| **1** | Navigeer naar **Stacks → + Add stack** | Geef een herkenbare naam (bijv. `respellion-platform`). |
|
||||
| **2** | **Repository URL** invullen | `https://github.com/<gebruiker>/learning-platform.git` |
|
||||
| **3** | **Compose path** = `docker-compose.prod.yml` | Zorg dat dit exact overeenkomt met de bestandsnaam in de repo. |
|
||||
| **4** | **Environment variables** toevoegen | Voeg `DOMAIN_NAME` toe. |
|
||||
| **5** | **Deploy the stack** | Portainer start de applicatie en Caddy haalt direct het SSL certificaat op (max. 60 sec). |
|
||||
| **6** | **Controle** | Surf naar `https://jouw-domein.nl`. Je zou de Respellion inlogscherm moeten zien. |
|
||||
|
||||
### Handmatige Force-Rebuild
|
||||
|
||||
Wanneer de repo is geüpdatet en je wil de nieuwe versie afdwingen via command-line:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml up -d --build --no-cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Veelvoorkomende valkuilen & Fixes
|
||||
|
||||
| Symptom | Oorzaak | Oplossing |
|
||||
|---------|---------|-----------|
|
||||
| **SSL Error / ERR_CONNECTION_REFUSED** | DNS is nog niet actief of poort 443 staat dicht in je firewall (Hetzner). | Check Hetzner Cloud Console en open TCP poort 80 & 443 in de Inbound Firewall. |
|
||||
| **Anthropic API Werkt Niet** | Foute key of Nginx proxy mist configuratie. | Open de app, ga naar Admin -> Settings en vul je Anthropic Key in. Controleer ook of `nginx.conf` correct wordt ingeladen in Nginx. |
|
||||
| **Witte pagina na deploy** | Oude Docker image wordt gebruikt. | Doe een force-rebuild (`--build --no-cache`) in Portainer of via de console. |
|
||||
|
||||
---
|
||||
|
||||
## 6. Checklist voor een nieuwe implementatie
|
||||
|
||||
- [ ] Repository up-to-date en gepusht naar `main`.
|
||||
- [ ] `docker-compose.prod.yml` is gedefinieerd in Portainer.
|
||||
- [ ] Environment variable `DOMAIN_NAME` is correct ingesteld.
|
||||
- [ ] DNS A-Record staat op de juiste server.
|
||||
- [ ] Na eerste inlog: Open "Admin" -> "Settings" en voeg de Anthropic API Key toe, klik op Opslaan.
|
||||
27
Dockerfile
27
Dockerfile
@@ -1,28 +1,21 @@
|
||||
# Stage 1: Build the React application
|
||||
FROM node:22-alpine as build
|
||||
FROM node:24-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files and install dependencies
|
||||
COPY package*.json ./
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install
|
||||
|
||||
# Copy the rest of the application files
|
||||
COPY . .
|
||||
|
||||
# Build the Vite project for production
|
||||
RUN npm run build
|
||||
ARG BUILD_MODE=development
|
||||
RUN npm run build -- --mode $BUILD_MODE
|
||||
|
||||
# Stage 2: Serve the application with Nginx
|
||||
FROM nginx:alpine
|
||||
FROM caddy:2-alpine
|
||||
|
||||
# Copy custom Nginx configuration
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=builder /app/dist /srv
|
||||
COPY Caddyfile /etc/caddy/Caddyfile
|
||||
|
||||
# Copy the build output to Nginx's html directory
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
EXPOSE 80 443
|
||||
|
||||
# Expose port 80
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
|
||||
|
||||
17
LICENSE.md
Normal file
17
LICENSE.md
Normal file
@@ -0,0 +1,17 @@
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2022 - present, Gani Georgiev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
82
README.md
82
README.md
@@ -1,16 +1,80 @@
|
||||
# React + Vite
|
||||
# Respellion Learning Platform
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
An internal AI-powered learning platform that keeps Respellion employees up to date with the company's evolving knowledge base.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
## Features
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
||||
- **Weekly Learning Station** — Each employee is assigned a topic each week (via deterministic hash of user ID + week number). They choose their preferred format: Article, Slides, or Infographic. Content is generated on-demand by Claude and cached per topic.
|
||||
- **Weekly Test** — AI-generated quiz based on the knowledge graph. Results are stored and feed the leaderboard.
|
||||
- **Leaderboard & Gamification** — Points for correct answers, badges for streaks and perfect scores.
|
||||
- **R42 Chatbot** — An always-available AI assistant (backed by Claude) with access to the full knowledge graph. Can propose graph updates that admins approve or reject.
|
||||
- **Admin Panel** — Manage the knowledge graph, sync from GitHub, review generated content, refine it with AI, and monitor team progress.
|
||||
|
||||
## React Compiler
|
||||
## Tech Stack
|
||||
|
||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||
| Layer | Technology |
|
||||
|---|---|
|
||||
| Frontend | React 18 + Vite |
|
||||
| Styling | Vanilla CSS (custom properties) + Tailwind utility classes |
|
||||
| Animations | Framer Motion |
|
||||
| Icons | Lucide React |
|
||||
| Graph viz | D3.js (admin knowledge graph only) |
|
||||
| Backend / DB | PocketBase (self-hosted) |
|
||||
| AI | Anthropic Claude (via Caddy reverse proxy) |
|
||||
| Infra | Docker + Caddy |
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
## Getting Started (local dev)
|
||||
|
||||
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
||||
```bash
|
||||
# 1. Install dependencies
|
||||
npm install
|
||||
|
||||
# 2. Start PocketBase (Windows)
|
||||
./pocketbase.exe serve
|
||||
|
||||
# 3. Start the dev server
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The Vite dev server proxies `/api/anthropic` and `/pb` — see `vite.config.js`.
|
||||
|
||||
## Deployment (Docker)
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
The `Caddyfile` handles:
|
||||
- SPA fallback routing
|
||||
- `/pb/*` → PocketBase sidecar
|
||||
- `/api/anthropic/*` → Anthropic API (with server-side API key injection)
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `src/lib/learningService.js` | Selective content generation (article / slides / infographic) |
|
||||
| `src/lib/extractionPipeline.js` | GitHub file → knowledge graph extraction |
|
||||
| `src/lib/api.js` | Anthropic API wrapper (`generateContent` + `chat`) |
|
||||
| `src/lib/db.js` | All PocketBase data access |
|
||||
| `src/lib/giteaService.js` | GitHub API client (folder listing + raw file fetch) |
|
||||
| `src/store/AppContext.jsx` | Global state; computes ISO week number on load |
|
||||
| `src/components/admin/UploadZone.jsx` | GitHub sync UI (default folder: `docs/knowledge-base/`) |
|
||||
| `AI_AGENT.md` | Detailed context guide for AI coding agents |
|
||||
|
||||
## Content Types
|
||||
|
||||
Learning content is generated **on demand per type** and merged into the cached object:
|
||||
|
||||
| Type | Key in DB | Description |
|
||||
|---|---|---|
|
||||
| Article | `content.article` | Long-form reading |
|
||||
| Slides | `content.slides` | Slide deck with speaker notes |
|
||||
| Infographic | `content.infographic` | Visual summary with stats and steps |
|
||||
|
||||
> The podcast type was removed. Do not re-add it.
|
||||
|
||||
## Documentation
|
||||
|
||||
- **`AI_AGENT.md`** — Full architectural guide for AI coding agents (patterns, gotchas, decisions).
|
||||
- **`CHANGELOG.md`** — PocketBase upstream changelog (not application changelog).
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
services:
|
||||
frontend:
|
||||
build: .
|
||||
restart: always
|
||||
expose:
|
||||
- "80"
|
||||
|
||||
caddy:
|
||||
image: caddy:2-alpine
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
environment:
|
||||
- DOMAIN_NAME=${DOMAIN_NAME}
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
# Build Caddyfile on container start
|
||||
CLEAN_DOMAIN=$(printf '%s' "$DOMAIN_NAME" | tr -d ';')
|
||||
cat > /etc/caddy/Caddyfile <<EOF
|
||||
$${CLEAN_DOMAIN} {
|
||||
reverse_proxy /* frontend:80
|
||||
}
|
||||
EOF
|
||||
# Run Caddy with the generated file
|
||||
caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
|
||||
volumes:
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
depends_on:
|
||||
- frontend
|
||||
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
@@ -2,5 +2,20 @@ services:
|
||||
frontend:
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||
ports:
|
||||
- "8080:80"
|
||||
|
||||
pocketbase-learning:
|
||||
image: ghcr.io/muchobien/pocketbase:latest
|
||||
container_name: pocketbase-learning
|
||||
restart: unless-stopped
|
||||
working_dir: /pb
|
||||
command: ["serve", "--http=0.0.0.0:8090", "--dir=/pb/pb_data", "--migrationsDir=/pb/pb_migrations"]
|
||||
volumes:
|
||||
- pb_data:/pb/pb_data
|
||||
- ./pb_migrations:/pb/pb_migrations
|
||||
|
||||
volumes:
|
||||
pb_data:
|
||||
|
||||
@@ -5,7 +5,7 @@ import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
globalIgnores(['dist', 'pb_migrations']),
|
||||
{
|
||||
files: ['**/*.{js,jsx}'],
|
||||
extends: [
|
||||
@@ -14,8 +14,32 @@ export default defineConfig([
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
globals: globals.browser,
|
||||
globals: {
|
||||
...globals.browser,
|
||||
__BUILD_SHA__: 'readonly',
|
||||
__BUILD_TIME__: 'readonly',
|
||||
},
|
||||
parserOptions: { ecmaFeatures: { jsx: true } },
|
||||
},
|
||||
rules: {
|
||||
// Conventional initial-load pattern across the admin/chat surface;
|
||||
// refactoring every effect is out of scope and not currently
|
||||
// observed to cause cascading renders in practice.
|
||||
'react-hooks/set-state-in-effect': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/store/AppContext.jsx'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['vite.config.js'],
|
||||
languageOptions: { globals: { ...globals.node } },
|
||||
},
|
||||
{
|
||||
files: ['src/lib/__tests__/**/*.{js,jsx}'],
|
||||
languageOptions: { globals: { ...globals.node } },
|
||||
},
|
||||
])
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>app</title>
|
||||
<title>Learning Platform</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
2
infra/development/hosts.ini
Normal file
2
infra/development/hosts.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
[development]
|
||||
development ansible_host=46.224.220.37 ansible_port=4484 ansible_user=root
|
||||
28
infra/development/site/compose.yml
Normal file
28
infra/development/site/compose.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
version: "3"
|
||||
|
||||
networks:
|
||||
learning-platform:
|
||||
external: true
|
||||
|
||||
services:
|
||||
learning-platform:
|
||||
image: git.labs.respellion.tech/respellion/learning-platform/learning-platform:latest
|
||||
container_name: learning-platform
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||
networks:
|
||||
- learning-platform
|
||||
|
||||
pocketbase-learning:
|
||||
image: ghcr.io/muchobien/pocketbase:latest
|
||||
container_name: pocketbase-learning
|
||||
restart: unless-stopped
|
||||
command: ["serve", "--http=0.0.0.0:8090", "--dir=/pb/pb_data"]
|
||||
volumes:
|
||||
- pb_data:/pb/pb_data
|
||||
networks:
|
||||
- learning-platform
|
||||
|
||||
volumes:
|
||||
pb_data:
|
||||
58
infra/development/site/deploy-playbook.yml
Normal file
58
infra/development/site/deploy-playbook.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
- name: Deploy Learning Platform with Ansible
|
||||
hosts: all
|
||||
become: yes
|
||||
gather_facts: yes
|
||||
vars:
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
|
||||
|
||||
tasks:
|
||||
- name: Set destination directory
|
||||
ansible.builtin.set_fact:
|
||||
dest_dir: "/opt/learning-platform"
|
||||
|
||||
- name: Ensure dependencies are installed
|
||||
ansible.builtin.package:
|
||||
name:
|
||||
- docker-ce
|
||||
state: present
|
||||
ignore_errors: yes
|
||||
register: docker_valid
|
||||
|
||||
- name: Include geerlingguy.docker role conditionally
|
||||
include_role:
|
||||
name: geerlingguy.docker
|
||||
when: docker_valid is failed
|
||||
|
||||
- name: Ensure target directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ dest_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Copy compose.yml to destination
|
||||
ansible.builtin.copy:
|
||||
src: compose.yml
|
||||
dest: "{{ dest_dir }}/compose.yml"
|
||||
|
||||
- name: Create .env file for secrets
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ dest_dir }}/.env"
|
||||
content: |
|
||||
ANTHROPIC_API_KEY={{ anthropic_api_key | default('') }}
|
||||
mode: '0600'
|
||||
|
||||
- name: Pull latest image
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ dest_dir }}"
|
||||
state: present
|
||||
files: compose.yml
|
||||
pull: always
|
||||
|
||||
- name: Start services with Docker Compose
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ dest_dir }}"
|
||||
state: present
|
||||
files: compose.yml
|
||||
recreate: always
|
||||
2
infra/production/hosts.ini
Normal file
2
infra/production/hosts.ini
Normal file
@@ -0,0 +1,2 @@
|
||||
[production]
|
||||
production ansible_host=91.107.213.195 ansible_port=22 ansible_user=root
|
||||
30
infra/production/site/compose.yml
Normal file
30
infra/production/site/compose.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
version: "3"
|
||||
|
||||
networks:
|
||||
learning-platform:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
pb_data:
|
||||
|
||||
services:
|
||||
learning-platform:
|
||||
image: git.labs.respellion.tech/respellion/learning-platform/learning-platform:latest
|
||||
container_name: learning-platform
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||
networks:
|
||||
- learning-platform
|
||||
|
||||
pocketbase-learning:
|
||||
image: ghcr.io/muchobien/pocketbase:latest
|
||||
container_name: pocketbase-learning
|
||||
restart: unless-stopped
|
||||
working_dir: /pb
|
||||
command: ["serve", "--http=0.0.0.0:8090", "--dir=/pb/pb_data", "--migrationsDir=/pb/pb_migrations"]
|
||||
volumes:
|
||||
- pb_data:/pb/pb_data
|
||||
- ./pb_migrations:/pb/pb_migrations
|
||||
networks:
|
||||
- learning-platform
|
||||
64
infra/production/site/deploy-playbook.yml
Normal file
64
infra/production/site/deploy-playbook.yml
Normal file
@@ -0,0 +1,64 @@
|
||||
- name: Deploy Learning Platform with Ansible
|
||||
hosts: all
|
||||
become: yes
|
||||
gather_facts: yes
|
||||
vars:
|
||||
ansible_ssh_common_args: '-o StrictHostKeyChecking=no'
|
||||
|
||||
tasks:
|
||||
- name: Set destination directory
|
||||
ansible.builtin.set_fact:
|
||||
dest_dir: "/opt/learning-platform"
|
||||
|
||||
- name: Ensure dependencies are installed
|
||||
ansible.builtin.package:
|
||||
name:
|
||||
- docker-ce
|
||||
state: present
|
||||
ignore_errors: yes
|
||||
register: docker_valid
|
||||
|
||||
- name: Include geerlingguy.docker role conditionally
|
||||
include_role:
|
||||
name: geerlingguy.docker
|
||||
when: docker_valid is failed
|
||||
|
||||
- name: Ensure target directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ dest_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Copy compose.yml to destination
|
||||
ansible.builtin.copy:
|
||||
src: compose.yml
|
||||
dest: "{{ dest_dir }}/compose.yml"
|
||||
|
||||
- name: Copy pb_migrations to destination
|
||||
ansible.builtin.copy:
|
||||
src: ../../../pb_migrations
|
||||
dest: "{{ dest_dir }}/"
|
||||
mode: "0755"
|
||||
|
||||
- name: Create .env file for secrets
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ dest_dir }}/.env"
|
||||
content: |
|
||||
ANTHROPIC_API_KEY={{ anthropic_api_key | default('') }}
|
||||
mode: '0600'
|
||||
|
||||
- name: Pull latest image
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ dest_dir }}"
|
||||
state: present
|
||||
files: compose.yml
|
||||
pull: always
|
||||
|
||||
- name: Start services with Docker Compose
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ dest_dir }}"
|
||||
state: present
|
||||
files: compose.yml
|
||||
recreate: always
|
||||
408
package-lock.json
generated
408
package-lock.json
generated
@@ -11,9 +11,11 @@
|
||||
"d3": "^7.9.0",
|
||||
"framer-motion": "^12.38.0",
|
||||
"lucide-react": "^1.14.0",
|
||||
"pocketbase": "^0.26.9",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"react-router-dom": "^7.15.0"
|
||||
"react-router-dom": "^7.15.0",
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
@@ -28,7 +30,8 @@
|
||||
"globals": "^17.5.0",
|
||||
"postcss": "^8.5.14",
|
||||
"tailwindcss": "^4.3.0",
|
||||
"vite": "^8.0.10"
|
||||
"vite": "^8.0.10",
|
||||
"vitest": "^4.1.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@@ -62,7 +65,6 @@
|
||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
@@ -272,6 +274,29 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.1",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||
@@ -820,6 +845,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz",
|
||||
@@ -1103,6 +1135,24 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/chai": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
|
||||
"integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/deep-eql": "*",
|
||||
"assertion-error": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/deep-eql": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
|
||||
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/esrecurse": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
|
||||
@@ -1130,7 +1180,6 @@
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
@@ -1171,13 +1220,125 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz",
|
||||
"integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.1.7",
|
||||
"@vitest/utils": "4.1.7",
|
||||
"chai": "^6.2.2",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz",
|
||||
"integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.1.7",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.21"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"msw": "^2.4.9",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"msw": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz",
|
||||
"integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz",
|
||||
"integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.1.7",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz",
|
||||
"integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.7",
|
||||
"@vitest/utils": "4.1.7",
|
||||
"magic-string": "^0.30.21",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz",
|
||||
"integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz",
|
||||
"integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.7",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -1212,6 +1373,16 @@
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz",
|
||||
@@ -1305,7 +1476,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.10.12",
|
||||
"caniuse-lite": "^1.0.30001782",
|
||||
@@ -1341,6 +1511,16 @@
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
||||
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||
@@ -1709,7 +1889,6 @@
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
@@ -1859,6 +2038,13 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
|
||||
"integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
@@ -1888,7 +2074,6 @@
|
||||
"integrity": "sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
@@ -2055,6 +2240,16 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/esutils": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
@@ -2065,6 +2260,16 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/expect-type": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
|
||||
"integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -2348,7 +2553,6 @@
|
||||
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
@@ -2808,6 +3012,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/obug": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
|
||||
"integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
"https://github.com/sponsors/sxzz",
|
||||
"https://opencollective.com/debug"
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -2878,6 +3093,13 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -2891,7 +3113,6 @@
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -2899,6 +3120,12 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pocketbase": {
|
||||
"version": "0.26.9",
|
||||
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.26.9.tgz",
|
||||
"integrity": "sha512-Tiv1/hNuUzRdvT0d8hF03dfzuefQ1WdSRp1A1q3wzFC/WYhQcbU/Qlaubl/3ZDo6xvFXBS8JAgBS/L+ms7nkVQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
||||
@@ -2919,7 +3146,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -2961,7 +3187,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
|
||||
"integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -2971,7 +3196,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz",
|
||||
"integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
@@ -3121,6 +3345,13 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/siginfo": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
|
||||
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -3131,6 +3362,20 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stackback": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/std-env": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
|
||||
"integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz",
|
||||
@@ -3152,6 +3397,23 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz",
|
||||
"integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby": {
|
||||
"version": "0.2.16",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
||||
@@ -3169,6 +3431,16 @@
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyrainbow": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
|
||||
"integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
@@ -3235,7 +3507,6 @@
|
||||
"integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
@@ -3308,6 +3579,96 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz",
|
||||
"integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.1.7",
|
||||
"@vitest/mocker": "4.1.7",
|
||||
"@vitest/pretty-format": "4.1.7",
|
||||
"@vitest/runner": "4.1.7",
|
||||
"@vitest/snapshot": "4.1.7",
|
||||
"@vitest/spy": "4.1.7",
|
||||
"@vitest/utils": "4.1.7",
|
||||
"es-module-lexer": "^2.0.0",
|
||||
"expect-type": "^1.3.0",
|
||||
"magic-string": "^0.30.21",
|
||||
"obug": "^2.1.1",
|
||||
"pathe": "^2.0.3",
|
||||
"picomatch": "^4.0.3",
|
||||
"std-env": "^4.0.0-rc.1",
|
||||
"tinybench": "^2.9.0",
|
||||
"tinyexec": "^1.0.2",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"tinyrainbow": "^3.1.0",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
|
||||
"why-is-node-running": "^2.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"vitest": "vitest.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edge-runtime/vm": "*",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||
"@vitest/browser-playwright": "4.1.7",
|
||||
"@vitest/browser-preview": "4.1.7",
|
||||
"@vitest/browser-webdriverio": "4.1.7",
|
||||
"@vitest/coverage-istanbul": "4.1.7",
|
||||
"@vitest/coverage-v8": "4.1.7",
|
||||
"@vitest/ui": "4.1.7",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edge-runtime/vm": {
|
||||
"optional": true
|
||||
},
|
||||
"@opentelemetry/api": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser-playwright": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser-preview": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser-webdriverio": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/coverage-istanbul": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/coverage-v8": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/ui": {
|
||||
"optional": true
|
||||
},
|
||||
"happy-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"jsdom": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
@@ -3324,6 +3685,23 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/why-is-node-running": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"siginfo": "^2.0.0",
|
||||
"stackback": "0.0.2"
|
||||
},
|
||||
"bin": {
|
||||
"why-is-node-running": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
@@ -3358,9 +3736,7 @@
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
|
||||
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -7,15 +7,18 @@
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"test": "vitest run",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"d3": "^7.9.0",
|
||||
"framer-motion": "^12.38.0",
|
||||
"lucide-react": "^1.14.0",
|
||||
"pocketbase": "^0.26.9",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"react-router-dom": "^7.15.0"
|
||||
"react-router-dom": "^7.15.0",
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
@@ -30,6 +33,7 @@
|
||||
"globals": "^17.5.0",
|
||||
"postcss": "^8.5.14",
|
||||
"tailwindcss": "^4.3.0",
|
||||
"vite": "^8.0.10"
|
||||
"vite": "^8.0.10",
|
||||
"vitest": "^4.1.7"
|
||||
}
|
||||
}
|
||||
|
||||
38
pb_migrations/1778948471_created_content.js
Normal file
38
pb_migrations/1778948471_created_content.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1872121667",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "content",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1872121667");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
38
pb_migrations/1778948471_created_quiz_banks.js
Normal file
38
pb_migrations/1778948471_created_quiz_banks.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2848292486",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_banks",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2848292486");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
38
pb_migrations/1778948471_created_relations.js
Normal file
38
pb_migrations/1778948471_created_relations.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1883724256",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "relations",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1883724256");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
38
pb_migrations/1778948471_created_sources.js
Normal file
38
pb_migrations/1778948471_created_sources.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1124997656",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "sources",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1124997656");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
38
pb_migrations/1778948471_created_team_members.js
Normal file
38
pb_migrations/1778948471_created_team_members.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3980519374",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "team_members",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3980519374");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
38
pb_migrations/1778948471_created_topics.js
Normal file
38
pb_migrations/1778948471_created_topics.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2800040823",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "topics",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2800040823");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
38
pb_migrations/1778948472_created_leaderboard.js
Normal file
38
pb_migrations/1778948472_created_leaderboard.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3906957607",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "leaderboard",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3906957607");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
38
pb_migrations/1778948472_created_learn_progress.js
Normal file
38
pb_migrations/1778948472_created_learn_progress.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1945839367",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "learn_progress",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1945839367");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
38
pb_migrations/1778948472_created_quiz_cache.js
Normal file
38
pb_migrations/1778948472_created_quiz_cache.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1132733175",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_cache",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1132733175");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
38
pb_migrations/1778948472_created_quiz_results.js
Normal file
38
pb_migrations/1778948472_created_quiz_results.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3663793586",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_results",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3663793586");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
38
pb_migrations/1778948472_created_settings.js
Normal file
38
pb_migrations/1778948472_created_settings.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2769025244",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "settings",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2769025244");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
53
pb_migrations/1778954289_created_test_col2.js
Normal file
53
pb_migrations/1778954289_created_test_col2.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": null,
|
||||
"deleteRule": null,
|
||||
"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": "text1345053523",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "test_field",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_730968720",
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"name": "test_col2",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": null,
|
||||
"viewRule": null
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_730968720");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
38
pb_migrations/1778954310_deleted_relations.js
Normal file
38
pb_migrations/1778954310_deleted_relations.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1883724256");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1883724256",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "relations",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
38
pb_migrations/1778954310_deleted_topics.js
Normal file
38
pb_migrations/1778954310_deleted_topics.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2800040823");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2800040823",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "topics",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
223
pb_migrations/1778954310_deleted_users.js
Normal file
223
pb_migrations/1778954310_deleted_users.js
Normal file
@@ -0,0 +1,223 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("_pb_users_auth_");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"authAlert": {
|
||||
"emailTemplate": {
|
||||
"body": "<p>Hello,</p>\n<p>We noticed a login to your {APP_NAME} account from a new location:</p>\n<p><em>{ALERT_INFO}</em></p>\n<p><strong>If this wasn't you, you should immediately change your {APP_NAME} account password to revoke access from all other locations.</strong></p>\n<p>If this was you, you may disregard this email.</p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>",
|
||||
"subject": "Login from a new location"
|
||||
},
|
||||
"enabled": true
|
||||
},
|
||||
"authRule": "",
|
||||
"authToken": {
|
||||
"duration": 604800
|
||||
},
|
||||
"confirmEmailChangeTemplate": {
|
||||
"body": "<p>Hello,</p>\n<p>Click on the button below to confirm your new email address.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-email-change/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Confirm new email</a>\n</p>\n<p><i>If you didn't ask to change your email address, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>",
|
||||
"subject": "Confirm your {APP_NAME} new email address"
|
||||
},
|
||||
"createRule": "",
|
||||
"deleteRule": "id = @request.auth.id",
|
||||
"emailChangeToken": {
|
||||
"duration": 1800
|
||||
},
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"cost": 0,
|
||||
"help": "",
|
||||
"hidden": true,
|
||||
"id": "password901924565",
|
||||
"max": 0,
|
||||
"min": 8,
|
||||
"name": "password",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": true,
|
||||
"type": "password"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "[a-zA-Z0-9]{50}",
|
||||
"help": "",
|
||||
"hidden": true,
|
||||
"id": "text2504183744",
|
||||
"max": 60,
|
||||
"min": 30,
|
||||
"name": "tokenKey",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"exceptDomains": null,
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "email3885137012",
|
||||
"name": "email",
|
||||
"onlyDomains": null,
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": true,
|
||||
"type": "email"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "bool1547992806",
|
||||
"name": "emailVisibility",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": true,
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "bool256245529",
|
||||
"name": "verified",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": true,
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1579384326",
|
||||
"max": 255,
|
||||
"min": 0,
|
||||
"name": "name",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "file376926767",
|
||||
"maxSelect": 1,
|
||||
"maxSize": 0,
|
||||
"mimeTypes": [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"image/gif",
|
||||
"image/webp"
|
||||
],
|
||||
"name": "avatar",
|
||||
"presentable": false,
|
||||
"protected": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"thumbs": null,
|
||||
"type": "file"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"fileToken": {
|
||||
"duration": 180
|
||||
},
|
||||
"id": "_pb_users_auth_",
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX `idx_tokenKey__pb_users_auth_` ON `users` (`tokenKey`)",
|
||||
"CREATE UNIQUE INDEX `idx_email__pb_users_auth_` ON `users` (`email`) WHERE `email` != ''"
|
||||
],
|
||||
"listRule": "id = @request.auth.id",
|
||||
"manageRule": null,
|
||||
"mfa": {
|
||||
"duration": 600,
|
||||
"enabled": false,
|
||||
"rule": ""
|
||||
},
|
||||
"name": "users",
|
||||
"oauth2": {
|
||||
"enabled": false,
|
||||
"mappedFields": {
|
||||
"avatarURL": "avatar",
|
||||
"id": "",
|
||||
"name": "name",
|
||||
"username": ""
|
||||
}
|
||||
},
|
||||
"otp": {
|
||||
"duration": 180,
|
||||
"emailTemplate": {
|
||||
"body": "<p>Hello,</p>\n<p>Your one-time password is: <strong>{OTP}</strong></p>\n<p><i>If you didn't ask for the one-time password, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>",
|
||||
"subject": "OTP for {APP_NAME}"
|
||||
},
|
||||
"enabled": false,
|
||||
"length": 8
|
||||
},
|
||||
"passwordAuth": {
|
||||
"enabled": true,
|
||||
"identityFields": [
|
||||
"email"
|
||||
]
|
||||
},
|
||||
"passwordResetToken": {
|
||||
"duration": 1800
|
||||
},
|
||||
"resetPasswordTemplate": {
|
||||
"body": "<p>Hello,</p>\n<p>Click on the button below to reset your password.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-password-reset/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Reset password</a>\n</p>\n<p><i>If you didn't ask to reset your password, you can ignore this email.</i></p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>",
|
||||
"subject": "Reset your {APP_NAME} password"
|
||||
},
|
||||
"system": false,
|
||||
"type": "auth",
|
||||
"updateRule": "id = @request.auth.id",
|
||||
"verificationTemplate": {
|
||||
"body": "<p>Hello,</p>\n<p>Thank you for joining us at {APP_NAME}.</p>\n<p>Click on the button below to verify your email address.</p>\n<p>\n <a class=\"btn\" href=\"{APP_URL}/_/#/auth/confirm-verification/{TOKEN}\" target=\"_blank\" rel=\"noopener\">Verify</a>\n</p>\n<p>\n Thanks,<br/>\n {APP_NAME} team\n</p>",
|
||||
"subject": "Verify your {APP_NAME} email"
|
||||
},
|
||||
"verificationToken": {
|
||||
"duration": 259200
|
||||
},
|
||||
"viewRule": "id = @request.auth.id"
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
38
pb_migrations/1778954311_deleted_content.js
Normal file
38
pb_migrations/1778954311_deleted_content.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1872121667");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1872121667",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "content",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
38
pb_migrations/1778954311_deleted_leaderboard.js
Normal file
38
pb_migrations/1778954311_deleted_leaderboard.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3906957607");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3906957607",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "leaderboard",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
38
pb_migrations/1778954311_deleted_learn_progress.js
Normal file
38
pb_migrations/1778954311_deleted_learn_progress.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1945839367");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1945839367",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "learn_progress",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
38
pb_migrations/1778954311_deleted_quiz_banks.js
Normal file
38
pb_migrations/1778954311_deleted_quiz_banks.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2848292486");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2848292486",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_banks",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
38
pb_migrations/1778954311_deleted_quiz_cache.js
Normal file
38
pb_migrations/1778954311_deleted_quiz_cache.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1132733175");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1132733175",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_cache",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
38
pb_migrations/1778954311_deleted_quiz_results.js
Normal file
38
pb_migrations/1778954311_deleted_quiz_results.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3663793586");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3663793586",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_results",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
38
pb_migrations/1778954311_deleted_settings.js
Normal file
38
pb_migrations/1778954311_deleted_settings.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2769025244");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2769025244",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "settings",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
38
pb_migrations/1778954311_deleted_sources.js
Normal file
38
pb_migrations/1778954311_deleted_sources.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1124997656");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1124997656",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "sources",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
38
pb_migrations/1778954311_deleted_team_members.js
Normal file
38
pb_migrations/1778954311_deleted_team_members.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3980519374");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3980519374",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "team_members",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
53
pb_migrations/1778954311_deleted_test_col2.js
Normal file
53
pb_migrations/1778954311_deleted_test_col2.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_730968720");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": null,
|
||||
"deleteRule": null,
|
||||
"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": "text1345053523",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "test_field",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_730968720",
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"name": "test_col2",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": null,
|
||||
"viewRule": null
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
64
pb_migrations/1778954317_created_content.js
Normal file
64
pb_migrations/1778954317_created_content.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text525672509",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "topic_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json2918445923",
|
||||
"maxSize": 0,
|
||||
"name": "data",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1872121667",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "content",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1872121667");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
107
pb_migrations/1778954317_created_leaderboard.js
Normal file
107
pb_migrations/1778954317_created_leaderboard.js
Normal file
@@ -0,0 +1,107 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2809058197",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "user_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1579384326",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "name",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number666537513",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "points",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number2161202778",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "tests_completed",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number684605397",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "learnings_completed",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3906957607",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "leaderboard",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3906957607");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
76
pb_migrations/1778954317_created_learn_progress.js
Normal file
76
pb_migrations/1778954317_created_learn_progress.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2809058197",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "user_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3807012359",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "week_number",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "bool271442091",
|
||||
"name": "done",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1945839367",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "learn_progress",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1945839367");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
64
pb_migrations/1778954317_created_quiz_banks.js
Normal file
64
pb_migrations/1778954317_created_quiz_banks.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text525672509",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "topic_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json2329695445",
|
||||
"maxSize": 0,
|
||||
"name": "questions",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2848292486",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_banks",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2848292486");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
88
pb_migrations/1778954317_created_quiz_cache.js
Normal file
88
pb_migrations/1778954317_created_quiz_cache.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2809058197",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "user_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3807012359",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "week_number",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json2329695445",
|
||||
"maxSize": 0,
|
||||
"name": "questions",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json3622966325",
|
||||
"maxSize": 0,
|
||||
"name": "meta",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1132733175",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_cache",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1132733175");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
157
pb_migrations/1778954317_created_quiz_results.js
Normal file
157
pb_migrations/1778954317_created_quiz_results.js
Normal file
@@ -0,0 +1,157 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2809058197",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "user_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3807012359",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "week_number",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number848901969",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "score",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3257917790",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "total",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number2466988094",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "percentage",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3573180981",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "time_used",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1410257210",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "completed_at",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json3012992423",
|
||||
"maxSize": 0,
|
||||
"name": "breakdown",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number2092809343",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "points_earned",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3663793586",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_results",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3663793586");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
83
pb_migrations/1778954317_created_relations.js
Normal file
83
pb_migrations/1778954317_created_relations.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text1602912115",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "source",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1181691900",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "target",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text2363381545",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "type",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1883724256",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "relations",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1883724256");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
68
pb_migrations/1778954317_created_settings.js
Normal file
68
pb_migrations/1778954317_created_settings.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2324736937",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "key",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text494360628",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "value",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2769025244",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "settings",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2769025244");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
83
pb_migrations/1778954317_created_sources.js
Normal file
83
pb_migrations/1778954317_created_sources.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text1579384326",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "name",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text2063623452",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "status",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1574812785",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "error",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1124997656",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "sources",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1124997656");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
83
pb_migrations/1778954317_created_team_members.js
Normal file
83
pb_migrations/1778954317_created_team_members.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text1579384326",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "name",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text3045404147",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "pin",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1466534506",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "role",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3980519374",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "team_members",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3980519374");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
83
pb_migrations/1778954317_created_topics.js
Normal file
83
pb_migrations/1778954317_created_topics.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text245846248",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "label",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text2363381545",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "type",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1843675174",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "description",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2800040823",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "topics",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2800040823");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
53
pb_migrations/1779005271_created_test_col3.js
Normal file
53
pb_migrations/1779005271_created_test_col3.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": null,
|
||||
"deleteRule": null,
|
||||
"fields": [
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text3208210256",
|
||||
"max": 255,
|
||||
"min": 1,
|
||||
"name": "id",
|
||||
"pattern": "^[a-z0-9]+$",
|
||||
"presentable": false,
|
||||
"primaryKey": true,
|
||||
"required": true,
|
||||
"system": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1345053523",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "test_field",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1553367558",
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"name": "test_col3",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": null,
|
||||
"viewRule": null
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1553367558");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
53
pb_migrations/1779005289_created_test_col4.js
Normal file
53
pb_migrations/1779005289_created_test_col4.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": null,
|
||||
"deleteRule": null,
|
||||
"fields": [
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text3208210256",
|
||||
"max": 255,
|
||||
"min": 1,
|
||||
"name": "id",
|
||||
"pattern": "^[a-zA-Z0-9_-]+$",
|
||||
"presentable": false,
|
||||
"primaryKey": true,
|
||||
"required": true,
|
||||
"system": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1345053523",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "test_field",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3270645669",
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"name": "test_col4",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": null,
|
||||
"viewRule": null
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3270645669");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
64
pb_migrations/1779005309_deleted_content.js
Normal file
64
pb_migrations/1779005309_deleted_content.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1872121667");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text525672509",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "topic_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json2918445923",
|
||||
"maxSize": 0,
|
||||
"name": "data",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1872121667",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "content",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
76
pb_migrations/1779005309_deleted_learn_progress.js
Normal file
76
pb_migrations/1779005309_deleted_learn_progress.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1945839367");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2809058197",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "user_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3807012359",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "week_number",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "bool271442091",
|
||||
"name": "done",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1945839367",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "learn_progress",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
64
pb_migrations/1779005309_deleted_quiz_banks.js
Normal file
64
pb_migrations/1779005309_deleted_quiz_banks.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2848292486");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text525672509",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "topic_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json2329695445",
|
||||
"maxSize": 0,
|
||||
"name": "questions",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2848292486",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_banks",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
88
pb_migrations/1779005309_deleted_quiz_cache.js
Normal file
88
pb_migrations/1779005309_deleted_quiz_cache.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1132733175");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2809058197",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "user_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3807012359",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "week_number",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json2329695445",
|
||||
"maxSize": 0,
|
||||
"name": "questions",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json3622966325",
|
||||
"maxSize": 0,
|
||||
"name": "meta",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1132733175",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_cache",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
157
pb_migrations/1779005309_deleted_quiz_results.js
Normal file
157
pb_migrations/1779005309_deleted_quiz_results.js
Normal file
@@ -0,0 +1,157 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3663793586");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2809058197",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "user_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3807012359",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "week_number",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number848901969",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "score",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3257917790",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "total",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number2466988094",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "percentage",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3573180981",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "time_used",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1410257210",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "completed_at",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json3012992423",
|
||||
"maxSize": 0,
|
||||
"name": "breakdown",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number2092809343",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "points_earned",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3663793586",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_results",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
83
pb_migrations/1779005309_deleted_relations.js
Normal file
83
pb_migrations/1779005309_deleted_relations.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1883724256");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text1602912115",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "source",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1181691900",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "target",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text2363381545",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "type",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1883724256",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "relations",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
83
pb_migrations/1779005309_deleted_sources.js
Normal file
83
pb_migrations/1779005309_deleted_sources.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1124997656");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text1579384326",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "name",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text2063623452",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "status",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1574812785",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "error",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1124997656",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "sources",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
83
pb_migrations/1779005309_deleted_team_members.js
Normal file
83
pb_migrations/1779005309_deleted_team_members.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3980519374");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text1579384326",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "name",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text3045404147",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "pin",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1466534506",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "role",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3980519374",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "team_members",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
83
pb_migrations/1779005309_deleted_topics.js
Normal file
83
pb_migrations/1779005309_deleted_topics.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2800040823");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text245846248",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "label",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text2363381545",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "type",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1843675174",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "description",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2800040823",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "topics",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
107
pb_migrations/1779005310_deleted_leaderboard.js
Normal file
107
pb_migrations/1779005310_deleted_leaderboard.js
Normal file
@@ -0,0 +1,107 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3906957607");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2809058197",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "user_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1579384326",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "name",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number666537513",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "points",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number2161202778",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "tests_completed",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number684605397",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "learnings_completed",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3906957607",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "leaderboard",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
68
pb_migrations/1779005310_deleted_settings.js
Normal file
68
pb_migrations/1779005310_deleted_settings.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2769025244");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2324736937",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "key",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text494360628",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "value",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2769025244",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "settings",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
53
pb_migrations/1779005310_deleted_test_col3.js
Normal file
53
pb_migrations/1779005310_deleted_test_col3.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1553367558");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": null,
|
||||
"deleteRule": null,
|
||||
"fields": [
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text3208210256",
|
||||
"max": 255,
|
||||
"min": 1,
|
||||
"name": "id",
|
||||
"pattern": "^[a-z0-9]+$",
|
||||
"presentable": false,
|
||||
"primaryKey": true,
|
||||
"required": true,
|
||||
"system": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1345053523",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "test_field",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1553367558",
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"name": "test_col3",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": null,
|
||||
"viewRule": null
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
53
pb_migrations/1779005310_deleted_test_col4.js
Normal file
53
pb_migrations/1779005310_deleted_test_col4.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3270645669");
|
||||
|
||||
return app.delete(collection);
|
||||
}, (app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": null,
|
||||
"deleteRule": null,
|
||||
"fields": [
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text3208210256",
|
||||
"max": 255,
|
||||
"min": 1,
|
||||
"name": "id",
|
||||
"pattern": "^[a-zA-Z0-9_-]+$",
|
||||
"presentable": false,
|
||||
"primaryKey": true,
|
||||
"required": true,
|
||||
"system": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1345053523",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "test_field",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3270645669",
|
||||
"indexes": [],
|
||||
"listRule": null,
|
||||
"name": "test_col4",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": null,
|
||||
"viewRule": null
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
})
|
||||
64
pb_migrations/1779005316_created_content.js
Normal file
64
pb_migrations/1779005316_created_content.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text525672509",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "topic_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json2918445923",
|
||||
"maxSize": 0,
|
||||
"name": "data",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1872121667",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "content",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1872121667");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
64
pb_migrations/1779005316_created_quiz_banks.js
Normal file
64
pb_migrations/1779005316_created_quiz_banks.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text525672509",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "topic_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json2329695445",
|
||||
"maxSize": 0,
|
||||
"name": "questions",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2848292486",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_banks",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2848292486");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
83
pb_migrations/1779005316_created_relations.js
Normal file
83
pb_migrations/1779005316_created_relations.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text1602912115",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "source",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1181691900",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "target",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text2363381545",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "type",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1883724256",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "relations",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1883724256");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
83
pb_migrations/1779005316_created_sources.js
Normal file
83
pb_migrations/1779005316_created_sources.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text1579384326",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "name",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text2063623452",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "status",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1574812785",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "error",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1124997656",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "sources",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1124997656");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
83
pb_migrations/1779005316_created_topics.js
Normal file
83
pb_migrations/1779005316_created_topics.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"fields": [
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text3208210256",
|
||||
"max": 255,
|
||||
"min": 1,
|
||||
"name": "id",
|
||||
"pattern": "^[a-zA-Z0-9_-]+$",
|
||||
"presentable": false,
|
||||
"primaryKey": true,
|
||||
"required": true,
|
||||
"system": true,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text245846248",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "label",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text2363381545",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "type",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1843675174",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "description",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2800040823",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "topics",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2800040823");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
107
pb_migrations/1779005317_created_leaderboard.js
Normal file
107
pb_migrations/1779005317_created_leaderboard.js
Normal file
@@ -0,0 +1,107 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2809058197",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "user_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1579384326",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "name",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number666537513",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "points",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number2161202778",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "tests_completed",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number684605397",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "learnings_completed",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3906957607",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "leaderboard",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3906957607");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
76
pb_migrations/1779005317_created_learn_progress.js
Normal file
76
pb_migrations/1779005317_created_learn_progress.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2809058197",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "user_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3807012359",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "week_number",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "bool271442091",
|
||||
"name": "done",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1945839367",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "learn_progress",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1945839367");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
88
pb_migrations/1779005317_created_quiz_cache.js
Normal file
88
pb_migrations/1779005317_created_quiz_cache.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2809058197",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "user_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3807012359",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "week_number",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json2329695445",
|
||||
"maxSize": 0,
|
||||
"name": "questions",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json3622966325",
|
||||
"maxSize": 0,
|
||||
"name": "meta",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
}
|
||||
],
|
||||
"id": "pbc_1132733175",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_cache",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1132733175");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
157
pb_migrations/1779005317_created_quiz_results.js
Normal file
157
pb_migrations/1779005317_created_quiz_results.js
Normal file
@@ -0,0 +1,157 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2809058197",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "user_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3807012359",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "week_number",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number848901969",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "score",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3257917790",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "total",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number2466988094",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "percentage",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3573180981",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "time_used",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1410257210",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "completed_at",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "json3012992423",
|
||||
"maxSize": 0,
|
||||
"name": "breakdown",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "json"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number2092809343",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "points_earned",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3663793586",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "quiz_results",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3663793586");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
68
pb_migrations/1779005317_created_settings.js
Normal file
68
pb_migrations/1779005317_created_settings.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text2324736937",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "key",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text494360628",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "value",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2769025244",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "settings",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2769025244");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
83
pb_migrations/1779005317_created_team_members.js
Normal file
83
pb_migrations/1779005317_created_team_members.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text1579384326",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "name",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text3045404147",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "pin",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text1466534506",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "role",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"id": "pbc_3980519374",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "team_members",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_3980519374");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
150
pb_migrations/1779127586_created_curriculum.js
Normal file
150
pb_migrations/1779127586_created_curriculum.js
Normal file
@@ -0,0 +1,150 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3145888567",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "year",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number3807012359",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "week_number",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text525672509",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "topic_id",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text2541086472",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "theme",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number478273799",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "quarter",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "bool1753762221",
|
||||
"name": "is_review_week",
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "number1169138922",
|
||||
"max": null,
|
||||
"min": null,
|
||||
"name": "sort_order",
|
||||
"onlyInt": false,
|
||||
"presentable": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_2236782419",
|
||||
"indexes": [],
|
||||
"listRule": "",
|
||||
"name": "curriculum",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2236782419");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
1599
pb_migrations/1779127759_collections_snapshot.js
Normal file
1599
pb_migrations/1779127759_collections_snapshot.js
Normal file
File diff suppressed because it is too large
Load Diff
45
pb_migrations/1779200000_updated_sources.js
Normal file
45
pb_migrations/1779200000_updated_sources.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1124997656")
|
||||
|
||||
if (collection) {
|
||||
// Check if created already exists
|
||||
let hasCreated = false;
|
||||
for (let f of collection.fields) {
|
||||
if (f.name === 'created') hasCreated = true;
|
||||
}
|
||||
|
||||
if (!hasCreated) {
|
||||
collection.fields.addAt(4, new Field({
|
||||
"hidden": false,
|
||||
"id": "autodate_sources_created",
|
||||
"name": "created",
|
||||
"onCreate": true,
|
||||
"onUpdate": false,
|
||||
"presentable": false,
|
||||
"system": false,
|
||||
"type": "autodate"
|
||||
}))
|
||||
|
||||
collection.fields.addAt(5, new Field({
|
||||
"hidden": false,
|
||||
"id": "autodate_sources_updated",
|
||||
"name": "updated",
|
||||
"onCreate": true,
|
||||
"onUpdate": true,
|
||||
"presentable": false,
|
||||
"system": false,
|
||||
"type": "autodate"
|
||||
}))
|
||||
|
||||
return app.save(collection)
|
||||
}
|
||||
}
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_1124997656")
|
||||
if (collection) {
|
||||
collection.fields.removeById("autodate_sources_created")
|
||||
collection.fields.removeById("autodate_sources_updated")
|
||||
return app.save(collection)
|
||||
}
|
||||
})
|
||||
90
pb_migrations/1780000000_created_handbook_sync_state.js
Normal file
90
pb_migrations/1780000000_created_handbook_sync_state.js
Normal file
@@ -0,0 +1,90 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = new Collection({
|
||||
"createRule": "",
|
||||
"deleteRule": "",
|
||||
"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": "text_path123",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "path",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text_sha123",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "sha",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": true,
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"id": "pbc_handbooksync123",
|
||||
"indexes": [
|
||||
"CREATE UNIQUE INDEX `idx_handbook_path` ON `handbook_sync_state` (`path`)"
|
||||
],
|
||||
"listRule": "",
|
||||
"name": "handbook_sync_state",
|
||||
"system": false,
|
||||
"type": "base",
|
||||
"updateRule": "",
|
||||
"viewRule": ""
|
||||
});
|
||||
|
||||
return app.save(collection);
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_handbooksync123");
|
||||
|
||||
return app.delete(collection);
|
||||
})
|
||||
30
pb_migrations/1780000001_updated_topics.js
Normal file
30
pb_migrations/1780000001_updated_topics.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/// <reference path="../pb_data/types.d.ts" />
|
||||
migrate((app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2800040823")
|
||||
|
||||
// add field
|
||||
collection.fields.addAt(4, new Field({
|
||||
"autogeneratePattern": "",
|
||||
"help": "",
|
||||
"hidden": false,
|
||||
"id": "text_learning_relevance",
|
||||
"max": 0,
|
||||
"min": 0,
|
||||
"name": "learning_relevance",
|
||||
"pattern": "",
|
||||
"presentable": false,
|
||||
"primaryKey": false,
|
||||
"required": false,
|
||||
"system": false,
|
||||
"type": "text"
|
||||
}))
|
||||
|
||||
return app.save(collection)
|
||||
}, (app) => {
|
||||
const collection = app.findCollectionByNameOrId("pbc_2800040823")
|
||||
|
||||
// remove field
|
||||
collection.fields.removeById("text_learning_relevance")
|
||||
|
||||
return app.save(collection)
|
||||
})
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 380 B |
BIN
public/fonts/BallPill-light.otf
Normal file
BIN
public/fonts/BallPill-light.otf
Normal file
Binary file not shown.
214
scripts/setup-pb-collections.mjs
Normal file
214
scripts/setup-pb-collections.mjs
Normal file
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* Creates all PocketBase collections for the learning platform.
|
||||
* Usage: node scripts/setup-pb-collections.mjs [PB_URL] [ADMIN_EMAIL] [ADMIN_PASSWORD]
|
||||
* Defaults: http://localhost:8090, prompted if not provided
|
||||
*/
|
||||
|
||||
const PB_URL = process.argv[2] || 'http://localhost:8090';
|
||||
const ADMIN_EMAIL = process.argv[3];
|
||||
const ADMIN_PASSWORD = process.argv[4];
|
||||
|
||||
if (!ADMIN_EMAIL || !ADMIN_PASSWORD) {
|
||||
console.error('Usage: node scripts/setup-pb-collections.mjs <PB_URL> <admin_email> <admin_password>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const OPEN_RULES = { listRule: '', viewRule: '', createRule: '', updateRule: '', deleteRule: '' };
|
||||
|
||||
// PocketBase v0.23: created/updated must be explicitly defined as autodate fields
|
||||
const AUTODATE_FIELDS = [
|
||||
{ name: 'created', type: 'autodate', onCreate: true, onUpdate: false },
|
||||
{ name: 'updated', type: 'autodate', onCreate: true, onUpdate: true },
|
||||
];
|
||||
|
||||
const COLLECTIONS = [
|
||||
{
|
||||
name: 'topics',
|
||||
type: 'base',
|
||||
...OPEN_RULES,
|
||||
fields: [
|
||||
{ name: 'id', type: 'text', primaryKey: true, system: true, min: 1, max: 255, pattern: '^[a-zA-Z0-9_-]+$' },
|
||||
{ name: 'label', type: 'text', required: true },
|
||||
{ name: 'type', type: 'text', required: false },
|
||||
{ name: 'description', type: 'text', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'relations',
|
||||
type: 'base',
|
||||
...OPEN_RULES,
|
||||
fields: [
|
||||
{ name: 'source', type: 'text', required: true },
|
||||
{ name: 'target', type: 'text', required: true },
|
||||
{ name: 'type', type: 'text', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'base',
|
||||
...OPEN_RULES,
|
||||
fields: [
|
||||
{ name: 'topic_id', type: 'text', required: true },
|
||||
{ name: 'data', type: 'json', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'quiz_banks',
|
||||
type: 'base',
|
||||
...OPEN_RULES,
|
||||
fields: [
|
||||
{ name: 'topic_id', type: 'text', required: true },
|
||||
{ name: 'questions', type: 'json', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'sources',
|
||||
type: 'base',
|
||||
...OPEN_RULES,
|
||||
fields: [
|
||||
{ name: 'name', type: 'text', required: true },
|
||||
{ name: 'status', type: 'text', required: false },
|
||||
{ name: 'error', type: 'text', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'team_members',
|
||||
type: 'base',
|
||||
...OPEN_RULES,
|
||||
fields: [
|
||||
{ name: 'name', type: 'text', required: true },
|
||||
{ name: 'pin', type: 'text', required: false },
|
||||
{ name: 'role', type: 'text', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'quiz_results',
|
||||
type: 'base',
|
||||
...OPEN_RULES,
|
||||
fields: [
|
||||
{ name: 'user_id', type: 'text', required: true },
|
||||
{ name: 'week_number', type: 'number', required: true },
|
||||
{ name: 'score', type: 'number', required: false },
|
||||
{ name: 'total', type: 'number', required: false },
|
||||
{ name: 'percentage', type: 'number', required: false },
|
||||
{ name: 'time_used', type: 'number', required: false },
|
||||
{ name: 'completed_at', type: 'text', required: false },
|
||||
{ name: 'breakdown', type: 'json', required: false },
|
||||
{ name: 'points_earned',type: 'number', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'quiz_cache',
|
||||
type: 'base',
|
||||
...OPEN_RULES,
|
||||
fields: [
|
||||
{ name: 'user_id', type: 'text', required: true },
|
||||
{ name: 'week_number', type: 'number', required: true },
|
||||
{ name: 'questions', type: 'json', required: false },
|
||||
{ name: 'meta', type: 'json', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'learn_progress',
|
||||
type: 'base',
|
||||
...OPEN_RULES,
|
||||
fields: [
|
||||
{ name: 'user_id', type: 'text', required: true },
|
||||
{ name: 'week_number', type: 'number', required: true },
|
||||
{ name: 'done', type: 'bool', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'leaderboard',
|
||||
type: 'base',
|
||||
...OPEN_RULES,
|
||||
fields: [
|
||||
{ name: 'user_id', type: 'text', required: true },
|
||||
{ name: 'name', type: 'text', required: false },
|
||||
{ name: 'points', type: 'number', required: false },
|
||||
{ name: 'tests_completed', type: 'number', required: false },
|
||||
{ name: 'learnings_completed', type: 'number', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'curriculum',
|
||||
type: 'base',
|
||||
...OPEN_RULES,
|
||||
fields: [
|
||||
{ name: 'year', type: 'number', required: true },
|
||||
{ name: 'week_number', type: 'number', required: true },
|
||||
{ name: 'topic_id', type: 'text', required: false },
|
||||
{ name: 'theme', type: 'text', required: false },
|
||||
{ name: 'quarter', type: 'number', required: false },
|
||||
{ name: 'is_review_week', type: 'bool', required: false },
|
||||
{ name: 'sort_order', type: 'number', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'settings',
|
||||
type: 'base',
|
||||
...OPEN_RULES,
|
||||
fields: [
|
||||
{ name: 'key', type: 'text', required: true },
|
||||
{ name: 'value', type: 'text', required: false },
|
||||
...AUTODATE_FIELDS,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
async function post(path, body, token) {
|
||||
const res = await fetch(`${PB_URL}${path}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { Authorization: token } : {}),
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (!res.ok) throw Object.assign(new Error(json.message || 'Request failed'), { status: res.status, data: json });
|
||||
return json;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log(`Connecting to PocketBase at ${PB_URL}...`);
|
||||
|
||||
// Authenticate as superuser (PocketBase v0.23+)
|
||||
const auth = await post('/api/collections/_superusers/auth-with-password', {
|
||||
identity: ADMIN_EMAIL,
|
||||
password: ADMIN_PASSWORD,
|
||||
});
|
||||
const token = auth.token;
|
||||
console.log('Authenticated as admin.\n');
|
||||
|
||||
for (const col of COLLECTIONS) {
|
||||
try {
|
||||
await post('/api/collections', col, token);
|
||||
console.log(` ✓ Created: ${col.name}`);
|
||||
} catch (err) {
|
||||
if (err.status === 400 && err.data?.data?.name?.code === 'validation_not_unique') {
|
||||
console.log(` ~ Skipped: ${col.name} (already exists)`);
|
||||
} else {
|
||||
console.error(` ✗ Failed: ${col.name} — ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\nDone. All collections are set up with open API rules.');
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('\nFatal error:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
328
sources/ROLES.md
Normal file
328
sources/ROLES.md
Normal file
@@ -0,0 +1,328 @@
|
||||
# Respellion — Roles & Accountabilities
|
||||
|
||||
Generated from `src/db/seed.ts` (source: GlassFrog export *Respellion Governance - 2026-04-26.pdf*).
|
||||
|
||||
Roles marked **(structural)** are constitutionally-required (Circle Lead, Facilitator, Secretary, Circle Rep).
|
||||
|
||||
---
|
||||
|
||||
## Respellion Anchor Circle
|
||||
|
||||
**Purpose:** Een maatschappij waarin open source technologie wordt ingezet om duurzame oplossingen te bouwen.
|
||||
|
||||
### Aandeelhouders
|
||||
|
||||
- **Filler:** Patrick Smulders
|
||||
|
||||
### Circle Lead *(structural)*
|
||||
|
||||
- **Purpose:** Een maatschappij waarin open source technologie wordt ingezet om duurzame oplossingen te bouwen.
|
||||
|
||||
### Facilitator *(structural)*
|
||||
|
||||
- **Purpose:** Circle governance and operational practices aligned with the Constitution.
|
||||
- **Accountabilities:**
|
||||
- Facilitating the Circle's regular Tactical Meetings
|
||||
- Facilitating the Circle's Governance Process
|
||||
- Triggering new elections for the Circle's elected Roles after each election term expires
|
||||
- Auditing a Sub-Circle's meetings and records on request and declaring a Process Breakdown if one is discovered
|
||||
|
||||
### Secretary *(structural)*
|
||||
|
||||
- **Purpose:** Stabilize the Circle's constitutionally-required records and meetings.
|
||||
- **Domains:** All governance records of the Circle
|
||||
- **Accountabilities:**
|
||||
- Scheduling regular Tactical Meetings for the Circle
|
||||
- Capturing and publishing Tactical Meeting outputs
|
||||
- Scheduling Governance Meetings for the Circle
|
||||
- Capturing and publishing the outputs of the Circle's Governance Process
|
||||
- Interpreting the Constitution and anything under its authority upon request
|
||||
|
||||
---
|
||||
|
||||
## Respellion Operations *(sub-circle of Anchor)*
|
||||
|
||||
### Buddy
|
||||
|
||||
- **Purpose:** Onboard a new Respellion employee so this person feels welcome and at home.
|
||||
- **Accountabilities:**
|
||||
- Following up the checklist until the new employee is satisfied
|
||||
- Checking (verifying) with the new employee if they indeed feel welcomed and at home
|
||||
|
||||
### Chief Azure
|
||||
|
||||
- **Purpose:** Ensure a maintainable and available Azure environment for Respellion.
|
||||
- **Filler:** Robert van Diest
|
||||
- **Accountabilities:**
|
||||
- Monitoring availability of the Respellion Azure environment
|
||||
- Communicating with CSP (ALSO)
|
||||
- Maintaining the infrastructure for internal and customer purposes
|
||||
- Following up any alerts from Azure
|
||||
- Coordinating changes in resources above SLA with service manager
|
||||
- Communicating to Respellion stakeholders about the Azure environment
|
||||
|
||||
### Circle Lead *(structural)*
|
||||
|
||||
- **Purpose:** The Circle Lead holds the Purpose of the overall Circle.
|
||||
- **Filler:** Patrick Smulders
|
||||
|
||||
### Circle Rep *(structural)*
|
||||
|
||||
- **Purpose:** Tensions relevant to process in a broader Circle channeled out and resolved.
|
||||
- **Accountabilities:**
|
||||
- Seeking to understand Tensions conveyed by Role Leads within the Circle
|
||||
- Discerning Tensions appropriate to process within a broader Circle that holds the Circle
|
||||
- Processing Tensions within a broader Circle to remove constraints on the Circle
|
||||
|
||||
### Event owner
|
||||
|
||||
- **Purpose:** Making sure Respellion colleagues have an awesome event experience.
|
||||
- **Accountabilities:**
|
||||
- Drafting list of congresses
|
||||
- Making sure there is a budget and using that budget
|
||||
- Making sure everything about a congress visit is facilitated (stay, transport, tickets, etc)
|
||||
- Being transparant about the budget and the use of it
|
||||
- Taking ownership for small team events
|
||||
|
||||
### Facilitator *(structural)*
|
||||
|
||||
- **Purpose:** Circle governance and operational practices aligned with the Constitution.
|
||||
- **Accountabilities:**
|
||||
- Facilitating the Circle's regular Tactical Meetings
|
||||
- Facilitating the Circle's Governance Process
|
||||
- Triggering new elections for the Circle's elected Roles after each election term expires
|
||||
- Auditing a Sub-Circle's meetings and records on request and declaring a Process Breakdown if one is discovered
|
||||
|
||||
### Huddle A
|
||||
|
||||
- **Purpose:** A group of three or four individuals that acts as buddy structure.
|
||||
- **Fillers:** Lisanne Farla, Jos van Aalderen, Raymond Verhoef
|
||||
- **Accountabilities:**
|
||||
- Holding BI weekly huddle checkins
|
||||
- Coaching each other on professional and personal issues
|
||||
- Strengthening the feedback culture with developing personal plans
|
||||
- Creating transparancy in individual progress and improvements
|
||||
|
||||
### Huddle B
|
||||
|
||||
- **Purpose:** A group of three or four individuals that acts as buddy structure.
|
||||
- **Fillers:** Patrick Smulders, Edwin Hensen, Thomas Gunadi, Robert van Diest
|
||||
- **Accountabilities:**
|
||||
- Holding BI weekly huddle checkins
|
||||
- Coaching each other on professional and personal issues
|
||||
- Strengthening the feedback culture with developing personal plans
|
||||
- Creating transparancy in individual progress and improvements
|
||||
|
||||
### Huddle C
|
||||
|
||||
- **Purpose:** A group of three or four individuals that acts as buddy structure.
|
||||
- **Filler:** Edwin van den Houdt
|
||||
- **Accountabilities:**
|
||||
- Holding BI weekly huddle checkins
|
||||
- Coaching each other on professional and personal issues
|
||||
- Strengthening the feedback culture with developing personal plans
|
||||
- Creating transparancy in individual progress and improvements
|
||||
|
||||
### Internal Auditor
|
||||
|
||||
- **Purpose:** Making sure that independent evaluations of risks, processes, and controls within Respellion are conducted regularly. The goal is to ensure effective and efficient operations and to ensure compliance with laws and regulations.
|
||||
- **Accountabilities:**
|
||||
- Identifying and assessing risks within Respellion
|
||||
- Analyzing the impact of these risks on operational and financial processes
|
||||
- Developing an annual (internal) audit plan based on risk analysis, including scope and objectives
|
||||
- Conducting operational, financial and compliance audits
|
||||
- Collecting and analyzing data to assess effectiveness of internal controls
|
||||
- Preparing audit reports (findings, conclusions, recommendations)
|
||||
- Presenting results to management and colleagues
|
||||
- Monitoring the implementation of recommendations
|
||||
- Evaluating the effectiveness of corrective actions
|
||||
- Ensuring compliance with internal guidelines and external law and regulations
|
||||
- Advising on improvements to processes and controls to ensure compliance
|
||||
- Contributing to the development of awareness of internal controls within the organization
|
||||
- Ensuring colleagues are aware of the importance of risk management and compliance
|
||||
|
||||
### Marketing
|
||||
|
||||
- **Purpose:** Respellion has an outstanding corporate reputation that alligns with the core values and manifest.
|
||||
- **Filler:** Raymond Verhoef
|
||||
- **Accountabilities:**
|
||||
- Maintaining our exposure on social media
|
||||
- Maintaining the website on functional level
|
||||
- Developing relevant exposure on the website
|
||||
- Improving the corporate reputation in a structured and managed way
|
||||
|
||||
### Networker
|
||||
|
||||
- **Purpose:** Maintain a valuable and effective network of stakeholders.
|
||||
- **Filler:** Patrick Smulders
|
||||
- **Accountabilities:**
|
||||
- Maintaining a network of partners (ICT brokers, IT companies)
|
||||
- Maintaining a network of potential customers
|
||||
- Monitoring a transparent sales funnel for potential tenders
|
||||
- Monitoring a transparent sales funnel for potential individual assignments
|
||||
- Aligning the sales funnel with the financial budget administration and forecast
|
||||
- Being transparent (communication) about the accountabilities above
|
||||
|
||||
### People Officer
|
||||
|
||||
- **Purpose:** Making sure Respellion has/maintains the organizational culture in which individuals can be the best versions of themselves. And making sure Respellion has/maintains an unique and attractive organizational structure.
|
||||
- **Filler:** Raymond Verhoef
|
||||
- **Domains:** An asset, process, or other thing this Role may exclusively control and regulate as its property, for its purpose.
|
||||
- **Accountabilities:**
|
||||
- Maintaining the performance model (salary etc)
|
||||
- Facilitating the monthly celebration meeting
|
||||
- Guarding the Respellion culture/DNA
|
||||
- Designing employment contracts
|
||||
- Maintaining an effective onboarding program
|
||||
- Approving leave requests
|
||||
- Maintaining the employee handbook
|
||||
- Maintaining a comprehensive sick leave administration
|
||||
- Maintaining a comprehensive leave administration
|
||||
- Taking initiatives to improve people wellbeing
|
||||
- Increasing the connection with people's social system at home
|
||||
- Making sure that successes are being celebrated
|
||||
|
||||
### Privacy Officer
|
||||
|
||||
- **Purpose:** To ensure Respellion maintains the highest standards of data privacy compliance while enabling the organization to effectively use data in alignment with our values of trust, courage, self-discipline, and entrepreneurship.
|
||||
- **Filler:** Jos van Aalderen
|
||||
- **Accountabilities:**
|
||||
- Developing, implementing, and maintaining a comprehensive privacy program aligned with GDPR, Dutch privacy laws, and other applicable regulations
|
||||
- Monitoring and interpreting changes in privacy legislation and updating organizational policies accordingly
|
||||
- Serving as the primary point of contact for supervisory authorities (like the Dutch Data Protection Authority)
|
||||
- Conducting regular privacy impact assessments for new and existing processing activities
|
||||
- Maintaining records of processing activities as required by Article 30 of GDPR
|
||||
- Identifying privacy risks across the organization and recommending appropriate risk mitigation strategies
|
||||
- Managing privacy incident response, including breach notification procedures
|
||||
- Reviewing data processing agreements with vendors and partners to ensure privacy requirements are adequately addressed
|
||||
- Developing and delivering privacy training programs for all team members
|
||||
- Establishing and overseeing processes for handling data subject rights requests (access, rectification, erasure, etc.)
|
||||
- Ensuring timely responses to privacy inquiries from customers, employees, and other stakeholders
|
||||
|
||||
### Process guardian
|
||||
|
||||
- **Purpose:** Making sure Respellions organizational processes are transparant, compliant and scalable.
|
||||
- **Filler:** Raymond Verhoef
|
||||
- **Accountabilities:**
|
||||
- Maintaining a file structure for documents that facilitates compliance and transparancy
|
||||
- Maintaining a comprehensive quality management system that complies with potentional ISO regulations
|
||||
- Advising the circle about effective software platforms
|
||||
- Advising the circle about (auditing) the quality management system(s)
|
||||
- Advising the circle about any other relevant internal processes
|
||||
- Organizing internal audits as described in ISO9001:2015 chapter 9
|
||||
|
||||
### Recruiter
|
||||
|
||||
- **Purpose:** Recruiting new employees who fit the Respellion's culture.
|
||||
- **Fillers:** Jos van Aalderen, Patrick Smulders
|
||||
- **Accountabilities:**
|
||||
- Selecting potential employees via necessary channels
|
||||
- Running the first telephone conversations in the application process
|
||||
- Organizing the introduction interview of the application process
|
||||
- Organizing the capacity interview of the application process
|
||||
- Monitoring the application process
|
||||
- Placing job vacancies on external websites
|
||||
- Making sure the application process is done within two weeks
|
||||
- Using a LinkedIn recruiter subscription
|
||||
- Placing posts for Respellion employer branding
|
||||
- Drafting job descriptions to attract potential employees
|
||||
- Reporting regularly on the recruiting process
|
||||
|
||||
### Secretary *(structural)*
|
||||
|
||||
- **Purpose:** Stabilize the Circle's constitutionally-required records and meetings.
|
||||
- **Filler:** Edwin van den Houdt
|
||||
- **Domains:** All governance records of the Circle
|
||||
- **Accountabilities:**
|
||||
- Scheduling regular Tactical Meetings for the Circle
|
||||
- Capturing and publishing Tactical Meeting outputs
|
||||
- Scheduling Governance Meetings for the Circle
|
||||
- Capturing and publishing the outputs of the Circle's Governance Process
|
||||
- Interpreting the Constitution and anything under its authority upon request
|
||||
|
||||
### Service Manager
|
||||
|
||||
- **Purpose:** Take ownership of the services we provide for customers with a SLA for hosting, maintenance and operations with the goal to be an excellent service provider.
|
||||
- **Filler:** Raymond Verhoef
|
||||
- **Accountabilities:**
|
||||
- Defining, aligning and agree upon SLA's.
|
||||
- Managing ITIL service management processes.
|
||||
- Acting as a single point of contact for hosting, operation and maintaining applications for customers.
|
||||
|
||||
### Sustainability officer
|
||||
|
||||
- **Purpose:** Ensuring the balance between people-profit-planet in the Respellion corporate activities.
|
||||
- **Filler:** Raymond Verhoef
|
||||
- **Accountabilities:**
|
||||
- Taking initiatives for social return activities
|
||||
- Challenging policy, activities and ideas against the corporate values and manifest and suggest changes/improvements
|
||||
- Communicating regularly to Respellion stakeholders about sustainability and social return activities
|
||||
|
||||
### System administrator
|
||||
|
||||
- **Purpose:** To maintain the Respellion company website and/or Respellion applications.
|
||||
- **Filler:** Thomas Gunadi
|
||||
- **Accountabilities:**
|
||||
- Monitoring the availability of the website
|
||||
- Coordinating activities with hosting partner (GRRR)
|
||||
- Maintaining an overview of the internal application landscape
|
||||
- Updating the Respellion website on a regular basis
|
||||
- Communicating to Respellion stakeholders about our website and internal application landscape
|
||||
|
||||
### Technology Officer
|
||||
|
||||
- **Purpose:** To create a technical strategy that aligns and add value to our purpose.
|
||||
- **Filler:** Edwin van den Houdt
|
||||
- **Accountabilities:**
|
||||
- Designing a strategy on open-source technology for Respellion and keeping it updated
|
||||
- Defining internal technology projects
|
||||
- Coordinating the internal projects (project owner)
|
||||
- Communicating to Respellion stakeholders about the open source strategy
|
||||
|
||||
### Treasury Keeper
|
||||
|
||||
- **Purpose:** Respellion has a sustainable and transparent financial administration that meets legal obligations.
|
||||
- **Filler:** Patrick Smulders
|
||||
- **Accountabilities:**
|
||||
- Maintaining salary administration
|
||||
- Maintaining declaration administration
|
||||
- Maintaining company budget sheets
|
||||
- Maintaining pension administration
|
||||
- Maintaining insurance administration
|
||||
- Reporting on the administrations to create transparency.
|
||||
- Communicating to Respellion stakeholders about the financial adminstration
|
||||
|
||||
### Workplace support
|
||||
|
||||
- **Purpose:** Ensure a safe and effective digital workplace.
|
||||
- **Filler:** Thomas Gunadi
|
||||
- **Accountabilities:**
|
||||
- Ensuring security compliancy conform Microsoft security policies
|
||||
- Facilitating healthy workplace
|
||||
- Supporting colleagues in IT (with regard to hardware and software)
|
||||
- Maintaining Microsoft 365
|
||||
- Providing software licenses to colleagues if needed
|
||||
|
||||
---
|
||||
|
||||
## Software Delivery *(sub-circle of Respellion Operations)*
|
||||
|
||||
**Purpose:** Deliver high quality software and have happy customers.
|
||||
|
||||
### Circle Lead *(structural)*
|
||||
|
||||
- **Purpose:** The Circle Lead holds the Purpose of the overall Circle.
|
||||
- **Filler:** Thomas Gunadi
|
||||
- **Accountabilities:**
|
||||
- Making sure the roles and their purposes and accountabilities in the Software Delivery Circle are described properly
|
||||
- Assigning people to roles
|
||||
- Monitoring the fit, offering feedback to enhance fit, and re-assigning roles to others when useful for enhancing fit
|
||||
- Defining priorities and strategies for the circle
|
||||
|
||||
### Facilitator *(structural)*
|
||||
|
||||
*No purpose, accountabilities, or domains defined.*
|
||||
|
||||
### Secretary *(structural)*
|
||||
|
||||
*No purpose, accountabilities, or domains defined.*
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user