PlotDirector/PlotLine/Sql/042_Phase5A_ProjectUserAccess.sql
2026-06-07 09:54:47 +01:00

379 lines
15 KiB
Transact-SQL

SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
IF OBJECT_ID(N'dbo.ProjectUserAccess', N'U') IS NULL
BEGIN
CREATE TABLE dbo.ProjectUserAccess
(
ProjectUserAccessID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ProjectUserAccess PRIMARY KEY,
ProjectID int NOT NULL,
UserID int NOT NULL,
AccessRole nvarchar(50) NOT NULL,
InvitedByUserID int NULL,
InvitedDateUTC datetime2 NULL,
AcceptedDateUTC datetime2 NULL,
IsActive bit NOT NULL CONSTRAINT DF_ProjectUserAccess_IsActive DEFAULT 1,
CONSTRAINT FK_ProjectUserAccess_Project FOREIGN KEY (ProjectID) REFERENCES dbo.Projects(ProjectID),
CONSTRAINT FK_ProjectUserAccess_User FOREIGN KEY (UserID) REFERENCES dbo.AppUser(UserID),
CONSTRAINT FK_ProjectUserAccess_InvitedByUser FOREIGN KEY (InvitedByUserID) REFERENCES dbo.AppUser(UserID),
CONSTRAINT CK_ProjectUserAccess_AccessRole CHECK (AccessRole IN (N'Owner', N'Collaborator'))
);
END;
GO
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'UX_ProjectUserAccess_Project_User_Active' AND object_id = OBJECT_ID(N'dbo.ProjectUserAccess'))
CREATE UNIQUE INDEX UX_ProjectUserAccess_Project_User_Active ON dbo.ProjectUserAccess(ProjectID, UserID) WHERE IsActive = 1;
GO
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_ProjectUserAccess_User_Project' AND object_id = OBJECT_ID(N'dbo.ProjectUserAccess'))
CREATE INDEX IX_ProjectUserAccess_User_Project ON dbo.ProjectUserAccess(UserID, ProjectID, IsActive, AccessRole);
GO
DECLARE @LegacyOwnerUserID int;
SELECT TOP (1) @LegacyOwnerUserID = UserID
FROM dbo.AppUser
ORDER BY UserID;
IF @LegacyOwnerUserID IS NOT NULL
BEGIN
INSERT INTO dbo.ProjectUserAccess (ProjectID, UserID, AccessRole, AcceptedDateUTC, IsActive)
SELECT p.ProjectID, @LegacyOwnerUserID, N'Owner', SYSUTCDATETIME(), 1
FROM dbo.Projects p
WHERE NOT EXISTS
(
SELECT 1
FROM dbo.ProjectUserAccess pua
WHERE pua.ProjectID = p.ProjectID
AND pua.UserID = @LegacyOwnerUserID
AND pua.IsActive = 1
);
END;
GO
CREATE OR ALTER PROCEDURE dbo.ProjectAccess_EnsureOwner
@ProjectID int,
@UserID int
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS
(
SELECT 1
FROM dbo.ProjectUserAccess
WHERE ProjectID = @ProjectID
AND UserID = @UserID
AND IsActive = 1
)
BEGIN
INSERT INTO dbo.ProjectUserAccess (ProjectID, UserID, AccessRole, AcceptedDateUTC, IsActive)
VALUES (@ProjectID, @UserID, N'Owner', SYSUTCDATETIME(), 1);
END;
END;
GO
CREATE OR ALTER PROCEDURE dbo.ProjectAccess_UserCanAccess
@ProjectID int,
@UserID int
AS
BEGIN
SET NOCOUNT ON;
SELECT CAST(CASE WHEN EXISTS
(
SELECT 1
FROM dbo.ProjectUserAccess pua
INNER JOIN dbo.Projects p ON p.ProjectID = pua.ProjectID
WHERE pua.ProjectID = @ProjectID
AND pua.UserID = @UserID
AND pua.IsActive = 1
AND p.IsArchived = 0
)
THEN 1 ELSE 0 END AS bit);
END;
GO
CREATE OR ALTER PROCEDURE dbo.ProjectAccess_UserCanOwn
@ProjectID int,
@UserID int
AS
BEGIN
SET NOCOUNT ON;
SELECT CAST(CASE WHEN EXISTS
(
SELECT 1
FROM dbo.ProjectUserAccess pua
INNER JOIN dbo.Projects p ON p.ProjectID = pua.ProjectID
WHERE pua.ProjectID = @ProjectID
AND pua.UserID = @UserID
AND pua.AccessRole = N'Owner'
AND pua.IsActive = 1
AND p.IsArchived = 0
)
THEN 1 ELSE 0 END AS bit);
END;
GO
CREATE OR ALTER PROCEDURE dbo.ProjectAccess_GetProjectIDForEntity
@EntityType nvarchar(100),
@EntityID int
AS
BEGIN
SET NOCOUNT ON;
DECLARE @ProjectID int = NULL;
IF @EntityType = N'Project'
SELECT @ProjectID = ProjectID FROM dbo.Projects WHERE ProjectID = @EntityID;
ELSE IF @EntityType = N'Book'
SELECT @ProjectID = ProjectID FROM dbo.Books WHERE BookID = @EntityID;
ELSE IF @EntityType = N'Chapter'
SELECT @ProjectID = b.ProjectID FROM dbo.Chapters c INNER JOIN dbo.Books b ON b.BookID = c.BookID WHERE c.ChapterID = @EntityID;
ELSE IF @EntityType = N'Scene'
SELECT @ProjectID = b.ProjectID FROM dbo.Scenes s INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID INNER JOIN dbo.Books b ON b.BookID = c.BookID WHERE s.SceneID = @EntityID;
ELSE IF @EntityType = N'Character'
SELECT @ProjectID = ProjectID FROM dbo.Characters WHERE CharacterID = @EntityID;
ELSE IF @EntityType = N'SceneCharacter'
SELECT @ProjectID = ch.ProjectID FROM dbo.SceneCharacters sc INNER JOIN dbo.Characters ch ON ch.CharacterID = sc.CharacterID WHERE sc.SceneCharacterID = @EntityID;
ELSE IF @EntityType = N'CharacterAttributeEvent'
SELECT @ProjectID = ch.ProjectID FROM dbo.CharacterAttributeEvents cae INNER JOIN dbo.Characters ch ON ch.CharacterID = cae.CharacterID WHERE cae.CharacterAttributeEventID = @EntityID;
ELSE IF @EntityType = N'CharacterKnowledge'
SELECT @ProjectID = ch.ProjectID FROM dbo.CharacterKnowledge ck INNER JOIN dbo.Characters ch ON ch.CharacterID = ck.CharacterID WHERE ck.CharacterKnowledgeID = @EntityID;
ELSE IF @EntityType = N'CharacterRelationship'
SELECT @ProjectID = ProjectID FROM dbo.CharacterRelationships WHERE CharacterRelationshipID = @EntityID;
ELSE IF @EntityType = N'RelationshipEvent'
SELECT @ProjectID = cr.ProjectID FROM dbo.RelationshipEvents re INNER JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID WHERE re.RelationshipEventID = @EntityID;
ELSE IF @EntityType = N'StoryAsset'
SELECT @ProjectID = ProjectID FROM dbo.StoryAssets WHERE StoryAssetID = @EntityID;
ELSE IF @EntityType = N'AssetEvent'
SELECT @ProjectID = sa.ProjectID FROM dbo.AssetEvents ae INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ae.StoryAssetID WHERE ae.AssetEventID = @EntityID;
ELSE IF @EntityType = N'AssetDependency'
SELECT @ProjectID = sa.ProjectID FROM dbo.AssetDependencies ad INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ad.SourceAssetID WHERE ad.AssetDependencyID = @EntityID;
ELSE IF @EntityType = N'AssetCustodyEvent'
SELECT @ProjectID = sa.ProjectID FROM dbo.AssetCustodyEvents ace INNER JOIN dbo.StoryAssets sa ON sa.StoryAssetID = ace.StoryAssetID WHERE ace.AssetCustodyEventID = @EntityID;
ELSE IF @EntityType = N'Location'
SELECT @ProjectID = ProjectID FROM dbo.Locations WHERE LocationID = @EntityID;
ELSE IF @EntityType = N'LocationRelationship'
SELECT @ProjectID = l.ProjectID FROM dbo.LocationRelationships lr INNER JOIN dbo.Locations l ON l.LocationID = lr.FromLocationID WHERE lr.LocationRelationshipID = @EntityID;
ELSE IF @EntityType = N'SceneAssetLocation'
SELECT @ProjectID = l.ProjectID FROM dbo.SceneAssetLocations sal INNER JOIN dbo.Locations l ON l.LocationID = sal.LocationID WHERE sal.SceneAssetLocationID = @EntityID;
ELSE IF @EntityType = N'PlotLine'
SELECT @ProjectID = ProjectID FROM dbo.PlotLines WHERE PlotLineID = @EntityID;
ELSE IF @EntityType = N'PlotThread'
SELECT @ProjectID = pl.ProjectID FROM dbo.PlotThreads pt INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID WHERE pt.PlotThreadID = @EntityID;
ELSE IF @EntityType = N'ThreadEvent'
SELECT @ProjectID = pl.ProjectID FROM dbo.ThreadEvents te INNER JOIN dbo.PlotThreads pt ON pt.PlotThreadID = te.PlotThreadID INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID WHERE te.ThreadEventID = @EntityID;
ELSE IF @EntityType = N'SceneDependency'
SELECT @ProjectID = ProjectID FROM dbo.SceneDependencies WHERE SceneDependencyID = @EntityID;
ELSE IF @EntityType = N'SceneMetricType'
SELECT @ProjectID = ProjectID FROM dbo.SceneMetricTypes WHERE MetricTypeID = @EntityID;
ELSE IF @EntityType = N'TimelineViewPreset'
SELECT @ProjectID = ProjectID FROM dbo.TimelineViewPresets WHERE TimelineViewPresetID = @EntityID;
ELSE IF @EntityType = N'ContinuityWarning'
SELECT @ProjectID = ProjectID FROM dbo.ContinuityWarnings WHERE ContinuityWarningID = @EntityID;
ELSE IF @EntityType = N'Scenario'
SELECT @ProjectID = ProjectID FROM dbo.Scenarios WHERE ScenarioID = @EntityID;
ELSE IF @EntityType = N'ScenarioWarning'
SELECT @ProjectID = sc.ProjectID FROM dbo.ScenarioWarnings sw INNER JOIN dbo.Scenarios sc ON sc.ScenarioID = sw.ScenarioID WHERE sw.ScenarioWarningID = @EntityID;
ELSE IF @EntityType = N'ProjectRestorePoint'
SELECT @ProjectID = ProjectID FROM dbo.ProjectRestorePoints WHERE ProjectRestorePointID = @EntityID;
ELSE IF @EntityType = N'ChapterGoal'
SELECT @ProjectID = b.ProjectID FROM dbo.ChapterGoals cg INNER JOIN dbo.Chapters c ON c.ChapterID = cg.ChapterID INNER JOIN dbo.Books b ON b.BookID = c.BookID WHERE cg.ChapterGoalID = @EntityID;
ELSE IF @EntityType = N'SceneWorkflow'
SELECT @ProjectID = b.ProjectID FROM dbo.SceneWorkflow sw INNER JOIN dbo.Scenes s ON s.SceneID = sw.SceneID INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID INNER JOIN dbo.Books b ON b.BookID = c.BookID WHERE sw.SceneWorkflowID = @EntityID;
ELSE IF @EntityType = N'SceneNote'
SELECT @ProjectID = b.ProjectID FROM dbo.SceneNotes sn INNER JOIN dbo.Scenes s ON s.SceneID = sn.SceneID INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID INNER JOIN dbo.Books b ON b.BookID = c.BookID WHERE sn.SceneNoteID = @EntityID;
ELSE IF @EntityType = N'SceneChecklistItem'
SELECT @ProjectID = b.ProjectID FROM dbo.SceneChecklistItems sci INNER JOIN dbo.Scenes s ON s.SceneID = sci.SceneID INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID INNER JOIN dbo.Books b ON b.BookID = c.BookID WHERE sci.SceneChecklistItemID = @EntityID;
ELSE IF @EntityType = N'SceneAttachment'
SELECT @ProjectID = b.ProjectID FROM dbo.SceneAttachments sa INNER JOIN dbo.Scenes s ON s.SceneID = sa.SceneID INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID INNER JOIN dbo.Books b ON b.BookID = c.BookID WHERE sa.SceneAttachmentID = @EntityID;
SELECT @ProjectID AS ProjectID;
END;
GO
CREATE OR ALTER PROCEDURE dbo.ProjectAccess_UserCanAccessEntity
@EntityType nvarchar(100),
@EntityID int,
@UserID int
AS
BEGIN
SET NOCOUNT ON;
DECLARE @ProjectID int;
DECLARE @Project TABLE (ProjectID int NULL);
INSERT INTO @Project
EXEC dbo.ProjectAccess_GetProjectIDForEntity @EntityType = @EntityType, @EntityID = @EntityID;
SELECT @ProjectID = ProjectID FROM @Project;
IF @ProjectID IS NULL
BEGIN
SELECT CAST(0 AS bit);
RETURN;
END;
EXEC dbo.ProjectAccess_UserCanAccess @ProjectID = @ProjectID, @UserID = @UserID;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Project_ListForUser
@UserID int
AS
BEGIN
SET NOCOUNT ON;
SELECT p.ProjectID, p.ProjectName, p.Description, p.CreatedDate, p.UpdatedDate, p.IsArchived
FROM dbo.Projects p
INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = p.ProjectID
WHERE p.IsArchived = 0
AND pua.UserID = @UserID
AND pua.IsActive = 1
ORDER BY p.ProjectName;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Project_GetForUser
@ProjectID int,
@UserID int
AS
BEGIN
SET NOCOUNT ON;
SELECT p.ProjectID, p.ProjectName, p.Description, p.CreatedDate, p.UpdatedDate, p.IsArchived
FROM dbo.Projects p
INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = p.ProjectID
WHERE p.ProjectID = @ProjectID
AND p.IsArchived = 0
AND pua.UserID = @UserID
AND pua.IsActive = 1;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Project_Save
@ProjectID int = NULL,
@ProjectName nvarchar(200),
@Description nvarchar(max) = NULL,
@GenreMetricPresetKey nvarchar(50) = NULL,
@UserID int = NULL
AS
BEGIN
SET NOCOUNT ON;
IF @ProjectID IS NULL OR @ProjectID = 0
BEGIN
INSERT dbo.Projects (ProjectName, Description)
VALUES (@ProjectName, @Description);
SET @ProjectID = CAST(SCOPE_IDENTITY() AS int);
IF @UserID IS NOT NULL
EXEC dbo.ProjectAccess_EnsureOwner @ProjectID = @ProjectID, @UserID = @UserID;
EXEC dbo.ProjectMetricPreset_Apply @ProjectID = @ProjectID, @GenreMetricPresetKey = @GenreMetricPresetKey;
SELECT @ProjectID AS ProjectID;
RETURN;
END;
UPDATE p
SET ProjectName = @ProjectName,
Description = @Description,
UpdatedDate = SYSUTCDATETIME()
FROM dbo.Projects p
WHERE p.ProjectID = @ProjectID
AND
(
@UserID IS NULL
OR EXISTS
(
SELECT 1
FROM dbo.ProjectUserAccess pua
WHERE pua.ProjectID = p.ProjectID
AND pua.UserID = @UserID
AND pua.IsActive = 1
)
);
SELECT CASE WHEN @@ROWCOUNT = 1 THEN @ProjectID ELSE 0 END AS ProjectID;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Project_ArchiveForUser
@ProjectID int,
@UserID int
AS
BEGIN
SET NOCOUNT ON;
UPDATE p
SET IsArchived = 1,
UpdatedDate = SYSUTCDATETIME()
FROM dbo.Projects p
WHERE p.ProjectID = @ProjectID
AND EXISTS
(
SELECT 1
FROM dbo.ProjectUserAccess pua
WHERE pua.ProjectID = p.ProjectID
AND pua.UserID = @UserID
AND pua.AccessRole = N'Owner'
AND pua.IsActive = 1
);
END;
GO
CREATE OR ALTER PROCEDURE dbo.Archive_ListForUser
@UserID int,
@ProjectID int = NULL,
@EntityType nvarchar(80) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE @Archived TABLE
(
EntityType nvarchar(80),
EntityID int,
DisplayName nvarchar(500),
ProjectID int,
ProjectName nvarchar(200),
BookID int NULL,
BookTitle nvarchar(200) NULL,
ArchivedDate datetime2 NULL,
ArchivedReason nvarchar(500) NULL,
UpdatedDate datetime2
);
INSERT INTO @Archived
EXEC dbo.Archive_List @ProjectID = @ProjectID, @EntityType = @EntityType;
SELECT a.EntityType, a.EntityID, a.DisplayName, a.ProjectID, a.ProjectName, a.BookID, a.BookTitle,
a.ArchivedDate, a.ArchivedReason, a.UpdatedDate
FROM @Archived a
INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = a.ProjectID
WHERE pua.UserID = @UserID
AND pua.IsActive = 1
ORDER BY ISNULL(a.ArchivedDate, a.UpdatedDate) DESC, a.EntityType, a.DisplayName;
END;
GO
CREATE OR ALTER PROCEDURE dbo.Archive_ProjectFilterListForUser
@UserID int
AS
BEGIN
SET NOCOUNT ON;
SELECT p.ProjectID, p.ProjectName, p.Description, p.CreatedDate, p.UpdatedDate, p.IsArchived
FROM dbo.Projects p
INNER JOIN dbo.ProjectUserAccess pua ON pua.ProjectID = p.ProjectID
WHERE pua.UserID = @UserID
AND pua.IsActive = 1
ORDER BY p.IsArchived, p.ProjectName;
END;
GO