Skip to content

Commit aa1986a

Browse files
committed
Revert empty content message & add content migration script
- Remove 'No content available' message - blank pages should be blank - Add migration API to fix pages with null content but existing versions - Migrate content from latest version back to page content field - Fix data model inconsistency where content exists in versions but not on page
1 parent 4b9099f commit aa1986a

File tree

2 files changed

+220
-17
lines changed

2 files changed

+220
-17
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { initAdmin } from '../../../firebase/admin';
3+
import { getCollectionName } from '../../../utils/environmentConfig';
4+
5+
/**
6+
* Migration API: Fix pages with null content but existing versions
7+
*
8+
* This API finds pages where:
9+
* - Page content is null
10+
* - But versions exist with actual content
11+
* - Migrates the latest version content back to the page
12+
*
13+
* Usage: GET /api/dev/migrate-version-content?dry-run=true
14+
*/
15+
16+
interface MigrationResult {
17+
pageId: string;
18+
title: string;
19+
status: 'migrated' | 'skipped' | 'error';
20+
reason?: string;
21+
contentLength?: number;
22+
versionId?: string;
23+
}
24+
25+
export async function GET(request: NextRequest) {
26+
try {
27+
const { searchParams } = new URL(request.url);
28+
const dryRun = searchParams.get('dry-run') === 'true';
29+
const limit = parseInt(searchParams.get('limit') || '50');
30+
const specificPageId = searchParams.get('pageId');
31+
32+
console.log('🔄 Starting version content migration', {
33+
dryRun,
34+
limit,
35+
specificPageId
36+
});
37+
38+
const admin = initAdmin();
39+
const db = admin.firestore();
40+
const pagesCollection = getCollectionName('pages');
41+
42+
// Build query
43+
let pagesQuery = db.collection(pagesCollection);
44+
45+
if (specificPageId) {
46+
// Migrate specific page
47+
const pageDoc = await db.collection(pagesCollection).doc(specificPageId).get();
48+
if (!pageDoc.exists) {
49+
return NextResponse.json({
50+
error: 'Page not found',
51+
pageId: specificPageId
52+
}, { status: 404 });
53+
}
54+
55+
const result = await migratePage(db, specificPageId, pageDoc.data()!, dryRun);
56+
return NextResponse.json({
57+
success: true,
58+
dryRun,
59+
results: [result],
60+
summary: {
61+
total: 1,
62+
migrated: result.status === 'migrated' ? 1 : 0,
63+
skipped: result.status === 'skipped' ? 1 : 0,
64+
errors: result.status === 'error' ? 1 : 0
65+
}
66+
});
67+
}
68+
69+
// Find pages with null content
70+
const pagesSnapshot = await pagesQuery
71+
.where('content', '==', null)
72+
.limit(limit)
73+
.get();
74+
75+
console.log(`📊 Found ${pagesSnapshot.size} pages with null content`);
76+
77+
const results: MigrationResult[] = [];
78+
const summary = {
79+
total: 0,
80+
migrated: 0,
81+
skipped: 0,
82+
errors: 0
83+
};
84+
85+
// Process each page
86+
for (const pageDoc of pagesSnapshot.docs) {
87+
const pageData = pageDoc.data();
88+
const result = await migratePage(db, pageDoc.id, pageData, dryRun);
89+
90+
results.push(result);
91+
summary.total++;
92+
93+
if (result.status === 'migrated') summary.migrated++;
94+
else if (result.status === 'skipped') summary.skipped++;
95+
else if (result.status === 'error') summary.errors++;
96+
}
97+
98+
return NextResponse.json({
99+
success: true,
100+
dryRun,
101+
results,
102+
summary,
103+
instructions: dryRun ? [
104+
'🔍 DRY RUN COMPLETE - No changes made',
105+
'📊 Review the results above',
106+
'🚀 To execute migration: Remove ?dry-run=true from URL',
107+
'⚠️ Always backup data before running live migration'
108+
] : [
109+
'✅ MIGRATION COMPLETE',
110+
'📊 Check results above for any errors',
111+
'🔄 Pages with migrated content should now display properly',
112+
'🧹 Consider running again to catch any remaining pages'
113+
]
114+
});
115+
116+
} catch (error) {
117+
console.error('Error in version content migration:', error);
118+
return NextResponse.json({
119+
error: 'Migration failed',
120+
message: error.message
121+
}, { status: 500 });
122+
}
123+
}
124+
125+
async function migratePage(
126+
db: FirebaseFirestore.Firestore,
127+
pageId: string,
128+
pageData: any,
129+
dryRun: boolean
130+
): Promise<MigrationResult> {
131+
try {
132+
console.log(`📄 Processing page ${pageId}: "${pageData.title}"`);
133+
134+
// Check if page already has content
135+
if (pageData.content && pageData.content !== null) {
136+
return {
137+
pageId,
138+
title: pageData.title || 'Untitled',
139+
status: 'skipped',
140+
reason: 'Page already has content'
141+
};
142+
}
143+
144+
// Get versions for this page
145+
const versionsSnapshot = await db
146+
.collection(getCollectionName('pages'))
147+
.doc(pageId)
148+
.collection('versions')
149+
.orderBy('createdAt', 'desc')
150+
.limit(1)
151+
.get();
152+
153+
if (versionsSnapshot.empty) {
154+
return {
155+
pageId,
156+
title: pageData.title || 'Untitled',
157+
status: 'skipped',
158+
reason: 'No versions found'
159+
};
160+
}
161+
162+
// Get the latest version
163+
const latestVersion = versionsSnapshot.docs[0];
164+
const versionData = latestVersion.data();
165+
166+
if (!versionData.content) {
167+
return {
168+
pageId,
169+
title: pageData.title || 'Untitled',
170+
status: 'skipped',
171+
reason: 'Latest version has no content'
172+
};
173+
}
174+
175+
console.log(` 📝 Found content in version ${latestVersion.id}`);
176+
console.log(` 📏 Content length: ${versionData.content.length} characters`);
177+
178+
if (dryRun) {
179+
return {
180+
pageId,
181+
title: pageData.title || 'Untitled',
182+
status: 'migrated',
183+
reason: 'DRY RUN: Would migrate content from version',
184+
contentLength: versionData.content.length,
185+
versionId: latestVersion.id
186+
};
187+
}
188+
189+
// Migrate content from version to page
190+
await db.collection(getCollectionName('pages')).doc(pageId).update({
191+
content: versionData.content,
192+
currentVersion: latestVersion.id,
193+
lastModified: new Date().toISOString(),
194+
migratedFromVersion: latestVersion.id,
195+
migrationTimestamp: new Date().toISOString()
196+
});
197+
198+
console.log(` ✅ Migrated content from version ${latestVersion.id} to page ${pageId}`);
199+
200+
return {
201+
pageId,
202+
title: pageData.title || 'Untitled',
203+
status: 'migrated',
204+
reason: 'Content migrated from latest version',
205+
contentLength: versionData.content.length,
206+
versionId: latestVersion.id
207+
};
208+
209+
} catch (error) {
210+
console.error(` ❌ Error processing page ${pageId}:`, error);
211+
return {
212+
pageId,
213+
title: pageData.title || 'Untitled',
214+
status: 'error',
215+
reason: error.message
216+
};
217+
}
218+
}

