add shared login pin mechansim
This commit is contained in:
+252
-188
@@ -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,236 +159,292 @@ 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",
|
||||
build: (ids) =>
|
||||
col("bonus_configs", [
|
||||
rel("famId", ids.fams, true),
|
||||
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"),
|
||||
rel("memberId", ids.users),
|
||||
select("period", ["schedule", "daily", "weekly", "monthly"]),
|
||||
select("status", ["active", "completed"], true),
|
||||
text("targetChoreId"),
|
||||
jsonField("targetChoreIds"),
|
||||
bool("isPocketMoney"),
|
||||
// Standalone/one-off reward progress window. `period` stays empty for
|
||||
// these (they're measured once, not per-cycle). `completeBy` bounds how
|
||||
// far progress counts: `unlimited` (cumulative since startDate),
|
||||
// `week` (end of current week), or `custom` (a specific completeByDate).
|
||||
// `startDate` is when tracking begins — without it "unlimited" would
|
||||
// count points earned before the reward existed.
|
||||
select("completeBy", ["unlimited", "week", "custom"]),
|
||||
text("startDate"),
|
||||
text("completeByDate"),
|
||||
], {
|
||||
createRule: RULE_PARENT_WRITE,
|
||||
updateRule: RULE_PARENT_SCOPED,
|
||||
deleteRule: RULE_PARENT_SCOPED,
|
||||
})(ids),
|
||||
col(
|
||||
"bonus_configs",
|
||||
[
|
||||
rel("famId", ids.fams, true),
|
||||
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"),
|
||||
rel("memberId", ids.users),
|
||||
select("period", ["schedule", "daily", "weekly", "monthly"]),
|
||||
select("status", ["active", "completed"], true),
|
||||
text("targetChoreId"),
|
||||
jsonField("targetChoreIds"),
|
||||
bool("isPocketMoney"),
|
||||
// Standalone/one-off reward progress window. `period` stays empty for
|
||||
// these (they're measured once, not per-cycle). `completeBy` bounds how
|
||||
// far progress counts: `unlimited` (cumulative since startDate),
|
||||
// `week` (end of current week), or `custom` (a specific completeByDate).
|
||||
// `startDate` is when tracking begins — without it "unlimited" would
|
||||
// count points earned before the reward existed.
|
||||
select("completeBy", ["unlimited", "week", "custom"]),
|
||||
text("startDate"),
|
||||
text("completeByDate"),
|
||||
],
|
||||
{
|
||||
createRule: RULE_PARENT_WRITE,
|
||||
updateRule: RULE_PARENT_SCOPED,
|
||||
deleteRule: RULE_PARENT_SCOPED,
|
||||
},
|
||||
)(ids),
|
||||
},
|
||||
{
|
||||
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),
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user