Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions my-sonicjs-app/migrations/032_redirect_plugin.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
-- Redirect Management Plugin Migration
-- Version: 1.0.0
-- Description: Initialize redirect management plugin with redirects and analytics tables

-- Insert plugin entry into plugins table
INSERT INTO plugins (
id,
name,
display_name,
description,
version,
author,
category,
status,
settings,
installed_at,
last_updated
) VALUES (
'redirect-management',
'redirect-management',
'Redirect Management',
'URL redirect management with exact, partial, and regex matching',
'1.0.0',
'SonicJS Community',
'utilities',
'inactive',
json('{
"enabled": true
}'),
strftime('%s', 'now') * 1000,
strftime('%s', 'now') * 1000
)
ON CONFLICT(id) DO UPDATE SET
display_name = excluded.display_name,
description = excluded.description,
version = excluded.version,
updated_at = excluded.last_updated;

-- Create redirects table
CREATE TABLE IF NOT EXISTS redirects (
id TEXT PRIMARY KEY,
source TEXT NOT NULL,
destination TEXT NOT NULL,
match_type INTEGER NOT NULL DEFAULT 0,
status_code INTEGER NOT NULL DEFAULT 301,
is_active INTEGER NOT NULL DEFAULT 1,
created_by TEXT NOT NULL REFERENCES users(id),
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);

-- Create indexes for redirects table
CREATE INDEX IF NOT EXISTS idx_redirects_source ON redirects(source);
CREATE INDEX IF NOT EXISTS idx_redirects_active ON redirects(is_active);
CREATE INDEX IF NOT EXISTS idx_redirects_match_type ON redirects(match_type);

-- Create redirect_analytics table
CREATE TABLE IF NOT EXISTS redirect_analytics (
id TEXT PRIMARY KEY,
redirect_id TEXT NOT NULL REFERENCES redirects(id) ON DELETE CASCADE,
hit_count INTEGER NOT NULL DEFAULT 0,
last_hit_at INTEGER,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);

-- Create UNIQUE index for redirect_analytics table (required for ON CONFLICT)
CREATE UNIQUE INDEX IF NOT EXISTS idx_redirect_analytics_redirect_id ON redirect_analytics(redirect_id);

-- Insert sample redirect data
-- Get the first active admin user ID (or use a placeholder)
INSERT OR IGNORE INTO redirects (id, source, destination, match_type, status_code, is_active, created_by, created_at, updated_at)
SELECT
'redirect-1',
'/old-page',
'/new-page',
0,
301,
1,
COALESCE((SELECT id FROM users WHERE role = 'admin' AND is_active = 1 ORDER BY created_at LIMIT 1), 'admin-user-id'),
strftime('%s', 'now') * 1000,
strftime('%s', 'now') * 1000;

INSERT OR IGNORE INTO redirects (id, source, destination, match_type, status_code, is_active, created_by, created_at, updated_at)
SELECT
'redirect-2',
'/blog/old-post',
'/blog/new-post',
0,
302,
1,
COALESCE((SELECT id FROM users WHERE role = 'admin' AND is_active = 1 ORDER BY created_at LIMIT 1), 'admin-user-id'),
strftime('%s', 'now') * 1000,
strftime('%s', 'now') * 1000;

INSERT OR IGNORE INTO redirects (id, source, destination, match_type, status_code, is_active, created_by, created_at, updated_at)
SELECT
'redirect-3',
'/temp-page',
'/permanent-page',
0,
307,
0,
COALESCE((SELECT id FROM users WHERE role = 'admin' AND is_active = 1 ORDER BY created_at LIMIT 1), 'admin-user-id'),
strftime('%s', 'now') * 1000,
strftime('%s', 'now') * 1000;

INSERT OR IGNORE INTO redirects (id, source, destination, match_type, status_code, is_active, created_by, created_at, updated_at)
SELECT
'redirect-4',
'/gone-page',
'/gone-page',
0,
410,
1,
COALESCE((SELECT id FROM users WHERE role = 'admin' AND is_active = 1 ORDER BY created_at LIMIT 1), 'admin-user-id'),
strftime('%s', 'now') * 1000,
strftime('%s', 'now') * 1000;
6 changes: 6 additions & 0 deletions my-sonicjs-app/migrations/033_redirect_query_params.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Add query parameter handling columns to redirects table
-- Version: 1.0.1
-- Description: Add include_query_params and preserve_query_params columns

