managarten/uload/backend/pb_hooks/team_invitations.pb.js.disabled
Till-JS c712a2504a feat: integrate uload and picture, unify package naming
- Add uload project with apps/web structure
  - Reorganize from flat to monorepo structure
  - Remove PocketBase binary and local data
  - Update to pnpm and @uload/web namespace

- Add picture project to monorepo
  - Remove embedded git repository

- Unify all package names to @{project}/{app} schema:
  - @maerchenzauber/* (was @storyteller/*)
  - @manacore/* (was manacore-*, manacore)
  - @manadeck/* (was web, backend, manadeck)
  - @memoro/* (was memoro-web, landing, memoro)
  - @picture/* (already unified)
  - @uload/web

- Add convenient dev scripts for all apps:
  - pnpm dev:{project}:web
  - pnpm dev:{project}:landing
  - pnpm dev:{project}:mobile
  - pnpm dev:{project}:backend

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 04:00:36 +01:00

292 lines
No EOL
14 KiB
Text

/// <reference path="../pb_data/types.d.ts" />
// Hook for sending team invitation emails
onRecordAfterCreateRequest((e) => {
// Handle new team invitations for existing users
if (e.collection.name === "workspace_members") {
const record = e.record;
// Only send email for pending invitations
if (record.get("invitation_status") !== "pending") {
return;
}
try {
// Get the invited user's email and workspace owner
const user = $app.dao().findRecordById("users", record.get("user"));
const workspace = $app.dao().findRecordById("workspaces", record.get("workspace"));
const owner = workspace ? $app.dao().findRecordById("users", workspace.get("owner")) : null;
if (!user || !owner) {
console.log("Could not find user or owner");
return;
}
const inviterName = owner.get("name") || owner.get("username") || owner.get("email");
const token = record.get("invitation_token");
const appUrl = "https://ulo.ad"; // Change this to your domain
// Email content
const subject = `${inviterName} hat dich zu seinem Team eingeladen / invited you to their team - ulo.ad 👥`;
const html = `
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f8fafc;">
<div style="text-align: center; margin-bottom: 30px; padding: 20px;">
<h1 style="color: #0ea5e9; font-size: 36px; margin: 0; font-weight: 700;">🔗 ulo.ad</h1>
</div>
<div style="background: #ffffff; border-radius: 16px; padding: 32px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<h2 style="color: #0f172a; font-size: 24px; margin-top: 0; margin-bottom: 16px; font-weight: 600;">
👥 Team-Einladung / Team Invitation
</h2>
<p style="color: #475569; font-size: 16px; line-height: 1.6; margin-bottom: 24px;">
<strong>🇩🇪</strong> ${inviterName} hat dich eingeladen, Teil des Teams zu werden!<br>
<strong>🇬🇧</strong> ${inviterName} has invited you to join their team!
</p>
<div style="background: #f0f9ff; border-radius: 12px; padding: 16px; margin: 24px 0; border: 1px solid #bae6fd;">
<p style="color: #0369a1; font-size: 14px; margin: 0;">
<strong>Von / From:</strong> ${owner.get("email")}<br>
<strong>An / To:</strong> ${user.get("email")}
</p>
</div>
<div style="text-align: center; margin: 32px 0;">
<a href="${appUrl}/team/accept-invite?token=${token}"
style="display: inline-block; background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);
color: white; padding: 16px 40px; border-radius: 10px;
text-decoration: none; font-weight: 600; font-size: 16px;">
✅ Einladung annehmen / Accept Invitation
</a>
</div>
<div style="background: #fef2f2; border: 1px solid #fecaca; border-radius: 12px; padding: 12px; margin: 24px 0; text-align: center;">
<p style="color: #991b1b; font-size: 13px; margin: 0;">
⏱️ Diese Einladung ist 7 Tage gültig / This invitation is valid for 7 days
</p>
</div>
</div>
</div>`;
// Send email using PocketBase's mailer
const message = new MailerMessage({
from: {
address: $app.settings().meta.senderAddress,
name: $app.settings().meta.senderName,
},
to: [{address: user.get("email"), name: user.get("name") || user.get("username")}],
subject: subject,
html: html,
});
$app.newMailClient().send(message);
console.log(`Team invitation email sent to ${user.get("email")}`);
// Create in-app notification for the invited user
try {
const notification = new Record($app.dao().findCollectionByNameOrId("notifications"));
notification.set("user", user.id);
notification.set("type", "team_invite");
notification.set("title", "Neue Team-Einladung / New Team Invitation");
notification.set("message", `${inviterName} hat dich zu seinem Team eingeladen / invited you to their team`);
notification.set("data", {
invitation_id: record.id,
owner_id: owner.id,
owner_name: inviterName,
invitation_token: token
});
notification.set("read", false);
notification.set("action_url", `${appUrl}/team/accept-invite?token=${token}`);
$app.dao().saveRecord(notification);
console.log(`In-app notification created for user ${user.get("email")}`);
} catch (notifError) {
console.error("Failed to create in-app notification:", notifError);
}
} catch (error) {
console.error("Failed to send team invitation email:", error);
}
}
}, "workspace_members");
// Hook for sending invitations to new users (not registered yet)
onRecordAfterCreateRequest((e) => {
if (e.collection.name === "pending_invitations") {
const record = e.record;
try {
const owner = $app.dao().findRecordById("users", record.get("owner"));
if (!owner) {
console.log("Could not find invitation owner");
return;
}
const inviterName = owner.get("name") || owner.get("username") || owner.get("email");
const token = record.get("token");
const recipientEmail = record.get("email");
const appUrl = "https://ulo.ad"; // Change this to your domain
// Email content for new users
const subject = `${inviterName} hat dich zu ulo.ad eingeladen / invited you to ulo.ad - 🚀`;
const html = `
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f8fafc;">
<div style="text-align: center; margin-bottom: 30px; padding: 20px;">
<h1 style="color: #0ea5e9; font-size: 36px; margin: 0; font-weight: 700;">🔗 ulo.ad</h1>
</div>
<div style="background: #ffffff; border-radius: 16px; padding: 32px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<h2 style="color: #0f172a; font-size: 24px; margin-top: 0; margin-bottom: 16px; font-weight: 600;">
🎉 Willkommen bei ulo.ad / Welcome to ulo.ad
</h2>
<p style="color: #475569; font-size: 16px; line-height: 1.6; margin-bottom: 24px;">
<strong>🇩🇪</strong> ${inviterName} hat dich eingeladen, Teil des Teams zu werden!<br>
<strong>🇬🇧</strong> ${inviterName} has invited you to join their team!
</p>
<div style="background: #dcfce7; border: 1px solid #86efac; border-radius: 12px; padding: 16px; margin: 24px 0;">
<p style="color: #14532d; font-size: 14px; margin: 0 0 12px 0; font-weight: 600;">
🆕 Neu bei ulo.ad? / New to ulo.ad?
</p>
<p style="color: #166534; font-size: 13px; margin: 0;">
<strong>🇩🇪</strong> Kein Problem! Erstelle einfach einen kostenlosen Account und die Einladung wird automatisch angenommen.<br>
<strong>🇬🇧</strong> No problem! Simply create a free account and the invitation will be accepted automatically.
</p>
</div>
<div style="text-align: center; margin: 32px 0;">
<a href="${appUrl}/register?invite=${token}"
style="display: inline-block; background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white; padding: 16px 40px; border-radius: 10px;
text-decoration: none; font-weight: 600; font-size: 16px;">
🚀 Account erstellen & Team beitreten / Create Account & Join Team
</a>
</div>
<div style="background: #fef2f2; border: 1px solid #fecaca; border-radius: 12px; padding: 12px; margin: 24px 0; text-align: center;">
<p style="color: #991b1b; font-size: 13px; margin: 0;">
⏱️ Diese Einladung ist 7 Tage gültig / This invitation is valid for 7 days
</p>
</div>
</div>
</div>`;
// Send email
const message = new MailerMessage({
from: {
address: $app.settings().meta.senderAddress,
name: $app.settings().meta.senderName,
},
to: [{address: recipientEmail}],
subject: subject,
html: html,
});
$app.newMailClient().send(message);
console.log(`New user invitation email sent to ${recipientEmail}`);
} catch (error) {
console.error("Failed to send new user invitation email:", error);
}
}
}, "pending_invitations");
// Hook for sending acceptance notifications
onRecordAfterUpdateRequest((e) => {
if (e.collection.name === "workspace_members") {
const record = e.record;
const originalRecord = e.originalRecord;
// Check if invitation was just accepted
if (originalRecord.get("invitation_status") === "pending" &&
record.get("invitation_status") === "accepted") {
try {
const user = $app.dao().findRecordById("users", record.get("user"));
const workspace = $app.dao().findRecordById("workspaces", record.get("workspace"));
const owner = workspace ? $app.dao().findRecordById("users", workspace.get("owner")) : null;
if (!user || !owner) {
return;
}
const subject = `${user.get("email")} hat deine Einladung angenommen / accepted your invitation - ulo.ad ✅`;
const html = `
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f8fafc;">
<div style="text-align: center; margin-bottom: 30px; padding: 20px;">
<h1 style="color: #0ea5e9; font-size: 36px; margin: 0; font-weight: 700;">🔗 ulo.ad</h1>
</div>
<div style="background: #ffffff; border-radius: 16px; padding: 32px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<div style="text-align: center; margin-bottom: 24px;">
<div style="display: inline-block; background: #dcfce7; border-radius: 50%; padding: 16px;">
<span style="font-size: 48px;">✅</span>
</div>
</div>
<h2 style="color: #0f172a; font-size: 24px; margin-top: 0; margin-bottom: 16px; font-weight: 600; text-align: center;">
Einladung angenommen / Invitation Accepted
</h2>
<p style="color: #475569; font-size: 16px; line-height: 1.6; margin-bottom: 24px; text-align: center;">
<strong>🇩🇪</strong> ${user.get("email")} hat deine Team-Einladung angenommen!<br>
<strong>🇬🇧</strong> ${user.get("email")} has accepted your team invitation!
</p>
<div style="text-align: center; margin: 32px 0;">
<a href="https://ulo.ad/settings/team"
style="display: inline-block; background: #0ea5e9;
color: white; padding: 12px 24px; border-radius: 8px;
text-decoration: none; font-weight: 600; font-size: 14px;">
👥 Team verwalten / Manage Team
</a>
</div>
</div>
</div>`;
// Send notification to owner
const message = new MailerMessage({
from: {
address: $app.settings().meta.senderAddress,
name: $app.settings().meta.senderName,
},
to: [{address: owner.get("email"), name: owner.get("name") || owner.get("username")}],
subject: subject,
html: html,
});
$app.newMailClient().send(message);
console.log(`Acceptance notification sent to ${owner.get("email")}`);
// Create in-app notification for the owner
try {
const notification = new Record($app.dao().findCollectionByNameOrId("notifications"));
notification.set("user", owner.id);
notification.set("type", "team_accepted");
notification.set("title", "Team-Einladung angenommen / Team Invitation Accepted");
notification.set("message", `${user.get("email")} ist deinem Team beigetreten / has joined your team`);
notification.set("data", {
member_id: user.id,
member_email: user.get("email"),
member_name: user.get("name") || user.get("username") || user.get("email")
});
notification.set("read", false);
notification.set("action_url", "https://ulo.ad/settings/team");
$app.dao().saveRecord(notification);
console.log(`In-app notification created for owner ${owner.get("email")}`);
} catch (notifError) {
console.error("Failed to create owner notification:", notifError);
}
} catch (error) {
console.error("Failed to send acceptance notification:", error);
}
}
}
}, "workspace_members");