Seed template recommendations
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
"title": "Community Rule",
|
"title": "Community Rule",
|
||||||
"author": "MEDLab",
|
"author": "MEDLab",
|
||||||
"description": "Community governance and rule-building app",
|
"description": "Community governance and rule-building app",
|
||||||
"version": "0.1.7",
|
"version": "0.1.8",
|
||||||
"httpPort": 3000,
|
"httpPort": 3000,
|
||||||
"healthCheckPath": "/api/health",
|
"healthCheckPath": "/api/health",
|
||||||
"memoryLimit": 805306368,
|
"memoryLimit": 805306368,
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ COPY --from=builder --chown=node:node /app/public ./public
|
|||||||
COPY --from=builder --chown=node:node /app/.next/standalone ./
|
COPY --from=builder --chown=node:node /app/.next/standalone ./
|
||||||
COPY --from=builder --chown=node:node /app/.next/static ./.next/static
|
COPY --from=builder --chown=node:node /app/.next/static ./.next/static
|
||||||
COPY --from=builder --chown=node:node /app/prisma ./prisma
|
COPY --from=builder --chown=node:node /app/prisma ./prisma
|
||||||
|
# Facet/template seed JSON — NOT under /app/data (localstorage mount overlays that).
|
||||||
|
COPY --from=builder --chown=node:node /app/data ./seed-data
|
||||||
|
ENV SEED_DATA_DIR=/app/seed-data
|
||||||
|
|
||||||
# Prisma CLI (devDependency) is not in the Next.js standalone trace. Install
|
# Prisma CLI (devDependency) is not in the Next.js standalone trace. Install
|
||||||
# globally in the runner so start.sh can run `prisma migrate deploy`.
|
# globally in the runner so start.sh can run `prisma migrate deploy`.
|
||||||
|
|||||||
@@ -390,6 +390,16 @@ production env vars, and verify the vertical slice before apex cutover
|
|||||||
custom-from allowed — §3).
|
custom-from allowed — §3).
|
||||||
5. **Confirm the app is running** in the Cloudron dashboard (Logs tab). Look
|
5. **Confirm the app is running** in the Cloudron dashboard (Logs tab). Look
|
||||||
for a clean `prisma migrate deploy` and Next.js listening on port 3000.
|
for a clean `prisma migrate deploy` and Next.js listening on port 3000.
|
||||||
|
6. **Seed facet data (one-time per environment)** — templates + `MethodFacet`
|
||||||
|
rows for create-flow "Recommended" tags are **not** applied at boot. After
|
||||||
|
first install (or when recommendations return all-zero scores), run:
|
||||||
|
```bash
|
||||||
|
cloudron exec --app staging.communityrule.info -- \
|
||||||
|
node prisma/seed.bundle.cjs
|
||||||
|
```
|
||||||
|
JSON lives at `/app/seed-data/` (`SEED_DATA_DIR`); do not use `/app/data`
|
||||||
|
(Cloudron localstorage overwrites it). Re-run after deploy is safe
|
||||||
|
(idempotent upserts / per-section swaps).
|
||||||
|
|
||||||
**Smoke checklist (acceptance):**
|
**Smoke checklist (acceptance):**
|
||||||
|
|
||||||
@@ -419,6 +429,8 @@ steps below are still required.
|
|||||||
| Magic link not sent | Mail addon or `SMTP_FROM` | Cloudron mail logs; `CLOUDRON_MAIL_SMTP_*` vars |
|
| Magic link not sent | Mail addon or `SMTP_FROM` | Cloudron mail logs; `CLOUDRON_MAIL_SMTP_*` vars |
|
||||||
| Upload `server_misconfigured` | `UPLOAD_ROOT` unset | Set to `/app/data/uploads` (§3) |
|
| Upload `server_misconfigured` | `UPLOAD_ROOT` unset | Set to `/app/data/uploads` (§3) |
|
||||||
| Container crash on start | Migration failure | App logs around `prisma migrate deploy` |
|
| Container crash on start | Migration failure | App logs around `prisma migrate deploy` |
|
||||||
|
| No "Recommended" on method cards | `MethodFacet` not seeded | §10 step 6; API should return `matches.score > 0` for some methods when `facet.*` set |
|
||||||
|
| `seed.bundle.cjs` ENOENT on `/app/data/...` | Old image without `/app/seed-data` | Deploy ≥ 0.1.8; JSON is at `SEED_DATA_DIR=/app/seed-data` |
|
||||||
|
|
||||||
**Done when:** all smoke checklist items pass. Then proceed to soft-launch
|
**Done when:** all smoke checklist items pass. Then proceed to soft-launch
|
||||||
(§5 phase 2) and, when ready, [CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover)
|
(§5 phase 2) and, when ready, [CR-99](https://linear.app/community-rule/issue/CR-99/backend-cloudron-production-install-apex-cutover)
|
||||||
|
|||||||
@@ -10,17 +10,14 @@ import {
|
|||||||
resolveFacetMatch,
|
resolveFacetMatch,
|
||||||
sectionFacetsSchema,
|
sectionFacetsSchema,
|
||||||
} from "../../lib/server/validation/methodFacetsSchemas";
|
} from "../../lib/server/validation/methodFacetsSchemas";
|
||||||
|
import { methodFacetsJsonDir } from "./seedDataPaths";
|
||||||
// Bundled seed runs from repo root (`process.cwd()`); __dirname breaks under esbuild.
|
|
||||||
const REPO_ROOT = process.cwd();
|
|
||||||
const DATA_DIR = path.join(REPO_ROOT, "data", "create", "customRule");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads + Zod-validates `data/create/customRule/<section>.json`.
|
* Reads + Zod-validates `data/create/customRule/<section>.json`.
|
||||||
* Throws on schema failures so the seed aborts before any DB write.
|
* Throws on schema failures so the seed aborts before any DB write.
|
||||||
*/
|
*/
|
||||||
async function loadSectionFacets(section: SectionId) {
|
async function loadSectionFacets(section: SectionId) {
|
||||||
const file = path.join(DATA_DIR, `${section}.json`);
|
const file = path.join(methodFacetsJsonDir(), `${section}.json`);
|
||||||
const raw = await readFile(file, "utf8");
|
const raw = await readFile(file, "utf8");
|
||||||
const parsed = JSON.parse(raw) as unknown;
|
const parsed = JSON.parse(raw) as unknown;
|
||||||
const result = sectionFacetsSchema.safeParse(parsed);
|
const result = sectionFacetsSchema.safeParse(parsed);
|
||||||
@@ -33,7 +30,7 @@ async function loadSectionFacets(section: SectionId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadFacetGroups() {
|
async function loadFacetGroups() {
|
||||||
const file = path.join(DATA_DIR, "_facetGroups.json");
|
const file = path.join(methodFacetsJsonDir(), "_facetGroups.json");
|
||||||
const raw = await readFile(file, "utf8");
|
const raw = await readFile(file, "utf8");
|
||||||
const parsed = JSON.parse(raw) as unknown;
|
const parsed = JSON.parse(raw) as unknown;
|
||||||
const result = facetGroupsFileSchema.safeParse(parsed);
|
const result = facetGroupsFileSchema.safeParse(parsed);
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root for committed seed JSON (`data/` in dev; `/app/seed-data` on Cloudron).
|
||||||
|
*
|
||||||
|
* Cloudron's localstorage addon mounts `/app/data` at runtime, so facet JSON
|
||||||
|
* must not live there. The Dockerfile copies `data/` → `/app/seed-data` and
|
||||||
|
* sets `SEED_DATA_DIR=/app/seed-data`.
|
||||||
|
*/
|
||||||
|
export function seedDataRoot(): string {
|
||||||
|
const override = process.env.SEED_DATA_DIR?.trim();
|
||||||
|
if (override) return override;
|
||||||
|
return path.join(process.cwd(), "data");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function methodFacetsJsonDir(): string {
|
||||||
|
return path.join(seedDataRoot(), "create", "customRule");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function templateFacetJsonPath(): string {
|
||||||
|
return path.join(seedDataRoot(), "templates", "templateFacet.json");
|
||||||
|
}
|
||||||
@@ -1,16 +1,8 @@
|
|||||||
import { readFile } from "node:fs/promises";
|
import { readFile } from "node:fs/promises";
|
||||||
import path from "node:path";
|
|
||||||
import type { PrismaClient } from "@prisma/client";
|
import type { PrismaClient } from "@prisma/client";
|
||||||
import { FACET_GROUP_IDS } from "../../lib/server/validation/methodFacetsSchemas";
|
import { FACET_GROUP_IDS } from "../../lib/server/validation/methodFacetsSchemas";
|
||||||
import { templateFacetFileSchema } from "../../lib/server/validation/templateFacetSchema";
|
import { templateFacetFileSchema } from "../../lib/server/validation/templateFacetSchema";
|
||||||
|
import { templateFacetJsonPath } from "./seedDataPaths";
|
||||||
const REPO_ROOT = process.cwd();
|
|
||||||
const TEMPLATE_FACET_FILE = path.join(
|
|
||||||
REPO_ROOT,
|
|
||||||
"data",
|
|
||||||
"templates",
|
|
||||||
"templateFacet.json",
|
|
||||||
);
|
|
||||||
|
|
||||||
type TemplateFacetRow = {
|
type TemplateFacetRow = {
|
||||||
templateSlug: string;
|
templateSlug: string;
|
||||||
@@ -20,12 +12,13 @@ type TemplateFacetRow = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function loadTemplateFacets() {
|
async function loadTemplateFacets() {
|
||||||
const raw = await readFile(TEMPLATE_FACET_FILE, "utf8");
|
const templateFacetFile = templateFacetJsonPath();
|
||||||
|
const raw = await readFile(templateFacetFile, "utf8");
|
||||||
const parsed = JSON.parse(raw) as unknown;
|
const parsed = JSON.parse(raw) as unknown;
|
||||||
const result = templateFacetFileSchema.safeParse(parsed);
|
const result = templateFacetFileSchema.safeParse(parsed);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid template facet file ${TEMPLATE_FACET_FILE}: ${JSON.stringify(
|
`Invalid template facet file ${templateFacetFile}: ${JSON.stringify(
|
||||||
result.error.flatten(),
|
result.error.flatten(),
|
||||||
null,
|
null,
|
||||||
2,
|
2,
|
||||||
|
|||||||
Reference in New Issue
Block a user