ALTER TABLE redirects ADD COLUMN include_query_params INTEGER NOT NULL DEFAULT 0;
ALTER TABLE redirects ADD COLUMN preserve_query_params INTEGER NOT NULL DEFAULT 0;
8 changes: 8 additions & 0 deletions my-sonicjs-app/migrations/034_add_updated_by_to_redirects.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Add updated_by column to track who last modified each redirect
ALTER TABLE redirects ADD COLUMN updated_by TEXT REFERENCES users(id);

-- Add index for JOIN performance
CREATE INDEX IF NOT EXISTS idx_redirects_updated_by ON redirects(updated_by);

-- Backfill existing records (set updated_by = created_by for existing redirects)
UPDATE redirects SET updated_by = created_by WHERE updated_by IS NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Add source_plugin column to track which plugin created the redirect
-- NULL = created via admin UI, otherwise contains plugin ID (e.g., 'qr-code')
ALTER TABLE redirects ADD COLUMN source_plugin TEXT;

-- Add index for filtering by source plugin
CREATE INDEX IF NOT EXISTS idx_redirects_source_plugin ON redirects(source_plugin);
25 changes: 25 additions & 0 deletions my-sonicjs-app/migrations/036_align_redirects_with_cloudflare.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-- Migration 036: Align redirects table with Cloudflare Bulk Redirects options
--
-- Changes:
-- 1. Rename preserve_query_params → preserve_query_string (Cloudflare alignment)
-- 2. Remove include_query_params (not a Cloudflare option) - column remains but unused
-- 3. Add include_subdomains column (Cloudflare: include_subdomains)
-- 4. Add subpath_matching column (Cloudflare: subpath_matching)
-- 5. Add preserve_path_suffix column (Cloudflare: preserve_path_suffix, default true)
--
-- Note: SQLite does not support DROP COLUMN, so include_query_params remains but is unused.
-- Note: SQLite does not support RENAME COLUMN in older versions, so we add new column and migrate data.

-- Step 1: Add preserve_query_string column (copy of preserve_query_params)
ALTER TABLE redirects ADD COLUMN preserve_query_string INTEGER DEFAULT 0;

-- Step 2: Copy existing data from preserve_query_params to preserve_query_string
UPDATE redirects SET preserve_query_string = COALESCE(preserve_query_params, 0);

-- Step 3: Add new Cloudflare-aligned columns
ALTER TABLE redirects ADD COLUMN include_subdomains INTEGER DEFAULT 0;
ALTER TABLE redirects ADD COLUMN subpath_matching INTEGER DEFAULT 0;
ALTER TABLE redirects ADD COLUMN preserve_path_suffix INTEGER DEFAULT 1;

-- Note: The old columns (include_query_params, preserve_query_params) remain in the table
-- but will be ignored by the application. Future migrations can clean these up.
12 changes: 12 additions & 0 deletions my-sonicjs-app/migrations/037_update_redirect_plugin_settings.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Migration: Update redirect-management plugin author and add autoOffloadEnabled setting
-- This updates the plugin metadata after the Cloudflare Bulk Redirects integration

UPDATE plugins
SET
author = 'ahaas',
settings = json_set(
COALESCE(settings, '{}'),
'$.autoOffloadEnabled',
json('false')
)
WHERE id = 'redirect-management';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- Migration: Add soft delete support to redirects table
-- Adds deleted_at column for soft delete timestamp (NULL = not deleted)

ALTER TABLE redirects ADD COLUMN deleted_at INTEGER DEFAULT NULL;

