cleanup
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// src/pages/api/auth/login.ts (ENHANCED - Consistent cookie handling)
|
||||
// src/pages/api/auth/login.ts
|
||||
import type { APIRoute } from 'astro';
|
||||
import { generateAuthUrl, generateState, logAuthEvent } from '../../../utils/auth.js';
|
||||
import { serialize } from 'cookie';
|
||||
@@ -18,7 +18,6 @@ export const GET: APIRoute = async ({ url, redirect }) => {
|
||||
|
||||
const stateData = JSON.stringify({ state, returnTo });
|
||||
|
||||
// Use consistent cookie serialization (same as session cookies)
|
||||
const publicBaseUrl = process.env.PUBLIC_BASE_URL || '';
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
const isSecure = publicBaseUrl.startsWith('https://') || isProduction;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/pages/api/auth/process.ts (ENHANCED - Proper auth success indication)
|
||||
// src/pages/api/auth/process.ts
|
||||
import type { APIRoute } from 'astro';
|
||||
import {
|
||||
verifyAuthState,
|
||||
@@ -49,7 +49,6 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
email: sessionResult.userEmail
|
||||
});
|
||||
|
||||
// Add auth success indicator to the return URL
|
||||
const returnUrl = new URL(stateVerification.stateData.returnTo, request.url);
|
||||
returnUrl.searchParams.set('auth', 'success');
|
||||
const redirectUrl = returnUrl.toString();
|
||||
|
||||
@@ -9,16 +9,16 @@ export const GET: APIRoute = async ({ request }) => {
|
||||
return await handleAPIRequest(async () => {
|
||||
const contributionAuth = await withAPIAuth(request, 'contributions');
|
||||
const aiAuth = await withAPIAuth(request, 'ai');
|
||||
const gatedContentAuth = await withAPIAuth(request, 'gatedcontent'); // ADDED
|
||||
const gatedContentAuth = await withAPIAuth(request, 'gatedcontent');
|
||||
|
||||
return apiResponse.success({
|
||||
authenticated: contributionAuth.authenticated || aiAuth.authenticated || gatedContentAuth.authenticated,
|
||||
contributionAuthRequired: contributionAuth.authRequired,
|
||||
aiAuthRequired: aiAuth.authRequired,
|
||||
gatedContentAuthRequired: gatedContentAuth.authRequired, // ADDED
|
||||
gatedContentAuthRequired: gatedContentAuth.authRequired,
|
||||
contributionAuthenticated: contributionAuth.authenticated,
|
||||
aiAuthenticated: aiAuth.authenticated,
|
||||
gatedContentAuthenticated: gatedContentAuth.authenticated, // ADDED
|
||||
gatedContentAuthenticated: gatedContentAuth.authenticated,
|
||||
expires: contributionAuth.session?.exp ? new Date(contributionAuth.session.exp * 1000).toISOString() : null
|
||||
});
|
||||
}, 'Status check failed');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/pages/api/contribute/knowledgebase.ts - SIMPLIFIED: Issues only, minimal validation
|
||||
// src/pages/api/contribute/knowledgebase.ts
|
||||
import type { APIRoute } from 'astro';
|
||||
import { withAPIAuth } from '../../../utils/auth.js';
|
||||
import { apiResponse, apiError, apiServerError, handleAPIRequest } from '../../../utils/api.js';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// src/pages/api/contribute/tool.ts (UPDATED - Using consolidated API responses + related_software)
|
||||
// src/pages/api/contribute/tool.ts
|
||||
import type { APIRoute } from 'astro';
|
||||
import { withAPIAuth } from '../../../utils/auth.js';
|
||||
import { apiResponse, apiError, apiServerError, apiSpecial, handleAPIRequest } from '../../../utils/api.js';
|
||||
@@ -82,31 +82,27 @@ function sanitizeInput(obj: any): any {
|
||||
}
|
||||
|
||||
function preprocessFormData(body: any): any {
|
||||
// Handle comma-separated strings from autocomplete inputs
|
||||
if (body.tool) {
|
||||
// Handle tags
|
||||
if (typeof body.tool.tags === 'string') {
|
||||
body.tool.tags = body.tool.tags.split(',').map((t: string) => t.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
// Handle related concepts
|
||||
if (body.tool.relatedConcepts) {
|
||||
if (typeof body.tool.relatedConcepts === 'string') {
|
||||
body.tool.related_concepts = body.tool.relatedConcepts.split(',').map((t: string) => t.trim()).filter(Boolean);
|
||||
} else {
|
||||
body.tool.related_concepts = body.tool.relatedConcepts;
|
||||
}
|
||||
delete body.tool.relatedConcepts; // Remove the original key
|
||||
delete body.tool.relatedConcepts;
|
||||
}
|
||||
|
||||
// Handle related software
|
||||
if (body.tool.relatedSoftware) {
|
||||
if (typeof body.tool.relatedSoftware === 'string') {
|
||||
body.tool.related_software = body.tool.relatedSoftware.split(',').map((t: string) => t.trim()).filter(Boolean);
|
||||
} else {
|
||||
body.tool.related_software = body.tool.relatedSoftware;
|
||||
}
|
||||
delete body.tool.relatedSoftware; // Remove the original key
|
||||
delete body.tool.relatedSoftware;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,14 +138,11 @@ async function validateToolData(tool: any, action: string): Promise<{ valid: boo
|
||||
}
|
||||
}
|
||||
|
||||
// Validate related items exist (optional validation - could be enhanced)
|
||||
if (tool.related_concepts && tool.related_concepts.length > 0) {
|
||||
// Could validate that referenced concepts actually exist
|
||||
console.log('[VALIDATION] Related concepts provided:', tool.related_concepts);
|
||||
}
|
||||
|
||||
if (tool.related_software && tool.related_software.length > 0) {
|
||||
// Could validate that referenced software actually exists
|
||||
console.log('[VALIDATION] Related software provided:', tool.related_software);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ export const POST: APIRoute = async ({ request }) => {
|
||||
);
|
||||
}
|
||||
|
||||
/* --- (rest of the handler unchanged) -------------------------- */
|
||||
const { embeddingsService } = await import('../../../utils/embeddings.js');
|
||||
|
||||
if (!embeddingsService.isEnabled()) {
|
||||
|
||||
@@ -23,7 +23,6 @@ const editToolName = Astro.url.searchParams.get('edit');
|
||||
const editTool = editToolName ? existingTools.find(tool => tool.name === editToolName) : null;
|
||||
const isEdit = !!editTool;
|
||||
|
||||
// Extract data for autocomplete
|
||||
const allTags = [...new Set(existingTools.flatMap(tool => tool.tags || []))].sort();
|
||||
const allSoftwareAndMethods = existingTools
|
||||
.filter(tool => tool.type === 'software' || tool.type === 'method')
|
||||
@@ -300,7 +299,6 @@ const allConcepts = existingTools
|
||||
</BaseLayout>
|
||||
|
||||
<script define:vars={{ isEdit, editTool, domains, phases, domainAgnosticSoftware, allTags, allSoftwareAndMethods, allConcepts }}>
|
||||
// Consolidated Autocomplete Functionality - inlined to avoid module loading issues
|
||||
class AutocompleteManager {
|
||||
constructor(inputElement, dataSource, options = {}) {
|
||||
this.input = inputElement;
|
||||
@@ -337,7 +335,6 @@ class AutocompleteManager {
|
||||
this.dropdown = document.createElement('div');
|
||||
this.dropdown.className = 'autocomplete-dropdown';
|
||||
|
||||
// Insert dropdown after input
|
||||
this.input.parentNode.style.position = 'relative';
|
||||
this.input.parentNode.insertBefore(this.dropdown, this.input.nextSibling);
|
||||
}
|
||||
@@ -358,7 +355,6 @@ class AutocompleteManager {
|
||||
});
|
||||
|
||||
this.input.addEventListener('blur', (e) => {
|
||||
// Delay to allow click events on dropdown items
|
||||
setTimeout(() => {
|
||||
if (!this.dropdown.contains(document.activeElement)) {
|
||||
this.hideDropdown();
|
||||
@@ -450,7 +446,6 @@ class AutocompleteManager {
|
||||
})
|
||||
.join('');
|
||||
|
||||
// Bind click events
|
||||
this.dropdown.querySelectorAll('.autocomplete-option').forEach((option, index) => {
|
||||
option.addEventListener('click', () => {
|
||||
this.selectItem(this.filteredData[index]);
|
||||
@@ -484,7 +479,6 @@ class AutocompleteManager {
|
||||
this.hideDropdown();
|
||||
}
|
||||
|
||||
// Trigger change event
|
||||
this.input.dispatchEvent(new CustomEvent('autocomplete:select', {
|
||||
detail: { item, text, selectedItems: Array.from(this.selectedItems) }
|
||||
}));
|
||||
@@ -510,7 +504,6 @@ class AutocompleteManager {
|
||||
`)
|
||||
.join('');
|
||||
|
||||
// Bind remove events
|
||||
this.selectedContainer.querySelectorAll('.autocomplete-remove').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
@@ -636,7 +629,6 @@ class ContributionForm {
|
||||
}
|
||||
|
||||
setupAutocomplete() {
|
||||
// Tags autocomplete
|
||||
if (this.elements.tagsInput && this.elements.tagsHidden) {
|
||||
const tagsManager = new AutocompleteManager(this.elements.tagsInput, allTags, {
|
||||
allowMultiple: true,
|
||||
@@ -644,7 +636,6 @@ class ContributionForm {
|
||||
placeholder: 'Beginne zu tippen, um Tags hinzuzufügen...'
|
||||
});
|
||||
|
||||
// Set initial values if editing
|
||||
if (this.editTool?.tags) {
|
||||
tagsManager.setSelectedItems(this.editTool.tags);
|
||||
}
|
||||
@@ -652,7 +643,6 @@ class ContributionForm {
|
||||
this.autocompleteManagers.set('tags', tagsManager);
|
||||
}
|
||||
|
||||
// Related concepts autocomplete
|
||||
if (this.elements.relatedConceptsInput && this.elements.relatedConceptsHidden) {
|
||||
const conceptsManager = new AutocompleteManager(this.elements.relatedConceptsInput, allConcepts, {
|
||||
allowMultiple: true,
|
||||
@@ -660,7 +650,6 @@ class ContributionForm {
|
||||
placeholder: 'Beginne zu tippen, um Konzepte zu finden...'
|
||||
});
|
||||
|
||||
// Set initial values if editing
|
||||
if (this.editTool?.related_concepts) {
|
||||
conceptsManager.setSelectedItems(this.editTool.related_concepts);
|
||||
}
|
||||
@@ -668,7 +657,6 @@ class ContributionForm {
|
||||
this.autocompleteManagers.set('relatedConcepts', conceptsManager);
|
||||
}
|
||||
|
||||
// Related software autocomplete
|
||||
if (this.elements.relatedSoftwareInput && this.elements.relatedSoftwareHidden) {
|
||||
const softwareManager = new AutocompleteManager(this.elements.relatedSoftwareInput, allSoftwareAndMethods, {
|
||||
allowMultiple: true,
|
||||
@@ -676,7 +664,6 @@ class ContributionForm {
|
||||
placeholder: 'Beginne zu tippen, um Software/Methoden zu finden...'
|
||||
});
|
||||
|
||||
// Set initial values if editing
|
||||
if (this.editTool?.related_software) {
|
||||
softwareManager.setSelectedItems(this.editTool.related_software);
|
||||
}
|
||||
@@ -684,7 +671,6 @@ class ContributionForm {
|
||||
this.autocompleteManagers.set('relatedSoftware', softwareManager);
|
||||
}
|
||||
|
||||
// Listen for autocomplete changes to update YAML preview
|
||||
Object.values(this.autocompleteManagers).forEach(manager => {
|
||||
if (manager.input) {
|
||||
manager.input.addEventListener('autocomplete:select', () => {
|
||||
@@ -726,14 +712,10 @@ class ContributionForm {
|
||||
updateFieldVisibility() {
|
||||
const type = this.elements.typeSelect.value;
|
||||
|
||||
// Only hide/show software-specific fields (platforms, license)
|
||||
// Relations should always be visible since all tool types can have relationships
|
||||
this.elements.softwareFields.style.display = type === 'software' ? 'block' : 'none';
|
||||
|
||||
// Always show relations - all tool types can have relationships
|
||||
this.elements.relationsFields.style.display = 'block';
|
||||
|
||||
// Only mark platform/license as required for software
|
||||
if (this.elements.platformsRequired) {
|
||||
this.elements.platformsRequired.style.display = type === 'software' ? 'inline' : 'none';
|
||||
}
|
||||
@@ -741,7 +723,6 @@ class ContributionForm {
|
||||
this.elements.licenseRequired.style.display = type === 'software' ? 'inline' : 'none';
|
||||
}
|
||||
|
||||
// Always show both relation sections - let users decide what's relevant
|
||||
const conceptsSection = document.getElementById('related-concepts-section');
|
||||
const softwareSection = document.getElementById('related-software-section');
|
||||
if (conceptsSection) conceptsSection.style.display = 'block';
|
||||
@@ -806,19 +787,16 @@ class ContributionForm {
|
||||
tool.knowledgebase = true;
|
||||
}
|
||||
|
||||
// Handle tags from autocomplete
|
||||
const tagsValue = this.elements.tagsHidden?.value || '';
|
||||
if (tagsValue) {
|
||||
tool.tags = tagsValue.split(',').map(t => t.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
// Handle related concepts from autocomplete
|
||||
const relatedConceptsValue = this.elements.relatedConceptsHidden?.value || '';
|
||||
if (relatedConceptsValue) {
|
||||
tool.related_concepts = relatedConceptsValue.split(',').map(t => t.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
// Handle related software from autocomplete
|
||||
const relatedSoftwareValue = this.elements.relatedSoftwareHidden?.value || '';
|
||||
if (relatedSoftwareValue) {
|
||||
tool.related_software = relatedSoftwareValue.split(',').map(t => t.trim()).filter(Boolean);
|
||||
@@ -983,19 +961,16 @@ class ContributionForm {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle tags from autocomplete
|
||||
const tagsValue = this.elements.tagsHidden?.value || '';
|
||||
if (tagsValue) {
|
||||
submission.tool.tags = tagsValue.split(',').map(t => t.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
// Handle related concepts from autocomplete
|
||||
const relatedConceptsValue = this.elements.relatedConceptsHidden?.value || '';
|
||||
if (relatedConceptsValue) {
|
||||
submission.tool.related_concepts = relatedConceptsValue.split(',').map(t => t.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
// Handle related software from autocomplete
|
||||
const relatedSoftwareValue = this.elements.relatedSoftwareHidden?.value || '';
|
||||
if (relatedSoftwareValue) {
|
||||
submission.tool.related_software = relatedSoftwareValue.split(',').map(t => t.trim()).filter(Boolean);
|
||||
@@ -1072,7 +1047,6 @@ class ContributionForm {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// Clean up autocomplete managers
|
||||
this.autocompleteManagers.forEach(manager => {
|
||||
manager.destroy();
|
||||
});
|
||||
|
||||
@@ -686,8 +686,6 @@ if (aiAuthRequired) {
|
||||
window.switchToAIView = () => switchToView('ai');
|
||||
window.switchToView = switchToView;
|
||||
|
||||
// CRITICAL: Handle shared URLs AFTER everything is set up
|
||||
// Increased timeout to ensure all components and utility functions are loaded
|
||||
setTimeout(() => {
|
||||
handleSharedURL();
|
||||
}, 1000);
|
||||
|
||||
@@ -10,7 +10,6 @@ const allKnowledgebaseEntries = await getCollection('knowledgebase', (entry) =>
|
||||
return entry.data.published !== false;
|
||||
});
|
||||
|
||||
// Check if gated content authentication is enabled globally
|
||||
const gatedContentAuthEnabled = isGatedContentAuthRequired();
|
||||
|
||||
const knowledgebaseEntries = allKnowledgebaseEntries.map((entry) => {
|
||||
@@ -27,8 +26,7 @@ const knowledgebaseEntries = allKnowledgebaseEntries.map((entry) => {
|
||||
difficulty: entry.data.difficulty,
|
||||
categories: entry.data.categories || [],
|
||||
tags: entry.data.tags || [],
|
||||
gated_content: entry.data.gated_content || false, // NEW: Include gated content flag
|
||||
|
||||
gated_content: entry.data.gated_content || false,
|
||||
tool_name: entry.data.tool_name,
|
||||
related_tools: entry.data.related_tools || [],
|
||||
associatedTool,
|
||||
@@ -45,7 +43,6 @@ const knowledgebaseEntries = allKnowledgebaseEntries.map((entry) => {
|
||||
|
||||
knowledgebaseEntries.sort((a: any, b: any) => a.title.localeCompare(b.title));
|
||||
|
||||
// Count gated vs public articles for statistics
|
||||
const gatedCount = knowledgebaseEntries.filter(entry => entry.gated_content).length;
|
||||
const publicCount = knowledgebaseEntries.length - gatedCount;
|
||||
---
|
||||
|
||||
@@ -21,7 +21,6 @@ export async function getStaticPaths() {
|
||||
|
||||
const { entry }: { entry: any } = Astro.props;
|
||||
|
||||
// Check if this article is gated and if gated content auth is required globally
|
||||
const isGatedContent = entry.data.gated_content === true;
|
||||
const gatedContentAuthRequired = isGatedContentAuthRequired();
|
||||
const requiresAuth = isGatedContent && gatedContentAuthRequired;
|
||||
@@ -62,17 +61,14 @@ const currentUrl = Astro.url.href;
|
||||
<BaseLayout title={entry.data.title} description={entry.data.description}>
|
||||
{requiresAuth && (
|
||||
<script define:vars={{ requiresAuth, articleTitle: entry.data.title }}>
|
||||
// Enhanced client-side authentication check for gated content with improved error handling
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
if (!requiresAuth) return;
|
||||
|
||||
console.log('[GATED CONTENT] Checking client-side auth for: ' + articleTitle);
|
||||
|
||||
// Check for auth success indicator in URL (from callback)
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const authSuccess = urlParams.get('auth') === 'success';
|
||||
|
||||
// Hide content immediately while checking auth
|
||||
const contentArea = document.querySelector('.article-content');
|
||||
const sidebar = document.querySelector('.article-sidebar');
|
||||
|
||||
@@ -80,12 +76,10 @@ const currentUrl = Astro.url.href;
|
||||
contentArea.style.display = 'none';
|
||||
}
|
||||
|
||||
// If this is a redirect from successful auth, wait a bit for session to be available
|
||||
if (authSuccess) {
|
||||
console.log('[GATED CONTENT] Auth success detected, waiting for session...');
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Clean the URL
|
||||
const cleanUrl = window.location.protocol + "//" + window.location.host + window.location.pathname;
|
||||
window.history.replaceState({}, document.title, cleanUrl);
|
||||
}
|
||||
@@ -102,7 +96,6 @@ const currentUrl = Astro.url.href;
|
||||
if (authRequired && !isAuthenticated) {
|
||||
console.log('[GATED CONTENT] Access denied - showing auth required message: ' + articleTitle);
|
||||
|
||||
// Show authentication required message
|
||||
if (contentArea) {
|
||||
const loginUrl = '/api/auth/login?returnTo=' + encodeURIComponent(window.location.href);
|
||||
contentArea.innerHTML = [
|
||||
@@ -130,11 +123,9 @@ const currentUrl = Astro.url.href;
|
||||
}
|
||||
} else {
|
||||
console.log('[GATED CONTENT] Access granted for: ' + articleTitle);
|
||||
// Show content for authenticated users
|
||||
if (contentArea) {
|
||||
contentArea.style.display = 'block';
|
||||
}
|
||||
// Generate TOC for authenticated users
|
||||
setTimeout(() => {
|
||||
if (typeof generateTOCContent === 'function') {
|
||||
generateTOCContent();
|
||||
@@ -143,7 +134,6 @@ const currentUrl = Astro.url.href;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[GATED CONTENT] Auth check failed:', error);
|
||||
// On error, show auth required message with retry option
|
||||
if (requiresAuth && contentArea) {
|
||||
const loginUrl = '/api/auth/login?returnTo=' + encodeURIComponent(window.location.href);
|
||||
contentArea.innerHTML = [
|
||||
@@ -411,13 +401,10 @@ const currentUrl = Astro.url.href;
|
||||
}
|
||||
|
||||
function generateSidebarTOC() {
|
||||
// Only generate TOC if not gated content OR user is authenticated
|
||||
if (requiresAuth) {
|
||||
// For gated content, TOC will be generated by the auth check script
|
||||
return;
|
||||
}
|
||||
|
||||
// For non-gated content, generate TOC normally
|
||||
generateTOCContent();
|
||||
}
|
||||
|
||||
@@ -523,17 +510,14 @@ const currentUrl = Astro.url.href;
|
||||
pre.dataset.copyEnhanced = 'true';
|
||||
pre.style.position ||= 'relative';
|
||||
|
||||
// Try to find an existing copy button we can reuse
|
||||
let btn =
|
||||
pre.querySelector('.copy-btn') || // our class
|
||||
pre.querySelector('.copy-btn') ||
|
||||
pre.querySelector('.btn-copy, .copy-button, .code-copy, .copy-code, button[aria-label*="copy" i]');
|
||||
|
||||
// If there is an "old" button that is NOT ours, prefer to reuse it by giving it our class.
|
||||
if (btn && !btn.classList.contains('copy-btn')) {
|
||||
btn.classList.add('copy-btn');
|
||||
}
|
||||
|
||||
// If no button at all, create one
|
||||
if (!btn) {
|
||||
btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
@@ -548,7 +532,6 @@ const currentUrl = Astro.url.href;
|
||||
pre.appendChild(btn);
|
||||
}
|
||||
|
||||
// If there is a SECOND old button lingering (top-left in your case), hide it
|
||||
const possibleOldButtons = pre.querySelectorAll(
|
||||
'.btn-copy, .copy-button, .code-copy, .copy-code, button[aria-label*="copy" i]'
|
||||
);
|
||||
@@ -556,7 +539,6 @@ const currentUrl = Astro.url.href;
|
||||
if (b !== btn) b.style.display = 'none';
|
||||
});
|
||||
|
||||
// Success pill
|
||||
if (!pre.querySelector('.copied-pill')) {
|
||||
const pill = document.createElement('div');
|
||||
pill.className = 'copied-pill';
|
||||
@@ -564,7 +546,6 @@ const currentUrl = Astro.url.href;
|
||||
pre.appendChild(pill);
|
||||
}
|
||||
|
||||
// Screen reader live region
|
||||
if (!pre.querySelector('.sr-live')) {
|
||||
const live = document.createElement('div');
|
||||
live.className = 'sr-live';
|
||||
|
||||
Reference in New Issue
Block a user