This commit is contained in:
JCEEE
2026-09-14 15:36:55 +01:00
37 changed files with 2396 additions and 464 deletions
+213 -157
View File
@@ -61,11 +61,19 @@ export function jsonField(name: string): FieldDef {
return { name, type: "json" };
}
export function select(name: string, values: string[], required = false): FieldDef {
export function select(
name: string,
values: string[],
required = false,
): FieldDef {
return { name, type: "select", required, values, maxSelect: 1 };
}
export function rel(name: string, collectionId: string, required = false): FieldDef {
export function rel(
name: string,
collectionId: string,
required = false,
): FieldDef {
return {
name,
type: "relation",
@@ -151,65 +159,85 @@ export const SCHEMA_PLAN: CollectionPlanEntry[] = [
text("accessCodeId"),
date("accessCodeEnteredAt"),
],
{ listRule: RULE_OWN_FAM, viewRule: RULE_OWN_FAM, updateRule: RULE_OWN_FAM },
{
listRule: RULE_OWN_FAM,
viewRule: RULE_OWN_FAM,
updateRule: RULE_OWN_FAM,
},
)(ids),
},
{
name: "bonus_templates",
build: (ids) =>
col("bonus_templates", [
rel("famId", ids.fams, false),
text("name", true),
text("description"),
select("target", ["individual", "competitive", "collaborative"], true),
select("type", ["threshold", "count", "manual"], true),
select("thresholdType", ["points", "percent"], false),
select("occurrence", ["recurring", "once"], true),
select("rewardType", ["points", "cash", "prize"], true),
text("rewardValue", false),
number("criteriaValue"),
select("period", ["schedule", "daily", "weekly", "monthly"]),
bool("isPocketMoney"),
bool("global"),
text("icon"),
text("color"),
], {
listRule: "global = true || famId = @request.auth.famId",
viewRule: "global = true || famId = @request.auth.famId",
createRule: RULE_PARENT_WRITE,
updateRule: RULE_PARENT_SCOPED,
deleteRule: RULE_PARENT_SCOPED,
})(ids),
col(
"bonus_templates",
[
rel("famId", ids.fams, false),
text("name", true),
text("description"),
select(
"target",
["individual", "competitive", "collaborative"],
true,
),
select("type", ["threshold", "count", "manual"], true),
select("thresholdType", ["points", "percent"], false),
select("occurrence", ["recurring", "once"], true),
select("rewardType", ["points", "cash", "prize"], true),
text("rewardValue", false),
number("criteriaValue"),
select("period", ["schedule", "daily", "weekly", "monthly"]),
bool("isPocketMoney"),
bool("global"),
text("icon"),
text("color"),
],
{
listRule: "global = true || famId = @request.auth.famId",
viewRule: "global = true || famId = @request.auth.famId",
createRule: RULE_PARENT_WRITE,
updateRule: RULE_PARENT_SCOPED,
deleteRule: RULE_PARENT_SCOPED,
},
)(ids),
},
{
name: "settings",
build: (ids) =>
col("settings", [rel("famId", ids.fams, true), text("webhookUrl")], {
createRule: RULE_PARENT_WRITE,
updateRule: RULE_PARENT_SCOPED,
deleteRule: RULE_PARENT_SCOPED,
})(ids),
col(
"settings",
[rel("famId", ids.fams, true), text("webhookUrl"), number("lockMins")],
{
createRule: RULE_PARENT_WRITE,
updateRule: RULE_PARENT_SCOPED,
deleteRule: RULE_PARENT_SCOPED,
},
)(ids),
},
{
name: "chore_templates",
build: (ids) =>
col("chore_templates", [
rel("famId", ids.fams, false),
text("name", true),
text("description"),
select("defaultFrequency", ["daily", "weekly"], true),
select("defaultType", ["points", "money"], true),
number("defaultValue", true),
bool("global"),
text("icon"),
text("color"),
], {
listRule: "global = true || famId = @request.auth.famId",
viewRule: "global = true || famId = @request.auth.famId",
createRule: RULE_PARENT_WRITE,
updateRule: RULE_PARENT_SCOPED,
deleteRule: RULE_PARENT_SCOPED,
})(ids),
col(
"chore_templates",
[
rel("famId", ids.fams, false),
text("name", true),
text("description"),
select("defaultFrequency", ["daily", "weekly"], true),
select("defaultType", ["points", "money"], true),
number("defaultValue", true),
bool("global"),
text("icon"),
text("color"),
],
{
listRule: "global = true || famId = @request.auth.famId",
viewRule: "global = true || famId = @request.auth.famId",
createRule: RULE_PARENT_WRITE,
updateRule: RULE_PARENT_SCOPED,
deleteRule: RULE_PARENT_SCOPED,
},
)(ids),
},
{
name: "bonus_configs",
@@ -251,138 +279,166 @@ export const SCHEMA_PLAN: CollectionPlanEntry[] = [
{
name: "weekly_history",
build: (ids) =>
col("weekly_history", [
rel("famId", ids.fams, true),
rel("memberId", ids.users, true),
date("weekStart"),
number("pointsEarned"),
number("moneyEarned"),
number("choresCompleted"),
number("bonusEarned"),
], {
createRule: RULE_PARENT_WRITE,
updateRule: RULE_PARENT_SCOPED,
deleteRule: RULE_PARENT_SCOPED,
})(ids),
col(
"weekly_history",
[
rel("famId", ids.fams, true),
rel("memberId", ids.users, true),
date("weekStart"),
number("pointsEarned"),
number("moneyEarned"),
number("choresCompleted"),
number("bonusEarned"),
],
{
createRule: RULE_PARENT_WRITE,
updateRule: RULE_PARENT_SCOPED,
deleteRule: RULE_PARENT_SCOPED,
},
)(ids),
},
{
name: "seasons",
build: (ids) =>
col("seasons", [
rel("famId", ids.fams, true),
text("name", true),
text("color"),
bool("active"),
date("autoDisable"),
date("autoStart"),
], {
createRule: RULE_PARENT_WRITE,
updateRule: RULE_PARENT_SCOPED,
deleteRule: RULE_PARENT_SCOPED,
})(ids),
col(
"seasons",
[
rel("famId", ids.fams, true),
text("name", true),
text("color"),
bool("active"),
date("autoDisable"),
date("autoStart"),
],
{
createRule: RULE_PARENT_WRITE,
updateRule: RULE_PARENT_SCOPED,
deleteRule: RULE_PARENT_SCOPED,
},
)(ids),
},
{
name: "messages",
build: (ids) =>
col("messages", [
rel("famId", ids.fams, true),
select("authorType", ["admin", "member"], true),
text("authorId", true),
text("authorName", true),
text("authorColor"),
text("content", true),
// Explicit createdAt: PB 0.39 does NOT auto-add createdAt to
// API-created collections (0.25 did). Chat filters/sorts on it.
date("createdAt"),
text("clientId"),
], {
createRule: RULE_FAM_WRITE,
updateRule: RULE_FAM_SCOPED,
deleteRule: RULE_FAM_SCOPED,
listRule: RULE_FAM_READ,
viewRule: RULE_FAM_READ,
})(ids),
col(
"messages",
[
rel("famId", ids.fams, true),
select("authorType", ["admin", "member"], true),
text("authorId", true),
text("authorName", true),
text("authorColor"),
text("content", true),
// Explicit createdAt: PB 0.39 does NOT auto-add createdAt to
// API-created collections (0.25 did). Chat filters/sorts on it.
date("createdAt"),
text("clientId"),
],
{
createRule: RULE_FAM_WRITE,
updateRule: RULE_FAM_SCOPED,
deleteRule: RULE_FAM_SCOPED,
listRule: RULE_FAM_READ,
viewRule: RULE_FAM_READ,
},
)(ids),
},
{
name: "chat_typing",
build: (ids) =>
col("chat_typing", [
rel("famId", ids.fams, true),
text("actorId", true),
select("actorType", ["admin", "member"], true),
text("authorName", true),
text("authorColor"),
bool("typing"),
], {
createRule: RULE_FAM_WRITE,
updateRule: RULE_FAM_SCOPED,
deleteRule: RULE_FAM_SCOPED,
listRule: RULE_FAM_READ,
viewRule: RULE_FAM_READ,
})(ids),
col(
"chat_typing",
[
rel("famId", ids.fams, true),
text("actorId", true),
select("actorType", ["admin", "member"], true),
text("authorName", true),
text("authorColor"),
bool("typing"),
],
{
createRule: RULE_FAM_WRITE,
updateRule: RULE_FAM_SCOPED,
deleteRule: RULE_FAM_SCOPED,
listRule: RULE_FAM_READ,
viewRule: RULE_FAM_READ,
},
)(ids),
},
{
name: "rewards",
build: (ids) =>
col("rewards", [
rel("famId", ids.fams, true),
rel("memberId", ids.users, true),
rel("bonusConfigId", ids.bonus_configs),
text("label", true),
number("value", true),
select("rewardType", ["cash", "prize", "points"], true),
select("status", ["unclaimed", "requested", "claimed"], true),
select("claimable", ["immediate", "payday"], false),
text("settleDate"),
date("claimedAt"),
date("requestedAt"),
text("date"),
], {
createRule: RULE_FAM_WRITE,
updateRule: RULE_FAM_SCOPED,
deleteRule: RULE_FAM_SCOPED,
})(ids),
col(
"rewards",
[
rel("famId", ids.fams, true),
rel("memberId", ids.users, true),
rel("bonusConfigId", ids.bonus_configs),
text("label", true),
number("value", true),
select("rewardType", ["cash", "prize", "points"], true),
select("status", ["unclaimed", "requested", "claimed"], true),
select("claimable", ["immediate", "payday"], false),
text("settleDate"),
date("claimedAt"),
date("requestedAt"),
text("date"),
],
{
createRule: RULE_FAM_WRITE,
updateRule: RULE_FAM_SCOPED,
deleteRule: RULE_FAM_SCOPED,
},
)(ids),
},
{
name: "assigned_chores",
build: (ids) =>
col("assigned_chores", [
rel("famId", ids.fams, true),
rel("memberId", ids.users, false), // null = shared with all members
bool("shared"), // true = initially shared (tracked for revoke)
rel("templateId", ids.chore_templates),
select("frequency", ["daily", "weekly"], true),
select("type", ["points", "money", "emoji"], true),
number("value", false),
text("customName"),
text("description"),
text("icon"),
text("color"),
text("emoji"),
jsonField("seasonIds"),
bool("isTodo"),
text("startDate"),
text("completeBy"),
], {
createRule: RULE_PARENT_WRITE,
updateRule: RULE_PARENT_SCOPED,
deleteRule: RULE_PARENT_SCOPED,
})(ids),
col(
"assigned_chores",
[
rel("famId", ids.fams, true),
rel("memberId", ids.users, false), // null = shared with all members
bool("shared"), // true = initially shared (tracked for revoke)
rel("templateId", ids.chore_templates),
select("frequency", ["daily", "weekly"], true),
select("type", ["points", "money", "emoji"], true),
number("value", false),
text("customName"),
text("description"),
text("icon"),
text("color"),
text("emoji"),
jsonField("seasonIds"),
bool("isTodo"),
text("startDate"),
text("completeBy"),
],
{
createRule: RULE_PARENT_WRITE,
updateRule: RULE_PARENT_SCOPED,
deleteRule: RULE_PARENT_SCOPED,
},
)(ids),
},
{
name: "completions",
build: (ids) =>
col("completions", [
rel("famId", ids.fams, true),
rel("memberId", ids.users, true),
rel("assignedChoreId", ids.assigned_chores, true),
date("date"),
date("completedAt"),
text("rewardId"),
], {
createRule: RULE_FAM_WRITE,
updateRule: RULE_FAM_SCOPED,
deleteRule: RULE_FAM_SCOPED,
})(ids),
col(
"completions",
[
rel("famId", ids.fams, true),
rel("memberId", ids.users, true),
rel("assignedChoreId", ids.assigned_chores, true),
date("date"),
date("completedAt"),
text("rewardId"),
],
{
createRule: RULE_FAM_WRITE,
updateRule: RULE_FAM_SCOPED,
deleteRule: RULE_FAM_SCOPED,
},
)(ids),
},
];