lmao
This commit is contained in:
parent
03445188ea
commit
524580eb25
102 changed files with 36625 additions and 1295 deletions
|
|
@ -1,6 +1,6 @@
|
|||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::info;
|
||||
use tracing::{info, warn};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AuthResponse {
|
||||
|
|
@ -79,7 +79,7 @@ impl Field {
|
|||
}
|
||||
}
|
||||
|
||||
async fn auth_superuser(
|
||||
pub async fn auth_superuser(
|
||||
client: &Client,
|
||||
base_url: &str,
|
||||
email: &str,
|
||||
|
|
@ -177,7 +177,82 @@ async fn find_users_collection_id(
|
|||
Ok(id.to_string())
|
||||
}
|
||||
|
||||
/// Ensure the `saved_searches` and `short_urls` collections exist in PocketBase.
|
||||
/// Ensure `is_admin` (bool) and `subscription` (text) fields exist on the `users` collection.
|
||||
/// PocketBase PATCH replaces the entire `fields` array, so we must preserve existing fields.
|
||||
async fn ensure_user_fields(
|
||||
client: &Client,
|
||||
base_url: &str,
|
||||
token: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let url = format!("{base_url}/api/collections/users");
|
||||
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 users collection ({status}): {text}");
|
||||
}
|
||||
|
||||
let body: serde_json::Value = resp.json().await?;
|
||||
let fields = body["fields"]
|
||||
.as_array()
|
||||
.ok_or_else(|| anyhow::anyhow!("users collection has no fields array"))?;
|
||||
|
||||
let has_is_admin = fields.iter().any(|f| f["name"] == "is_admin");
|
||||
let has_subscription = fields.iter().any(|f| f["name"] == "subscription");
|
||||
let has_newsletter = fields.iter().any(|f| f["name"] == "newsletter");
|
||||
|
||||
if has_is_admin && has_subscription && has_newsletter {
|
||||
info!("PocketBase users collection already has is_admin, subscription, and newsletter fields");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut new_fields = fields.clone();
|
||||
|
||||
if !has_is_admin {
|
||||
new_fields.push(serde_json::json!({
|
||||
"name": "is_admin",
|
||||
"type": "bool",
|
||||
}));
|
||||
}
|
||||
|
||||
if !has_subscription {
|
||||
new_fields.push(serde_json::json!({
|
||||
"name": "subscription",
|
||||
"type": "text",
|
||||
}));
|
||||
}
|
||||
|
||||
if !has_newsletter {
|
||||
new_fields.push(serde_json::json!({
|
||||
"name": "newsletter",
|
||||
"type": "bool",
|
||||
}));
|
||||
}
|
||||
|
||||
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 patch users collection ({status}): {text}");
|
||||
}
|
||||
|
||||
info!("Added missing fields to PocketBase users collection");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure the `saved_searches` and `short_urls` collections exist in PocketBase,
|
||||
/// and that the `users` collection has `is_admin` and `subscription` fields.
|
||||
/// Authenticates as superuser, checks existing collections, and creates any that are missing.
|
||||
pub async fn ensure_collections(
|
||||
client: &Client,
|
||||
|
|
@ -190,6 +265,8 @@ pub async fn ensure_collections(
|
|||
let token = auth_superuser(client, base_url, admin_email, admin_password).await?;
|
||||
let existing = list_collections(client, base_url, &token).await?;
|
||||
|
||||
ensure_user_fields(client, base_url, &token).await?;
|
||||
|
||||
if !existing.iter().any(|n| n == "saved_searches") {
|
||||
let users_id = find_users_collection_id(client, base_url, &token).await?;
|
||||
create_collection(
|
||||
|
|
@ -212,6 +289,28 @@ pub async fn ensure_collections(
|
|||
info!("PocketBase collection 'saved_searches' already exists");
|
||||
}
|
||||
|
||||
if !existing.iter().any(|n| n == "invites") {
|
||||
create_collection(
|
||||
client,
|
||||
base_url,
|
||||
&token,
|
||||
CreateCollection {
|
||||
name: "invites".to_string(),
|
||||
r#type: "base".to_string(),
|
||||
fields: vec![
|
||||
Field::text("code", true),
|
||||
Field::text("created_by", true),
|
||||
Field::text("invite_type", true),
|
||||
Field::text("used_by_id", false),
|
||||
Field::text("used_at", false),
|
||||
],
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
info!("PocketBase collection 'invites' already exists");
|
||||
}
|
||||
|
||||
if !existing.iter().any(|n| n == "short_urls") {
|
||||
create_collection(
|
||||
client,
|
||||
|
|
@ -233,3 +332,94 @@ pub async fn ensure_collections(
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Configure Google and Apple OAuth2 providers in PocketBase settings.
|
||||
/// Also sets `meta.appUrl` so OAuth callbacks route to `{public_url}/pb`.
|
||||
pub async fn ensure_oauth_providers(
|
||||
client: &Client,
|
||||
base_url: &str,
|
||||
admin_email: &str,
|
||||
admin_password: &str,
|
||||
public_url: &str,
|
||||
google_client_id: &str,
|
||||
google_client_secret: &str,
|
||||
apple_client_id: &str,
|
||||
apple_client_secret: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
let base_url = base_url.trim_end_matches('/');
|
||||
let token = auth_superuser(client, base_url, admin_email, admin_password).await?;
|
||||
|
||||
// GET current settings
|
||||
let settings_url = format!("{base_url}/api/settings");
|
||||
let resp = client
|
||||
.get(&settings_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 PocketBase settings ({status}): {text}");
|
||||
}
|
||||
|
||||
let mut settings: serde_json::Value = resp.json().await?;
|
||||
|
||||
// Set meta.appUrl for OAuth redirect
|
||||
let app_url = format!("{}/pb", public_url.trim_end_matches('/'));
|
||||
if let Some(meta) = settings.get_mut("meta") {
|
||||
meta["appUrl"] = serde_json::json!(app_url);
|
||||
} else {
|
||||
settings["meta"] = serde_json::json!({ "appUrl": app_url });
|
||||
}
|
||||
|
||||
// Update OAuth2 providers
|
||||
let providers = settings
|
||||
.pointer_mut("/oauth2/providers")
|
||||
.and_then(|v| v.as_array_mut());
|
||||
|
||||
if let Some(providers) = providers {
|
||||
for provider in providers.iter_mut() {
|
||||
let name = provider
|
||||
.get("name")
|
||||
.and_then(|n| n.as_str())
|
||||
.unwrap_or("");
|
||||
|
||||
match name {
|
||||
"google" => {
|
||||
provider["clientId"] = serde_json::json!(google_client_id);
|
||||
provider["clientSecret"] = serde_json::json!(google_client_secret);
|
||||
provider["enabled"] = serde_json::json!(true);
|
||||
info!("Configured Google OAuth provider");
|
||||
}
|
||||
"apple" => {
|
||||
provider["clientId"] = serde_json::json!(apple_client_id);
|
||||
provider["clientSecret"] = serde_json::json!(apple_client_secret);
|
||||
provider["enabled"] = serde_json::json!(true);
|
||||
info!("Configured Apple OAuth provider");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("PocketBase settings missing oauth2.providers array — cannot configure OAuth");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// PATCH settings back
|
||||
let patch_resp = client
|
||||
.patch(&settings_url)
|
||||
.header("Authorization", format!("Bearer {token}"))
|
||||
.json(&settings)
|
||||
.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 update PocketBase settings ({status}): {text}");
|
||||
}
|
||||
|
||||
info!("PocketBase OAuth settings updated (appUrl: {app_url})");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue