feat(web): add PocketBase backend with migrations and database

This commit is contained in:
Thomas Camlong
2025-10-01 15:47:15 +02:00
parent a93034d5b5
commit b8920b912a
39 changed files with 24653 additions and 0 deletions

0
web/backend/.gitkeep Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
web/backend/pb_data/data.db Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

View File

@@ -0,0 +1 @@
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/webp","user.metadata":{"original-filename":"image.webp"},"md5":"4vuiFJkERotZ//rKQ1NLug=="}

View File

@@ -0,0 +1 @@
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/webp","user.metadata":null,"md5":"D9i8C8bRgTIApmXQEVBQMQ=="}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -0,0 +1 @@
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":{"original-filename":"CleanShot 2025-09-18 at 13.32.55@2x.png"},"md5":"/0+nmrAe0HXihDQ/VrDz0g=="}

View File

@@ -0,0 +1 @@
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/png","user.metadata":null,"md5":"M/Mn43nnA2xzo/0+DPdHvw=="}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 82.9 512 346.2"><path d="M169.3 102.7c28.8 0 52.2 23.4 52.2 52.2v66.8c1.8-.6 3.6-1 5.5-1 5.9 0 11.1 2.9 14.2 7.4v-73.2c0-39.7-32.3-72-72-72-24.8 0-46.8 12.6-59.7 31.8 5.6 3.6 10.9 7.5 16 11.8 9.4-14.3 25.5-23.8 43.8-23.8m115.6 118.1c1.9 0 3.8.4 5.5 1V155c0-28.8 23.4-52.2 52.2-52.2 18.3 0 34.4 9.5 43.8 23.8 5.1-4.2 10.4-8.2 16-11.8C389.5 95.6 367.5 83 342.7 83c-39.7 0-72 32.3-72 72v73.2c3.1-4.5 8.3-7.4 14.2-7.4m-69.3 104.8c-35.7 8.6-66 28.1-88.1 54-12.2 14.3-21.9 30.6-28.6 48l46.6.3 265.5 1.2v-.1c-29.2-77.8-112.5-123.4-195.4-103.4m11.5-61.9c-9.7 0-17.5 7.8-17.5 17.5s7.8 17.5 17.5 17.5 17.5-7.8 17.5-17.5-7.8-17.5-17.5-17.5m57.8 35.1c9.7 0 17.5-7.8 17.5-17.5s-7.8-17.5-17.5-17.5-17.5 7.8-17.5 17.5 7.8 17.5 17.5 17.5m162.5-75.6 26.7-108.8c-22.6 2.8-43.5 11.1-61.5 23.4-6.3 4.3-12.3 9.2-17.8 14.4-26.4 25.4-42.9 61.1-42.9 100.6 0 30.7 9.9 59.1 26.7 82.1 9.2 12.7 20.6 23.7 33.4 32.6l9.3-37.8c47.9-14.3 84.1-55.7 90.7-106.5zM133.5 334.9c16.8-23 26.7-51.4 26.7-82.1 0-39.5-16.5-75.2-42.9-100.6-5.5-5.3-11.5-10.1-17.8-14.4-17.9-12.3-38.8-20.6-61.5-23.4l26.7 108.8H0c6.6 50.8 42.8 92.2 90.7 106.5l9.3 37.8c12.9-8.9 24.2-19.9 33.5-32.6" style="fill:#fa5252"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/svg+xml","user.metadata":{"original-filename":"homarr-icon.svg"},"md5":"ROY4BuPSLxEW+8PvpZxvew=="}

23816
web/backend/pb_data/types.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,99 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"indexes": [
"CREATE UNIQUE INDEX `idx_tokenKey__pb_users_auth_` ON `users` (`tokenKey`)",
"CREATE UNIQUE INDEX `idx_email__pb_users_auth_` ON `users` (`username`) WHERE `email` != ''",
"CREATE UNIQUE INDEX `idx_xcjpYhojHH` ON `users` (`email`)"
],
"oauth2": {
"mappedFields": {
"name": ""
}
}
}, collection)
// update field
collection.fields.addAt(1, new Field({
"cost": 0,
"hidden": true,
"id": "password901924565",
"max": 0,
"min": 5,
"name": "password",
"pattern": "",
"presentable": false,
"required": true,
"system": true,
"type": "password"
}))
// update field
collection.fields.addAt(3, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 255,
"min": 5,
"name": "username",
"pattern": "",
"presentable": true,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"indexes": [
"CREATE UNIQUE INDEX `idx_tokenKey__pb_users_auth_` ON `users` (`tokenKey`)",
"CREATE UNIQUE INDEX `idx_email__pb_users_auth_` ON `users` (`email`) WHERE `email` != ''"
],
"oauth2": {
"mappedFields": {
"name": "name"
}
}
}, collection)
// update field
collection.fields.addAt(1, new Field({
"cost": 0,
"hidden": true,
"id": "password901924565",
"max": 0,
"min": 8,
"name": "password",
"pattern": "",
"presentable": false,
"required": true,
"system": true,
"type": "password"
}))
// update field
collection.fields.addAt(6, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 255,
"min": 0,
"name": "name",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
return app.save(collection)
})

View File

@@ -0,0 +1,36 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update field
collection.fields.addAt(4, new Field({
"exceptDomains": null,
"hidden": false,
"id": "email3885137012",
"name": "email",
"onlyDomains": null,
"presentable": false,
"required": false,
"system": true,
"type": "email"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update field
collection.fields.addAt(4, new Field({
"exceptDomains": null,
"hidden": false,
"id": "email3885137012",
"name": "email",
"onlyDomains": null,
"presentable": false,
"required": true,
"system": true,
"type": "email"
}))
return app.save(collection)
})

View File

@@ -0,0 +1,34 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"authAlert": {
"enabled": false
},
"passwordAuth": {
"identityFields": [
"username"
]
}
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"authAlert": {
"enabled": true
},
"passwordAuth": {
"identityFields": [
"email"
]
}
}, collection)
return app.save(collection)
})

View File

@@ -0,0 +1,146 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = new Collection({
"createRule": "@request.auth.id = created_by.id || @request.auth.admin = true",
"deleteRule": "@request.auth.id = created_by.id || @request.auth.admin = true",
"fields": [
{
"autogeneratePattern": "[a-z0-9]{15}",
"hidden": false,
"id": "text3208210256",
"max": 15,
"min": 15,
"name": "id",
"pattern": "^[a-z0-9]+$",
"presentable": false,
"primaryKey": true,
"required": true,
"system": true,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1579384326",
"max": 128,
"min": 0,
"name": "name",
"pattern": "",
"presentable": true,
"primaryKey": false,
"required": true,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "file2043772302",
"maxSelect": 99,
"maxSize": 0,
"mimeTypes": [
"image/png",
"image/svg+xml"
],
"name": "assets",
"presentable": false,
"protected": false,
"required": true,
"system": false,
"thumbs": [],
"type": "file"
},
{
"cascadeDelete": false,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "relation3725765462",
"maxSelect": 1,
"minSelect": 0,
"name": "created_by",
"presentable": true,
"required": true,
"system": false,
"type": "relation"
},
{
"hidden": false,
"id": "select2063623452",
"maxSelect": 1,
"name": "status",
"presentable": true,
"required": true,
"system": false,
"type": "select",
"values": [
"waiting_approval",
"approved",
"refused"
]
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text1595063097",
"max": 0,
"min": 0,
"name": "aliases",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"autogeneratePattern": "",
"hidden": false,
"id": "text989021800",
"max": 0,
"min": 0,
"name": "categories",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
},
{
"hidden": false,
"id": "autodate2990389176",
"name": "created",
"onCreate": true,
"onUpdate": false,
"presentable": false,
"system": false,
"type": "autodate"
},
{
"hidden": false,
"id": "autodate3332085495",
"name": "updated",
"onCreate": true,
"onUpdate": true,
"presentable": false,
"system": false,
"type": "autodate"
}
],
"id": "pbc_632646243",
"indexes": [
"CREATE UNIQUE INDEX `idx_vB4iK1BfdV` ON `submission` (`name`)"
],
"listRule": "@request.auth.id = created_by.id || @request.auth.admin = true",
"name": "submission",
"system": false,
"type": "base",
"updateRule": "@request.auth.id = created_by.id || @request.auth.admin = true",
"viewRule": "@request.auth.id = created_by.id || @request.auth.admin = true"
});
return app.save(collection);
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_632646243");
return app.delete(collection);
})

