ServiceChannel webhook processor, proposal approval cards, attachment auto-post, CollabSupport commands, and Docker deployment configuration. Co-authored-by: Cursor <cursoragent@cursor.com>
107 lines
3.1 KiB
JavaScript
107 lines
3.1 KiB
JavaScript
/**
|
|
* Temporary discovery script.
|
|
* Goal: Inspect the shape of notes returned by the ServiceChannel API
|
|
* to determine the correct timestamp field name.
|
|
*
|
|
* Run with: node discover-note-timestamps.js
|
|
*
|
|
* This script now loads credentials from environment variables (via .env)
|
|
* — same as the main app — so there's no plaintext credential in this file.
|
|
* Feel free to delete once you're done using it.
|
|
*/
|
|
|
|
import 'dotenv/config';
|
|
import axios from 'axios';
|
|
import qs from 'node:querystring';
|
|
|
|
const SC = {
|
|
clientId: process.env.SC_CLIENT_ID,
|
|
clientSecret: process.env.SC_CLIENT_SECRET,
|
|
username: process.env.SC_USERNAME,
|
|
password: process.env.SC_PASSWORD,
|
|
baseUrl: process.env.SC_BASE_URL || 'https://api.servicechannel.com/v3',
|
|
oauthUrl: process.env.SC_OAUTH_URL || 'https://login.servicechannel.com/oauth/token',
|
|
};
|
|
|
|
if (!SC.clientId || !SC.clientSecret || !SC.username || !SC.password) {
|
|
console.error('Missing ServiceChannel credentials. Set SC_CLIENT_ID, SC_CLIENT_SECRET, SC_USERNAME, SC_PASSWORD in .env.');
|
|
process.exit(1);
|
|
}
|
|
|
|
async function getToken() {
|
|
const basicAuth = Buffer.from(`${SC.clientId}:${SC.clientSecret}`).toString('base64');
|
|
|
|
const response = await axios.post(
|
|
SC.oauthUrl,
|
|
qs.stringify({
|
|
grant_type: 'password',
|
|
username: SC.username,
|
|
password: SC.password,
|
|
}),
|
|
{
|
|
headers: {
|
|
'Authorization': `Basic ${basicAuth}`,
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
timeout: 15000,
|
|
}
|
|
);
|
|
|
|
return response.data.access_token;
|
|
}
|
|
|
|
async function main() {
|
|
const workOrderId = Number(process.argv[2] || 352088878);
|
|
|
|
console.log(`Discovering note structure for work order ${workOrderId}...\n`);
|
|
|
|
try {
|
|
const token = await getToken();
|
|
|
|
const notesRes = await axios.get(
|
|
`${SC.baseUrl}/workorders/${workOrderId}/notes`,
|
|
{
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
timeout: 30000,
|
|
}
|
|
);
|
|
|
|
const notes = notesRes.data?.Notes || notesRes.data || [];
|
|
|
|
console.log(`Found ${notes.length} notes.\n`);
|
|
|
|
if (notes.length === 0) {
|
|
console.log("No notes found on this work order.");
|
|
return;
|
|
}
|
|
|
|
const firstNote = notes[0];
|
|
console.log("Keys on the first note object:");
|
|
console.log(Object.keys(firstNote));
|
|
console.log("\n");
|
|
|
|
console.log("=== First note (full object) ===");
|
|
console.dir(firstNote, { depth: null, colors: true });
|
|
|
|
console.log("\n=== Last 3 notes - looking for timestamp fields ===");
|
|
const lastNotes = notes.slice(-3).reverse();
|
|
lastNotes.forEach((note, i) => {
|
|
console.log(`\nNote ${i + 1}:`);
|
|
const possibleTimestampFields = Object.keys(note).filter(k =>
|
|
/time|date|stamp|created|updated/i.test(k)
|
|
);
|
|
console.log(" Possible timestamp fields:", possibleTimestampFields);
|
|
possibleTimestampFields.forEach(field => {
|
|
console.log(` ${field}: ${note[field]}`);
|
|
});
|
|
});
|
|
|
|
} catch (err) {
|
|
console.error("Error:", err.response?.data || err.message);
|
|
if (err.response) {
|
|
console.error("Status:", err.response.status);
|
|
}
|
|
}
|
|
}
|
|
|
|
main();
|