app/components/viewer/ContentViewer.tsx

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -147,13 +147,7 @@ const ContentViewer: React.FC<ContentViewerProps> = ({
147147
}`}
148148
data-mode={lineMode}
149149
>
150-
{(!parsedContents || (Array.isArray(parsedContents) && parsedContents.length === 0)) && !isSearch && (
151-
<div className="text-muted-foreground">
152-
<span className="text-sm">No content available</span>
153-
</div>
154-
)}
155-
156-
{parsedContents && parsedContents.length > 0 && (
150+
{parsedContents && (
157151
<ViewerContent
158152
key={`${renderKey}-${lineMode}`}
159153
contents={parsedContents}
@@ -180,16 +174,7 @@ const ContentViewer: React.FC<ContentViewerProps> = ({
180174
}`}
181175
data-mode={lineMode}
182176
>
183-
{(!parsedContents || (Array.isArray(parsedContents) && parsedContents.length === 0)) && !isSearch && (
184-
<div className="text-muted-foreground">
185-
<div className="viewer-paragraph">
186-
{showLineNumbers && <span className="paragraph-number">1</span>}
187-
<span className="viewer-text-content">No content available</span>
188-
</div>
189-
</div>
190-
)}
191-
192-
{parsedContents && parsedContents.length > 0 && (
177+
{parsedContents && (
193178
<ViewerContent
194179
key={`${renderKey}-${lineMode}`}
195180
contents={parsedContents}

0 commit comments

Comments
 (0)