View File

@@ -0,0 +1,34 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"updateRule": "id = @request.auth.id && (@request.body.admin = null || @request.body.admin = admin)"
}, collection)
// add field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "bool2282622326",
"name": "admin",
"presentable": true,
"required": false,
"system": false,
"type": "bool"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"updateRule": "id = @request.auth.id"
}, collection)
// remove field
collection.fields.removeById("bool2282622326")
return app.save(collection)
})

View File

@@ -0,0 +1,32 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "bool2282622326",
"name": "admin",
"presentable": true,
"required": true,
"system": false,
"type": "bool"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "bool2282622326",
"name": "admin",
"presentable": true,
"required": false,
"system": false,
"type": "bool"
}))
return app.save(collection)
})

View File

@@ -0,0 +1,32 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "bool2282622326",
"name": "admin",
"presentable": true,
"required": false,
"system": false,
"type": "bool"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update field
collection.fields.addAt(8, new Field({
"hidden": false,
"id": "bool2282622326",
"name": "admin",
"presentable": true,
"required": true,
"system": false,
"type": "bool"
}))
return app.save(collection)
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_632646243")
// add field
collection.fields.addAt(5, new Field({
"cascadeDelete": true,
"collectionId": "_pb_users_auth_",
"hidden": false,
"id": "relation1319357245",
"maxSelect": 1,
"minSelect": 0,
"name": "approved_by",
"presentable": true,
"required": false,
"system": false,
"type": "relation"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_632646243")
// remove field
collection.fields.removeById("relation1319357245")
return app.save(collection)
})

View File

@@ -0,0 +1,32 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update field
collection.fields.addAt(4, new Field({
"hidden": false,
"id": "bool2282622326",
"name": "admin",
"presentable": false,
"required": false,
"system": false,
"type": "bool"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update field
collection.fields.addAt(4, new Field({
"hidden": false,
"id": "bool2282622326",
"name": "admin",
"presentable": true,
"required": false,
"system": false,
"type": "bool"
}))
return app.save(collection)
})

View File

@@ -0,0 +1,28 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"indexes": [
"CREATE UNIQUE INDEX `idx_tokenKey__pb_users_auth_` ON `users` (`tokenKey`)",
"CREATE UNIQUE INDEX `idx_email__pb_users_auth_` ON `users` (`username`) WHERE `email` != ''",
"CREATE UNIQUE INDEX `idx_email_3ug5rzmspg` ON `test` (`email`) WHERE `email` != ''"
]
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"indexes": [
"CREATE UNIQUE INDEX `idx_tokenKey__pb_users_auth_` ON `users` (`tokenKey`)",
"CREATE UNIQUE INDEX `idx_email__pb_users_auth_` ON `users` (`username`) WHERE `email` != ''",
"CREATE UNIQUE INDEX `idx_xcjpYhojHH` ON `users` (`email`)"
]
}, collection)
return app.save(collection)
})

View File

@@ -0,0 +1,26 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_632646243")
// update collection data
unmarshal({
"indexes": [
"CREATE UNIQUE INDEX `idx_vB4iK1BfdV` ON `submissions` (`name`)"
],
"name": "submissions"
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_632646243")
// update collection data
unmarshal({
"indexes": [
"CREATE UNIQUE INDEX `idx_vB4iK1BfdV` ON `submission` (`name`)"
],
"name": "submission"
}, collection)
return app.save(collection)
})

View File

