@wakastellar/ui 2.1.2 → 2.3.2

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.
Files changed (123) hide show
  1. package/dist/blocks/apm-overview/index.d.ts +58 -0
  2. package/dist/blocks/cicd-builder/index.d.ts +47 -0
  3. package/dist/blocks/cloud-cost-dashboard/index.d.ts +49 -0
  4. package/dist/blocks/container-orchestrator/index.d.ts +63 -0
  5. package/dist/blocks/database-admin/index.d.ts +84 -0
  6. package/dist/blocks/gitops-sync-status/index.d.ts +45 -0
  7. package/dist/blocks/incident-manager/index.d.ts +44 -0
  8. package/dist/blocks/index.d.ts +10 -0
  9. package/dist/blocks/infrastructure-map/index.d.ts +32 -0
  10. package/dist/blocks/on-call-schedule/index.d.ts +43 -0
  11. package/dist/blocks/release-notes/index.d.ts +49 -0
  12. package/dist/components/index.d.ts +34 -0
  13. package/dist/components/waka-ad-banner/index.d.ts +36 -0
  14. package/dist/components/waka-ad-fallback/index.d.ts +33 -0
  15. package/dist/components/waka-ad-inline/index.d.ts +15 -0
  16. package/dist/components/waka-ad-interstitial/index.d.ts +26 -0
  17. package/dist/components/waka-ad-placeholder/index.d.ts +17 -0
  18. package/dist/components/waka-ad-provider/index.d.ts +103 -0
  19. package/dist/components/waka-ad-sidebar/index.d.ts +18 -0
  20. package/dist/components/waka-ad-sticky-footer/index.d.ts +17 -0
  21. package/dist/components/waka-alert-panel/index.d.ts +45 -0
  22. package/dist/components/waka-artifact-list/index.d.ts +32 -0
  23. package/dist/components/waka-build-matrix/index.d.ts +36 -0
  24. package/dist/components/waka-config-comparator/index.d.ts +37 -0
  25. package/dist/components/waka-container-list/index.d.ts +51 -0
  26. package/dist/components/waka-content-recommendation/index.d.ts +23 -0
  27. package/dist/components/waka-database-card/index.d.ts +46 -0
  28. package/dist/components/waka-dependency-tree/index.d.ts +38 -0
  29. package/dist/components/waka-env-var-editor/index.d.ts +30 -0
  30. package/dist/components/waka-feature-flag-row/index.d.ts +45 -0
  31. package/dist/components/waka-kubernetes-overview/index.d.ts +98 -0
  32. package/dist/components/waka-log-viewer/index.d.ts +38 -0
  33. package/dist/components/waka-migration-list/index.d.ts +36 -0
  34. package/dist/components/waka-outstream-video/index.d.ts +24 -0
  35. package/dist/components/waka-pod-card/index.d.ts +73 -0
  36. package/dist/components/waka-query-explain/index.d.ts +48 -0
  37. package/dist/components/waka-secret-card/index.d.ts +43 -0
  38. package/dist/components/waka-security-scan-result/index.d.ts +45 -0
  39. package/dist/components/waka-service-graph/index.d.ts +44 -0
  40. package/dist/components/waka-sponsored-badge/index.d.ts +20 -0
  41. package/dist/components/waka-sponsored-card/index.d.ts +25 -0
  42. package/dist/components/waka-sponsored-feed/index.d.ts +31 -0
  43. package/dist/components/waka-test-report/index.d.ts +60 -0
  44. package/dist/components/waka-trace-viewer/index.d.ts +36 -0
  45. package/dist/components/waka-video-ad/index.d.ts +32 -0
  46. package/dist/components/waka-video-overlay/index.d.ts +26 -0
  47. package/dist/index.cjs.js +251 -200
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.es.js +47315 -35823
  50. package/dist/utils/security.d.ts +96 -0
  51. package/package.json +4 -4
  52. package/src/blocks/apm-overview/index.tsx +672 -0
  53. package/src/blocks/cicd-builder/index.tsx +738 -0
  54. package/src/blocks/cloud-cost-dashboard/index.tsx +597 -0
  55. package/src/blocks/container-orchestrator/index.tsx +729 -0
  56. package/src/blocks/database-admin/index.tsx +679 -0
  57. package/src/blocks/gitops-sync-status/index.tsx +557 -0
  58. package/src/blocks/incident-manager/index.tsx +586 -0
  59. package/src/blocks/index.ts +119 -0
  60. package/src/blocks/infrastructure-map/index.tsx +638 -0
  61. package/src/blocks/on-call-schedule/index.tsx +615 -0
  62. package/src/blocks/release-notes/index.tsx +643 -0
  63. package/src/blocks/sidebar/index.tsx +6 -6
  64. package/src/components/DataTable/templates/index.tsx +3 -2
  65. package/src/components/index.ts +283 -0
  66. package/src/components/waka-3d-pie-chart/index.tsx +11 -11
  67. package/src/components/waka-achievement-unlock/index.tsx +16 -16
  68. package/src/components/waka-ad-banner/index.tsx +275 -0
  69. package/src/components/waka-ad-fallback/index.tsx +181 -0
  70. package/src/components/waka-ad-inline/index.tsx +103 -0
  71. package/src/components/waka-ad-interstitial/index.tsx +278 -0
  72. package/src/components/waka-ad-placeholder/index.tsx +84 -0
  73. package/src/components/waka-ad-provider/index.tsx +329 -0
  74. package/src/components/waka-ad-sidebar/index.tsx +113 -0
  75. package/src/components/waka-ad-sticky-footer/index.tsx +125 -0
  76. package/src/components/waka-alert-panel/index.tsx +493 -0
  77. package/src/components/waka-artifact-list/index.tsx +416 -0
  78. package/src/components/waka-badge-showcase/index.tsx +12 -11
  79. package/src/components/waka-build-matrix/index.tsx +396 -0
  80. package/src/components/waka-command-bar/index.tsx +2 -1
  81. package/src/components/waka-config-comparator/index.tsx +416 -0
  82. package/src/components/waka-container-list/index.tsx +475 -0
  83. package/src/components/waka-content-recommendation/index.tsx +294 -0
  84. package/src/components/waka-cost-breakdown/index.tsx +10 -10
  85. package/src/components/waka-database-card/index.tsx +473 -0
  86. package/src/components/waka-dependency-tree/index.tsx +542 -0
  87. package/src/components/waka-env-var-editor/index.tsx +417 -0
  88. package/src/components/waka-feature-flag-row/index.tsx +386 -0
  89. package/src/components/waka-funnel-chart/index.tsx +8 -8
  90. package/src/components/waka-health-pulse/index.tsx +6 -6
  91. package/src/components/waka-kubernetes-overview/index.tsx +536 -0
  92. package/src/components/waka-leaderboard/index.tsx +9 -9
  93. package/src/components/waka-log-viewer/index.tsx +386 -0
  94. package/src/components/waka-loot-box/index.tsx +20 -20
  95. package/src/components/waka-migration-list/index.tsx +487 -0
  96. package/src/components/waka-outstream-video/index.tsx +240 -0
  97. package/src/components/waka-player-card/index.tsx +5 -5
  98. package/src/components/waka-pod-card/index.tsx +528 -0
  99. package/src/components/waka-query-explain/index.tsx +657 -0
  100. package/src/components/waka-quota-bar/index.tsx +4 -4
  101. package/src/components/waka-radar-score/index.tsx +10 -10
  102. package/src/components/waka-scratch-card/index.tsx +5 -4
  103. package/src/components/waka-secret-card/index.tsx +371 -0
  104. package/src/components/waka-security-scan-result/index.tsx +473 -0
  105. package/src/components/waka-server-rack/index.tsx +28 -27
  106. package/src/components/waka-service-graph/index.tsx +445 -0
  107. package/src/components/waka-sponsored-badge/index.tsx +97 -0
  108. package/src/components/waka-sponsored-card/index.tsx +275 -0
  109. package/src/components/waka-sponsored-feed/index.tsx +127 -0
  110. package/src/components/waka-spotlight/index.tsx +2 -1
  111. package/src/components/waka-success-explosion/index.tsx +4 -4
  112. package/src/components/waka-test-report/index.tsx +469 -0
  113. package/src/components/waka-trace-viewer/index.tsx +490 -0
  114. package/src/components/waka-video-ad/index.tsx +406 -0
  115. package/src/components/waka-video-overlay/index.tsx +257 -0
  116. package/src/components/waka-xp-bar/index.tsx +13 -13
  117. package/src/styles/base.css +16 -0
  118. package/src/styles/tailwind.preset.js +12 -0
  119. package/src/styles/themes/forest.css +16 -0
  120. package/src/styles/themes/monochrome.css +16 -0
  121. package/src/styles/themes/perpetuity.css +16 -0
  122. package/src/styles/themes/sunset.css +16 -0
  123. package/src/styles/themes/twilight.css +16 -0
@@ -0,0 +1,487 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../../utils/cn"
5
+ import { Badge } from "../badge"
6
+ import { Button } from "../button"
7
+ import { ScrollArea } from "../scroll-area"
8
+ import {
9
+ Tooltip,
10
+ TooltipContent,
11
+ TooltipProvider,
12
+ TooltipTrigger,
13
+ } from "../tooltip"
14
+ import {
15
+ DropdownMenu,
16
+ DropdownMenuContent,
17
+ DropdownMenuItem,
18
+ DropdownMenuSeparator,
19
+ DropdownMenuTrigger,
20
+ } from "../dropdown-menu"
21
+ import {
22
+ Database,
23
+ ArrowUp,
24
+ ArrowDown,
25
+ RotateCcw,
26
+ CheckCircle2,
27
+ XCircle,
28
+ Clock,
29
+ Play,
30
+ Pause,
31
+ AlertTriangle,
32
+ MoreVertical,
33
+ FileCode,
34
+ Calendar,
35
+ User,
36
+ Timer,
37
+ ChevronDown,
38
+ ChevronRight,
39
+ } from "lucide-react"
40
+
41
+ export type MigrationStatus = "pending" | "applied" | "failed" | "rolled_back" | "running"
42
+
43
+ export interface Migration {
44
+ id: string
45
+ version: string
46
+ name: string
47
+ description?: string
48
+ status: MigrationStatus
49
+ appliedAt?: Date
50
+ rolledBackAt?: Date
51
+ duration?: number // in ms
52
+ author?: string
53
+ checksum?: string
54
+ upScript?: string
55
+ downScript?: string
56
+ error?: string
57
+ }
58
+
59
+ export interface WakaMigrationListProps {
60
+ /** List of migrations */
61
+ migrations: Migration[]
62
+ /** Callback when applying a migration */
63
+ onApply?: (migration: Migration) => void
64
+ /** Callback when rolling back a migration */
65
+ onRollback?: (migration: Migration) => void
66
+ /** Callback when viewing migration details */
67
+ onViewDetails?: (migration: Migration) => void
68
+ /** Current database version */
69
+ currentVersion?: string
70
+ /** Show scripts */
71
+ showScripts?: boolean
72
+ /** Title */
73
+ title?: string
74
+ /** Custom class name */
75
+ className?: string
76
+ }
77
+
78
+ const statusConfig: Record<MigrationStatus, { label: string; icon: React.ElementType; color: string; bgColor: string }> = {
79
+ pending: { label: "Pending", icon: Clock, color: "text-muted-foreground", bgColor: "bg-muted" },
80
+ applied: { label: "Applied", icon: CheckCircle2, color: "text-green-500", bgColor: "bg-green-500/10" },
81
+ failed: { label: "Failed", icon: XCircle, color: "text-red-500", bgColor: "bg-red-500/10" },
82
+ rolled_back: { label: "Rolled Back", icon: RotateCcw, color: "text-yellow-500", bgColor: "bg-yellow-500/10" },
83
+ running: { label: "Running", icon: Play, color: "text-blue-500", bgColor: "bg-blue-500/10" },
84
+ }
85
+
86
+ function formatDate(date: Date): string {
87
+ return date.toLocaleDateString("fr-FR", {
88
+ day: "2-digit",
89
+ month: "short",
90
+ year: "numeric",
91
+ hour: "2-digit",
92
+ minute: "2-digit",
93
+ })
94
+ }
95
+
96
+ function formatDuration(ms: number): string {
97
+ if (ms < 1000) return `${ms}ms`
98
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`
99
+ return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`
100
+ }
101
+
102
+ function MigrationRow({
103
+ migration,
104
+ isCurrentVersion,
105
+ onApply,
106
+ onRollback,
107
+ onViewDetails,
108
+ showScripts,
109
+ }: {
110
+ migration: Migration
111
+ isCurrentVersion: boolean
112
+ onApply?: (migration: Migration) => void
113
+ onRollback?: (migration: Migration) => void
114
+ onViewDetails?: (migration: Migration) => void
115
+ showScripts?: boolean
116
+ }) {
117
+ const [expanded, setExpanded] = React.useState(false)
118
+ const statusConf = statusConfig[migration.status]
119
+ const StatusIcon = statusConf.icon
120
+
121
+ const canApply = migration.status === "pending" || migration.status === "rolled_back"
122
+ const canRollback = migration.status === "applied"
123
+
124
+ return (
125
+ <div className={cn(
126
+ "border-b last:border-b-0",
127
+ isCurrentVersion && "bg-primary/5 border-primary/30",
128
+ migration.status === "failed" && "bg-red-500/5"
129
+ )}>
130
+ <div className="flex items-center gap-3 p-3">
131
+ {/* Expand button */}
132
+ {showScripts && (migration.upScript || migration.downScript) && (
133
+ <button
134
+ className="text-muted-foreground hover:text-foreground transition-colors"
135
+ onClick={() => setExpanded(!expanded)}
136
+ >
137
+ {expanded ? (
138
+ <ChevronDown className="h-4 w-4" />
139
+ ) : (
140
+ <ChevronRight className="h-4 w-4" />
141
+ )}
142
+ </button>
143
+ )}
144
+
145
+ {/* Status icon */}
146
+ <div className={cn("p-1.5 rounded", statusConf.bgColor)}>
147
+ <StatusIcon className={cn("h-4 w-4", statusConf.color)} />
148
+ </div>
149
+
150
+ {/* Version and name */}
151
+ <div className="flex-1 min-w-0">
152
+ <div className="flex items-center gap-2">
153
+ <code className="font-semibold text-sm">{migration.version}</code>
154
+ {isCurrentVersion && (
155
+ <Badge className="bg-primary text-xs">Current</Badge>
156
+ )}
157
+ <span className="font-medium truncate">{migration.name}</span>
158
+ </div>
159
+ {migration.description && (
160
+ <p className="text-sm text-muted-foreground truncate mt-0.5">
161
+ {migration.description}
162
+ </p>
163
+ )}
164
+ </div>
165
+
166
+ {/* Metadata */}
167
+ <div className="flex items-center gap-4 text-xs text-muted-foreground shrink-0">
168
+ {migration.author && (
169
+ <span className="flex items-center gap-1">
170
+ <User className="h-3 w-3" />
171
+ {migration.author}
172
+ </span>
173
+ )}
174
+ {migration.appliedAt && (
175
+ <span className="flex items-center gap-1">
176
+ <Calendar className="h-3 w-3" />
177
+ {formatDate(migration.appliedAt)}
178
+ </span>
179
+ )}
180
+ {migration.duration !== undefined && (
181
+ <span className="flex items-center gap-1">
182
+ <Timer className="h-3 w-3" />
183
+ {formatDuration(migration.duration)}
184
+ </span>
185
+ )}
186
+ </div>
187
+
188
+ {/* Status badge */}
189
+ <Badge variant="outline" className={cn("shrink-0", statusConf.color)}>
190
+ {statusConf.label}
191
+ </Badge>
192
+
193
+ {/* Actions */}
194
+ <DropdownMenu>
195
+ <DropdownMenuTrigger asChild>
196
+ <Button variant="ghost" size="sm" className="h-8 w-8 p-0">
197
+ <MoreVertical className="h-4 w-4" />
198
+ </Button>
199
+ </DropdownMenuTrigger>
200
+ <DropdownMenuContent align="end">
201
+ {onViewDetails && (
202
+ <DropdownMenuItem onClick={() => onViewDetails(migration)}>
203
+ <FileCode className="h-4 w-4 mr-2" />
204
+ View Details
205
+ </DropdownMenuItem>
206
+ )}
207
+ {onApply && canApply && (
208
+ <DropdownMenuItem onClick={() => onApply(migration)}>
209
+ <ArrowUp className="h-4 w-4 mr-2" />
210
+ Apply Migration
211
+ </DropdownMenuItem>
212
+ )}
213
+ {onRollback && canRollback && (
214
+ <>
215
+ <DropdownMenuSeparator />
216
+ <DropdownMenuItem
217
+ onClick={() => onRollback(migration)}
218
+ className="text-destructive"
219
+ >
220
+ <ArrowDown className="h-4 w-4 mr-2" />
221
+ Rollback
222
+ </DropdownMenuItem>
223
+ </>
224
+ )}
225
+ </DropdownMenuContent>
226
+ </DropdownMenu>
227
+ </div>
228
+
229
+ {/* Error message */}
230
+ {migration.status === "failed" && migration.error && (
231
+ <div className="px-3 pb-3">
232
+ <div className="flex items-start gap-2 p-2 bg-red-500/10 rounded text-sm text-red-500">
233
+ <AlertTriangle className="h-4 w-4 shrink-0 mt-0.5" />
234
+ <pre className="whitespace-pre-wrap font-mono text-xs">{migration.error}</pre>
235
+ </div>
236
+ </div>
237
+ )}
238
+
239
+ {/* Scripts */}
240
+ {showScripts && expanded && (
241
+ <div className="px-3 pb-3 space-y-3">
242
+ {migration.upScript && (
243
+ <div>
244
+ <div className="flex items-center gap-2 text-xs text-muted-foreground mb-1">
245
+ <ArrowUp className="h-3 w-3" />
246
+ Up Migration
247
+ </div>
248
+ <pre className="p-2 bg-muted rounded text-xs font-mono overflow-x-auto">
249
+ {migration.upScript}
250
+ </pre>
251
+ </div>
252
+ )}
253
+ {migration.downScript && (
254
+ <div>
255
+ <div className="flex items-center gap-2 text-xs text-muted-foreground mb-1">
256
+ <ArrowDown className="h-3 w-3" />
257
+ Down Migration (Rollback)
258
+ </div>
259
+ <pre className="p-2 bg-muted rounded text-xs font-mono overflow-x-auto">
260
+ {migration.downScript}
261
+ </pre>
262
+ </div>
263
+ )}
264
+ </div>
265
+ )}
266
+ </div>
267
+ )
268
+ }
269
+
270
+ export function WakaMigrationList({
271
+ migrations,
272
+ onApply,
273
+ onRollback,
274
+ onViewDetails,
275
+ currentVersion,
276
+ showScripts = false,
277
+ title = "Migrations",
278
+ className,
279
+ }: WakaMigrationListProps) {
280
+ const [filter, setFilter] = React.useState<MigrationStatus | "all">("all")
281
+
282
+ // Calculate stats
283
+ const stats = React.useMemo(() => {
284
+ return {
285
+ total: migrations.length,
286
+ pending: migrations.filter((m) => m.status === "pending").length,
287
+ applied: migrations.filter((m) => m.status === "applied").length,
288
+ failed: migrations.filter((m) => m.status === "failed").length,
289
+ }
290
+ }, [migrations])
291
+
292
+ // Filter migrations
293
+ const filteredMigrations = React.useMemo(() => {
294
+ if (filter === "all") return migrations
295
+ return migrations.filter((m) => m.status === filter)
296
+ }, [migrations, filter])
297
+
298
+ // Sort by version (newest first)
299
+ const sortedMigrations = React.useMemo(() => {
300
+ return [...filteredMigrations].sort((a, b) => b.version.localeCompare(a.version))
301
+ }, [filteredMigrations])
302
+
303
+ return (
304
+ <div className={cn("flex flex-col border rounded-lg bg-background", className)}>
305
+ {/* Header */}
306
+ <div className="flex items-center justify-between gap-4 p-3 border-b">
307
+ <div className="flex items-center gap-3">
308
+ <Database className="h-5 w-5" />
309
+ <h3 className="font-semibold">{title}</h3>
310
+ <Badge variant="secondary">{stats.total}</Badge>
311
+ {currentVersion && (
312
+ <Badge variant="outline" className="text-xs">
313
+ v{currentVersion}
314
+ </Badge>
315
+ )}
316
+ </div>
317
+
318
+ <div className="flex items-center gap-2">
319
+ {stats.pending > 0 && (
320
+ <Badge variant="outline">{stats.pending} pending</Badge>
321
+ )}
322
+ {stats.failed > 0 && (
323
+ <Badge className="bg-red-500">{stats.failed} failed</Badge>
324
+ )}
325
+ </div>
326
+ </div>
327
+
328
+ {/* Filter toolbar */}
329
+ <div className="flex items-center gap-2 p-2 border-b bg-muted/30">
330
+ <span className="text-xs text-muted-foreground">Filter:</span>
331
+ {(["all", "pending", "applied", "failed", "rolled_back"] as const).map((status) => (
332
+ <Button
333
+ key={status}
334
+ variant={filter === status ? "default" : "ghost"}
335
+ size="sm"
336
+ className="h-7 text-xs"
337
+ onClick={() => setFilter(status)}
338
+ >
339
+ {status === "all" ? "All" : statusConfig[status].label}
340
+ </Button>
341
+ ))}
342
+ </div>
343
+
344
+ {/* Migration list */}
345
+ <ScrollArea className="flex-1 max-h-[500px]">
346
+ {sortedMigrations.length === 0 ? (
347
+ <div className="flex flex-col items-center justify-center h-32 text-muted-foreground">
348
+ <Database className="h-8 w-8 mb-2" />
349
+ <span>No migrations found</span>
350
+ </div>
351
+ ) : (
352
+ sortedMigrations.map((migration) => (
353
+ <MigrationRow
354
+ key={migration.id}
355
+ migration={migration}
356
+ isCurrentVersion={migration.version === currentVersion}
357
+ onApply={onApply}
358
+ onRollback={onRollback}
359
+ onViewDetails={onViewDetails}
360
+ showScripts={showScripts}
361
+ />
362
+ ))
363
+ )}
364
+ </ScrollArea>
365
+
366
+ {/* Footer */}
367
+ {stats.pending > 0 && onApply && (
368
+ <div className="p-3 border-t bg-muted/30">
369
+ <Button
370
+ size="sm"
371
+ onClick={() => {
372
+ const pending = migrations.filter((m) => m.status === "pending")
373
+ pending.forEach((m) => onApply(m))
374
+ }}
375
+ >
376
+ <ArrowUp className="h-4 w-4 mr-1" />
377
+ Apply All Pending ({stats.pending})
378
+ </Button>
379
+ </div>
380
+ )}
381
+ </div>
382
+ )
383
+ }
384
+
385
+ // Default sample migrations for demo
386
+ export const defaultMigrations: Migration[] = [
387
+ {
388
+ id: "1",
389
+ version: "20240115_001",
390
+ name: "create_users_table",
391
+ description: "Create users table with basic fields",
392
+ status: "applied",
393
+ appliedAt: new Date(Date.now() - 30 * 24 * 3600000),
394
+ duration: 125,
395
+ author: "john.doe",
396
+ upScript: `CREATE TABLE users (
397
+ id SERIAL PRIMARY KEY,
398
+ email VARCHAR(255) UNIQUE NOT NULL,
399
+ name VARCHAR(255),
400
+ created_at TIMESTAMP DEFAULT NOW()
401
+ );`,
402
+ downScript: "DROP TABLE users;",
403
+ },
404
+ {
405
+ id: "2",
406
+ version: "20240120_001",
407
+ name: "add_users_password",
408
+ description: "Add password hash column to users",
409
+ status: "applied",
410
+ appliedAt: new Date(Date.now() - 25 * 24 * 3600000),
411
+ duration: 45,
412
+ author: "jane.smith",
413
+ upScript: "ALTER TABLE users ADD COLUMN password_hash VARCHAR(255);",
414
+ downScript: "ALTER TABLE users DROP COLUMN password_hash;",
415
+ },
416
+ {
417
+ id: "3",
418
+ version: "20240125_001",
419
+ name: "create_posts_table",
420
+ description: "Create posts table with foreign key to users",
421
+ status: "applied",
422
+ appliedAt: new Date(Date.now() - 20 * 24 * 3600000),
423
+ duration: 230,
424
+ author: "john.doe",
425
+ upScript: `CREATE TABLE posts (
426
+ id SERIAL PRIMARY KEY,
427
+ user_id INTEGER REFERENCES users(id),
428
+ title VARCHAR(255) NOT NULL,
429
+ content TEXT,
430
+ created_at TIMESTAMP DEFAULT NOW()
431
+ );
432
+ CREATE INDEX idx_posts_user_id ON posts(user_id);`,
433
+ downScript: `DROP INDEX idx_posts_user_id;
434
+ DROP TABLE posts;`,
435
+ },
436
+ {
437
+ id: "4",
438
+ version: "20240130_001",
439
+ name: "add_posts_status",
440
+ description: "Add status enum to posts",
441
+ status: "failed",
442
+ author: "jane.smith",
443
+ error: "ERROR: type \"post_status\" already exists\n at migration.up (migrations/20240130_001.ts:5:3)",
444
+ upScript: `CREATE TYPE post_status AS ENUM ('draft', 'published', 'archived');
445
+ ALTER TABLE posts ADD COLUMN status post_status DEFAULT 'draft';`,
446
+ downScript: `ALTER TABLE posts DROP COLUMN status;
447
+ DROP TYPE post_status;`,
448
+ },
449
+ {
450
+ id: "5",
451
+ version: "20240205_001",
452
+ name: "create_comments_table",
453
+ description: "Create comments table",
454
+ status: "pending",
455
+ author: "john.doe",
456
+ upScript: `CREATE TABLE comments (
457
+ id SERIAL PRIMARY KEY,
458
+ post_id INTEGER REFERENCES posts(id),
459
+ user_id INTEGER REFERENCES users(id),
460
+ content TEXT NOT NULL,
461
+ created_at TIMESTAMP DEFAULT NOW()
462
+ );`,
463
+ downScript: "DROP TABLE comments;",
464
+ },
465
+ {
466
+ id: "6",
467
+ version: "20240210_001",
468
+ name: "add_full_text_search",
469
+ description: "Add full-text search to posts",
470
+ status: "pending",
471
+ author: "jane.smith",
472
+ upScript: `ALTER TABLE posts ADD COLUMN search_vector tsvector;
473
+ CREATE INDEX idx_posts_search ON posts USING GIN(search_vector);
474
+ CREATE FUNCTION update_posts_search() RETURNS trigger AS $$
475
+ BEGIN
476
+ NEW.search_vector := to_tsvector('english', coalesce(NEW.title, '') || ' ' || coalesce(NEW.content, ''));
477
+ RETURN NEW;
478
+ END;
479
+ $$ LANGUAGE plpgsql;
480
+ CREATE TRIGGER posts_search_update BEFORE INSERT OR UPDATE ON posts
481
+ FOR EACH ROW EXECUTE FUNCTION update_posts_search();`,
482
+ downScript: `DROP TRIGGER posts_search_update ON posts;
483
+ DROP FUNCTION update_posts_search;
484
+ DROP INDEX idx_posts_search;
485
+ ALTER TABLE posts DROP COLUMN search_vector;`,
486
+ },
487
+ ]