@nextsparkjs/theme-blog 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/about.md +93 -0
- package/api/authors/[username]/route.ts +150 -0
- package/api/authors/route.ts +63 -0
- package/api/posts/public/route.ts +151 -0
- package/components/ExportPostsButton.tsx +102 -0
- package/components/ImportPostsDialog.tsx +284 -0
- package/components/PostsToolbar.tsx +24 -0
- package/components/editor/FeaturedImageUpload.tsx +185 -0
- package/components/editor/WysiwygEditor.tsx +340 -0
- package/components/index.ts +4 -0
- package/components/public/AuthorBio.tsx +105 -0
- package/components/public/AuthorCard.tsx +130 -0
- package/components/public/BlogFooter.tsx +185 -0
- package/components/public/BlogNavbar.tsx +201 -0
- package/components/public/PostCard.tsx +306 -0
- package/components/public/ReadingProgress.tsx +70 -0
- package/components/public/RelatedPosts.tsx +78 -0
- package/config/app.config.ts +200 -0
- package/config/billing.config.ts +146 -0
- package/config/dashboard.config.ts +333 -0
- package/config/dev.config.ts +48 -0
- package/config/features.config.ts +196 -0
- package/config/flows.config.ts +333 -0
- package/config/permissions.config.ts +101 -0
- package/config/theme.config.ts +128 -0
- package/entities/categories/categories.config.ts +60 -0
- package/entities/categories/categories.fields.ts +115 -0
- package/entities/categories/categories.service.ts +333 -0
- package/entities/categories/categories.types.ts +58 -0
- package/entities/categories/messages/en.json +33 -0
- package/entities/categories/messages/es.json +33 -0
- package/entities/posts/messages/en.json +100 -0
- package/entities/posts/messages/es.json +100 -0
- package/entities/posts/migrations/001_posts_table.sql +110 -0
- package/entities/posts/migrations/002_add_featured.sql +19 -0
- package/entities/posts/migrations/003_post_categories_pivot.sql +47 -0
- package/entities/posts/posts.config.ts +61 -0
- package/entities/posts/posts.fields.ts +234 -0
- package/entities/posts/posts.service.ts +464 -0
- package/entities/posts/posts.types.ts +80 -0
- package/lib/selectors.ts +179 -0
- package/messages/en.json +113 -0
- package/messages/es.json +113 -0
- package/migrations/002_author_profile_fields.sql +37 -0
- package/migrations/003_categories_table.sql +90 -0
- package/migrations/999_sample_data.sql +412 -0
- package/migrations/999_theme_sample_data.sql +1070 -0
- package/package.json +18 -0
- package/permissions-matrix.md +63 -0
- package/styles/article.css +333 -0
- package/styles/components.css +204 -0
- package/styles/globals.css +327 -0
- package/styles/theme.css +167 -0
- package/templates/(public)/author/[username]/page.tsx +247 -0
- package/templates/(public)/authors/page.tsx +161 -0
- package/templates/(public)/layout.tsx +44 -0
- package/templates/(public)/page.tsx +276 -0
- package/templates/(public)/posts/[slug]/page.tsx +342 -0
- package/templates/dashboard/(main)/page.tsx +385 -0
- package/templates/dashboard/(main)/posts/[id]/edit/page.tsx +529 -0
- package/templates/dashboard/(main)/posts/[id]/page.tsx +33 -0
- package/templates/dashboard/(main)/posts/create/page.tsx +353 -0
- package/templates/dashboard/(main)/posts/page.tsx +833 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
-- ============================================================================
|
|
2
|
+
-- Blog Demo Sample Data
|
|
3
|
+
-- Creates 3 authors with their teams, posts, and categories
|
|
4
|
+
-- ============================================================================
|
|
5
|
+
|
|
6
|
+
-- Note: This migration creates users with a default password "Test123!"
|
|
7
|
+
-- In production, users should sign up normally through the auth flow
|
|
8
|
+
|
|
9
|
+
DO $$
|
|
10
|
+
DECLARE
|
|
11
|
+
v_marcos_user_id TEXT;
|
|
12
|
+
v_marcos_team_id TEXT;
|
|
13
|
+
v_lucia_user_id TEXT;
|
|
14
|
+
v_lucia_team_id TEXT;
|
|
15
|
+
v_carlos_user_id TEXT;
|
|
16
|
+
v_carlos_team_id TEXT;
|
|
17
|
+
v_cat_ai TEXT;
|
|
18
|
+
v_cat_saas TEXT;
|
|
19
|
+
v_cat_startups TEXT;
|
|
20
|
+
v_cat_travel TEXT;
|
|
21
|
+
v_cat_remote TEXT;
|
|
22
|
+
v_cat_lifestyle TEXT;
|
|
23
|
+
v_cat_investing TEXT;
|
|
24
|
+
v_cat_finance TEXT;
|
|
25
|
+
v_cat_entrepreneurship TEXT;
|
|
26
|
+
v_post_id TEXT;
|
|
27
|
+
BEGIN
|
|
28
|
+
-- =====================================================
|
|
29
|
+
-- AUTHOR 1: Marcos Tech (Technology & Startups)
|
|
30
|
+
-- =====================================================
|
|
31
|
+
|
|
32
|
+
-- Create user
|
|
33
|
+
INSERT INTO "users" (id, email, "emailVerified", name, "firstName", "lastName",
|
|
34
|
+
username, bio, "social_twitter", "social_linkedin", "role", "createdAt", "updatedAt")
|
|
35
|
+
VALUES (
|
|
36
|
+
gen_random_uuid()::text,
|
|
37
|
+
'blog_author_marcos@nextspark.dev',
|
|
38
|
+
true,
|
|
39
|
+
'Marcos Tech',
|
|
40
|
+
'Marcos',
|
|
41
|
+
'Tech',
|
|
42
|
+
'marcos',
|
|
43
|
+
'Tech entrepreneur and startup advisor. Writing about AI, SaaS, and building products that scale.',
|
|
44
|
+
'https://twitter.com/marcostech',
|
|
45
|
+
'https://linkedin.com/in/marcostech',
|
|
46
|
+
'member',
|
|
47
|
+
NOW(),
|
|
48
|
+
NOW()
|
|
49
|
+
)
|
|
50
|
+
ON CONFLICT (email) DO UPDATE SET username = 'marcos'
|
|
51
|
+
RETURNING id INTO v_marcos_user_id;
|
|
52
|
+
|
|
53
|
+
-- Create team for Marcos
|
|
54
|
+
INSERT INTO "teams" (id, name, slug, "ownerId", "createdAt", "updatedAt")
|
|
55
|
+
VALUES (
|
|
56
|
+
gen_random_uuid()::text,
|
|
57
|
+
'Marcos Tech Blog',
|
|
58
|
+
'marcos-tech-blog',
|
|
59
|
+
v_marcos_user_id,
|
|
60
|
+
NOW(),
|
|
61
|
+
NOW()
|
|
62
|
+
)
|
|
63
|
+
ON CONFLICT DO NOTHING
|
|
64
|
+
RETURNING id INTO v_marcos_team_id;
|
|
65
|
+
|
|
66
|
+
-- Handle case where team already exists
|
|
67
|
+
IF v_marcos_team_id IS NULL THEN
|
|
68
|
+
SELECT id INTO v_marcos_team_id FROM "teams" WHERE slug = 'marcos-tech-blog';
|
|
69
|
+
END IF;
|
|
70
|
+
|
|
71
|
+
-- Add Marcos as owner of his team
|
|
72
|
+
INSERT INTO "team_members" ("teamId", "userId", "role", "createdAt", "updatedAt")
|
|
73
|
+
VALUES (v_marcos_team_id, v_marcos_user_id, 'owner', NOW(), NOW())
|
|
74
|
+
ON CONFLICT DO NOTHING;
|
|
75
|
+
|
|
76
|
+
-- Create Marcos's categories
|
|
77
|
+
INSERT INTO "categories" (id, "teamId", "userId", name, slug, "createdAt", "updatedAt")
|
|
78
|
+
VALUES
|
|
79
|
+
(gen_random_uuid()::text, v_marcos_team_id, v_marcos_user_id, 'AI', 'ai', NOW(), NOW()),
|
|
80
|
+
(gen_random_uuid()::text, v_marcos_team_id, v_marcos_user_id, 'SaaS', 'saas', NOW(), NOW()),
|
|
81
|
+
(gen_random_uuid()::text, v_marcos_team_id, v_marcos_user_id, 'Startups', 'startups', NOW(), NOW())
|
|
82
|
+
ON CONFLICT DO NOTHING;
|
|
83
|
+
|
|
84
|
+
SELECT id INTO v_cat_ai FROM "categories" WHERE "teamId" = v_marcos_team_id AND slug = 'ai' LIMIT 1;
|
|
85
|
+
SELECT id INTO v_cat_saas FROM "categories" WHERE "teamId" = v_marcos_team_id AND slug = 'saas' LIMIT 1;
|
|
86
|
+
SELECT id INTO v_cat_startups FROM "categories" WHERE "teamId" = v_marcos_team_id AND slug = 'startups' LIMIT 1;
|
|
87
|
+
|
|
88
|
+
-- Marcos's Posts (5 total: 4 published, 1 draft)
|
|
89
|
+
-- Post 1: The Future of AI in SaaS (published, featured)
|
|
90
|
+
INSERT INTO "posts" (id, "teamId", "userId", title, slug, excerpt, content, status, "publishedAt", featured, "createdAt", "updatedAt")
|
|
91
|
+
VALUES (
|
|
92
|
+
gen_random_uuid()::text, v_marcos_team_id, v_marcos_user_id,
|
|
93
|
+
'The Future of AI in SaaS',
|
|
94
|
+
'future-of-ai-in-saas',
|
|
95
|
+
'How artificial intelligence is transforming the SaaS landscape and what it means for founders building the next generation of products.',
|
|
96
|
+
'<p>The software-as-a-service industry is undergoing a fundamental transformation driven by artificial intelligence. What started as simple automation is now evolving into intelligent systems that can predict, adapt, and optimize in ways we couldn''t imagine just a few years ago.</p><h2>The AI Revolution</h2><p>From customer support chatbots to predictive analytics, AI is reshaping every aspect of how we build and deliver software. Companies that embrace this shift early will have a significant competitive advantage.</p><h2>Key Trends to Watch</h2><p>1. <strong>Autonomous Customer Success:</strong> AI-powered systems that predict churn before it happens and automatically take action to retain customers.</p><p>2. <strong>Intelligent Product Development:</strong> Using machine learning to analyze user behavior and automatically suggest or even implement feature improvements.</p><p>3. <strong>Hyper-Personalization:</strong> Moving beyond simple segmentation to truly individualized user experiences powered by AI.</p><h2>What This Means for Founders</h2><p>The bar for SaaS products is rising. Users now expect intelligent features as standard. The question isn''t whether to integrate AI, but how quickly you can do it effectively.</p>',
|
|
97
|
+
'published', NOW() - INTERVAL '5 days', true, NOW() - INTERVAL '5 days', NOW()
|
|
98
|
+
)
|
|
99
|
+
ON CONFLICT DO NOTHING
|
|
100
|
+
RETURNING id INTO v_post_id;
|
|
101
|
+
|
|
102
|
+
IF v_post_id IS NOT NULL AND v_cat_ai IS NOT NULL THEN
|
|
103
|
+
INSERT INTO "post_categories" ("postId", "categoryId") VALUES (v_post_id, v_cat_ai) ON CONFLICT DO NOTHING;
|
|
104
|
+
END IF;
|
|
105
|
+
IF v_post_id IS NOT NULL AND v_cat_saas IS NOT NULL THEN
|
|
106
|
+
INSERT INTO "post_categories" ("postId", "categoryId") VALUES (v_post_id, v_cat_saas) ON CONFLICT DO NOTHING;
|
|
107
|
+
END IF;
|
|
108
|
+
|
|
109
|
+
-- Post 2: Building MVPs Fast (published)
|
|
110
|
+
INSERT INTO "posts" (id, "teamId", "userId", title, slug, excerpt, content, status, "publishedAt", featured, "createdAt", "updatedAt")
|
|
111
|
+
VALUES (
|
|
112
|
+
gen_random_uuid()::text, v_marcos_team_id, v_marcos_user_id,
|
|
113
|
+
'Building MVPs Fast: A Practical Guide',
|
|
114
|
+
'building-mvps-fast',
|
|
115
|
+
'Learn the proven strategies for getting your minimum viable product to market in weeks, not months.',
|
|
116
|
+
'<p>Speed is everything in the startup world. The faster you can validate your idea with real users, the faster you can iterate and find product-market fit. But how do you build fast without sacrificing quality?</p><h2>The 4-Week MVP Framework</h2><p><strong>Week 1: Define your core value proposition.</strong> What is the ONE thing your product does that makes it valuable? Everything else is a distraction.</p><p><strong>Week 2: Build the absolute minimum.</strong> Focus only on the features that deliver your core value. Nothing else matters for your MVP.</p><p><strong>Week 3: Test with real users.</strong> Get your MVP in front of at least 10 potential customers. Watch how they use it.</p><p><strong>Week 4: Iterate based on feedback.</strong> Make improvements based on actual user behavior, not assumptions.</p><h2>Tools That Help</h2><p>Use no-code tools where possible. Leverage existing APIs. Don''t build what you can buy or integrate. Your goal is to test your unique value proposition, not to reinvent the wheel.</p>',
|
|
117
|
+
'published', NOW() - INTERVAL '10 days', false, NOW() - INTERVAL '10 days', NOW()
|
|
118
|
+
)
|
|
119
|
+
ON CONFLICT DO NOTHING
|
|
120
|
+
RETURNING id INTO v_post_id;
|
|
121
|
+
|
|
122
|
+
IF v_post_id IS NOT NULL AND v_cat_startups IS NOT NULL THEN
|
|
123
|
+
INSERT INTO "post_categories" ("postId", "categoryId") VALUES (v_post_id, v_cat_startups) ON CONFLICT DO NOTHING;
|
|
124
|
+
END IF;
|
|
125
|
+
|
|
126
|
+
-- Post 3: Why TypeScript Won (published)
|
|
127
|
+
INSERT INTO "posts" (id, "teamId", "userId", title, slug, excerpt, content, status, "publishedAt", featured, "createdAt", "updatedAt")
|
|
128
|
+
VALUES (
|
|
129
|
+
gen_random_uuid()::text, v_marcos_team_id, v_marcos_user_id,
|
|
130
|
+
'Why TypeScript Won the JavaScript Wars',
|
|
131
|
+
'why-typescript-won',
|
|
132
|
+
'A deep dive into how TypeScript became the de facto standard for serious JavaScript development.',
|
|
133
|
+
'<p>Five years ago, the debate between TypeScript and JavaScript was heated. Today, it''s essentially over. TypeScript won. But why?</p><h2>Type Safety Matters</h2><p>The main argument for TypeScript has always been type safety. In large codebases, the ability to catch errors at compile time rather than runtime is invaluable. It''s not just about preventing bugs - it''s about developer confidence and velocity.</p><h2>The Developer Experience</h2><p>Modern IDEs with TypeScript provide autocomplete, inline documentation, and refactoring tools that simply aren''t possible with plain JavaScript. This makes developers significantly more productive.</p><h2>The Ecosystem Effect</h2><p>Once major libraries like React, Next.js, and Node.js embraced TypeScript, the network effects kicked in. Now, almost every new JavaScript library ships with TypeScript definitions out of the box.</p><h2>For New Projects</h2><p>If you''re starting a new project today, using TypeScript is a no-brainer. The setup is trivial, the benefits are immediate, and the ecosystem support is comprehensive.</p>',
|
|
134
|
+
'published', NOW() - INTERVAL '15 days', false, NOW() - INTERVAL '15 days', NOW()
|
|
135
|
+
)
|
|
136
|
+
ON CONFLICT DO NOTHING
|
|
137
|
+
RETURNING id INTO v_post_id;
|
|
138
|
+
|
|
139
|
+
IF v_post_id IS NOT NULL AND v_cat_saas IS NOT NULL THEN
|
|
140
|
+
INSERT INTO "post_categories" ("postId", "categoryId") VALUES (v_post_id, v_cat_saas) ON CONFLICT DO NOTHING;
|
|
141
|
+
END IF;
|
|
142
|
+
|
|
143
|
+
-- Post 4: Startup Metrics That Matter (published)
|
|
144
|
+
INSERT INTO "posts" (id, "teamId", "userId", title, slug, excerpt, content, status, "publishedAt", featured, "createdAt", "updatedAt")
|
|
145
|
+
VALUES (
|
|
146
|
+
gen_random_uuid()::text, v_marcos_team_id, v_marcos_user_id,
|
|
147
|
+
'Startup Metrics That Actually Matter',
|
|
148
|
+
'startup-metrics-that-matter',
|
|
149
|
+
'Forget vanity metrics. These are the numbers that will determine if your startup succeeds or fails.',
|
|
150
|
+
'<p>Every startup founder obsesses over metrics. Page views, signups, social media followers - these numbers feel good to watch go up. But they''re often meaningless.</p><h2>The Only 5 Metrics You Need</h2><p><strong>1. Monthly Recurring Revenue (MRR):</strong> The lifeblood of any SaaS business. Everything else is a proxy for this.</p><p><strong>2. Customer Acquisition Cost (CAC):</strong> How much you spend to acquire one customer. If this is higher than...</p><p><strong>3. Lifetime Value (LTV):</strong> How much revenue you get from a customer over their lifetime. The LTV:CAC ratio should be at least 3:1.</p><p><strong>4. Churn Rate:</strong> What percentage of customers leave each month. For SaaS, anything above 5% monthly churn is a red flag.</p><p><strong>5. Gross Margin:</strong> Revenue minus cost of goods sold. For SaaS, this should be 80%+ or you have a scaling problem.</p><h2>Focus on What Matters</h2><p>Track these five metrics religiously. Everything else is just noise.</p>',
|
|
151
|
+
'published', NOW() - INTERVAL '20 days', false, NOW() - INTERVAL '20 days', NOW()
|
|
152
|
+
)
|
|
153
|
+
ON CONFLICT DO NOTHING
|
|
154
|
+
RETURNING id INTO v_post_id;
|
|
155
|
+
|
|
156
|
+
IF v_post_id IS NOT NULL AND v_cat_startups IS NOT NULL THEN
|
|
157
|
+
INSERT INTO "post_categories" ("postId", "categoryId") VALUES (v_post_id, v_cat_startups) ON CONFLICT DO NOTHING;
|
|
158
|
+
END IF;
|
|
159
|
+
|
|
160
|
+
-- Post 5: Remote Team Culture (draft)
|
|
161
|
+
INSERT INTO "posts" (id, "teamId", "userId", title, slug, excerpt, content, status, "publishedAt", featured, "createdAt", "updatedAt")
|
|
162
|
+
VALUES (
|
|
163
|
+
gen_random_uuid()::text, v_marcos_team_id, v_marcos_user_id,
|
|
164
|
+
'Building Remote Team Culture',
|
|
165
|
+
'remote-team-culture',
|
|
166
|
+
'Lessons learned from running a fully distributed startup for three years.',
|
|
167
|
+
'<p>Draft in progress... This will cover asynchronous communication, building trust remotely, and maintaining culture without an office.</p>',
|
|
168
|
+
'draft', NULL, false, NOW() - INTERVAL '2 days', NOW()
|
|
169
|
+
)
|
|
170
|
+
ON CONFLICT DO NOTHING;
|
|
171
|
+
|
|
172
|
+
-- =====================================================
|
|
173
|
+
-- AUTHOR 2: Lucia Lifestyle (Travel & Lifestyle)
|
|
174
|
+
-- =====================================================
|
|
175
|
+
|
|
176
|
+
INSERT INTO "users" (id, email, "emailVerified", name, "firstName", "lastName",
|
|
177
|
+
username, bio, "social_twitter", "social_linkedin", "social_website", "role", "createdAt", "updatedAt")
|
|
178
|
+
VALUES (
|
|
179
|
+
gen_random_uuid()::text,
|
|
180
|
+
'blog_author_lucia@nextspark.dev',
|
|
181
|
+
true,
|
|
182
|
+
'Lucia Lifestyle',
|
|
183
|
+
'Lucia',
|
|
184
|
+
'Lifestyle',
|
|
185
|
+
'lucia',
|
|
186
|
+
'Digital nomad exploring the world while working remotely. Coffee addict and sunset chaser.',
|
|
187
|
+
'https://twitter.com/lucialifestyle',
|
|
188
|
+
'https://linkedin.com/in/lucialifestyle',
|
|
189
|
+
'https://lucialifestyle.com',
|
|
190
|
+
'member',
|
|
191
|
+
NOW(),
|
|
192
|
+
NOW()
|
|
193
|
+
)
|
|
194
|
+
ON CONFLICT (email) DO UPDATE SET username = 'lucia'
|
|
195
|
+
RETURNING id INTO v_lucia_user_id;
|
|
196
|
+
|
|
197
|
+
INSERT INTO "teams" (id, name, slug, "ownerId", "createdAt", "updatedAt")
|
|
198
|
+
VALUES (
|
|
199
|
+
gen_random_uuid()::text,
|
|
200
|
+
'Lucia Lifestyle Blog',
|
|
201
|
+
'lucia-lifestyle-blog',
|
|
202
|
+
v_lucia_user_id,
|
|
203
|
+
NOW(),
|
|
204
|
+
NOW()
|
|
205
|
+
)
|
|
206
|
+
ON CONFLICT DO NOTHING
|
|
207
|
+
RETURNING id INTO v_lucia_team_id;
|
|
208
|
+
|
|
209
|
+
IF v_lucia_team_id IS NULL THEN
|
|
210
|
+
SELECT id INTO v_lucia_team_id FROM "teams" WHERE slug = 'lucia-lifestyle-blog';
|
|
211
|
+
END IF;
|
|
212
|
+
|
|
213
|
+
INSERT INTO "team_members" ("teamId", "userId", "role", "createdAt", "updatedAt")
|
|
214
|
+
VALUES (v_lucia_team_id, v_lucia_user_id, 'owner', NOW(), NOW())
|
|
215
|
+
ON CONFLICT DO NOTHING;
|
|
216
|
+
|
|
217
|
+
INSERT INTO "categories" (id, "teamId", "userId", name, slug, "createdAt", "updatedAt")
|
|
218
|
+
VALUES
|
|
219
|
+
(gen_random_uuid()::text, v_lucia_team_id, v_lucia_user_id, 'Travel', 'travel', NOW(), NOW()),
|
|
220
|
+
(gen_random_uuid()::text, v_lucia_team_id, v_lucia_user_id, 'Remote Work', 'remote-work', NOW(), NOW()),
|
|
221
|
+
(gen_random_uuid()::text, v_lucia_team_id, v_lucia_user_id, 'Lifestyle', 'lifestyle', NOW(), NOW())
|
|
222
|
+
ON CONFLICT DO NOTHING;
|
|
223
|
+
|
|
224
|
+
SELECT id INTO v_cat_travel FROM "categories" WHERE "teamId" = v_lucia_team_id AND slug = 'travel' LIMIT 1;
|
|
225
|
+
SELECT id INTO v_cat_remote FROM "categories" WHERE "teamId" = v_lucia_team_id AND slug = 'remote-work' LIMIT 1;
|
|
226
|
+
SELECT id INTO v_cat_lifestyle FROM "categories" WHERE "teamId" = v_lucia_team_id AND slug = 'lifestyle' LIMIT 1;
|
|
227
|
+
|
|
228
|
+
-- Lucia's Posts (4 total: 3 published, 1 draft)
|
|
229
|
+
INSERT INTO "posts" (id, "teamId", "userId", title, slug, excerpt, content, status, "publishedAt", featured, "createdAt", "updatedAt")
|
|
230
|
+
VALUES (
|
|
231
|
+
gen_random_uuid()::text, v_lucia_team_id, v_lucia_user_id,
|
|
232
|
+
'Best Cafes for Remote Work in Lisbon',
|
|
233
|
+
'best-cafes-remote-work-lisbon',
|
|
234
|
+
'A curated guide to the most laptop-friendly cafes in Portugal''s capital.',
|
|
235
|
+
'<p>Lisbon has become a hotspot for digital nomads, and for good reason. The weather is amazing, the cost of living is reasonable, and the cafe scene is incredible.</p><h2>Top 10 Cafes</h2><p><strong>1. Fabrica Coffee Roasters:</strong> The best coffee in Lisbon, hands down. Great wifi, plenty of power outlets, and a atmosphere perfect for focused work.</p><p><strong>2. Copenhagen Coffee Lab:</strong> Multiple locations around the city. Consistently excellent coffee and a laptop-friendly vibe.</p><p><strong>3. Hello, Kristof:</strong> A bit off the beaten path in Anjos, but worth the trip. Local crowd, great prices, and super fast wifi.</p><h2>Remote Work Tips</h2><p>Most cafes in Lisbon are understanding of remote workers, but it''s good etiquette to buy something every 2-3 hours if you''re staying a while. And always ask before taking up a table during lunch rush!</p>',
|
|
236
|
+
'published', NOW() - INTERVAL '3 days', true, NOW() - INTERVAL '3 days', NOW()
|
|
237
|
+
)
|
|
238
|
+
ON CONFLICT DO NOTHING
|
|
239
|
+
RETURNING id INTO v_post_id;
|
|
240
|
+
|
|
241
|
+
IF v_post_id IS NOT NULL AND v_cat_travel IS NOT NULL THEN
|
|
242
|
+
INSERT INTO "post_categories" ("postId", "categoryId") VALUES (v_post_id, v_cat_travel) ON CONFLICT DO NOTHING;
|
|
243
|
+
END IF;
|
|
244
|
+
IF v_post_id IS NOT NULL AND v_cat_remote IS NOT NULL THEN
|
|
245
|
+
INSERT INTO "post_categories" ("postId", "categoryId") VALUES (v_post_id, v_cat_remote) ON CONFLICT DO NOTHING;
|
|
246
|
+
END IF;
|
|
247
|
+
|
|
248
|
+
INSERT INTO "posts" (id, "teamId", "userId", title, slug, excerpt, content, status, "publishedAt", featured, "createdAt", "updatedAt")
|
|
249
|
+
VALUES (
|
|
250
|
+
gen_random_uuid()::text, v_lucia_team_id, v_lucia_user_id,
|
|
251
|
+
'Digital Nomad Essentials: What to Pack',
|
|
252
|
+
'digital-nomad-essentials',
|
|
253
|
+
'Everything you need in your backpack for location-independent work.',
|
|
254
|
+
'<p>After 3 years of nomadic living across 30+ countries, I''ve refined my packing list to the essentials. Here''s what actually matters.</p><h2>The Ultimate Packing List</h2><p><strong>Tech Essentials:</strong></p><ul><li>13" MacBook Pro (or similar lightweight laptop)</li><li>Portable SSD for backups</li><li>Universal travel adapter</li><li>Power bank (20,000mAh minimum)</li><li>Noise-cancelling headphones</li></ul><p><strong>Work Setup:</strong></p><ul><li>Portable laptop stand</li><li>Compact wireless mouse</li><li>Microfiber cleaning cloth</li></ul><h2>What I Don''t Pack Anymore</h2><p>After years of trial and error, I''ve stopped bringing: multiple cables (USB-C is enough), books (Kindle is better), and more than one week of clothes (you can do laundry anywhere).</p>',
|
|
255
|
+
'published', NOW() - INTERVAL '8 days', false, NOW() - INTERVAL '8 days', NOW()
|
|
256
|
+
)
|
|
257
|
+
ON CONFLICT DO NOTHING
|
|
258
|
+
RETURNING id INTO v_post_id;
|
|
259
|
+
|
|
260
|
+
IF v_post_id IS NOT NULL AND v_cat_remote IS NOT NULL THEN
|
|
261
|
+
INSERT INTO "post_categories" ("postId", "categoryId") VALUES (v_post_id, v_cat_remote) ON CONFLICT DO NOTHING;
|
|
262
|
+
END IF;
|
|
263
|
+
|
|
264
|
+
INSERT INTO "posts" (id, "teamId", "userId", title, slug, excerpt, content, status, "publishedAt", featured, "createdAt", "updatedAt")
|
|
265
|
+
VALUES (
|
|
266
|
+
gen_random_uuid()::text, v_lucia_team_id, v_lucia_user_id,
|
|
267
|
+
'Hidden Gems of Southeast Asia',
|
|
268
|
+
'hidden-gems-southeast-asia',
|
|
269
|
+
'Off-the-beaten-path destinations that most tourists miss.',
|
|
270
|
+
'<p>Southeast Asia offers incredible diversity, but most travelers stick to the same circuit: Bangkok, Angkor Wat, Ha Long Bay. Here are some places that deserve more attention.</p><h2>Beyond the Tourist Trail</h2><p><strong>1. Koh Rong Samloem, Cambodia:</strong> Like Koh Phi Phi was 20 years ago. Pristine beaches, bioluminescent plankton, and very few tourists.</p><p><strong>2. Pai, Thailand:</strong> A mountain town in northern Thailand with a creative vibe, great coffee, and stunning nature. Skip Chiang Mai and come here instead.</p><p><strong>3. Kampot, Cambodia:</strong> A riverside town known for its pepper farms and laid-back atmosphere. Perfect for slowing down.</p><h2>Why Go Off the Beaten Path</h2><p>You''ll have more authentic experiences, spend less money, and avoid the crowds. Plus, you''re supporting local economies that really need it.</p>',
|
|
271
|
+
'published', NOW() - INTERVAL '12 days', false, NOW() - INTERVAL '12 days', NOW()
|
|
272
|
+
)
|
|
273
|
+
ON CONFLICT DO NOTHING
|
|
274
|
+
RETURNING id INTO v_post_id;
|
|
275
|
+
|
|
276
|
+
IF v_post_id IS NOT NULL AND v_cat_travel IS NOT NULL THEN
|
|
277
|
+
INSERT INTO "post_categories" ("postId", "categoryId") VALUES (v_post_id, v_cat_travel) ON CONFLICT DO NOTHING;
|
|
278
|
+
END IF;
|
|
279
|
+
|
|
280
|
+
INSERT INTO "posts" (id, "teamId", "userId", title, slug, excerpt, content, status, "publishedAt", featured, "createdAt", "updatedAt")
|
|
281
|
+
VALUES (
|
|
282
|
+
gen_random_uuid()::text, v_lucia_team_id, v_lucia_user_id,
|
|
283
|
+
'Work-Life Balance Tips for Remote Workers',
|
|
284
|
+
'work-life-balance-remote',
|
|
285
|
+
'How to avoid burnout when your office is everywhere.',
|
|
286
|
+
'<p>Draft in progress... Will cover setting boundaries, maintaining routines, and knowing when to log off.</p>',
|
|
287
|
+
'draft', NULL, false, NOW() - INTERVAL '1 day', NOW()
|
|
288
|
+
)
|
|
289
|
+
ON CONFLICT DO NOTHING;
|
|
290
|
+
|
|
291
|
+
-- =====================================================
|
|
292
|
+
-- AUTHOR 3: Carlos Finance (Business & Finance)
|
|
293
|
+
-- =====================================================
|
|
294
|
+
|
|
295
|
+
INSERT INTO "users" (id, email, "emailVerified", name, "firstName", "lastName",
|
|
296
|
+
username, bio, "social_twitter", "social_linkedin", "role", "createdAt", "updatedAt")
|
|
297
|
+
VALUES (
|
|
298
|
+
gen_random_uuid()::text,
|
|
299
|
+
'blog_author_carlos@nextspark.dev',
|
|
300
|
+
true,
|
|
301
|
+
'Carlos Finance',
|
|
302
|
+
'Carlos',
|
|
303
|
+
'Finance',
|
|
304
|
+
'carlos',
|
|
305
|
+
'Former investment banker turned financial educator. Making personal finance accessible to everyone.',
|
|
306
|
+
'https://twitter.com/carlosfinance',
|
|
307
|
+
'https://linkedin.com/in/carlosfinance',
|
|
308
|
+
'member',
|
|
309
|
+
NOW(),
|
|
310
|
+
NOW()
|
|
311
|
+
)
|
|
312
|
+
ON CONFLICT (email) DO UPDATE SET username = 'carlos'
|
|
313
|
+
RETURNING id INTO v_carlos_user_id;
|
|
314
|
+
|
|
315
|
+
INSERT INTO "teams" (id, name, slug, "ownerId", "createdAt", "updatedAt")
|
|
316
|
+
VALUES (
|
|
317
|
+
gen_random_uuid()::text,
|
|
318
|
+
'Carlos Finance Blog',
|
|
319
|
+
'carlos-finance-blog',
|
|
320
|
+
v_carlos_user_id,
|
|
321
|
+
NOW(),
|
|
322
|
+
NOW()
|
|
323
|
+
)
|
|
324
|
+
ON CONFLICT DO NOTHING
|
|
325
|
+
RETURNING id INTO v_carlos_team_id;
|
|
326
|
+
|
|
327
|
+
IF v_carlos_team_id IS NULL THEN
|
|
328
|
+
SELECT id INTO v_carlos_team_id FROM "teams" WHERE slug = 'carlos-finance-blog';
|
|
329
|
+
END IF;
|
|
330
|
+
|
|
331
|
+
INSERT INTO "team_members" ("teamId", "userId", "role", "createdAt", "updatedAt")
|
|
332
|
+
VALUES (v_carlos_team_id, v_carlos_user_id, 'owner', NOW(), NOW())
|
|
333
|
+
ON CONFLICT DO NOTHING;
|
|
334
|
+
|
|
335
|
+
INSERT INTO "categories" (id, "teamId", "userId", name, slug, "createdAt", "updatedAt")
|
|
336
|
+
VALUES
|
|
337
|
+
(gen_random_uuid()::text, v_carlos_team_id, v_carlos_user_id, 'Investing', 'investing', NOW(), NOW()),
|
|
338
|
+
(gen_random_uuid()::text, v_carlos_team_id, v_carlos_user_id, 'Personal Finance', 'personal-finance', NOW(), NOW()),
|
|
339
|
+
(gen_random_uuid()::text, v_carlos_team_id, v_carlos_user_id, 'Entrepreneurship', 'entrepreneurship', NOW(), NOW())
|
|
340
|
+
ON CONFLICT DO NOTHING;
|
|
341
|
+
|
|
342
|
+
SELECT id INTO v_cat_investing FROM "categories" WHERE "teamId" = v_carlos_team_id AND slug = 'investing' LIMIT 1;
|
|
343
|
+
SELECT id INTO v_cat_finance FROM "categories" WHERE "teamId" = v_carlos_team_id AND slug = 'personal-finance' LIMIT 1;
|
|
344
|
+
SELECT id INTO v_cat_entrepreneurship FROM "categories" WHERE "teamId" = v_carlos_team_id AND slug = 'entrepreneurship' LIMIT 1;
|
|
345
|
+
|
|
346
|
+
-- Carlos's Posts (4 total: 3 published, 1 draft)
|
|
347
|
+
INSERT INTO "posts" (id, "teamId", "userId", title, slug, excerpt, content, status, "publishedAt", featured, "createdAt", "updatedAt")
|
|
348
|
+
VALUES (
|
|
349
|
+
gen_random_uuid()::text, v_carlos_team_id, v_carlos_user_id,
|
|
350
|
+
'Investing 101: A Beginner''s Guide',
|
|
351
|
+
'investing-101-beginners-guide',
|
|
352
|
+
'Everything you need to know to start building wealth through smart investing.',
|
|
353
|
+
'<p>Investing can seem intimidating if you''re just starting out. But the basics are actually quite simple, and the sooner you start, the better.</p><h2>Start Simple</h2><p>The best time to start investing was yesterday. The second best time is today. Thanks to compound interest, even small amounts invested early can grow significantly over time.</p><h2>The Three-Fund Portfolio</h2><p>For most people, a simple three-fund portfolio is all you need:</p><ul><li>60% Total Stock Market Index Fund</li><li>30% International Stock Market Index Fund</li><li>10% Bond Index Fund</li></ul><p>Adjust the percentages based on your age and risk tolerance, but this basic framework works for almost everyone.</p><h2>Key Principles</h2><p><strong>1. Start early:</strong> Time in the market beats timing the market.</p><p><strong>2. Invest regularly:</strong> Set up automatic monthly transfers.</p><p><strong>3. Keep fees low:</strong> Choose index funds over actively managed funds.</p><p><strong>4. Don''t panic sell:</strong> Market downturns are normal and temporary.</p>',
|
|
354
|
+
'published', NOW() - INTERVAL '4 days', true, NOW() - INTERVAL '4 days', NOW()
|
|
355
|
+
)
|
|
356
|
+
ON CONFLICT DO NOTHING
|
|
357
|
+
RETURNING id INTO v_post_id;
|
|
358
|
+
|
|
359
|
+
IF v_post_id IS NOT NULL AND v_cat_investing IS NOT NULL THEN
|
|
360
|
+
INSERT INTO "post_categories" ("postId", "categoryId") VALUES (v_post_id, v_cat_investing) ON CONFLICT DO NOTHING;
|
|
361
|
+
END IF;
|
|
362
|
+
|
|
363
|
+
INSERT INTO "posts" (id, "teamId", "userId", title, slug, excerpt, content, status, "publishedAt", featured, "createdAt", "updatedAt")
|
|
364
|
+
VALUES (
|
|
365
|
+
gen_random_uuid()::text, v_carlos_team_id, v_carlos_user_id,
|
|
366
|
+
'Side Hustles That Actually Work in 2024',
|
|
367
|
+
'side-hustles-that-work-2024',
|
|
368
|
+
'Realistic ways to earn extra income without quitting your day job.',
|
|
369
|
+
'<p>The gig economy has evolved significantly. Here are side hustles that actually generate meaningful income in 2024.</p><h2>Top 5 Side Hustles</h2><p><strong>1. Freelance Consulting:</strong> Use your professional expertise to consult for other companies. Rates: $100-$500/hour depending on your field.</p><p><strong>2. Online Course Creation:</strong> Package your knowledge into a course. Initial work is significant, but it can generate passive income long-term.</p><p><strong>3. Technical Writing:</strong> Many companies pay well for clear, technical documentation. Rates: $50-$150/hour.</p><p><strong>4. Web Development/Design:</strong> Always in demand. Build websites for small businesses. Projects: $2,000-$10,000 each.</p><p><strong>5. Rental Income:</strong> Rent out a spare room on Airbnb or your parking space. Passive income once set up.</p><h2>What Doesn''t Work</h2><p>Avoid: Surveys ($5/hour), most multi-level marketing schemes, and "make money fast" courses. Focus on leveraging your existing skills.</p>',
|
|
370
|
+
'published', NOW() - INTERVAL '9 days', false, NOW() - INTERVAL '9 days', NOW()
|
|
371
|
+
)
|
|
372
|
+
ON CONFLICT DO NOTHING
|
|
373
|
+
RETURNING id INTO v_post_id;
|
|
374
|
+
|
|
375
|
+
IF v_post_id IS NOT NULL AND v_cat_entrepreneurship IS NOT NULL THEN
|
|
376
|
+
INSERT INTO "post_categories" ("postId", "categoryId") VALUES (v_post_id, v_cat_entrepreneurship) ON CONFLICT DO NOTHING;
|
|
377
|
+
END IF;
|
|
378
|
+
|
|
379
|
+
INSERT INTO "posts" (id, "teamId", "userId", title, slug, excerpt, content, status, "publishedAt", featured, "createdAt", "updatedAt")
|
|
380
|
+
VALUES (
|
|
381
|
+
gen_random_uuid()::text, v_carlos_team_id, v_carlos_user_id,
|
|
382
|
+
'Understanding Crypto Markets',
|
|
383
|
+
'understanding-crypto-markets',
|
|
384
|
+
'A balanced view on cryptocurrency investing: risks, rewards, and reality.',
|
|
385
|
+
'<p>Cryptocurrency has gone from niche to mainstream, but that doesn''t mean everyone should invest. Here''s what you need to know.</p><h2>The Basics</h2><p>Before investing in crypto, understand that it''s highly speculative and volatile. Never invest more than you can afford to lose completely.</p><h2>If You Do Invest</h2><p><strong>Stick to the majors:</strong> Bitcoin and Ethereum are the only ones with established track records. Everything else is extremely high risk.</p><p><strong>Don''t try to time it:</strong> If you believe in crypto long-term, dollar-cost average your way in over time.</p><p><strong>Secure your holdings:</strong> Use a hardware wallet for any significant amounts. Exchanges get hacked.</p><h2>The Reality</h2><p>Crypto might be the future of finance, or it might not. No one knows. Treat it as a small, speculative part of a diversified portfolio, not as your path to quick riches.</p>',
|
|
386
|
+
'published', NOW() - INTERVAL '14 days', false, NOW() - INTERVAL '14 days', NOW()
|
|
387
|
+
)
|
|
388
|
+
ON CONFLICT DO NOTHING
|
|
389
|
+
RETURNING id INTO v_post_id;
|
|
390
|
+
|
|
391
|
+
IF v_post_id IS NOT NULL AND v_cat_investing IS NOT NULL THEN
|
|
392
|
+
INSERT INTO "post_categories" ("postId", "categoryId") VALUES (v_post_id, v_cat_investing) ON CONFLICT DO NOTHING;
|
|
393
|
+
END IF;
|
|
394
|
+
|
|
395
|
+
INSERT INTO "posts" (id, "teamId", "userId", title, slug, excerpt, content, status, "publishedAt", featured, "createdAt", "updatedAt")
|
|
396
|
+
VALUES (
|
|
397
|
+
gen_random_uuid()::text, v_carlos_team_id, v_carlos_user_id,
|
|
398
|
+
'Building Passive Income Streams',
|
|
399
|
+
'building-passive-income',
|
|
400
|
+
'How to create income that works while you sleep.',
|
|
401
|
+
'<p>Draft in progress... Will cover dividend investing, rental properties, and digital products.</p>',
|
|
402
|
+
'draft', NULL, false, NOW() - INTERVAL '3 days', NOW()
|
|
403
|
+
)
|
|
404
|
+
ON CONFLICT DO NOTHING;
|
|
405
|
+
|
|
406
|
+
END $$;
|
|
407
|
+
|
|
408
|
+
-- Log completion
|
|
409
|
+
DO $$
|
|
410
|
+
BEGIN
|
|
411
|
+
RAISE NOTICE 'Sample data migration complete: 3 authors with 13 posts (10 published, 3 drafts) created';
|
|
412
|
+
END $$;
|