All changes
This commit is contained in:
parent
593f380581
commit
49f7ec2f5a
60 changed files with 1783 additions and 679 deletions
|
|
@ -50,6 +50,10 @@ struct Field {
|
|||
max_size: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
mime_types: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
on_create: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
on_update: Option<bool>,
|
||||
}
|
||||
|
||||
impl Field {
|
||||
|
|
@ -62,6 +66,8 @@ impl Field {
|
|||
collection_id: None,
|
||||
max_size: None,
|
||||
mime_types: None,
|
||||
on_create: None,
|
||||
on_update: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +80,8 @@ impl Field {
|
|||
collection_id: None,
|
||||
max_size: Some(10 * 1024 * 1024), // 10 MB
|
||||
mime_types: Some(mime_types.into_iter().map(String::from).collect()),
|
||||
on_create: None,
|
||||
on_update: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +94,22 @@ impl Field {
|
|||
collection_id: Some(collection_id.to_string()),
|
||||
max_size: None,
|
||||
mime_types: None,
|
||||
on_create: None,
|
||||
on_update: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn autodate(name: &str, on_create: bool, on_update: bool) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
r#type: "autodate".to_string(),
|
||||
required: None,
|
||||
max_select: None,
|
||||
collection_id: None,
|
||||
max_size: None,
|
||||
mime_types: None,
|
||||
on_create: Some(on_create),
|
||||
on_update: Some(on_update),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -146,7 +170,7 @@ async fn create_collection(
|
|||
) -> anyhow::Result<()> {
|
||||
let name = collection.name.clone();
|
||||
let resp = client
|
||||
.post(&format!("{base_url}/api/collections"))
|
||||
.post(format!("{base_url}/api/collections"))
|
||||
.header("Authorization", format!("Bearer {token}"))
|
||||
.json(&collection)
|
||||
.send()
|
||||
|
|
@ -262,13 +286,14 @@ async fn ensure_user_fields(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure the `saved_searches` collection has API rules allowing users to manage their own records.
|
||||
async fn ensure_saved_searches_rules(
|
||||
/// Ensure a collection has API rules allowing users to manage their own records.
|
||||
async fn ensure_user_owned_rules(
|
||||
client: &Client,
|
||||
base_url: &str,
|
||||
token: &str,
|
||||
collection_name: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let url = format!("{base_url}/api/collections/saved_searches");
|
||||
let url = format!("{base_url}/api/collections/{collection_name}");
|
||||
let user_only = "user = @request.auth.id";
|
||||
let resp = client
|
||||
.patch(&url)
|
||||
|
|
@ -286,10 +311,89 @@ async fn ensure_saved_searches_rules(
|
|||
if !resp.status().is_success() {
|
||||
let status = resp.status();
|
||||
let text = resp.text().await.unwrap_or_default();
|
||||
anyhow::bail!("Failed to update saved_searches API rules ({status}): {text}");
|
||||
anyhow::bail!("Failed to update {collection_name} API rules ({status}): {text}");
|
||||
}
|
||||
|
||||
info!("PocketBase collection 'saved_searches' API rules updated");
|
||||
info!("PocketBase collection '{collection_name}' API rules updated");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure the `saved_searches` collection has API rules allowing users to manage their own records.
|
||||
async fn ensure_saved_searches_rules(
|
||||
client: &Client,
|
||||
base_url: &str,
|
||||
token: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
ensure_user_owned_rules(client, base_url, token, "saved_searches").await
|
||||
}
|
||||
|
||||
/// Ensure a collection has `created` and `updated` autodate fields.
|
||||
/// PocketBase 0.23+ no longer adds these automatically — they must be explicit.
|
||||
async fn ensure_autodate_fields(
|
||||
client: &Client,
|
||||
base_url: &str,
|
||||
token: &str,
|
||||
collection_name: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let url = format!("{base_url}/api/collections/{collection_name}");
|
||||
let resp = client
|
||||
.get(&url)
|
||||
.header("Authorization", format!("Bearer {token}"))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
let status = resp.status();
|
||||
let text = resp.text().await.unwrap_or_default();
|
||||
anyhow::bail!("Failed to fetch {collection_name} collection ({status}): {text}");
|
||||
}
|
||||
|
||||
let body: serde_json::Value = resp.json().await?;
|
||||
let fields = body["fields"]
|
||||
.as_array()
|
||||
.ok_or_else(|| anyhow::anyhow!("{collection_name} collection has no fields array"))?;
|
||||
|
||||
let has_created = fields.iter().any(|f| f["name"] == "created");
|
||||
let has_updated = fields.iter().any(|f| f["name"] == "updated");
|
||||
|
||||
if has_created && has_updated {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut new_fields = fields.clone();
|
||||
|
||||
if !has_created {
|
||||
new_fields.push(serde_json::json!({
|
||||
"name": "created",
|
||||
"type": "autodate",
|
||||
"onCreate": true,
|
||||
"onUpdate": false,
|
||||
}));
|
||||
}
|
||||
|
||||
if !has_updated {
|
||||
new_fields.push(serde_json::json!({
|
||||
"name": "updated",
|
||||
"type": "autodate",
|
||||
"onCreate": true,
|
||||
"onUpdate": true,
|
||||
}));
|
||||
}
|
||||
|
||||
let patch_resp = client
|
||||
.patch(&url)
|
||||
.header("Authorization", format!("Bearer {token}"))
|
||||
.json(&serde_json::json!({ "fields": new_fields }))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !patch_resp.status().is_success() {
|
||||
let status = patch_resp.status();
|
||||
let text = patch_resp.text().await.unwrap_or_default();
|
||||
anyhow::bail!("Failed to add autodate fields to {collection_name} ({status}): {text}");
|
||||
}
|
||||
|
||||
info!("Added created/updated autodate fields to PocketBase collection '{collection_name}'");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -324,6 +428,8 @@ pub async fn ensure_collections(
|
|||
Field::text("name", true),
|
||||
Field::text("params", true),
|
||||
Field::file("screenshot", vec!["image/png", "image/jpeg", "image/webp"]),
|
||||
Field::autodate("created", true, false),
|
||||
Field::autodate("updated", true, true),
|
||||
],
|
||||
list_rule: user_only.clone(),
|
||||
view_rule: user_only.clone(),
|
||||
|
|
@ -335,6 +441,38 @@ pub async fn ensure_collections(
|
|||
.await?;
|
||||
} else {
|
||||
ensure_saved_searches_rules(client, base_url, &token).await?;
|
||||
ensure_autodate_fields(client, base_url, &token, "saved_searches").await?;
|
||||
}
|
||||
|
||||
if !existing.iter().any(|n| n == "saved_properties") {
|
||||
let users_id = find_users_collection_id(client, base_url, &token).await?;
|
||||
let user_only = Some("user = @request.auth.id".to_string());
|
||||
create_collection(
|
||||
client,
|
||||
base_url,
|
||||
&token,
|
||||
CreateCollection {
|
||||
name: "saved_properties".to_string(),
|
||||
r#type: "base".to_string(),
|
||||
fields: vec![
|
||||
Field::relation("user", &users_id),
|
||||
Field::text("address", true),
|
||||
Field::text("postcode", true),
|
||||
Field::text("data", false),
|
||||
Field::autodate("created", true, false),
|
||||
Field::autodate("updated", true, true),
|
||||
],
|
||||
list_rule: user_only.clone(),
|
||||
view_rule: user_only.clone(),
|
||||
create_rule: user_only.clone(),
|
||||
update_rule: user_only.clone(),
|
||||
delete_rule: user_only,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
ensure_user_owned_rules(client, base_url, &token, "saved_properties").await?;
|
||||
ensure_autodate_fields(client, base_url, &token, "saved_properties").await?;
|
||||
}
|
||||
|
||||
if !existing.iter().any(|n| n == "invites") {
|
||||
|
|
@ -351,6 +489,8 @@ pub async fn ensure_collections(
|
|||
Field::text("invite_type", true),
|
||||
Field::text("used_by_id", false),
|
||||
Field::text("used_at", false),
|
||||
Field::autodate("created", true, false),
|
||||
Field::autodate("updated", true, true),
|
||||
],
|
||||
list_rule: None,
|
||||
view_rule: None,
|
||||
|
|
@ -361,7 +501,7 @@ pub async fn ensure_collections(
|
|||
)
|
||||
.await?;
|
||||
} else {
|
||||
info!("PocketBase collection 'invites' already exists");
|
||||
ensure_autodate_fields(client, base_url, &token, "invites").await?;
|
||||
}
|
||||
|
||||
if !existing.iter().any(|n| n == "short_urls") {
|
||||
|
|
@ -375,6 +515,8 @@ pub async fn ensure_collections(
|
|||
fields: vec![
|
||||
Field::text("code", true),
|
||||
Field::text("params", true),
|
||||
Field::autodate("created", true, false),
|
||||
Field::autodate("updated", true, true),
|
||||
],
|
||||
list_rule: None,
|
||||
view_rule: None,
|
||||
|
|
@ -385,7 +527,7 @@ pub async fn ensure_collections(
|
|||
)
|
||||
.await?;
|
||||
} else {
|
||||
info!("PocketBase collection 'short_urls' already exists");
|
||||
ensure_autodate_fields(client, base_url, &token, "short_urls").await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue