Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions server/integrations/google/gmail/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,13 +295,17 @@ export const parseMail = async (
}

// Permissions include all unique email addresses involved
const permissions = Array.from(
new Set(
[from, ...to, ...cc, ...bcc]
.map((email) => email?.toLowerCase() ?? "")
.filter((v) => !!v),
),
)
// mails come from calendar event invitations. Each attendee receives a unique email with their name in the subject line
// for those email we just add current user-email
const permissions = mailId?.startsWith("calendar-")
? [userEmail]
: Array.from(
new Set(
[from, ...to, ...cc, ...bcc]
.map((email) => email?.toLowerCase() ?? "")
.filter((v) => !!v),
),
)

// Extract body and chunks
const body = getBody(payload)
Expand Down
120 changes: 120 additions & 0 deletions server/scripts/fix-calendar-mail-permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env bun

import { getLogger } from "@/logger"
import { Subsystem } from "@/types"
import { UpdateDocument } from "@/search/vespa"
import config, { CLUSTER } from "@/config"

const logger = getLogger(Subsystem.Vespa).child({
module: "fix-calendar-permissions",
})

// Query calendar emails using YQL
async function queryCalendarEmails(offset = 0, limit = 100) {
const yql = `select * from sources mail where mailId matches 'calendar-' limit ${limit} offset ${offset}`

const params = new URLSearchParams({
yql,
hits: limit.toString(),
})

const url = `${config.vespaEndpoint}/search/?${params.toString()}`

const response = await fetch(url, {
method: "GET",
headers: { Accept: "application/json" },
})

if (!response.ok) {
throw new Error(`Failed to query calendar emails: ${response.status}`)
}

const data = await response.json()
return {
documents: data.root?.children || [],
totalCount: data.root?.fields?.totalCount || 0,
}
}

// Main function to fix calendar permissions
async function fixCalendarPermissions() {
let offset = 0
const limit = 100
let totalFixed = 0
let alreadyCorrect = 0

let count = 0
logger.info("Starting calendar permissions fix using YQL query...")

while (count < 2) {
const { documents, totalCount } = await queryCalendarEmails(offset, limit)

if (documents.length === 0) break

logger.info(
`Processing batch: ${offset + 1}-${offset + documents.length} of ${totalCount} calendar emails`,
)

for (const doc of documents) {
const fields = doc.fields
const mailId = fields.mailId || ""
const userMap = fields.userMap || {}
const currentPermissions = fields.permissions || []
const docId = fields.docId

// Get user email from userMap (the key)
const userEmails = Object.keys(userMap)
if (userEmails.length === 0) {
logger.warn(`No user found in userMap for calendar email: ${docId}`)
continue
}

const userEmail = userEmails[0].toLowerCase()
const expectedPermissions = [userEmail]

// Check if already correct
if (
currentPermissions.length === 1 &&
currentPermissions[0].toLowerCase() === userEmail
) {
alreadyCorrect++
continue
}

// Update permissions
try {
await UpdateDocument("mail", docId, {
permissions: expectedPermissions,
})

logger.info(
`Fixed calendar email ${docId}: [${currentPermissions.join(", ")}] → [${userEmail}]`,
)
totalFixed++
count++
} catch (error) {
logger.error(`Failed to update ${docId}: ${error}`)
}
}

offset += limit

// Break if we've processed all documents
if (documents.length < limit) break
}

logger.info(
`Migration complete! Fixed ${totalFixed} calendar emails, ${alreadyCorrect} were already correct`,
)
}

// Run the script
fixCalendarPermissions()
.then(() => {
logger.info("Calendar permissions migration completed successfully")
process.exit(0)
})
.catch((error) => {
logger.error("Migration failed:", error)
process.exit(1)
})