SET ANSI_NULLS ON; GO SET QUOTED_IDENTIFIER ON; GO IF COL_LENGTH(N'dbo.FeatureRequests', N'UserLastViewedUtc') IS NULL BEGIN ALTER TABLE dbo.FeatureRequests ADD UserLastViewedUtc datetime2 NULL; END; GO IF COL_LENGTH(N'dbo.FeatureRequests', N'UserAttentionUtc') IS NULL BEGIN ALTER TABLE dbo.FeatureRequests ADD UserAttentionUtc datetime2 NULL; END; GO IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_FeatureRequests_UserAttention' AND object_id = OBJECT_ID(N'dbo.FeatureRequests')) BEGIN CREATE INDEX IX_FeatureRequests_UserAttention ON dbo.FeatureRequests(UserID, UserAttentionUtc) WHERE UserAttentionUtc IS NOT NULL; END; GO CREATE OR ALTER PROCEDURE dbo.FeatureRequest_GetForUser @FeatureRequestID int, @UserID int AS BEGIN SET NOCOUNT ON; SELECT fr.FeatureRequestID, fr.UserID, au.Email AS UserEmail, au.DisplayName AS UserDisplayName, fr.Title, fr.Description, fr.AppArea, fr.Importance, fr.Status, fr.IsPublicCandidate, fr.AdminNotes, fr.CreatedUtc, fr.UpdatedUtc, fr.ClosedUtc, fr.ShippedUtc, COALESCE(latest.CreatedUtc, fr.UpdatedUtc, fr.CreatedUtc) AS LatestActivityUtc, latest.IsAdmin AS LatestMessageIsAdmin, latest.MessageBody AS LatestMessagePreview, messageCounts.MessageCount, fr.UserLastViewedUtc, fr.UserAttentionUtc, CAST(CASE WHEN fr.UserAttentionUtc IS NOT NULL THEN 1 ELSE 0 END AS bit) AS UserNeedsAttention FROM dbo.FeatureRequests fr INNER JOIN dbo.AppUser au ON au.UserID = fr.UserID OUTER APPLY ( SELECT TOP (1) frm.IsAdmin, frm.MessageBody, frm.CreatedUtc FROM dbo.FeatureRequestMessages frm WHERE frm.FeatureRequestID = fr.FeatureRequestID ORDER BY frm.CreatedUtc DESC, frm.FeatureRequestMessageID DESC ) latest OUTER APPLY ( SELECT COUNT(1) AS MessageCount FROM dbo.FeatureRequestMessages frm WHERE frm.FeatureRequestID = fr.FeatureRequestID ) messageCounts WHERE fr.FeatureRequestID = @FeatureRequestID AND fr.UserID = @UserID; END; GO CREATE OR ALTER PROCEDURE dbo.FeatureRequest_GetForAdmin @FeatureRequestID int AS BEGIN SET NOCOUNT ON; SELECT fr.FeatureRequestID, fr.UserID, au.Email AS UserEmail, au.DisplayName AS UserDisplayName, fr.Title, fr.Description, fr.AppArea, fr.Importance, fr.Status, fr.IsPublicCandidate, fr.AdminNotes, fr.CreatedUtc, fr.UpdatedUtc, fr.ClosedUtc, fr.ShippedUtc, COALESCE(latest.CreatedUtc, fr.UpdatedUtc, fr.CreatedUtc) AS LatestActivityUtc, latest.IsAdmin AS LatestMessageIsAdmin, latest.MessageBody AS LatestMessagePreview, messageCounts.MessageCount, fr.UserLastViewedUtc, fr.UserAttentionUtc, CAST(CASE WHEN fr.UserAttentionUtc IS NOT NULL THEN 1 ELSE 0 END AS bit) AS UserNeedsAttention FROM dbo.FeatureRequests fr INNER JOIN dbo.AppUser au ON au.UserID = fr.UserID OUTER APPLY ( SELECT TOP (1) frm.IsAdmin, frm.MessageBody, frm.CreatedUtc FROM dbo.FeatureRequestMessages frm WHERE frm.FeatureRequestID = fr.FeatureRequestID ORDER BY frm.CreatedUtc DESC, frm.FeatureRequestMessageID DESC ) latest OUTER APPLY ( SELECT COUNT(1) AS MessageCount FROM dbo.FeatureRequestMessages frm WHERE frm.FeatureRequestID = fr.FeatureRequestID ) messageCounts WHERE fr.FeatureRequestID = @FeatureRequestID; END; GO CREATE OR ALTER PROCEDURE dbo.FeatureRequest_ListForUser @UserID int AS BEGIN SET NOCOUNT ON; SELECT fr.FeatureRequestID, fr.UserID, au.Email AS UserEmail, au.DisplayName AS UserDisplayName, fr.Title, fr.Description, fr.AppArea, fr.Importance, fr.Status, fr.IsPublicCandidate, CAST(NULL AS nvarchar(max)) AS AdminNotes, fr.CreatedUtc, fr.UpdatedUtc, fr.ClosedUtc, fr.ShippedUtc, COALESCE(latest.CreatedUtc, fr.UpdatedUtc, fr.CreatedUtc) AS LatestActivityUtc, latest.IsAdmin AS LatestMessageIsAdmin, latest.MessageBody AS LatestMessagePreview, messageCounts.MessageCount, fr.UserLastViewedUtc, fr.UserAttentionUtc, CAST(CASE WHEN fr.UserAttentionUtc IS NOT NULL THEN 1 ELSE 0 END AS bit) AS UserNeedsAttention FROM dbo.FeatureRequests fr INNER JOIN dbo.AppUser au ON au.UserID = fr.UserID OUTER APPLY ( SELECT TOP (1) frm.IsAdmin, frm.MessageBody, frm.CreatedUtc FROM dbo.FeatureRequestMessages frm WHERE frm.FeatureRequestID = fr.FeatureRequestID ORDER BY frm.CreatedUtc DESC, frm.FeatureRequestMessageID DESC ) latest OUTER APPLY ( SELECT COUNT(1) AS MessageCount FROM dbo.FeatureRequestMessages frm WHERE frm.FeatureRequestID = fr.FeatureRequestID ) messageCounts WHERE fr.UserID = @UserID ORDER BY COALESCE(fr.UserAttentionUtc, latest.CreatedUtc, fr.UpdatedUtc, fr.CreatedUtc) DESC, fr.FeatureRequestID DESC; END; GO CREATE OR ALTER PROCEDURE dbo.FeatureRequest_ListForAdmin @Status nvarchar(50) = NULL, @AppArea nvarchar(100) = NULL, @Importance nvarchar(50) = NULL, @Search nvarchar(200) = NULL, @Sort nvarchar(50) = N'LatestActivity' AS BEGIN SET NOCOUNT ON; DECLARE @CleanSearch nvarchar(200) = NULLIF(LTRIM(RTRIM(@Search)), N''); SELECT fr.FeatureRequestID, fr.UserID, au.Email AS UserEmail, au.DisplayName AS UserDisplayName, fr.Title, fr.Description, fr.AppArea, fr.Importance, fr.Status, fr.IsPublicCandidate, fr.AdminNotes, fr.CreatedUtc, fr.UpdatedUtc, fr.ClosedUtc, fr.ShippedUtc, COALESCE(latest.CreatedUtc, fr.UpdatedUtc, fr.CreatedUtc) AS LatestActivityUtc, latest.IsAdmin AS LatestMessageIsAdmin, latest.MessageBody AS LatestMessagePreview, messageCounts.MessageCount, fr.UserLastViewedUtc, fr.UserAttentionUtc, CAST(CASE WHEN fr.UserAttentionUtc IS NOT NULL THEN 1 ELSE 0 END AS bit) AS UserNeedsAttention FROM dbo.FeatureRequests fr INNER JOIN dbo.AppUser au ON au.UserID = fr.UserID OUTER APPLY ( SELECT TOP (1) frm.IsAdmin, frm.MessageBody, frm.CreatedUtc FROM dbo.FeatureRequestMessages frm WHERE frm.FeatureRequestID = fr.FeatureRequestID ORDER BY frm.CreatedUtc DESC, frm.FeatureRequestMessageID DESC ) latest OUTER APPLY ( SELECT COUNT(1) AS MessageCount FROM dbo.FeatureRequestMessages frm WHERE frm.FeatureRequestID = fr.FeatureRequestID ) messageCounts WHERE (@Status IS NULL OR fr.Status = @Status) AND (@AppArea IS NULL OR fr.AppArea = @AppArea) AND (@Importance IS NULL OR fr.Importance = @Importance) AND ( @CleanSearch IS NULL OR fr.Title LIKE N'%' + @CleanSearch + N'%' OR fr.Description LIKE N'%' + @CleanSearch + N'%' OR au.DisplayName LIKE N'%' + @CleanSearch + N'%' OR au.Email LIKE N'%' + @CleanSearch + N'%' ) ORDER BY CASE WHEN @Sort = N'OldestFirst' THEN fr.CreatedUtc END ASC, CASE WHEN @Sort = N'NewestFirst' THEN fr.CreatedUtc END DESC, CASE WHEN @Sort IS NULL OR @Sort = N'LatestActivity' THEN COALESCE(latest.CreatedUtc, fr.UpdatedUtc, fr.CreatedUtc) END DESC, fr.FeatureRequestID DESC; END; GO CREATE OR ALTER PROCEDURE dbo.FeatureRequest_UpdateStatus @FeatureRequestID int, @Status nvarchar(50), @IsPublicCandidate bit = NULL AS BEGIN SET NOCOUNT ON; UPDATE dbo.FeatureRequests SET Status = @Status, IsPublicCandidate = COALESCE(@IsPublicCandidate, IsPublicCandidate), UpdatedUtc = sysutcdatetime(), UserAttentionUtc = sysutcdatetime(), ClosedUtc = CASE WHEN @Status IN (N'Shipped', N'Not Planned') THEN COALESCE(ClosedUtc, sysutcdatetime()) ELSE NULL END, ShippedUtc = CASE WHEN @Status = N'Shipped' THEN COALESCE(ShippedUtc, sysutcdatetime()) ELSE NULL END WHERE FeatureRequestID = @FeatureRequestID; EXEC dbo.FeatureRequest_GetForAdmin @FeatureRequestID = @FeatureRequestID; END; GO CREATE OR ALTER PROCEDURE dbo.FeatureRequest_UpdateRoadmapVisibility @FeatureRequestID int, @IsPublicCandidate bit AS BEGIN SET NOCOUNT ON; UPDATE dbo.FeatureRequests SET IsPublicCandidate = @IsPublicCandidate, UpdatedUtc = sysutcdatetime() WHERE FeatureRequestID = @FeatureRequestID; EXEC dbo.FeatureRequest_GetForAdmin @FeatureRequestID = @FeatureRequestID; END; GO CREATE OR ALTER PROCEDURE dbo.FeatureRequestMessage_Add @FeatureRequestID int, @UserID int = NULL, @IsAdmin bit = 0, @MessageBody nvarchar(max) AS BEGIN SET NOCOUNT ON; IF NOT EXISTS (SELECT 1 FROM dbo.FeatureRequests WHERE FeatureRequestID = @FeatureRequestID) RETURN; INSERT INTO dbo.FeatureRequestMessages (FeatureRequestID, UserID, IsAdmin, MessageBody) VALUES (@FeatureRequestID, @UserID, @IsAdmin, @MessageBody); UPDATE dbo.FeatureRequests SET UpdatedUtc = sysutcdatetime(), UserAttentionUtc = CASE WHEN @IsAdmin = 1 THEN sysutcdatetime() ELSE UserAttentionUtc END WHERE FeatureRequestID = @FeatureRequestID; SELECT frm.FeatureRequestMessageID, frm.FeatureRequestID, frm.UserID, au.Email AS UserEmail, au.DisplayName AS UserDisplayName, frm.IsAdmin, frm.MessageBody, frm.CreatedUtc FROM dbo.FeatureRequestMessages frm LEFT JOIN dbo.AppUser au ON au.UserID = frm.UserID WHERE frm.FeatureRequestMessageID = SCOPE_IDENTITY(); END; GO CREATE OR ALTER PROCEDURE dbo.FeatureRequest_MarkViewedForUser @FeatureRequestID int, @UserID int AS BEGIN SET NOCOUNT ON; UPDATE dbo.FeatureRequests SET UserLastViewedUtc = sysutcdatetime(), UserAttentionUtc = NULL WHERE FeatureRequestID = @FeatureRequestID AND UserID = @UserID; SELECT CAST(@@ROWCOUNT AS bit) AS Succeeded; END; GO CREATE OR ALTER PROCEDURE dbo.FeatureRequest_CountAttentionForUser @UserID int AS BEGIN SET NOCOUNT ON; SELECT COUNT(1) FROM dbo.FeatureRequests WHERE UserID = @UserID AND UserAttentionUtc IS NOT NULL; END; GO CREATE OR ALTER PROCEDURE dbo.FeatureRequest_CountWaitingForAdmin AS BEGIN SET NOCOUNT ON; SELECT COUNT(1) FROM dbo.FeatureRequests fr OUTER APPLY ( SELECT TOP (1) frm.IsAdmin FROM dbo.FeatureRequestMessages frm WHERE frm.FeatureRequestID = fr.FeatureRequestID ORDER BY frm.CreatedUtc DESC, frm.FeatureRequestMessageID DESC ) latest WHERE fr.Status NOT IN (N'Shipped', N'Not Planned') AND (latest.IsAdmin IS NULL OR latest.IsAdmin = 0); END; GO CREATE OR ALTER PROCEDURE dbo.FeatureRequest_ListRoadmap AS BEGIN SET NOCOUNT ON; SELECT FeatureRequestID, Title, CAST(NULL AS int) AS UserID, CAST(NULL AS nvarchar(256)) AS UserEmail, CAST(NULL AS nvarchar(200)) AS UserDisplayName, Description, AppArea, Importance, Status, IsPublicCandidate, CAST(NULL AS nvarchar(max)) AS AdminNotes, CreatedUtc, UpdatedUtc, ClosedUtc, ShippedUtc, UpdatedUtc AS LatestActivityUtc, CAST(NULL AS bit) AS LatestMessageIsAdmin, CAST(NULL AS nvarchar(max)) AS LatestMessagePreview, 0 AS MessageCount, CAST(NULL AS datetime2) AS UserLastViewedUtc, CAST(NULL AS datetime2) AS UserAttentionUtc, CAST(0 AS bit) AS UserNeedsAttention FROM dbo.FeatureRequests WHERE IsPublicCandidate = 1 AND Status IN (N'Candidate', N'Planned', N'In Progress', N'Shipped') ORDER BY CASE Status WHEN N'Candidate' THEN 1 WHEN N'Planned' THEN 2 WHEN N'In Progress' THEN 3 WHEN N'Shipped' THEN 4 ELSE 5 END, CASE WHEN Status = N'Shipped' THEN ShippedUtc END DESC, UpdatedUtc DESC, FeatureRequestID DESC; END; GO