-- Create index for efficient filtering of non-deleted records
CREATE INDEX IF NOT EXISTS idx_redirects_deleted_at ON redirects(deleted_at);
109 changes: 109 additions & 0 deletions my-sonicjs-app/src/plugins/redirect-management/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { PluginBuilder } from '@sonicjs-cms/core'
import type { Plugin, PluginContext } from '@sonicjs-cms/core'
import manifest from './manifest.json'
import { RedirectService } from './services/redirect'
import { createRedirectAdminRoutes } from './routes/admin'
import { createRedirectApiRoutes } from './routes/api'

// Export middleware for direct mounting in app
export { createRedirectMiddleware, invalidateRedirectCache, warmRedirectCache } from './middleware/redirect'

// Export admin routes for mounting
export { createRedirectAdminRoutes } from './routes/admin'

// Export API routes for mounting
export { createRedirectApiRoutes } from './routes/api'

export function createRedirectPlugin(): Plugin {
const builder = PluginBuilder.create({
name: manifest.id,
version: manifest.version,
description: manifest.description
})

builder.metadata({
author: { name: manifest.author },
license: manifest.license,
compatibility: '^2.0.0'
})

// Admin routes
builder.addRoute('/admin/redirects', createRedirectAdminRoutes(), {
description: 'Redirect management admin routes',
requiresAuth: true,
priority: 100
})

// API routes
builder.addRoute('/api/redirects', createRedirectApiRoutes(), {
description: 'Redirect management REST API',
requiresAuth: false, // API handles its own auth via Bearer tokens
priority: 100
})

// Add admin page
builder.addAdminPage(
'/redirect-management/settings',
'Redirect Management Settings',
'RedirectManagementSettings',
{
description: 'Configure redirect settings and manage URL redirects',
icon: 'arrow-right',
permissions: ['admin', 'redirect.manage']
}
)

// Add menu item
builder.addMenuItem('Redirects', '/admin/redirects', {
icon: 'arrow-right',
order: 85,
permissions: ['admin', 'redirect.manage']
})

// Register service
let redirectService: RedirectService | null = null

builder.addService('redirectService', {
implementation: RedirectService,
description: 'Redirect management service for lifecycle and settings',
singleton: true
})

// Lifecycle
builder.lifecycle({
install: async (context: PluginContext) => {
redirectService = new RedirectService(context.db)
await redirectService.install()
console.log('Redirect Management plugin installed successfully')
},
activate: async (context: PluginContext) => {
redirectService = new RedirectService(context.db)
await redirectService.activate()
console.log('Redirect Management plugin activated')
},
deactivate: async (context: PluginContext) => {
if (redirectService) {
await redirectService.deactivate()
redirectService = null
}
console.log('Redirect Management plugin deactivated')
},
uninstall: async (context: PluginContext) => {
if (redirectService) {
await redirectService.uninstall()
redirectService = null
}
console.log('Redirect Management plugin uninstalled')
},
configure: async (config: any) => {
if (redirectService) {
await redirectService.saveSettings(config)
}
console.log('Redirect Management plugin configured', config)
}
})

return builder.build()
}

export default createRedirectPlugin()
37 changes: 37 additions & 0 deletions my-sonicjs-app/src/plugins/redirect-management/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"id": "redirect-management",
"name": "Redirect Management",
"version": "1.0.0",
"description": "URL redirect management with exact, partial, and regex matching",
"author": "ahaas",
"homepage": "https://sonicjs.com/plugins/redirect-management",
"license": "MIT",
"category": "utilities",
"tags": ["redirects", "seo", "urls", "utilities"],
"dependencies": [],
"settings": {
"enabled": {
"type": "boolean",
"label": "Enable Redirects",
"description": "Enable or disable redirect processing",
"default": true
},
"autoOffloadEnabled": {
"type": "boolean",
"label": "Auto-Sync to Cloudflare",
"description": "Automatically sync eligible redirects (EXACT/WILDCARD with 301/302/307/308) to Cloudflare Bulk Redirects. Requires CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID environment variables.",
"default": false
}
},
"permissions": {
"redirect.manage": "Manage redirects and settings",
"redirect.view": "View redirects"
},
"routes": [],
"adminMenu": {
"label": "Redirects",
"icon": "arrow-right",
"path": "/admin/redirects",
"order": 85
}
}
Loading