2026-03-12 17:09:22 -05:00
|
|
|
import { google } from 'googleapis';
|
|
|
|
|
import { Readable } from 'stream';
|
2026-03-13 16:00:33 -05:00
|
|
|
import { prisma } from './prisma';
|
2026-03-12 17:09:22 -05:00
|
|
|
|
2026-03-13 16:00:33 -05:00
|
|
|
export async function getGoogleAuth(userId: string) {
|
|
|
|
|
const account = await prisma.account.findFirst({
|
|
|
|
|
where: { userId, provider: 'google' },
|
|
|
|
|
});
|
2026-03-12 17:09:22 -05:00
|
|
|
|
2026-03-13 16:00:33 -05:00
|
|
|
if (!account) {
|
|
|
|
|
throw new Error('Google account not found');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auth = new google.auth.OAuth2(
|
|
|
|
|
process.env.GOOGLE_CLIENT_ID,
|
|
|
|
|
process.env.GOOGLE_CLIENT_SECRET
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
auth.setCredentials({
|
|
|
|
|
access_token: account.access_token,
|
|
|
|
|
refresh_token: account.refresh_token,
|
|
|
|
|
expiry_date: account.expires_at ? account.expires_at * 1000 : null,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Check if the token is expired or will expire in the next 1 minute
|
|
|
|
|
// NextAuth stores expires_at in seconds
|
|
|
|
|
const isExpired = account.expires_at ? (account.expires_at * 1000) < (Date.now() + 60000) : true;
|
|
|
|
|
|
|
|
|
|
if (isExpired && account.refresh_token) {
|
|
|
|
|
try {
|
|
|
|
|
const { credentials } = await auth.refreshAccessToken();
|
|
|
|
|
auth.setCredentials(credentials);
|
|
|
|
|
|
|
|
|
|
// Update database with new tokens
|
|
|
|
|
await prisma.account.update({
|
|
|
|
|
where: { id: account.id },
|
|
|
|
|
data: {
|
|
|
|
|
access_token: credentials.access_token,
|
|
|
|
|
refresh_token: credentials.refresh_token || account.refresh_token,
|
|
|
|
|
expires_at: credentials.expiry_date ? Math.floor(credentials.expiry_date / 1000) : null,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
console.log('Successfully refreshed Google access token for user:', userId);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error refreshing access token:', error);
|
|
|
|
|
// If refresh fails, we still return the auth object, but requests will fail with 401
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return auth;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function uploadToDrive(auth: any, fileName: string, content: string, folderId?: string) {
|
2026-03-12 17:09:22 -05:00
|
|
|
const drive = google.drive({ version: 'v3', auth });
|
|
|
|
|
|
|
|
|
|
const fileMetadata: any = {
|
|
|
|
|
name: fileName,
|
|
|
|
|
mimeType: 'application/vnd.google-apps.document', // Automatically convert to Google Doc
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (folderId) {
|
|
|
|
|
fileMetadata.parents = [folderId];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const media = {
|
|
|
|
|
mimeType: 'text/markdown',
|
|
|
|
|
body: Readable.from([content]),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await drive.files.create({
|
|
|
|
|
requestBody: fileMetadata,
|
|
|
|
|
media: media,
|
|
|
|
|
fields: 'id, webViewLink'
|
|
|
|
|
});
|
|
|
|
|
return response.data;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error uploading to Google Drive:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 16:00:33 -05:00
|
|
|
export async function updateDriveFile(auth: any, fileId: string, content: string) {
|
2026-03-13 11:13:45 -05:00
|
|
|
const drive = google.drive({ version: 'v3', auth });
|
|
|
|
|
|
|
|
|
|
const media = {
|
|
|
|
|
mimeType: 'text/markdown',
|
|
|
|
|
body: Readable.from([content]),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const response = await drive.files.update({
|
|
|
|
|
fileId,
|
|
|
|
|
media,
|
|
|
|
|
fields: 'id, webViewLink',
|
|
|
|
|
});
|
|
|
|
|
return response.data;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error updating Google Drive file:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-12 17:09:22 -05:00
|
|
|
export function generateReportMarkdown(report: any) {
|
|
|
|
|
let md = `# WFH Daily Report - ${new Date(report.date).toLocaleDateString()}\n`;
|
|
|
|
|
md += `**Employee:** ${report.user.name}\n`;
|
|
|
|
|
md += `**Manager:** ${report.managerName}\n\n`;
|
|
|
|
|
|
|
|
|
|
md += `## Planned Tasks\n`;
|
|
|
|
|
report.tasks.filter((t: any) => t.type === 'PLANNED').forEach((t: any) => {
|
|
|
|
|
md += `- [ ] ${t.description} (Est: ${t.timeEstimate})\n`;
|
|
|
|
|
if (t.notes) md += ` - Notes: ${t.notes}\n`;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
md += `\n## Completed Tasks\n`;
|
|
|
|
|
report.tasks.filter((t: any) => t.type === 'COMPLETED').forEach((t: any) => {
|
|
|
|
|
md += `- [x] ${t.description}\n`;
|
|
|
|
|
md += ` - Status: ${t.status}\n`;
|
|
|
|
|
if (t.link) md += ` - Work Link: ${t.link}\n`;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return md;
|
|
|
|
|
}
|