Skip to content

Commit a130235

Browse files
committed
feat(files): transform file names to file urls
This commit adds built in support for files in PocketBase collections. The loader now automatically transforms file names retured by the PocketBase API to URLs where these files can be accessed. While testing this change, I found another bug in the incremental updates, where unchanged entries would not update the file URL with the new values, due to the same digest value. This is why the loader now forces a complete refresh when the version of this package is updated.
1 parent a0b5a6f commit a130235

File tree

4 files changed

+96
-2
lines changed

4 files changed

+96
-2
lines changed

src/generate-schema.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { PocketBaseCollection } from "./types/pocketbase-schema.type";
55
import { getRemoteSchema } from "./utils/get-remote-schema";
66
import { parseSchema } from "./utils/parse-schema";
77
import { readLocalSchema } from "./utils/read-local-schema";
8+
import { transformFiles } from "./utils/transform-files";
89

910
/**
1011
* Basic schema for every PocketBase collection.
@@ -84,8 +85,21 @@ export async function generateSchema(
8485
const base = collection.type === "view" ? VIEW_SCHEMA : BASIC_SCHEMA;
8586

8687
// Combine the basic schema with the parsed fields
87-
return z.object({
88+
const schema = z.object({
8889
...base,
8990
...fields
9091
});
92+
93+
// Get all file fields
94+
const fileFields = collection.schema.filter((field) => field.type === "file");
95+
96+
if (fileFields.length === 0) {
97+
return schema;
98+
}
99+
100+
// Transform file names to file urls
101+
return schema.transform((entry) =>
102+
// @ts-expect-error - `updated` and `created` are already transformed to dates
103+
transformFiles(options.url, fileFields, entry)
104+
);
91105
}

src/pocketbase-loader.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Loader, LoaderContext } from "astro/loaders";
2+
import packageJson from "./../package.json";
23
import { cleanupEntries } from "./cleanup-entries";
34
import { generateSchema } from "./generate-schema";
45
import { loadEntries } from "./load-entries";
@@ -14,6 +15,16 @@ export function pocketbaseLoader(options: PocketBaseLoaderOptions): Loader {
1415
return {
1516
name: "pocketbase-loader",
1617
load: async (context: LoaderContext): Promise<void> => {
18+
// Check if the version has changed to force an update
19+
const lastVersion = context.meta.get("version");
20+
if (lastVersion !== packageJson.version) {
21+
context.logger.info(
22+
`PocketBase loader was updated from ${lastVersion} to ${packageJson.version}. All entries will be loaded again.`
23+
);
24+
25+
options.forceUpdate = true;
26+
}
27+
1728
// Get the date of the last fetch to only update changed entries.
1829
// If `forceUpdate` is set to `true`, this will be `undefined` to fetch all entries again.
1930
const lastModified = options.forceUpdate
@@ -67,6 +78,8 @@ export function pocketbaseLoader(options: PocketBaseLoaderOptions): Loader {
6778

6879
// Set the last modified date to the current date
6980
context.meta.set("last-modified", new Date().toISOString());
81+
82+
context.meta.set("version", packageJson.version);
7083
},
7184
schema: async () => {
7285
// Generate the schema for the collection according to the API

src/utils/parse-schema.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ export function parseSchema(
4444
break;
4545
case "relation":
4646
case "file":
47-
// NOTE: Relations and files are currently not supported and are treated as strings
47+
// NOTE: Relations are currently not supported and are treated as strings
48+
// NOTE: Files are later transformed to URLs
4849

4950
// Parse the field type based on the number of values it can have
5051
fieldType = parseSingleOrMultipleValues(field, z.string());

src/utils/transform-files.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type { PocketBaseEntry } from "../types/pocketbase-entry.type";
2+
import type { PocketBaseSchemaEntry } from "../types/pocketbase-schema.type";
3+
4+
/**
5+
* Transforms file names in a PocketBase entry to file URLs.
6+
*
7+
* @param baseUrl URL of the PocketBase instance.
8+
* @param collection Collection of the entry.
9+
* @param entry Entry to transform.
10+
*/
11+
export function transformFiles(
12+
baseUrl: string,
13+
fileFields: Array<PocketBaseSchemaEntry>,
14+
entry: PocketBaseEntry
15+
): PocketBaseEntry {
16+
// Transform all file names to file URLs
17+
for (const field of fileFields) {
18+
const fieldName = field.name;
19+
20+
if (field.options.maxSelect === 1) {
21+
const fileName = entry[fieldName] as string | undefined;
22+
// Check if a file name is present
23+
if (!fileName) {
24+
continue;
25+
}
26+
27+
// Transform the file name to a file URL
28+
entry[fieldName] = transformFileUrl(
29+
baseUrl,
30+
entry.collectionName,
31+
entry.id,
32+
fileName
33+
);
34+
} else {
35+
const fileNames = entry[fieldName] as Array<string> | undefined;
36+
// Check if file names are present
37+
if (!fileNames) {
38+
continue;
39+
}
40+
41+
// Transform all file names to file URLs
42+
entry[fieldName] = fileNames.map((file) =>
43+
transformFileUrl(baseUrl, entry.collectionName, entry.id, file)
44+
);
45+
}
46+
}
47+
48+
return entry;
49+
}
50+
51+
/**
52+
* Transforms a file name to a PocketBase file URL.
53+
*
54+
* @param base Base URL of the PocketBase instance.
55+
* @param collectionName Name of the collection.
56+
* @param entryId ID of the entry.
57+
* @param file Name of the file.
58+
*/
59+
function transformFileUrl(
60+
base: string,
61+
collectionName: string,
62+
entryId: string,
63+
file: string
64+
): string {
65+
return `${base}/api/files/${collectionName}/${entryId}/${file}`;
66+
}

0 commit comments

Comments
 (0)