@@ -0,0 +1,78 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_632646243")
// remove field
collection.fields.removeById("text1595063097")
// remove field
collection.fields.removeById("text989021800")
// add field
collection.fields.addAt(6, new Field({
"hidden": false,
"id": "json1595063097",
"maxSize": 0,
"name": "aliases",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}))
// add field
collection.fields.addAt(7, new Field({
"hidden": false,
"id": "json989021800",
"maxSize": 0,
"name": "categories",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_632646243")
// add field
collection.fields.addAt(6, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text1595063097",
"max": 0,
"min": 0,
"name": "aliases",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// add field
collection.fields.addAt(7, new Field({
"autogeneratePattern": "",
"hidden": false,
"id": "text989021800",
"max": 0,
"min": 0,
"name": "categories",
"pattern": "",
"presentable": false,
"primaryKey": false,
"required": false,
"system": false,
"type": "text"
}))
// remove field
collection.fields.removeById("json1595063097")
// remove field
collection.fields.removeById("json989021800")
return app.save(collection)
})

View File

@@ -0,0 +1,49 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_632646243")
// remove field
collection.fields.removeById("json989021800")
// update field
collection.fields.addAt(6, new Field({
"hidden": false,
"id": "json1595063097",
"maxSize": 0,
"name": "extras",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_632646243")
// add field
collection.fields.addAt(7, new Field({
"hidden": false,
"id": "json989021800",
"maxSize": 0,
"name": "categories",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}))
// update field
collection.fields.addAt(6, new Field({
"hidden": false,
"id": "json1595063097",
"maxSize": 0,
"name": "aliases",
"presentable": false,
"required": false,
"system": false,
"type": "json"
}))
return app.save(collection)
})

View File

@@ -0,0 +1,22 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"createRule": "@request.body.admin = false || @request.body.admin = null",
"updateRule": "id = @request.auth.id && (@request.body.admin = false || @request.body.admin = admin)"
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"createRule": "",
"updateRule": "id = @request.auth.id && (@request.body.admin = null || @request.body.admin = admin)"
}, collection)
return app.save(collection)
})

View File

@@ -0,0 +1,44 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("pbc_632646243")
// update field
collection.fields.addAt(4, new Field({
"hidden": false,
"id": "select2063623452",
"maxSelect": 1,
"name": "status",
"presentable": true,
"required": true,
"system": false,
"type": "select",
"values": [
"approved",
"refused",
"pending"
]
}))
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("pbc_632646243")
// update field
collection.fields.addAt(4, new Field({
"hidden": false,
"id": "select2063623452",
"maxSelect": 1,
"name": "status",
"presentable": true,
"required": true,
"system": false,
"type": "select",
"values": [
"waiting_approval",
"approved",
"refused"
]
}))
return app.save(collection)
})

View File

@@ -0,0 +1,29 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"passwordAuth": {
"identityFields": [
"username",
"email"
]
}
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"passwordAuth": {
"identityFields": [
"username"
]
}
}, collection)
return app.save(collection)
})

View File

@@ -0,0 +1,20 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"viewRule": "id = @request.auth.id || @request.body.admin = true"
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"viewRule": "id = @request.auth.id"
}, collection)
return app.save(collection)
})

View File

@@ -0,0 +1,20 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"listRule": "id = @request.auth.id || @request.body.admin = true"
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"listRule": "id = @request.auth.id"
}, collection)
return app.save(collection)
})

View File

@@ -0,0 +1,22 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"listRule": "id = @request.auth.id || @request.auth.admin = true",
"manageRule": "@request.auth.admin = true"
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"listRule": "id = @request.auth.id || @request.body.admin = true",
"manageRule": null
}, collection)
return app.save(collection)
})

View File

@@ -0,0 +1,20 @@
/// <reference path="../pb_data/types.d.ts" />
migrate((app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"viewRule": "id = @request.auth.id || @request.auth.admin = true"
}, collection)
return app.save(collection)
}, (app) => {
const collection = app.findCollectionByNameOrId("_pb_users_auth_")
// update collection data
unmarshal({
"viewRule": "id = @request.auth.id || @request.body.admin = true"
}, collection)
return app.save(collection)
})

BIN
web/backend/pocketbase Executable file

Binary file not shown.