322 lines
16 KiB
Transact-SQL
322 lines
16 KiB
Transact-SQL
-- ============================================================
|
|
-- 001_ChannelConfig.sql
|
|
-- Move channel provider configuration from appsettings.json
|
|
-- into database-driven configuration.
|
|
-- ============================================================
|
|
|
|
-- ── Table ──────────────────────────────────────────────────
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'tbChannelConfig')
|
|
BEGIN
|
|
CREATE TABLE dbo.tbChannelConfig (
|
|
chcChannelType VARCHAR(50) NOT NULL PRIMARY KEY,
|
|
chcDisplayName NVARCHAR(100) NOT NULL,
|
|
chcDescription NVARCHAR(500) NULL,
|
|
chcIcon VARCHAR(50) NULL,
|
|
chcColor VARCHAR(20) NULL,
|
|
chcEnabled BIT NOT NULL DEFAULT 1,
|
|
chcIsStub BIT NOT NULL DEFAULT 1,
|
|
chcEndpoint VARCHAR(500) NULL,
|
|
chcInternalKey VARCHAR(500) NULL,
|
|
chcMinDailyBudget DECIMAL(10,2) NOT NULL DEFAULT 5.00,
|
|
chcMinMonthlyBudget DECIMAL(10,2) NOT NULL DEFAULT 150.00,
|
|
chcSupportedObjectives NVARCHAR(500) NULL, -- JSON array: ["sales","leads","traffic"]
|
|
chcSupportedCreativeFormats NVARCHAR(500) NULL, -- JSON array: ["text","image","video"]
|
|
chcApprovalEstimateHours INT NOT NULL DEFAULT 24,
|
|
chcMetricsRefreshIntervalMinutes INT NOT NULL DEFAULT 60,
|
|
chcAuthMethod VARCHAR(50) NULL,
|
|
chcKeyVaultSecretName VARCHAR(200) NULL,
|
|
chcStatusMappings NVARCHAR(MAX) NULL, -- JSON object: {"ENABLED":"active",...}
|
|
chcSortOrder INT NOT NULL DEFAULT 0,
|
|
chcCreatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
|
|
chcUpdatedAt DATETIME2 NOT NULL DEFAULT GETUTCDATE()
|
|
);
|
|
PRINT 'Created table tbChannelConfig';
|
|
END
|
|
GO
|
|
|
|
-- ── Stored Procedure ───────────────────────────────────────
|
|
|
|
CREATE OR ALTER PROCEDURE dbo.spChannelConfig
|
|
@action VARCHAR(50),
|
|
@rqst NVARCHAR(MAX) = '{}',
|
|
@resp NVARCHAR(MAX) OUTPUT
|
|
AS
|
|
BEGIN
|
|
SET NOCOUNT ON;
|
|
|
|
-- ── list: return all enabled channels ──
|
|
IF @action = 'list'
|
|
BEGIN
|
|
SET @resp = (
|
|
SELECT
|
|
chcChannelType AS channelType,
|
|
chcDisplayName AS displayName,
|
|
chcDescription AS [description],
|
|
chcIcon AS icon,
|
|
chcColor AS color,
|
|
chcEnabled AS [enabled],
|
|
chcIsStub AS isStub,
|
|
chcEndpoint AS endpoint,
|
|
chcInternalKey AS internalKey,
|
|
chcMinDailyBudget AS minDailyBudget,
|
|
chcMinMonthlyBudget AS minMonthlyBudget,
|
|
JSON_QUERY(chcSupportedObjectives) AS supportedObjectives,
|
|
JSON_QUERY(chcSupportedCreativeFormats) AS supportedCreativeFormats,
|
|
chcApprovalEstimateHours AS approvalEstimateHours,
|
|
chcMetricsRefreshIntervalMinutes AS metricsRefreshIntervalMinutes,
|
|
chcAuthMethod AS authMethod,
|
|
chcKeyVaultSecretName AS keyVaultSecretName,
|
|
JSON_QUERY(chcStatusMappings) AS statusMappings,
|
|
chcSortOrder AS sortOrder
|
|
FROM dbo.tbChannelConfig
|
|
ORDER BY chcSortOrder, chcChannelType
|
|
FOR JSON PATH
|
|
);
|
|
|
|
IF @resp IS NULL SET @resp = '[]';
|
|
SET @resp = '{"ok":true,"channels":' + @resp + '}';
|
|
RETURN;
|
|
END
|
|
|
|
-- ── listAll: return all channels including disabled (for admin) ──
|
|
IF @action = 'listAll'
|
|
BEGIN
|
|
SET @resp = (
|
|
SELECT
|
|
chcChannelType AS channelType,
|
|
chcDisplayName AS displayName,
|
|
chcDescription AS [description],
|
|
chcIcon AS icon,
|
|
chcColor AS color,
|
|
chcEnabled AS [enabled],
|
|
chcIsStub AS isStub,
|
|
chcEndpoint AS endpoint,
|
|
chcMinDailyBudget AS minDailyBudget,
|
|
chcMinMonthlyBudget AS minMonthlyBudget,
|
|
JSON_QUERY(chcSupportedObjectives) AS supportedObjectives,
|
|
JSON_QUERY(chcSupportedCreativeFormats) AS supportedCreativeFormats,
|
|
chcApprovalEstimateHours AS approvalEstimateHours,
|
|
chcMetricsRefreshIntervalMinutes AS metricsRefreshIntervalMinutes,
|
|
chcAuthMethod AS authMethod,
|
|
chcKeyVaultSecretName AS keyVaultSecretName,
|
|
JSON_QUERY(chcStatusMappings) AS statusMappings,
|
|
chcSortOrder AS sortOrder,
|
|
chcCreatedAt AS createdAt,
|
|
chcUpdatedAt AS updatedAt
|
|
FROM dbo.tbChannelConfig
|
|
ORDER BY chcSortOrder, chcChannelType
|
|
FOR JSON PATH
|
|
);
|
|
|
|
IF @resp IS NULL SET @resp = '[]';
|
|
SET @resp = '{"ok":true,"channels":' + @resp + '}';
|
|
RETURN;
|
|
END
|
|
|
|
-- ── get: return single channel by type ──
|
|
IF @action = 'get'
|
|
BEGIN
|
|
DECLARE @channelType VARCHAR(50) = JSON_VALUE(@rqst, '$.channelType');
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.tbChannelConfig WHERE chcChannelType = @channelType)
|
|
BEGIN
|
|
SET @resp = '{"ok":false,"error":"Channel not found"}';
|
|
RETURN;
|
|
END
|
|
|
|
SET @resp = (
|
|
SELECT
|
|
chcChannelType AS channelType,
|
|
chcDisplayName AS displayName,
|
|
chcDescription AS [description],
|
|
chcIcon AS icon,
|
|
chcColor AS color,
|
|
chcEnabled AS [enabled],
|
|
chcIsStub AS isStub,
|
|
chcEndpoint AS endpoint,
|
|
chcMinDailyBudget AS minDailyBudget,
|
|
chcMinMonthlyBudget AS minMonthlyBudget,
|
|
JSON_QUERY(chcSupportedObjectives) AS supportedObjectives,
|
|
JSON_QUERY(chcSupportedCreativeFormats) AS supportedCreativeFormats,
|
|
chcApprovalEstimateHours AS approvalEstimateHours,
|
|
chcMetricsRefreshIntervalMinutes AS metricsRefreshIntervalMinutes,
|
|
chcAuthMethod AS authMethod,
|
|
chcKeyVaultSecretName AS keyVaultSecretName,
|
|
JSON_QUERY(chcStatusMappings) AS statusMappings
|
|
FROM dbo.tbChannelConfig
|
|
WHERE chcChannelType = @channelType
|
|
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
|
|
);
|
|
|
|
SET @resp = '{"ok":true,"channel":' + @resp + '}';
|
|
RETURN;
|
|
END
|
|
|
|
-- ── upsert: create or update a channel (admin) ──
|
|
IF @action = 'upsert'
|
|
BEGIN
|
|
DECLARE @uChannelType VARCHAR(50) = JSON_VALUE(@rqst, '$.channelType');
|
|
|
|
IF @uChannelType IS NULL
|
|
BEGIN
|
|
SET @resp = '{"ok":false,"error":"channelType is required"}';
|
|
RETURN;
|
|
END
|
|
|
|
MERGE dbo.tbChannelConfig AS tgt
|
|
USING (SELECT @uChannelType AS chcChannelType) AS src
|
|
ON tgt.chcChannelType = src.chcChannelType
|
|
WHEN MATCHED THEN
|
|
UPDATE SET
|
|
chcDisplayName = ISNULL(JSON_VALUE(@rqst, '$.displayName'), tgt.chcDisplayName),
|
|
chcDescription = ISNULL(JSON_VALUE(@rqst, '$.description'), tgt.chcDescription),
|
|
chcIcon = ISNULL(JSON_VALUE(@rqst, '$.icon'), tgt.chcIcon),
|
|
chcColor = ISNULL(JSON_VALUE(@rqst, '$.color'), tgt.chcColor),
|
|
chcEnabled = ISNULL(CAST(JSON_VALUE(@rqst, '$.enabled') AS BIT), tgt.chcEnabled),
|
|
chcIsStub = ISNULL(CAST(JSON_VALUE(@rqst, '$.isStub') AS BIT), tgt.chcIsStub),
|
|
chcEndpoint = CASE WHEN JSON_VALUE(@rqst, '$.endpoint') IS NOT NULL
|
|
THEN JSON_VALUE(@rqst, '$.endpoint')
|
|
ELSE tgt.chcEndpoint END,
|
|
chcInternalKey = CASE WHEN JSON_VALUE(@rqst, '$.internalKey') IS NOT NULL
|
|
THEN JSON_VALUE(@rqst, '$.internalKey')
|
|
ELSE tgt.chcInternalKey END,
|
|
chcMinDailyBudget = ISNULL(CAST(JSON_VALUE(@rqst, '$.minDailyBudget') AS DECIMAL(10,2)), tgt.chcMinDailyBudget),
|
|
chcMinMonthlyBudget = ISNULL(CAST(JSON_VALUE(@rqst, '$.minMonthlyBudget') AS DECIMAL(10,2)), tgt.chcMinMonthlyBudget),
|
|
chcSupportedObjectives = CASE WHEN JSON_QUERY(@rqst, '$.supportedObjectives') IS NOT NULL
|
|
THEN JSON_QUERY(@rqst, '$.supportedObjectives')
|
|
ELSE tgt.chcSupportedObjectives END,
|
|
chcSupportedCreativeFormats = CASE WHEN JSON_QUERY(@rqst, '$.supportedCreativeFormats') IS NOT NULL
|
|
THEN JSON_QUERY(@rqst, '$.supportedCreativeFormats')
|
|
ELSE tgt.chcSupportedCreativeFormats END,
|
|
chcApprovalEstimateHours = ISNULL(CAST(JSON_VALUE(@rqst, '$.approvalEstimateHours') AS INT), tgt.chcApprovalEstimateHours),
|
|
chcMetricsRefreshIntervalMinutes = ISNULL(CAST(JSON_VALUE(@rqst, '$.metricsRefreshIntervalMinutes') AS INT), tgt.chcMetricsRefreshIntervalMinutes),
|
|
chcAuthMethod = ISNULL(JSON_VALUE(@rqst, '$.authMethod'), tgt.chcAuthMethod),
|
|
chcKeyVaultSecretName = ISNULL(JSON_VALUE(@rqst, '$.keyVaultSecretName'), tgt.chcKeyVaultSecretName),
|
|
chcStatusMappings = CASE WHEN JSON_QUERY(@rqst, '$.statusMappings') IS NOT NULL
|
|
THEN JSON_QUERY(@rqst, '$.statusMappings')
|
|
ELSE tgt.chcStatusMappings END,
|
|
chcSortOrder = ISNULL(CAST(JSON_VALUE(@rqst, '$.sortOrder') AS INT), tgt.chcSortOrder),
|
|
chcUpdatedAt = GETUTCDATE()
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (chcChannelType, chcDisplayName, chcDescription, chcIcon, chcColor,
|
|
chcEnabled, chcIsStub, chcEndpoint, chcInternalKey,
|
|
chcMinDailyBudget, chcMinMonthlyBudget,
|
|
chcSupportedObjectives, chcSupportedCreativeFormats,
|
|
chcApprovalEstimateHours, chcMetricsRefreshIntervalMinutes,
|
|
chcAuthMethod, chcKeyVaultSecretName, chcStatusMappings, chcSortOrder)
|
|
VALUES (
|
|
@uChannelType,
|
|
JSON_VALUE(@rqst, '$.displayName'),
|
|
JSON_VALUE(@rqst, '$.description'),
|
|
JSON_VALUE(@rqst, '$.icon'),
|
|
JSON_VALUE(@rqst, '$.color'),
|
|
ISNULL(CAST(JSON_VALUE(@rqst, '$.enabled') AS BIT), 1),
|
|
ISNULL(CAST(JSON_VALUE(@rqst, '$.isStub') AS BIT), 1),
|
|
JSON_VALUE(@rqst, '$.endpoint'),
|
|
JSON_VALUE(@rqst, '$.internalKey'),
|
|
ISNULL(CAST(JSON_VALUE(@rqst, '$.minDailyBudget') AS DECIMAL(10,2)), 5.00),
|
|
ISNULL(CAST(JSON_VALUE(@rqst, '$.minMonthlyBudget') AS DECIMAL(10,2)), 150.00),
|
|
JSON_QUERY(@rqst, '$.supportedObjectives'),
|
|
JSON_QUERY(@rqst, '$.supportedCreativeFormats'),
|
|
ISNULL(CAST(JSON_VALUE(@rqst, '$.approvalEstimateHours') AS INT), 24),
|
|
ISNULL(CAST(JSON_VALUE(@rqst, '$.metricsRefreshIntervalMinutes') AS INT), 60),
|
|
JSON_VALUE(@rqst, '$.authMethod'),
|
|
JSON_VALUE(@rqst, '$.keyVaultSecretName'),
|
|
JSON_QUERY(@rqst, '$.statusMappings'),
|
|
ISNULL(CAST(JSON_VALUE(@rqst, '$.sortOrder') AS INT), 0)
|
|
);
|
|
|
|
SET @resp = '{"ok":true}';
|
|
RETURN;
|
|
END
|
|
|
|
-- ── delete: remove a channel (admin) ──
|
|
IF @action = 'delete'
|
|
BEGIN
|
|
DECLARE @dChannelType VARCHAR(50) = JSON_VALUE(@rqst, '$.channelType');
|
|
|
|
DELETE FROM dbo.tbChannelConfig WHERE chcChannelType = @dChannelType;
|
|
|
|
SET @resp = '{"ok":true,"deleted":' + CAST(@@ROWCOUNT AS VARCHAR) + '}';
|
|
RETURN;
|
|
END
|
|
|
|
SET @resp = '{"ok":false,"error":"Unknown action: ' + @action + '"}';
|
|
END
|
|
GO
|
|
|
|
-- ── Seed Data ──────────────────────────────────────────────
|
|
|
|
-- Google Ads
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.tbChannelConfig WHERE chcChannelType = 'google_ads')
|
|
INSERT INTO dbo.tbChannelConfig (
|
|
chcChannelType, chcDisplayName, chcDescription, chcIcon, chcColor,
|
|
chcEnabled, chcIsStub, chcEndpoint,
|
|
chcMinDailyBudget, chcMinMonthlyBudget,
|
|
chcSupportedObjectives, chcSupportedCreativeFormats,
|
|
chcApprovalEstimateHours, chcAuthMethod, chcKeyVaultSecretName,
|
|
chcStatusMappings, chcSortOrder
|
|
) VALUES (
|
|
'google_ads',
|
|
'Google Ads',
|
|
'Search, Display, Shopping & Performance Max across Google properties',
|
|
'google', '#4285F4',
|
|
1, 0, NULL,
|
|
10.00, 300.00,
|
|
'["awareness","traffic","conversions","leads","sales"]',
|
|
'["text","image","responsive","video"]',
|
|
24, 'mcc', 'google-ads-refresh-token',
|
|
'{"ENABLED":"active","Enabled":"active","PAUSED":"paused","Paused":"paused","REMOVED":"cancelled","Removed":"cancelled","UNKNOWN":"error","UNSPECIFIED":"error"}',
|
|
1
|
|
);
|
|
|
|
-- Meta
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.tbChannelConfig WHERE chcChannelType = 'meta')
|
|
INSERT INTO dbo.tbChannelConfig (
|
|
chcChannelType, chcDisplayName, chcDescription, chcIcon, chcColor,
|
|
chcEnabled, chcIsStub, chcEndpoint,
|
|
chcMinDailyBudget, chcMinMonthlyBudget,
|
|
chcSupportedObjectives, chcSupportedCreativeFormats,
|
|
chcApprovalEstimateHours, chcAuthMethod, chcKeyVaultSecretName,
|
|
chcStatusMappings, chcSortOrder
|
|
) VALUES (
|
|
'meta',
|
|
'Meta Ads',
|
|
'Facebook, Instagram, Messenger & Threads advertising',
|
|
'meta', '#1877F2',
|
|
1, 1, NULL,
|
|
5.00, 250.00,
|
|
'["awareness","traffic","conversions","leads","sales"]',
|
|
'["image","video","carousel","stories"]',
|
|
48, 'oauth2', 'meta-access-token',
|
|
'{"ACTIVE":"active","PAUSED":"paused","DELETED":"cancelled","ARCHIVED":"completed","IN_PROCESS":"pending","WITH_ISSUES":"error","CAMPAIGN_PAUSED":"paused","ADSET_PAUSED":"paused","DISAPPROVED":"error","PREAPPROVED":"pending","PENDING_REVIEW":"pending","PENDING_BILLING_INFO":"error"}',
|
|
2
|
|
);
|
|
|
|
-- TikTok
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.tbChannelConfig WHERE chcChannelType = 'tiktok')
|
|
INSERT INTO dbo.tbChannelConfig (
|
|
chcChannelType, chcDisplayName, chcDescription, chcIcon, chcColor,
|
|
chcEnabled, chcIsStub, chcEndpoint,
|
|
chcMinDailyBudget, chcMinMonthlyBudget,
|
|
chcSupportedObjectives, chcSupportedCreativeFormats,
|
|
chcApprovalEstimateHours, chcAuthMethod, chcKeyVaultSecretName,
|
|
chcStatusMappings, chcSortOrder
|
|
) VALUES (
|
|
'tiktok',
|
|
'TikTok Ads',
|
|
'In-feed video ads across TikTok and partner apps',
|
|
'tiktok', '#000000',
|
|
1, 1, NULL,
|
|
20.00, 200.00,
|
|
'["awareness","traffic","conversions","leads","sales"]',
|
|
'["video","image","spark_ads"]',
|
|
24, 'oauth2', 'tiktok-access-token',
|
|
'{"ENABLE":"active","CAMPAIGN_STATUS_ENABLE":"active","DISABLE":"paused","CAMPAIGN_STATUS_DISABLE":"paused","DELETE":"cancelled","CAMPAIGN_STATUS_DELETE":"cancelled","BUDGET_EXCEED":"paused","CAMPAIGN_STATUS_BUDGET_EXCEED":"paused","ADVERTISER_AUDIT_DENY":"error","CAMPAIGN_STATUS_ADVERTISER_AUDIT_DENY":"error","NOT_DELETE":"active","ADVERTISER_AUDIT":"pending","CAMPAIGN_STATUS_ADVERTISER_AUDIT":"pending","REAUDIT":"pending","ALL":"active"}',
|
|
3
|
|
);
|
|
|
|
PRINT 'Channel config seeded successfully';
|
|
GO
|