Add API rules setup to database initialization
All checks were successful
Deploy / deploy (push) Successful in 2m29s
All checks were successful
Deploy / deploy (push) Successful in 2m29s
The period_logs collection was returning 403 errors because API rules were only configured in the e2e test harness, not in the production setup script. This consolidates the setup logic so both prod and test use the same setupApiRules() function. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,9 +8,11 @@ import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import PocketBase from "pocketbase";
|
||||
import {
|
||||
addUserFields,
|
||||
createCollection,
|
||||
getExistingCollectionNames,
|
||||
getMissingCollections,
|
||||
setupApiRules,
|
||||
} from "../scripts/setup-db";
|
||||
|
||||
/**
|
||||
@@ -172,77 +174,6 @@ async function createAdminUser(
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds custom fields to the users collection.
|
||||
*/
|
||||
async function addUserFields(pb: PocketBase): Promise<void> {
|
||||
const usersCollection = await pb.collections.getOne("users");
|
||||
|
||||
// Define the custom user fields
|
||||
const customFields = [
|
||||
{ name: "garminConnected", type: "bool" },
|
||||
{ name: "garminOauth1Token", type: "text" },
|
||||
{ name: "garminOauth2Token", type: "text" },
|
||||
{ name: "garminTokenExpiresAt", type: "date" },
|
||||
{ name: "calendarToken", type: "text" },
|
||||
{ name: "lastPeriodDate", type: "date" },
|
||||
{ name: "cycleLength", type: "number" },
|
||||
{ name: "notificationTime", type: "text" },
|
||||
{ name: "timezone", type: "text" },
|
||||
{ name: "activeOverrides", type: "json" },
|
||||
];
|
||||
|
||||
// Get existing field names
|
||||
const existingFieldNames = new Set(
|
||||
(usersCollection.fields || []).map((f: { name: string }) => f.name),
|
||||
);
|
||||
|
||||
// Filter to only new fields
|
||||
const newFields = customFields.filter((f) => !existingFieldNames.has(f.name));
|
||||
|
||||
if (newFields.length > 0) {
|
||||
// Combine existing fields with new ones
|
||||
const allFields = [...(usersCollection.fields || []), ...newFields];
|
||||
|
||||
await pb.collections.update(usersCollection.id, {
|
||||
fields: allFields,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up API rules for collections to allow user access.
|
||||
*/
|
||||
async function setupApiRules(pb: PocketBase): Promise<void> {
|
||||
// Allow users to update their own user record
|
||||
// viewRule allows reading user records by ID (needed for ICS calendar feed)
|
||||
const usersCollection = await pb.collections.getOne("users");
|
||||
await pb.collections.update(usersCollection.id, {
|
||||
viewRule: "", // Empty string = allow all authenticated & unauthenticated reads
|
||||
updateRule: "id = @request.auth.id",
|
||||
});
|
||||
|
||||
// Allow users to read/write their own period_logs
|
||||
const periodLogs = await pb.collections.getOne("period_logs");
|
||||
await pb.collections.update(periodLogs.id, {
|
||||
listRule: "user = @request.auth.id",
|
||||
viewRule: "user = @request.auth.id",
|
||||
createRule: "user = @request.auth.id",
|
||||
updateRule: "user = @request.auth.id",
|
||||
deleteRule: "user = @request.auth.id",
|
||||
});
|
||||
|
||||
// Allow users to read/write their own dailyLogs
|
||||
const dailyLogs = await pb.collections.getOne("dailyLogs");
|
||||
await pb.collections.update(dailyLogs.id, {
|
||||
listRule: "user = @request.auth.id",
|
||||
viewRule: "user = @request.auth.id",
|
||||
createRule: "user = @request.auth.id",
|
||||
updateRule: "user = @request.auth.id",
|
||||
deleteRule: "user = @request.auth.id",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the database collections using the SDK.
|
||||
*/
|
||||
|
||||
@@ -181,3 +181,51 @@ describe("USER_CUSTOM_FIELDS garmin token max lengths", () => {
|
||||
expect(oauth1Field?.max).toBeGreaterThanOrEqual(10000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("setupApiRules", () => {
|
||||
it("configures user-owned record rules for period_logs and dailyLogs", async () => {
|
||||
const { setupApiRules } = await import("./setup-db");
|
||||
|
||||
const updateMock = vi.fn().mockResolvedValue({});
|
||||
const mockPb = {
|
||||
collections: {
|
||||
getOne: vi.fn().mockImplementation((name: string) => {
|
||||
return Promise.resolve({ id: `${name}-id`, name });
|
||||
}),
|
||||
update: updateMock,
|
||||
},
|
||||
};
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: test mock
|
||||
await setupApiRules(mockPb as any);
|
||||
|
||||
// Should have called getOne for users, period_logs, and dailyLogs
|
||||
expect(mockPb.collections.getOne).toHaveBeenCalledWith("users");
|
||||
expect(mockPb.collections.getOne).toHaveBeenCalledWith("period_logs");
|
||||
expect(mockPb.collections.getOne).toHaveBeenCalledWith("dailyLogs");
|
||||
|
||||
// Check users collection rules
|
||||
expect(updateMock).toHaveBeenCalledWith("users-id", {
|
||||
viewRule: "",
|
||||
updateRule: "id = @request.auth.id",
|
||||
});
|
||||
|
||||
// Check period_logs collection rules
|
||||
expect(updateMock).toHaveBeenCalledWith("period_logs-id", {
|
||||
listRule: "user = @request.auth.id",
|
||||
viewRule: "user = @request.auth.id",
|
||||
createRule: "user = @request.auth.id",
|
||||
updateRule: "user = @request.auth.id",
|
||||
deleteRule: "user = @request.auth.id",
|
||||
});
|
||||
|
||||
// Check dailyLogs collection rules
|
||||
expect(updateMock).toHaveBeenCalledWith("dailyLogs-id", {
|
||||
listRule: "user = @request.auth.id",
|
||||
viewRule: "user = @request.auth.id",
|
||||
createRule: "user = @request.auth.id",
|
||||
updateRule: "user = @request.auth.id",
|
||||
deleteRule: "user = @request.auth.id",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -285,6 +285,40 @@ export async function createCollection(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up API rules for collections to allow user access.
|
||||
* Configures row-level security so users can only access their own records.
|
||||
*/
|
||||
export async function setupApiRules(pb: PocketBase): Promise<void> {
|
||||
// Allow users to view any user record (needed for ICS calendar feed)
|
||||
// and update only their own record
|
||||
const usersCollection = await pb.collections.getOne("users");
|
||||
await pb.collections.update(usersCollection.id, {
|
||||
viewRule: "",
|
||||
updateRule: "id = @request.auth.id",
|
||||
});
|
||||
|
||||
// Allow users to read/write their own period_logs
|
||||
const periodLogs = await pb.collections.getOne("period_logs");
|
||||
await pb.collections.update(periodLogs.id, {
|
||||
listRule: "user = @request.auth.id",
|
||||
viewRule: "user = @request.auth.id",
|
||||
createRule: "user = @request.auth.id",
|
||||
updateRule: "user = @request.auth.id",
|
||||
deleteRule: "user = @request.auth.id",
|
||||
});
|
||||
|
||||
// Allow users to read/write their own dailyLogs
|
||||
const dailyLogs = await pb.collections.getOne("dailyLogs");
|
||||
await pb.collections.update(dailyLogs.id, {
|
||||
listRule: "user = @request.auth.id",
|
||||
viewRule: "user = @request.auth.id",
|
||||
createRule: "user = @request.auth.id",
|
||||
updateRule: "user = @request.auth.id",
|
||||
deleteRule: "user = @request.auth.id",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Main setup function - creates missing collections.
|
||||
*/
|
||||
@@ -337,10 +371,8 @@ async function main(): Promise<void> {
|
||||
const missing = getMissingCollections(existingNames);
|
||||
|
||||
if (missing.length === 0) {
|
||||
console.log("All required collections already exist. Nothing to do.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("All required collections already exist.");
|
||||
} else {
|
||||
console.log(
|
||||
`Creating ${missing.length} missing collection(s):`,
|
||||
missing.map((c) => c.name),
|
||||
@@ -355,6 +387,12 @@ async function main(): Promise<void> {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up API rules for all collections
|
||||
console.log("Configuring API rules...");
|
||||
await setupApiRules(pb);
|
||||
console.log(" API rules configured.");
|
||||
|
||||
console.log("Database setup complete!");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user