Fix PocketBase auth and note submission
- Replace popup OAuth with SDK authWithOAuth2 realtime channel (urlCallback) so login works in Edge app mode without navigating the main window - Remove manual redirect/sessionStorage OAuth flow that broke in Edge app mode - Serve PocketBase config (URL, collection, provider) from server endpoint backed by env vars instead of hardcoded localhost - Fix /api/submit: target Notes collection with correct field mapping (body_plain, body_html, title, type, email, Username, userId, job_note, Job_Number) instead of Job_Info_Prod with wrong field names - Remove localhost fallbacks from auth-module backend and frontend
This commit is contained in:
@@ -100,7 +100,7 @@ export class PocketBaseValidator {
|
|||||||
private pb: PocketBase;
|
private pb: PocketBase;
|
||||||
|
|
||||||
constructor(pbUrl?: string) {
|
constructor(pbUrl?: string) {
|
||||||
this.pb = new PocketBase(pbUrl || process.env.PB_DB || 'http://127.0.0.1:8090');
|
this.pb = new PocketBase(pbUrl || process.env.PB_URL || process.env.PB_DB || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export class PocketBaseAuth {
|
|||||||
|
|
||||||
constructor(config: AuthConfig, callbacks?: Partial<AuthCallbacks>) {
|
constructor(config: AuthConfig, callbacks?: Partial<AuthCallbacks>) {
|
||||||
this.config = {
|
this.config = {
|
||||||
pbUrl: config.pbUrl || 'http://localhost:8090',
|
pbUrl: config.pbUrl || '',
|
||||||
collection: config.collection || 'Users',
|
collection: config.collection || 'Users',
|
||||||
provider: config.provider || 'microsoft',
|
provider: config.provider || 'microsoft',
|
||||||
loginContainerId: config.loginContainerId || 'loginContainer',
|
loginContainerId: config.loginContainerId || 'loginContainer',
|
||||||
|
|||||||
+127
-26
@@ -19,14 +19,45 @@
|
|||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
padding: 0.55rem 1rem;
|
padding: 0.55rem 1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
transition: all 120ms ease;
|
transition: all 160ms ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.btn:focus-visible,
|
.btn:focus-visible,
|
||||||
.field:focus-visible {
|
.field:focus-visible {
|
||||||
outline: 2px solid rgb(34 211 238 / 0.8);
|
outline: 2px solid rgb(34 211 238 / 0.8);
|
||||||
outline-offset: 1px;
|
outline-offset: 1px;
|
||||||
}
|
}
|
||||||
.btn:hover { transform: translateY(-1px); }
|
.btn::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(120deg, transparent 0%, rgb(255 255 255 / 0.2) 48%, transparent 100%);
|
||||||
|
transform: translateX(-130%);
|
||||||
|
transition: transform 260ms ease;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.btn:hover {
|
||||||
|
transform: translateY(-1px) scale(1.01);
|
||||||
|
box-shadow: 0 10px 22px rgb(15 23 42 / 0.28);
|
||||||
|
}
|
||||||
|
.btn:hover::before { transform: translateX(130%); }
|
||||||
|
.btn-accent-cyan {
|
||||||
|
color: rgb(8 47 73 / 1);
|
||||||
|
background: rgb(6 182 212 / 1);
|
||||||
|
}
|
||||||
|
.btn-accent-emerald {
|
||||||
|
color: rgb(6 44 32 / 1);
|
||||||
|
background: rgb(34 197 94 / 1);
|
||||||
|
}
|
||||||
|
.btn-accent-indigo {
|
||||||
|
color: rgb(255 255 255 / 0.98);
|
||||||
|
background: rgb(99 102 241 / 1);
|
||||||
|
}
|
||||||
|
.btn-accent-violet {
|
||||||
|
color: rgb(255 255 255 / 0.98);
|
||||||
|
background: rgb(139 92 246 / 1);
|
||||||
|
}
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
border: 1px solid rgb(51 65 85 / 1);
|
border: 1px solid rgb(51 65 85 / 1);
|
||||||
background: rgb(15 23 42 / 1);
|
background: rgb(15 23 42 / 1);
|
||||||
@@ -42,7 +73,21 @@
|
|||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
color: rgb(248 250 252 / 1);
|
color: rgb(248 250 252 / 1);
|
||||||
}
|
}
|
||||||
|
.field option {
|
||||||
|
background: rgb(15 23 42 / 0.96);
|
||||||
|
color: rgb(248 250 252 / 1);
|
||||||
|
}
|
||||||
.field::placeholder { color: rgb(100 116 139 / 1); }
|
.field::placeholder { color: rgb(100 116 139 / 1); }
|
||||||
|
#output {
|
||||||
|
border: 1px solid rgb(30 41 59 / 1);
|
||||||
|
background: rgb(2 6 23 / 1);
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
border: 1px solid rgb(51 65 85 / 1);
|
||||||
|
background: rgb(15 23 42 / 1);
|
||||||
|
padding: 0.12rem 0.35rem;
|
||||||
|
border-radius: 0.45rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen bg-gradient-to-b from-slate-950 via-slate-950 to-slate-900 text-slate-100">
|
<body class="min-h-screen bg-gradient-to-b from-slate-950 via-slate-950 to-slate-900 text-slate-100">
|
||||||
@@ -78,7 +123,7 @@
|
|||||||
<h2 class="text-xl font-medium">1) Prism Notes Login</h2>
|
<h2 class="text-xl font-medium">1) Prism Notes Login</h2>
|
||||||
<p class="mt-2 text-sm text-slate-300">Uses PocketBase OAuth with Microsoft to validate the Prism Notes user session.</p>
|
<p class="mt-2 text-sm text-slate-300">Uses PocketBase OAuth with Microsoft to validate the Prism Notes user session.</p>
|
||||||
<div class="mt-4 flex flex-wrap gap-3">
|
<div class="mt-4 flex flex-wrap gap-3">
|
||||||
<button id="loginBtn" class="btn bg-cyan-500 text-slate-950 hover:bg-cyan-400">Login with Microsoft</button>
|
<button id="loginBtn" class="btn btn-accent-cyan">Login with Microsoft</button>
|
||||||
<button id="logoutBtn" class="btn btn-secondary">Logout</button>
|
<button id="logoutBtn" class="btn btn-secondary">Logout</button>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -88,7 +133,7 @@
|
|||||||
<p class="mt-2 text-sm text-slate-300">Gets delegated Graph token used for OneNote APIs.</p>
|
<p class="mt-2 text-sm text-slate-300">Gets delegated Graph token used for OneNote APIs.</p>
|
||||||
<p id="oneNoteAuthStatus" class="mt-3 text-sm font-medium">Disconnected</p>
|
<p id="oneNoteAuthStatus" class="mt-3 text-sm font-medium">Disconnected</p>
|
||||||
<div class="mt-4 flex flex-wrap gap-3">
|
<div class="mt-4 flex flex-wrap gap-3">
|
||||||
<button id="connectOneNoteBtn" class="btn bg-emerald-500 text-slate-950 hover:bg-emerald-400">Connect OneNote</button>
|
<button id="connectOneNoteBtn" class="btn btn-accent-emerald">Connect OneNote</button>
|
||||||
<button id="refreshOneNoteTokenBtn" class="btn btn-secondary">Refresh token</button>
|
<button id="refreshOneNoteTokenBtn" class="btn btn-secondary">Refresh token</button>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -120,8 +165,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex flex-wrap gap-3">
|
<div class="mt-4 flex flex-wrap gap-3">
|
||||||
<button id="submitNoteBtn" class="btn bg-indigo-500 text-white hover:bg-indigo-400">Submit to Prism Notes</button>
|
<button id="submitNoteBtn" class="btn btn-accent-indigo">Submit to Prism Notes</button>
|
||||||
<button id="syncOneNoteBtn" class="btn bg-violet-500 text-white hover:bg-violet-400">Sync to OneNote</button>
|
<button id="syncOneNoteBtn" class="btn btn-accent-violet">Sync to OneNote</button>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
@@ -146,7 +191,7 @@
|
|||||||
<label class="text-sm font-medium text-slate-300">Proof Message</label>
|
<label class="text-sm font-medium text-slate-300">Proof Message</label>
|
||||||
<textarea id="proofBody" rows="6" class="field mt-1"></textarea>
|
<textarea id="proofBody" rows="6" class="field mt-1"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<button id="createProofPageBtn" class="btn bg-emerald-500 text-slate-950 hover:bg-emerald-400">Create OneNote Proof Page</button>
|
<button id="createProofPageBtn" class="btn btn-accent-emerald">Create OneNote Proof Page</button>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
@@ -160,11 +205,12 @@
|
|||||||
<script type="module">
|
<script type="module">
|
||||||
import PocketBase from 'https://cdn.jsdelivr.net/npm/pocketbase@0.26.5/+esm';
|
import PocketBase from 'https://cdn.jsdelivr.net/npm/pocketbase@0.26.5/+esm';
|
||||||
|
|
||||||
const PB_URL = 'http://127.0.0.1:8090';
|
const PB_DEFAULT_AUTH_COLLECTION = 'Users';
|
||||||
const PB_AUTH_COLLECTION = 'Users';
|
const PB_DEFAULT_OAUTH_PROVIDER = 'microsoft';
|
||||||
const SCOPES = ['User.Read', 'Notes.ReadWrite.All', 'Sites.Read.All', 'offline_access'];
|
const SCOPES = ['User.Read', 'Notes.ReadWrite.All', 'Sites.Read.All', 'offline_access'];
|
||||||
|
|
||||||
const pb = new PocketBase(PB_URL);
|
let pb = null;
|
||||||
|
let pbConfigPromise = null;
|
||||||
|
|
||||||
const output = document.getElementById('output');
|
const output = document.getElementById('output');
|
||||||
const healthStatus = document.getElementById('healthStatus');
|
const healthStatus = document.getElementById('healthStatus');
|
||||||
@@ -194,6 +240,35 @@
|
|||||||
let delegatedAccessToken = '';
|
let delegatedAccessToken = '';
|
||||||
let delegatedExpiresAt = 0;
|
let delegatedExpiresAt = 0;
|
||||||
|
|
||||||
|
async function getPocketBaseConfig() {
|
||||||
|
if (!pbConfigPromise) {
|
||||||
|
pbConfigPromise = fetch('/api/auth/pocketbase-config')
|
||||||
|
.then(async (resp) => {
|
||||||
|
const data = await resp.json().catch(() => ({}));
|
||||||
|
if (!resp.ok || !data?.success || !data?.pbUrl) {
|
||||||
|
throw new Error(data?.message || 'PocketBase config unavailable');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
pbUrl: data.pbUrl,
|
||||||
|
collection: data.collection || PB_DEFAULT_AUTH_COLLECTION,
|
||||||
|
provider: data.provider || PB_DEFAULT_OAUTH_PROVIDER,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
pbConfigPromise = null;
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return pbConfigPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensurePocketBaseClient() {
|
||||||
|
if (pb) return pb;
|
||||||
|
const config = await getPocketBaseConfig();
|
||||||
|
pb = new PocketBase(config.pbUrl);
|
||||||
|
return pb;
|
||||||
|
}
|
||||||
|
|
||||||
function writeOutput(value) {
|
function writeOutput(value) {
|
||||||
output.textContent = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
output.textContent = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
||||||
}
|
}
|
||||||
@@ -207,9 +282,10 @@
|
|||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePbStatus() {
|
async function updatePbStatus() {
|
||||||
if (pb.authStore?.isValid) {
|
const client = await ensurePocketBaseClient();
|
||||||
const email = pb.authStore.model?.email || '(unknown user)';
|
if (client.authStore?.isValid) {
|
||||||
|
const email = client.authStore.model?.email || '(unknown user)';
|
||||||
pbStatus.textContent = `Logged in: ${email}`;
|
pbStatus.textContent = `Logged in: ${email}`;
|
||||||
} else {
|
} else {
|
||||||
pbStatus.textContent = 'Not logged in';
|
pbStatus.textContent = 'Not logged in';
|
||||||
@@ -352,15 +428,36 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loginPocketBase() {
|
async function loginPocketBase() {
|
||||||
writeOutput('Opening PocketBase Microsoft login...');
|
writeOutput('Opening Microsoft login...');
|
||||||
await pb.collection(PB_AUTH_COLLECTION).authWithOAuth2({ provider: 'microsoft' });
|
const client = await ensurePocketBaseClient();
|
||||||
updatePbStatus();
|
const config = await getPocketBaseConfig();
|
||||||
writeOutput({ message: 'PocketBase login completed.', user: pb.authStore.model?.email || null });
|
|
||||||
|
// Use the SDK's authWithOAuth2 realtime channel.
|
||||||
|
// Auth result is delivered back to this window via SSE from PocketBase,
|
||||||
|
// so it works regardless of how/where the login popup opens (including
|
||||||
|
// Edge app mode where external URLs open outside the app window).
|
||||||
|
const authData = await client.collection(config.collection).authWithOAuth2({
|
||||||
|
provider: config.provider,
|
||||||
|
urlCallback(url) {
|
||||||
|
const w = Math.min(900, window.screen.availWidth || 900);
|
||||||
|
const h = Math.min(680, window.screen.availHeight || 680);
|
||||||
|
const left = Math.floor(((window.screen.availWidth || 1280) - w) / 2);
|
||||||
|
const top = Math.floor(((window.screen.availHeight || 800) - h) / 2);
|
||||||
|
window.open(url, 'pb_oauth', `width=${w},height=${h},top=${top},left=${left},resizable=yes,menubar=no`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await updatePbStatus();
|
||||||
|
writeOutput({
|
||||||
|
message: 'PocketBase login completed.',
|
||||||
|
user: authData?.record?.email || client.authStore.model?.email || null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function logoutPocketBase() {
|
async function logoutPocketBase() {
|
||||||
pb.authStore.clear();
|
const client = await ensurePocketBaseClient();
|
||||||
updatePbStatus();
|
client.authStore.clear();
|
||||||
|
await updatePbStatus();
|
||||||
writeOutput({ message: 'PocketBase session cleared.' });
|
writeOutput({ message: 'PocketBase session cleared.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,13 +472,14 @@
|
|||||||
title,
|
title,
|
||||||
bodyPlain,
|
bodyPlain,
|
||||||
noteType: noteType.value || 'personal',
|
noteType: noteType.value || 'personal',
|
||||||
userEmail: pb.authStore.model?.email || '',
|
userEmail: pb?.authStore.model?.email || '',
|
||||||
jobNumber: noteJobNumber.value.trim(),
|
jobNumber: noteJobNumber.value.trim(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitNoteToPrismNotes() {
|
async function submitNoteToPrismNotes() {
|
||||||
if (!pb.authStore.isValid || !pb.authStore.token) {
|
const client = await ensurePocketBaseClient();
|
||||||
|
if (!client.authStore.isValid || !client.authStore.token) {
|
||||||
throw new Error('PocketBase login required');
|
throw new Error('PocketBase login required');
|
||||||
}
|
}
|
||||||
const payload = buildNotePayload();
|
const payload = buildNotePayload();
|
||||||
@@ -390,7 +488,7 @@
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
...payload,
|
...payload,
|
||||||
pbToken: pb.authStore.token,
|
pbToken: client.authStore.token,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const data = await resp.json().catch(() => ({}));
|
const data = await resp.json().catch(() => ({}));
|
||||||
@@ -401,7 +499,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function syncCurrentNoteToOneNote() {
|
async function syncCurrentNoteToOneNote() {
|
||||||
if (!pb.authStore.isValid || !pb.authStore.token) {
|
const client = await ensurePocketBaseClient();
|
||||||
|
if (!client.authStore.isValid || !client.authStore.token) {
|
||||||
throw new Error('PocketBase login required');
|
throw new Error('PocketBase login required');
|
||||||
}
|
}
|
||||||
if (!hasUsableToken()) {
|
if (!hasUsableToken()) {
|
||||||
@@ -414,7 +513,7 @@
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: `Bearer ${delegatedAccessToken}`,
|
Authorization: `Bearer ${delegatedAccessToken}`,
|
||||||
'x-pb-token': pb.authStore.token,
|
'x-pb-token': client.authStore.token,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
@@ -505,7 +604,9 @@
|
|||||||
bind('resolveSectionsBtn', resolveSiteSections);
|
bind('resolveSectionsBtn', resolveSiteSections);
|
||||||
bind('createProofPageBtn', createProofPage);
|
bind('createProofPageBtn', createProofPage);
|
||||||
|
|
||||||
updatePbStatus();
|
ensurePocketBaseClient()
|
||||||
|
.then(() => updatePbStatus())
|
||||||
|
.catch((error) => writeOutput({ error: error?.message || String(error) }));
|
||||||
updateOneNoteStatus();
|
updateOneNoteStatus();
|
||||||
loadHealth();
|
loadHealth();
|
||||||
loadGraphStatus();
|
loadGraphStatus();
|
||||||
|
|||||||
@@ -34,14 +34,18 @@ app.use('/*', cors({ origin: '*', credentials: true }));
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// PocketBase
|
// PocketBase
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
const pb = new PocketBase(process.env.PB_DB || 'http://127.0.0.1:8090');
|
const PB_BASE_URL = process.env.PB_URL || process.env.PB_DB || '';
|
||||||
|
const PB_AUTH_COLLECTION = process.env.PB_AUTH_COLLECTION || 'Users';
|
||||||
|
const PB_OAUTH_PROVIDER = 'microsoft';
|
||||||
|
|
||||||
|
const pb = new PocketBase(PB_BASE_URL);
|
||||||
|
|
||||||
function setPbToken(token: string) { pb.authStore.save(token, null); }
|
function setPbToken(token: string) { pb.authStore.save(token, null); }
|
||||||
function clearPbToken() { pb.authStore.clear(); }
|
function clearPbToken() { pb.authStore.clear(); }
|
||||||
|
|
||||||
async function validatePbToken(token: string): Promise<void> {
|
async function validatePbToken(token: string): Promise<void> {
|
||||||
setPbToken(token);
|
setPbToken(token);
|
||||||
await pb.collection(process.env.PB_AUTH_COLLECTION || 'Users').authRefresh();
|
await pb.collection(PB_AUTH_COLLECTION).authRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -110,7 +114,19 @@ async function resolveSiteId(delegatedToken: string): Promise<string> {
|
|||||||
// Routes
|
// Routes
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
app.get('/health', (c) => c.json({ ok: true, pbDB: process.env.PB_DB }));
|
app.get('/health', (c) => c.json({ ok: true, pbDB: PB_BASE_URL }));
|
||||||
|
|
||||||
|
app.get('/api/auth/pocketbase-config', (c) => {
|
||||||
|
if (!PB_BASE_URL) {
|
||||||
|
return c.json({ success: false, message: 'Missing PB_URL or PB_DB' }, 500);
|
||||||
|
}
|
||||||
|
return c.json({
|
||||||
|
success: true,
|
||||||
|
pbUrl: PB_BASE_URL,
|
||||||
|
collection: PB_AUTH_COLLECTION,
|
||||||
|
provider: PB_OAUTH_PROVIDER,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/api/auth/msal-config', (c) => {
|
app.get('/api/auth/msal-config', (c) => {
|
||||||
const clientId = process.env.CLIENT_ID || '';
|
const clientId = process.env.CLIENT_ID || '';
|
||||||
@@ -275,8 +291,10 @@ app.post('/api/onenote/append', async (c) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// POST /api/submit — save a record to PocketBase
|
// POST /api/submit — save a note record to PocketBase Notes collection
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
const PB_NOTES_COLLECTION = process.env.PB_NOTES_COLLECTION || 'Notes';
|
||||||
|
|
||||||
app.post('/api/submit', async (c) => {
|
app.post('/api/submit', async (c) => {
|
||||||
try {
|
try {
|
||||||
const body = await c.req.json();
|
const body = await c.req.json();
|
||||||
@@ -288,19 +306,42 @@ app.post('/api/submit', async (c) => {
|
|||||||
return c.json({ success: false, message: 'Invalid token' }, 401);
|
return c.json({ success: false, message: 'Invalid token' }, 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
const record = await pb
|
const user = pb.authStore.model;
|
||||||
.collection(process.env.PB_COLLECTION || 'Job_Info_TestEnv')
|
const bodyPlain = String(body.bodyPlain || '').trim();
|
||||||
.create({
|
const title = String(body.title || 'Untitled').trim();
|
||||||
...body,
|
const noteType = String(body.noteType || 'personal').trim();
|
||||||
submittedBy: pb.authStore.model?.email || 'unknown',
|
const jobNumber = String(body.jobNumber || '').trim();
|
||||||
submittedAt: new Date().toISOString(),
|
|
||||||
});
|
// Convert plain text to simple HTML (matches what the main app stores)
|
||||||
|
const bodyHtml = bodyPlain
|
||||||
|
.split(/\r\n|\r|\n/)
|
||||||
|
.map((line) => `<p>${line || ' '}</p>`)
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
const payload: Record<string, unknown> = {
|
||||||
|
body_plain: bodyPlain,
|
||||||
|
body_html: bodyHtml,
|
||||||
|
title,
|
||||||
|
type: noteType,
|
||||||
|
email: user?.email || body.userEmail || '',
|
||||||
|
Username: user?.name || user?.username || user?.email || '',
|
||||||
|
userId: user?.id || '',
|
||||||
|
job_note: noteType === 'job' || !!jobNumber,
|
||||||
|
shared: false,
|
||||||
|
shared_with: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (jobNumber) {
|
||||||
|
payload['Job_Number'] = jobNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = await pb.collection(PB_NOTES_COLLECTION).create(payload);
|
||||||
|
|
||||||
console.log('submission_success', { recordId: record.id });
|
console.log('submission_success', { recordId: record.id });
|
||||||
return c.json({ success: true, recordId: record.id });
|
return c.json({ success: true, recordId: record.id });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('submit_error', { message: (err as Error)?.message });
|
console.error('submit_error', { message: (err as Error)?.message });
|
||||||
return c.json({ success: false, message: 'Internal error' }, 500);
|
return c.json({ success: false, message: (err as Error)?.message || 'Internal error' }, 500);
|
||||||
} finally {
|
} finally {
|
||||||
clearPbToken();
|
clearPbToken();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user