Backup / restore / export / import working.
This commit is contained in:
parent
f8bf5af17f
commit
cbadc9635d
@ -29,7 +29,26 @@ foreach (var batch in batches)
|
|||||||
|
|
||||||
await using var command = new SqlCommand(batch, connection);
|
await using var command = new SqlCommand(batch, connection);
|
||||||
command.CommandTimeout = 120;
|
command.CommandTimeout = 120;
|
||||||
await command.ExecuteNonQueryAsync();
|
await using var reader = await command.ExecuteReaderAsync();
|
||||||
|
var resultSet = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (reader.FieldCount > 0)
|
||||||
|
{
|
||||||
|
resultSet++;
|
||||||
|
Console.WriteLine($"-- Result set {count + 1}.{resultSet}");
|
||||||
|
var headers = Enumerable.Range(0, reader.FieldCount).Select(reader.GetName);
|
||||||
|
Console.WriteLine(string.Join("\t", headers));
|
||||||
|
while (await reader.ReadAsync())
|
||||||
|
{
|
||||||
|
var values = Enumerable.Range(0, reader.FieldCount)
|
||||||
|
.Select(i => reader.IsDBNull(i) ? "NULL" : Convert.ToString(reader.GetValue(i))?.Replace("\r", "\\r").Replace("\n", "\\n"));
|
||||||
|
Console.WriteLine(string.Join("\t", values));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (await reader.NextResultAsync());
|
||||||
|
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,12 @@ public sealed class ProjectRestorePointsController(
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (await restorePoints.HasReachedManualLimitAsync(projectId))
|
||||||
|
{
|
||||||
|
TempData["ProjectRestorePointMessage"] = ProjectRestorePointService.LimitReachedMessage;
|
||||||
|
return RedirectToAction(nameof(Index), new { projectId });
|
||||||
|
}
|
||||||
|
|
||||||
var created = await restorePoints.CreateManualAsync(projectId, restorePointName, notes);
|
var created = await restorePoints.CreateManualAsync(projectId, restorePointName, notes);
|
||||||
TempData["ProjectRestorePointMessage"] = created is null
|
TempData["ProjectRestorePointMessage"] = created is null
|
||||||
? "Restore point could not be created because the project was not found."
|
? "Restore point could not be created because the project was not found."
|
||||||
@ -72,4 +78,24 @@ public sealed class ProjectRestorePointsController(
|
|||||||
|
|
||||||
return RedirectToAction(nameof(Index), new { projectId });
|
return RedirectToAction(nameof(Index), new { projectId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> Delete(int projectId, int id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var deleted = await restorePoints.DeleteAsync(id, projectId);
|
||||||
|
TempData["ProjectRestorePointMessage"] = deleted
|
||||||
|
? "Restore point deleted."
|
||||||
|
: "Restore point could not be deleted. It may not exist.";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Restore point delete failed unexpectedly for restore point {ProjectRestorePointID} in project {ProjectID}.", id, projectId);
|
||||||
|
TempData["ProjectRestorePointMessage"] = "Restore point could not be deleted. Please try again, or check the application logs if the problem continues.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(Index), new { projectId });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,11 @@ public interface IImportRepository
|
|||||||
{
|
{
|
||||||
Task<bool> ProjectTitleExistsAsync(string projectTitle);
|
Task<bool> ProjectTitleExistsAsync(string projectTitle);
|
||||||
Task<ImportResult> ImportAsync(PlotLineImportPackage package, string? importedProjectTitle);
|
Task<ImportResult> ImportAsync(PlotLineImportPackage package, string? importedProjectTitle);
|
||||||
|
Task<IReadOnlyDictionary<string, int>> GetIdentityMaxValuesAsync(IEnumerable<string> tableNames);
|
||||||
|
Task<ProjectBackupRelationshipImportLookups> GetRelationshipImportLookupsAsync();
|
||||||
|
Task<ProjectBackupRelationshipImportDiagnostics> GetRelationshipImportDiagnosticsAsync(int projectId);
|
||||||
|
Task<int> CreateProjectShellAsync(string projectName, string? description);
|
||||||
|
Task ApplyProjectBackupJsonAsync(int projectId, string jsonData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ImportRepository(ISqlConnectionFactory connectionFactory) : IImportRepository
|
public sealed class ImportRepository(ISqlConnectionFactory connectionFactory) : IImportRepository
|
||||||
@ -29,6 +34,90 @@ public sealed class ImportRepository(ISqlConnectionFactory connectionFactory) :
|
|||||||
return count > 0;
|
return count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyDictionary<string, int>> GetIdentityMaxValuesAsync(IEnumerable<string> tableNames)
|
||||||
|
{
|
||||||
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
var requestedTables = tableNames.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
||||||
|
var rows = await connection.QueryAsync<IdentityMaxRow>(
|
||||||
|
"dbo.ProjectBackup_GetIdentityMaxValues",
|
||||||
|
new { TableNames = string.Join(',', requestedTables) },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return rows.ToDictionary(row => row.TableName, row => row.MaxID, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProjectBackupRelationshipImportLookups> GetRelationshipImportLookupsAsync()
|
||||||
|
{
|
||||||
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
using var result = await connection.QueryMultipleAsync(
|
||||||
|
"dbo.ProjectBackup_GetRelationshipImportLookups",
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
|
||||||
|
return new ProjectBackupRelationshipImportLookups
|
||||||
|
{
|
||||||
|
RevisionStatuses = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
TimeModes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
DurationUnits = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
TimeConfidences = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
LocationTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
LocationRelationshipTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
PlotLineTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
ThreadTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
ThreadStatuses = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
ThreadEventTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
AssetKinds = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
AssetStates = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
AssetEventTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
AssetDependencyTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
AssetCustodyEventTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
CustodyRoles = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
CharacterRoleTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
PresenceTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
KnowledgeStates = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
SceneDependencyTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
SceneNoteTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
SceneAttachmentTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
ScenePurposeTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
SceneMetricTypes = (await result.ReadAsync<ProjectBackupLookupRow>()).ToList(),
|
||||||
|
RelationshipTypes = (await result.ReadAsync<RelationshipType>()).ToList(),
|
||||||
|
RelationshipStates = (await result.ReadAsync<RelationshipState>()).ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ProjectBackupRelationshipImportDiagnostics> GetRelationshipImportDiagnosticsAsync(int projectId)
|
||||||
|
{
|
||||||
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
return await connection.QuerySingleAsync<ProjectBackupRelationshipImportDiagnostics>(
|
||||||
|
"dbo.ProjectBackup_RelationshipImportDiagnostics",
|
||||||
|
new { ProjectID = projectId },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> CreateProjectShellAsync(string projectName, string? description)
|
||||||
|
{
|
||||||
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
return await connection.QuerySingleAsync<int>(
|
||||||
|
"dbo.Project_Save",
|
||||||
|
new { ProjectID = 0, ProjectName = projectName, Description = description },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ApplyProjectBackupJsonAsync(int projectId, string jsonData)
|
||||||
|
{
|
||||||
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
await connection.ExecuteAsync(
|
||||||
|
"dbo.ProjectRestore_ApplyFromJson",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
ProjectID = projectId,
|
||||||
|
SourceRestorePointID = (int?)null,
|
||||||
|
SafetyRestorePointID = (int?)null,
|
||||||
|
JsonData = jsonData
|
||||||
|
},
|
||||||
|
commandType: CommandType.StoredProcedure,
|
||||||
|
commandTimeout: 180);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<ImportResult> ImportAsync(PlotLineImportPackage package, string? importedProjectTitle)
|
public async Task<ImportResult> ImportAsync(PlotLineImportPackage package, string? importedProjectTitle)
|
||||||
{
|
{
|
||||||
using var connection = connectionFactory.CreateConnection();
|
using var connection = connectionFactory.CreateConnection();
|
||||||
@ -1322,4 +1411,10 @@ public sealed class ImportRepository(ISqlConnectionFactory connectionFactory) :
|
|||||||
public int LookupID { get; set; }
|
public int LookupID { get; set; }
|
||||||
public string StatusName { get; set; } = string.Empty;
|
public string StatusName { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class IdentityMaxRow
|
||||||
|
{
|
||||||
|
public string TableName { get; set; } = string.Empty;
|
||||||
|
public int MaxID { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,7 +62,9 @@ public sealed class ProjectBackupRepository(ISqlConnectionFactory connectionFact
|
|||||||
Scenarios = (await result.ReadAsync<Scenario>()).ToList(),
|
Scenarios = (await result.ReadAsync<Scenario>()).ToList(),
|
||||||
ScenarioSceneOrder = (await result.ReadAsync<ProjectBackupScenarioSceneOrder>()).ToList(),
|
ScenarioSceneOrder = (await result.ReadAsync<ProjectBackupScenarioSceneOrder>()).ToList(),
|
||||||
ScenarioValidationRuns = (await result.ReadAsync<ProjectBackupScenarioValidationRun>()).ToList(),
|
ScenarioValidationRuns = (await result.ReadAsync<ProjectBackupScenarioValidationRun>()).ToList(),
|
||||||
ScenarioWarnings = (await result.ReadAsync<ScenarioWarning>()).ToList()
|
ScenarioWarnings = (await result.ReadAsync<ScenarioWarning>()).ToList(),
|
||||||
|
GenreMetricPresets = (await result.ReadAsync<GenreMetricPreset>()).ToList(),
|
||||||
|
GenreMetricPresetMetrics = (await result.ReadAsync<ProjectBackupGenreMetricPresetMetric>()).ToList()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (project is null)
|
if (project is null)
|
||||||
@ -120,7 +122,9 @@ public sealed class ProjectBackupRepository(ISqlConnectionFactory connectionFact
|
|||||||
nameof(data.Scenarios),
|
nameof(data.Scenarios),
|
||||||
nameof(data.ScenarioSceneOrder),
|
nameof(data.ScenarioSceneOrder),
|
||||||
nameof(data.ScenarioValidationRuns),
|
nameof(data.ScenarioValidationRuns),
|
||||||
nameof(data.ScenarioWarnings)
|
nameof(data.ScenarioWarnings),
|
||||||
|
nameof(data.GenreMetricPresets),
|
||||||
|
nameof(data.GenreMetricPresetMetrics)
|
||||||
],
|
],
|
||||||
Note = "Original database IDs are included for diagnostics. Future import/restore should map by logical project structure and names where practical."
|
Note = "Original database IDs are included for diagnostics. Future import/restore should map by logical project structure and names where practical."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,9 @@ public interface IProjectRestorePointRepository
|
|||||||
{
|
{
|
||||||
Task<IReadOnlyList<ProjectRestorePoint>> ListAsync(int projectId);
|
Task<IReadOnlyList<ProjectRestorePoint>> ListAsync(int projectId);
|
||||||
Task<ProjectRestorePoint?> GetAsync(int projectRestorePointId, int projectId);
|
Task<ProjectRestorePoint?> GetAsync(int projectRestorePointId, int projectId);
|
||||||
|
Task<int> CountAsync(int projectId);
|
||||||
Task<ProjectRestorePoint> CreateAsync(int projectId, string restorePointName, string restorePointType, string jsonData, string? notes);
|
Task<ProjectRestorePoint> CreateAsync(int projectId, string restorePointName, string restorePointType, string jsonData, string? notes);
|
||||||
|
Task<bool> DeleteAsync(int projectRestorePointId, int projectId);
|
||||||
Task RestoreAsync(int projectId, int sourceRestorePointId, int safetyRestorePointId, string jsonData);
|
Task RestoreAsync(int projectId, int sourceRestorePointId, int safetyRestorePointId, string jsonData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,6 +35,15 @@ public sealed class ProjectRestorePointRepository(ISqlConnectionFactory connecti
|
|||||||
commandType: CommandType.StoredProcedure);
|
commandType: CommandType.StoredProcedure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<int> CountAsync(int projectId)
|
||||||
|
{
|
||||||
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
return await connection.QuerySingleAsync<int>(
|
||||||
|
"dbo.ProjectRestorePoint_Count",
|
||||||
|
new { ProjectID = projectId },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<ProjectRestorePoint> CreateAsync(int projectId, string restorePointName, string restorePointType, string jsonData, string? notes)
|
public async Task<ProjectRestorePoint> CreateAsync(int projectId, string restorePointName, string restorePointType, string jsonData, string? notes)
|
||||||
{
|
{
|
||||||
using var connection = connectionFactory.CreateConnection();
|
using var connection = connectionFactory.CreateConnection();
|
||||||
@ -49,6 +60,15 @@ public sealed class ProjectRestorePointRepository(ISqlConnectionFactory connecti
|
|||||||
commandType: CommandType.StoredProcedure);
|
commandType: CommandType.StoredProcedure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int projectRestorePointId, int projectId)
|
||||||
|
{
|
||||||
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
return await connection.QuerySingleAsync<bool>(
|
||||||
|
"dbo.ProjectRestorePoint_Delete",
|
||||||
|
new { ProjectRestorePointID = projectRestorePointId, ProjectID = projectId },
|
||||||
|
commandType: CommandType.StoredProcedure);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task RestoreAsync(int projectId, int sourceRestorePointId, int safetyRestorePointId, string jsonData)
|
public async Task RestoreAsync(int projectId, int sourceRestorePointId, int safetyRestorePointId, string jsonData)
|
||||||
{
|
{
|
||||||
using var connection = connectionFactory.CreateConnection();
|
using var connection = connectionFactory.CreateConnection();
|
||||||
|
|||||||
@ -8,6 +8,8 @@ public sealed class Project
|
|||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
public DateTime UpdatedDate { get; set; }
|
public DateTime UpdatedDate { get; set; }
|
||||||
public bool IsArchived { get; set; }
|
public bool IsArchived { get; set; }
|
||||||
|
public DateTime? ArchivedDate { get; set; }
|
||||||
|
public string? ArchivedReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class GenreMetricPreset
|
public sealed class GenreMetricPreset
|
||||||
@ -31,6 +33,8 @@ public sealed class Book
|
|||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
public DateTime UpdatedDate { get; set; }
|
public DateTime UpdatedDate { get; set; }
|
||||||
public bool IsArchived { get; set; }
|
public bool IsArchived { get; set; }
|
||||||
|
public DateTime? ArchivedDate { get; set; }
|
||||||
|
public string? ArchivedReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Chapter
|
public sealed class Chapter
|
||||||
@ -47,6 +51,8 @@ public sealed class Chapter
|
|||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
public DateTime UpdatedDate { get; set; }
|
public DateTime UpdatedDate { get; set; }
|
||||||
public bool IsArchived { get; set; }
|
public bool IsArchived { get; set; }
|
||||||
|
public DateTime? ArchivedDate { get; set; }
|
||||||
|
public string? ArchivedReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Scene
|
public sealed class Scene
|
||||||
@ -78,6 +84,8 @@ public sealed class Scene
|
|||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
public DateTime UpdatedDate { get; set; }
|
public DateTime UpdatedDate { get; set; }
|
||||||
public bool IsArchived { get; set; }
|
public bool IsArchived { get; set; }
|
||||||
|
public DateTime? ArchivedDate { get; set; }
|
||||||
|
public string? ArchivedReason { get; set; }
|
||||||
public List<int> SelectedPurposeTypeIds { get; set; } = [];
|
public List<int> SelectedPurposeTypeIds { get; set; } = [];
|
||||||
public List<ScenePurposeLabel> PurposeLabels { get; set; } = [];
|
public List<ScenePurposeLabel> PurposeLabels { get; set; } = [];
|
||||||
public List<SceneMetricValue> Metrics { get; set; } = [];
|
public List<SceneMetricValue> Metrics { get; set; } = [];
|
||||||
@ -153,6 +161,9 @@ public sealed class SceneMetricType
|
|||||||
public int SortOrder { get; set; }
|
public int SortOrder { get; set; }
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
public bool IsEnabledForProject { get; set; }
|
public bool IsEnabledForProject { get; set; }
|
||||||
|
public bool IsCustom { get; set; }
|
||||||
|
public string? GenrePresetKeys { get; set; }
|
||||||
|
public string? GenrePresetNames { get; set; }
|
||||||
public int ValueCount { get; set; }
|
public int ValueCount { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,6 +226,7 @@ public sealed class ProjectTimelineMetricSetting
|
|||||||
public int MetricTypeID { get; set; }
|
public int MetricTypeID { get; set; }
|
||||||
public string MetricName { get; set; } = string.Empty;
|
public string MetricName { get; set; } = string.Empty;
|
||||||
public int SortOrder { get; set; }
|
public int SortOrder { get; set; }
|
||||||
|
public DateTime CreatedDate { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlotLineType
|
public sealed class PlotLineType
|
||||||
@ -244,6 +256,8 @@ public sealed class PlotLineItem
|
|||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
public DateTime UpdatedDate { get; set; }
|
public DateTime UpdatedDate { get; set; }
|
||||||
public bool IsArchived { get; set; }
|
public bool IsArchived { get; set; }
|
||||||
|
public DateTime? ArchivedDate { get; set; }
|
||||||
|
public string? ArchivedReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ThreadType
|
public sealed class ThreadType
|
||||||
@ -283,6 +297,8 @@ public sealed class PlotThread
|
|||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
public DateTime UpdatedDate { get; set; }
|
public DateTime UpdatedDate { get; set; }
|
||||||
public bool IsArchived { get; set; }
|
public bool IsArchived { get; set; }
|
||||||
|
public DateTime? ArchivedDate { get; set; }
|
||||||
|
public string? ArchivedReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ThreadEventType
|
public sealed class ThreadEventType
|
||||||
@ -343,6 +359,7 @@ public sealed class AssetState
|
|||||||
public int AssetStateID { get; set; }
|
public int AssetStateID { get; set; }
|
||||||
public int? ProjectID { get; set; }
|
public int? ProjectID { get; set; }
|
||||||
public int? AssetKindID { get; set; }
|
public int? AssetKindID { get; set; }
|
||||||
|
public string? KindName { get; set; }
|
||||||
public string StateName { get; set; } = string.Empty;
|
public string StateName { get; set; } = string.Empty;
|
||||||
public int SortOrder { get; set; }
|
public int SortOrder { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
@ -369,6 +386,8 @@ public sealed class StoryAsset
|
|||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
public DateTime UpdatedDate { get; set; }
|
public DateTime UpdatedDate { get; set; }
|
||||||
public bool IsArchived { get; set; }
|
public bool IsArchived { get; set; }
|
||||||
|
public DateTime? ArchivedDate { get; set; }
|
||||||
|
public string? ArchivedReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class AssetEventType
|
public sealed class AssetEventType
|
||||||
@ -433,6 +452,8 @@ public sealed class AssetDependency
|
|||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
public DateTime UpdatedDate { get; set; }
|
public DateTime UpdatedDate { get; set; }
|
||||||
public bool IsArchived { get; set; }
|
public bool IsArchived { get; set; }
|
||||||
|
public DateTime? ArchivedDate { get; set; }
|
||||||
|
public string? ArchivedReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class AssetCustodyEventType
|
public sealed class AssetCustodyEventType
|
||||||
@ -508,6 +529,8 @@ public sealed class Character
|
|||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
public DateTime UpdatedDate { get; set; }
|
public DateTime UpdatedDate { get; set; }
|
||||||
public bool IsArchived { get; set; }
|
public bool IsArchived { get; set; }
|
||||||
|
public DateTime? ArchivedDate { get; set; }
|
||||||
|
public string? ArchivedReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class CharacterRoleInSceneType
|
public sealed class CharacterRoleInSceneType
|
||||||
@ -677,6 +700,8 @@ public sealed class CharacterRelationship
|
|||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
public DateTime UpdatedDate { get; set; }
|
public DateTime UpdatedDate { get; set; }
|
||||||
public bool IsArchived { get; set; }
|
public bool IsArchived { get; set; }
|
||||||
|
public DateTime? ArchivedDate { get; set; }
|
||||||
|
public string? ArchivedReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RelationshipEvent
|
public sealed class RelationshipEvent
|
||||||
@ -762,6 +787,8 @@ public sealed class LocationItem
|
|||||||
public DateTime CreatedDate { get; set; }
|
public DateTime CreatedDate { get; set; }
|
||||||
public DateTime UpdatedDate { get; set; }
|
public DateTime UpdatedDate { get; set; }
|
||||||
public bool IsArchived { get; set; }
|
public bool IsArchived { get; set; }
|
||||||
|
public DateTime? ArchivedDate { get; set; }
|
||||||
|
public string? ArchivedReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class LocationRelationship
|
public sealed class LocationRelationship
|
||||||
|
|||||||
@ -29,6 +29,59 @@ public sealed class ImportAliasesDto
|
|||||||
public Dictionary<string, string> Locations { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
public Dictionary<string, string> Locations { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class ProjectBackupRelationshipImportLookups
|
||||||
|
{
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> RevisionStatuses { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> TimeModes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> DurationUnits { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> TimeConfidences { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> LocationTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> LocationRelationshipTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> PlotLineTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> ThreadTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> ThreadStatuses { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> ThreadEventTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> AssetKinds { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> AssetStates { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> AssetEventTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> AssetDependencyTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> AssetCustodyEventTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> CustodyRoles { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> CharacterRoleTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> PresenceTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> KnowledgeStates { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> SceneDependencyTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> SceneNoteTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> SceneAttachmentTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> ScenePurposeTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupLookupRow> SceneMetricTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<RelationshipType> RelationshipTypes { get; init; } = [];
|
||||||
|
public IReadOnlyList<RelationshipState> RelationshipStates { get; init; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ProjectBackupLookupRow
|
||||||
|
{
|
||||||
|
public int LookupID { get; set; }
|
||||||
|
public string LookupName { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ProjectBackupRelationshipImportDiagnostics
|
||||||
|
{
|
||||||
|
public int CharacterCount { get; set; }
|
||||||
|
public int RelationshipCount { get; set; }
|
||||||
|
public int RelationshipEventCount { get; set; }
|
||||||
|
public int MapRelationshipCount { get; set; }
|
||||||
|
public int MissingSourceCharacterCount { get; set; }
|
||||||
|
public int MissingTargetCharacterCount { get; set; }
|
||||||
|
public int MissingRelationshipTypeCount { get; set; }
|
||||||
|
public int MissingInitialStateCount { get; set; }
|
||||||
|
public int MissingEventRelationshipCount { get; set; }
|
||||||
|
public int MissingEventSceneCount { get; set; }
|
||||||
|
public int MissingEventStateCount { get; set; }
|
||||||
|
public int ArchivedRelationshipCount { get; set; }
|
||||||
|
public int ArchivedCharacterEndpointCount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class ImportProjectDto
|
public sealed class ImportProjectDto
|
||||||
{
|
{
|
||||||
public string Title { get; set; } = string.Empty;
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|||||||
@ -54,6 +54,8 @@ public sealed class ProjectBackupData
|
|||||||
public IReadOnlyList<ProjectBackupScenarioSceneOrder> ScenarioSceneOrder { get; init; } = [];
|
public IReadOnlyList<ProjectBackupScenarioSceneOrder> ScenarioSceneOrder { get; init; } = [];
|
||||||
public IReadOnlyList<ProjectBackupScenarioValidationRun> ScenarioValidationRuns { get; init; } = [];
|
public IReadOnlyList<ProjectBackupScenarioValidationRun> ScenarioValidationRuns { get; init; } = [];
|
||||||
public IReadOnlyList<ScenarioWarning> ScenarioWarnings { get; init; } = [];
|
public IReadOnlyList<ScenarioWarning> ScenarioWarnings { get; init; } = [];
|
||||||
|
public IReadOnlyList<GenreMetricPreset> GenreMetricPresets { get; init; } = [];
|
||||||
|
public IReadOnlyList<ProjectBackupGenreMetricPresetMetric> GenreMetricPresetMetrics { get; init; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ProjectBackupDiagnostics
|
public sealed class ProjectBackupDiagnostics
|
||||||
@ -130,3 +132,13 @@ public sealed class ProjectBackupScenarioValidationRun
|
|||||||
public DateTime? CompletedDate { get; set; }
|
public DateTime? CompletedDate { get; set; }
|
||||||
public int WarningCount { get; set; }
|
public int WarningCount { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class ProjectBackupGenreMetricPresetMetric
|
||||||
|
{
|
||||||
|
public int GenreMetricPresetID { get; set; }
|
||||||
|
public string PresetKey { get; set; } = string.Empty;
|
||||||
|
public string PresetName { get; set; } = string.Empty;
|
||||||
|
public int MetricTypeID { get; set; }
|
||||||
|
public string MetricName { get; set; } = string.Empty;
|
||||||
|
public int SortOrder { get; set; }
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,8 @@ public interface IProjectRestorePointService
|
|||||||
Task<ProjectRestorePoint?> CreateManualAsync(int projectId, string? restorePointName, string? notes);
|
Task<ProjectRestorePoint?> CreateManualAsync(int projectId, string? restorePointName, string? notes);
|
||||||
Task<ProjectRestorePoint?> CreatePreRestoreAsync(int projectId, string sourceRestorePointName);
|
Task<ProjectRestorePoint?> CreatePreRestoreAsync(int projectId, string sourceRestorePointName);
|
||||||
Task<ProjectRestorePointDownload?> DownloadAsync(int projectRestorePointId, int projectId);
|
Task<ProjectRestorePointDownload?> DownloadAsync(int projectRestorePointId, int projectId);
|
||||||
|
Task<bool> HasReachedManualLimitAsync(int projectId);
|
||||||
|
Task<bool> DeleteAsync(int projectRestorePointId, int projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ProjectRestorePointService(
|
public sealed class ProjectRestorePointService(
|
||||||
@ -20,6 +22,8 @@ public sealed class ProjectRestorePointService(
|
|||||||
{
|
{
|
||||||
private const string ManualRestorePointType = "Manual";
|
private const string ManualRestorePointType = "Manual";
|
||||||
private const string PreRestorePointType = "PreRestore";
|
private const string PreRestorePointType = "PreRestore";
|
||||||
|
private const int MaxRestorePointsPerProject = 10;
|
||||||
|
public const string LimitReachedMessage = "You have reached the limit of 10 restore points for this project. Delete an old restore point before creating a new one. You can download a restore point first if you want to keep a local backup copy.";
|
||||||
|
|
||||||
public async Task<ProjectRestorePointListViewModel?> GetAsync(int projectId)
|
public async Task<ProjectRestorePointListViewModel?> GetAsync(int projectId)
|
||||||
{
|
{
|
||||||
@ -38,6 +42,14 @@ public sealed class ProjectRestorePointService(
|
|||||||
|
|
||||||
public async Task<ProjectRestorePoint?> CreateManualAsync(int projectId, string? restorePointName, string? notes)
|
public async Task<ProjectRestorePoint?> CreateManualAsync(int projectId, string? restorePointName, string? notes)
|
||||||
{
|
{
|
||||||
|
if (await HasReachedManualLimitAsync(projectId))
|
||||||
|
{
|
||||||
|
logger.LogInformation("Manual restore point creation blocked for project {ProjectID} because the project already has {MaxRestorePointCount} restore points.",
|
||||||
|
projectId,
|
||||||
|
MaxRestorePointsPerProject);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var name = string.IsNullOrWhiteSpace(restorePointName)
|
var name = string.IsNullOrWhiteSpace(restorePointName)
|
||||||
? $"Manual restore point - {DateTime.Now:yyyy-MM-dd HH:mm}"
|
? $"Manual restore point - {DateTime.Now:yyyy-MM-dd HH:mm}"
|
||||||
: restorePointName.Trim();
|
: restorePointName.Trim();
|
||||||
@ -88,6 +100,20 @@ public sealed class ProjectRestorePointService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> HasReachedManualLimitAsync(int projectId)
|
||||||
|
=> await restorePoints.CountAsync(projectId) >= MaxRestorePointsPerProject;
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(int projectRestorePointId, int projectId)
|
||||||
|
{
|
||||||
|
var deleted = await restorePoints.DeleteAsync(projectRestorePointId, projectId);
|
||||||
|
if (!deleted)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Restore point delete failed for restore point {ProjectRestorePointID} in project {ProjectID}.", projectRestorePointId, projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
private static string? Clean(string? value)
|
private static string? Clean(string? value)
|
||||||
{
|
{
|
||||||
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||||
|
|||||||
@ -9,17 +9,19 @@ AS
|
|||||||
BEGIN
|
BEGIN
|
||||||
SET NOCOUNT ON;
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
SELECT ProjectID, ProjectName, Description, CreatedDate, UpdatedDate, IsArchived
|
SELECT ProjectID, ProjectName, Description, CreatedDate, UpdatedDate, IsArchived, ArchivedDate, ArchivedReason
|
||||||
FROM dbo.Projects
|
FROM dbo.Projects
|
||||||
WHERE ProjectID = @ProjectID;
|
WHERE ProjectID = @ProjectID;
|
||||||
|
|
||||||
SELECT BookID, ProjectID, BookTitle, BookNumber, Description, SortOrder, CreatedDate, UpdatedDate, IsArchived
|
SELECT BookID, ProjectID, BookTitle, BookNumber, Description, SortOrder, CreatedDate, UpdatedDate,
|
||||||
|
IsArchived, ArchivedDate, ArchivedReason
|
||||||
FROM dbo.Books
|
FROM dbo.Books
|
||||||
WHERE ProjectID = @ProjectID
|
WHERE ProjectID = @ProjectID
|
||||||
ORDER BY SortOrder, BookNumber, BookID;
|
ORDER BY SortOrder, BookNumber, BookID;
|
||||||
|
|
||||||
SELECT c.ChapterID, c.BookID, c.ChapterNumber, c.ChapterTitle, c.POVCharacterID, c.Summary, c.SortOrder,
|
SELECT c.ChapterID, c.BookID, c.ChapterNumber, c.ChapterTitle, c.POVCharacterID, c.Summary, c.SortOrder,
|
||||||
c.RevisionStatusID, rs.StatusName AS RevisionStatusName, c.CreatedDate, c.UpdatedDate, c.IsArchived
|
c.RevisionStatusID, rs.StatusName AS RevisionStatusName, c.CreatedDate, c.UpdatedDate,
|
||||||
|
c.IsArchived, c.ArchivedDate, c.ArchivedReason
|
||||||
FROM dbo.Chapters c
|
FROM dbo.Chapters c
|
||||||
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = c.RevisionStatusID
|
INNER JOIN dbo.RevisionStatuses rs ON rs.RevisionStatusID = c.RevisionStatusID
|
||||||
@ -30,7 +32,8 @@ BEGIN
|
|||||||
l.LocationName AS PrimaryLocationName, l.LocationName AS PrimaryLocationPath, s.SortOrder, s.TimeModeID, tm.TimeModeName,
|
l.LocationName AS PrimaryLocationName, l.LocationName AS PrimaryLocationPath, s.SortOrder, s.TimeModeID, tm.TimeModeName,
|
||||||
s.StartDateTime, s.EndDateTime, s.DurationAmount, s.DurationUnitID, du.DurationUnitName,
|
s.StartDateTime, s.EndDateTime, s.DurationAmount, s.DurationUnitID, du.DurationUnitName,
|
||||||
s.RelativeTimeText, s.TimeConfidenceID, tc.TimeConfidenceName, s.ScenePurposeNotes, s.SceneOutcomeNotes,
|
s.RelativeTimeText, s.TimeConfidenceID, tc.TimeConfidenceName, s.ScenePurposeNotes, s.SceneOutcomeNotes,
|
||||||
s.RevisionStatusID, rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate, s.IsArchived
|
s.RevisionStatusID, rs.StatusName AS RevisionStatusName, s.CreatedDate, s.UpdatedDate,
|
||||||
|
s.IsArchived, s.ArchivedDate, s.ArchivedReason
|
||||||
FROM dbo.Scenes s
|
FROM dbo.Scenes s
|
||||||
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
@ -60,9 +63,33 @@ BEGIN
|
|||||||
ORDER BY so.SceneID, so.SceneOutcomeID;
|
ORDER BY so.SceneID, so.SceneOutcomeID;
|
||||||
|
|
||||||
SELECT mt.MetricTypeID, mt.ProjectID, mt.MetricName, mt.Description, mt.MinValue, mt.MaxValue,
|
SELECT mt.MetricTypeID, mt.ProjectID, mt.MetricName, mt.Description, mt.MinValue, mt.MaxValue,
|
||||||
mt.DefaultValue, mt.SortOrder, mt.IsActive, CAST(1 AS bit) AS IsEnabledForProject,
|
mt.DefaultValue, COALESCE(ptms.SortOrder, mt.SortOrder) AS SortOrder, mt.IsActive,
|
||||||
(SELECT COUNT(1) FROM dbo.SceneMetricValues smv WHERE smv.MetricTypeID = mt.MetricTypeID) AS ValueCount
|
CAST(CASE WHEN mt.IsActive = 1 AND ptms.MetricTypeID IS NOT NULL THEN 1 ELSE 0 END AS bit) AS IsEnabledForProject,
|
||||||
|
CAST(CASE WHEN mt.ProjectID = @ProjectID THEN 1 ELSE 0 END AS bit) AS IsCustom,
|
||||||
|
STUFF((
|
||||||
|
SELECT N',' + gmp.PresetKey
|
||||||
|
FROM dbo.GenreMetricPresetMetrics gpmm
|
||||||
|
INNER JOIN dbo.GenreMetricPresets gmp ON gmp.GenreMetricPresetID = gpmm.GenreMetricPresetID
|
||||||
|
WHERE gpmm.MetricTypeID = mt.MetricTypeID
|
||||||
|
ORDER BY gmp.SortOrder, gmp.PresetName
|
||||||
|
FOR XML PATH(N''), TYPE).value(N'.', N'nvarchar(max)'), 1, 1, N'') AS GenrePresetKeys,
|
||||||
|
STUFF((
|
||||||
|
SELECT N', ' + gmp.PresetName
|
||||||
|
FROM dbo.GenreMetricPresetMetrics gpmm
|
||||||
|
INNER JOIN dbo.GenreMetricPresets gmp ON gmp.GenreMetricPresetID = gpmm.GenreMetricPresetID
|
||||||
|
WHERE gpmm.MetricTypeID = mt.MetricTypeID
|
||||||
|
ORDER BY gmp.SortOrder, gmp.PresetName
|
||||||
|
FOR XML PATH(N''), TYPE).value(N'.', N'nvarchar(max)'), 1, 2, N'') AS GenrePresetNames,
|
||||||
|
(
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM dbo.SceneMetricValues smv
|
||||||
|
INNER JOIN dbo.Scenes s ON s.SceneID = smv.SceneID
|
||||||
|
INNER JOIN dbo.Chapters c ON c.ChapterID = s.ChapterID
|
||||||
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
|
WHERE smv.MetricTypeID = mt.MetricTypeID AND b.ProjectID = @ProjectID
|
||||||
|
) AS ValueCount
|
||||||
FROM dbo.SceneMetricTypes mt
|
FROM dbo.SceneMetricTypes mt
|
||||||
|
LEFT JOIN dbo.ProjectTimelineMetricSettings ptms ON ptms.ProjectID = @ProjectID AND ptms.MetricTypeID = mt.MetricTypeID
|
||||||
WHERE mt.ProjectID IS NULL
|
WHERE mt.ProjectID IS NULL
|
||||||
OR mt.ProjectID = @ProjectID
|
OR mt.ProjectID = @ProjectID
|
||||||
OR EXISTS
|
OR EXISTS
|
||||||
@ -74,7 +101,7 @@ BEGIN
|
|||||||
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
INNER JOIN dbo.Books b ON b.BookID = c.BookID
|
||||||
WHERE smv.MetricTypeID = mt.MetricTypeID AND b.ProjectID = @ProjectID
|
WHERE smv.MetricTypeID = mt.MetricTypeID AND b.ProjectID = @ProjectID
|
||||||
)
|
)
|
||||||
ORDER BY mt.SortOrder, mt.MetricName;
|
ORDER BY COALESCE(ptms.SortOrder, mt.SortOrder), mt.MetricName;
|
||||||
|
|
||||||
SELECT smv.SceneID, smv.MetricTypeID, mt.MetricName, mt.Description, mt.MinValue, mt.MaxValue,
|
SELECT smv.SceneID, smv.MetricTypeID, mt.MetricName, mt.Description, mt.MinValue, mt.MaxValue,
|
||||||
mt.DefaultValue, mt.SortOrder, mt.IsActive, smv.Value, smv.Notes
|
mt.DefaultValue, mt.SortOrder, mt.IsActive, smv.Value, smv.Notes
|
||||||
@ -91,7 +118,7 @@ BEGIN
|
|||||||
FROM dbo.ProjectTimelineSettings
|
FROM dbo.ProjectTimelineSettings
|
||||||
WHERE ProjectID = @ProjectID;
|
WHERE ProjectID = @ProjectID;
|
||||||
|
|
||||||
SELECT ptms.ProjectID, ptms.MetricTypeID, mt.MetricName, ptms.SortOrder
|
SELECT ptms.ProjectID, ptms.MetricTypeID, mt.MetricName, ptms.SortOrder, ptms.CreatedDate
|
||||||
FROM dbo.ProjectTimelineMetricSettings ptms
|
FROM dbo.ProjectTimelineMetricSettings ptms
|
||||||
INNER JOIN dbo.SceneMetricTypes mt ON mt.MetricTypeID = ptms.MetricTypeID
|
INNER JOIN dbo.SceneMetricTypes mt ON mt.MetricTypeID = ptms.MetricTypeID
|
||||||
WHERE ptms.ProjectID = @ProjectID
|
WHERE ptms.ProjectID = @ProjectID
|
||||||
@ -105,7 +132,8 @@ BEGIN
|
|||||||
|
|
||||||
SELECT pl.PlotLineID, pl.ProjectID, pl.BookID, b.BookTitle, pl.PlotLineName, pl.PlotLineTypeID,
|
SELECT pl.PlotLineID, pl.ProjectID, pl.BookID, b.BookTitle, pl.PlotLineName, pl.PlotLineTypeID,
|
||||||
plt.TypeName AS PlotLineTypeName, pl.Description, pl.ParentPlotLineID, parent.PlotLineName AS ParentPlotLineName,
|
plt.TypeName AS PlotLineTypeName, pl.Description, pl.ParentPlotLineID, parent.PlotLineName AS ParentPlotLineName,
|
||||||
pl.SortOrder, pl.Colour, pl.IsMainPlot, pl.IsVisibleOnTimeline, pl.CreatedDate, pl.UpdatedDate, pl.IsArchived
|
pl.SortOrder, pl.Colour, pl.IsMainPlot, pl.IsVisibleOnTimeline, pl.CreatedDate, pl.UpdatedDate,
|
||||||
|
pl.IsArchived, pl.ArchivedDate, pl.ArchivedReason
|
||||||
FROM dbo.PlotLines pl
|
FROM dbo.PlotLines pl
|
||||||
INNER JOIN dbo.PlotLineTypes plt ON plt.PlotLineTypeID = pl.PlotLineTypeID
|
INNER JOIN dbo.PlotLineTypes plt ON plt.PlotLineTypeID = pl.PlotLineTypeID
|
||||||
LEFT JOIN dbo.Books b ON b.BookID = pl.BookID
|
LEFT JOIN dbo.Books b ON b.BookID = pl.BookID
|
||||||
@ -116,7 +144,7 @@ BEGIN
|
|||||||
SELECT pt.PlotThreadID, pt.PlotLineID, pl.ProjectID, pl.PlotLineName, pt.ThreadTitle, pt.ThreadTypeID,
|
SELECT pt.PlotThreadID, pt.PlotLineID, pl.ProjectID, pl.PlotLineName, pt.ThreadTitle, pt.ThreadTypeID,
|
||||||
tt.TypeName AS ThreadTypeName, pt.ThreadStatusID, ts.StatusName AS ThreadStatusName, pt.Importance,
|
tt.TypeName AS ThreadTypeName, pt.ThreadStatusID, ts.StatusName AS ThreadStatusName, pt.Importance,
|
||||||
pt.Summary, pt.IntroducedSceneID, pt.PlannedResolutionSceneID, pt.ActualResolutionSceneID,
|
pt.Summary, pt.IntroducedSceneID, pt.PlannedResolutionSceneID, pt.ActualResolutionSceneID,
|
||||||
pt.CreatedDate, pt.UpdatedDate, pt.IsArchived
|
pt.CreatedDate, pt.UpdatedDate, pt.IsArchived, pt.ArchivedDate, pt.ArchivedReason
|
||||||
FROM dbo.PlotThreads pt
|
FROM dbo.PlotThreads pt
|
||||||
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
|
INNER JOIN dbo.PlotLines pl ON pl.PlotLineID = pt.PlotLineID
|
||||||
INNER JOIN dbo.ThreadTypes tt ON tt.ThreadTypeID = pt.ThreadTypeID
|
INNER JOIN dbo.ThreadTypes tt ON tt.ThreadTypeID = pt.ThreadTypeID
|
||||||
@ -137,7 +165,7 @@ BEGIN
|
|||||||
SELECT sa.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName, sa.Description,
|
SELECT sa.StoryAssetID, sa.ProjectID, sa.AssetName, sa.AssetKindID, ak.KindName, sa.Description,
|
||||||
sa.Importance, sa.CurrentStateID, ast.StateName AS CurrentStateName, sa.CurrentLocationID,
|
sa.Importance, sa.CurrentStateID, ast.StateName AS CurrentStateName, sa.CurrentLocationID,
|
||||||
l.LocationName AS CurrentLocationName, l.LocationName AS CurrentLocationPath, sa.IsResolved,
|
l.LocationName AS CurrentLocationName, l.LocationName AS CurrentLocationPath, sa.IsResolved,
|
||||||
sa.ShowInQuickAddBar, sa.CreatedDate, sa.UpdatedDate, sa.IsArchived
|
sa.ShowInQuickAddBar, sa.CreatedDate, sa.UpdatedDate, sa.IsArchived, sa.ArchivedDate, sa.ArchivedReason
|
||||||
FROM dbo.StoryAssets sa
|
FROM dbo.StoryAssets sa
|
||||||
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
INNER JOIN dbo.AssetKinds ak ON ak.AssetKindID = sa.AssetKindID
|
||||||
LEFT JOIN dbo.AssetStates ast ON ast.AssetStateID = sa.CurrentStateID
|
LEFT JOIN dbo.AssetStates ast ON ast.AssetStateID = sa.CurrentStateID
|
||||||
@ -145,9 +173,10 @@ BEGIN
|
|||||||
WHERE sa.ProjectID = @ProjectID
|
WHERE sa.ProjectID = @ProjectID
|
||||||
ORDER BY sa.Importance DESC, sa.AssetName;
|
ORDER BY sa.Importance DESC, sa.AssetName;
|
||||||
|
|
||||||
SELECT ast.AssetStateID, ast.ProjectID, ast.AssetKindID, ast.StateName, ast.SortOrder, ast.Description,
|
SELECT ast.AssetStateID, ast.ProjectID, ast.AssetKindID, ak.KindName, ast.StateName, ast.SortOrder, ast.Description,
|
||||||
ast.IsResolvedState, ast.IsActive
|
ast.IsResolvedState, ast.IsActive
|
||||||
FROM dbo.AssetStates ast
|
FROM dbo.AssetStates ast
|
||||||
|
LEFT JOIN dbo.AssetKinds ak ON ak.AssetKindID = ast.AssetKindID
|
||||||
WHERE ast.ProjectID IS NULL
|
WHERE ast.ProjectID IS NULL
|
||||||
OR ast.ProjectID = @ProjectID
|
OR ast.ProjectID = @ProjectID
|
||||||
OR EXISTS (SELECT 1 FROM dbo.StoryAssets sa WHERE sa.ProjectID = @ProjectID AND sa.CurrentStateID = ast.AssetStateID)
|
OR EXISTS (SELECT 1 FROM dbo.StoryAssets sa WHERE sa.ProjectID = @ProjectID AND sa.CurrentStateID = ast.AssetStateID)
|
||||||
@ -215,7 +244,7 @@ BEGIN
|
|||||||
|
|
||||||
SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart,
|
SELECT CharacterID, ProjectID, CharacterName, ShortName, Sex, BirthDate, AgeAtSeriesStart,
|
||||||
AgeReferenceSceneID, Height, EyeColour, CharacterImportance, ShowInQuickAddBar,
|
AgeReferenceSceneID, Height, EyeColour, CharacterImportance, ShowInQuickAddBar,
|
||||||
DefaultDescription, CreatedDate, UpdatedDate, IsArchived
|
DefaultDescription, CreatedDate, UpdatedDate, IsArchived, ArchivedDate, ArchivedReason
|
||||||
FROM dbo.Characters
|
FROM dbo.Characters
|
||||||
WHERE ProjectID = @ProjectID
|
WHERE ProjectID = @ProjectID
|
||||||
ORDER BY CharacterImportance DESC, CharacterName;
|
ORDER BY CharacterImportance DESC, CharacterName;
|
||||||
@ -281,7 +310,8 @@ BEGIN
|
|||||||
rt.SortOrder AS RelationshipTypeSortOrder, cr.IsPermanent, cr.StartSceneID, cr.EndSceneID,
|
rt.SortOrder AS RelationshipTypeSortOrder, cr.IsPermanent, cr.StartSceneID, cr.EndSceneID,
|
||||||
cr.IsKnownToReader, cr.Notes, cr.IsInitialRelationship, cr.InitialBookID, ib.BookTitle AS InitialBookTitle,
|
cr.IsKnownToReader, cr.Notes, cr.IsInitialRelationship, cr.InitialBookID, ib.BookTitle AS InitialBookTitle,
|
||||||
cr.ReaderInitiallyKnows, cr.InitialRelationshipStateID, irs.StateName AS InitialRelationshipStateName,
|
cr.ReaderInitiallyKnows, cr.InitialRelationshipStateID, irs.StateName AS InitialRelationshipStateName,
|
||||||
cr.InitialIntensity, cr.IsReciprocal, cr.CreatedDate, cr.UpdatedDate, cr.IsArchived
|
cr.InitialIntensity, cr.IsReciprocal, cr.CreatedDate, cr.UpdatedDate,
|
||||||
|
cr.IsArchived, cr.ArchivedDate, cr.ArchivedReason
|
||||||
FROM dbo.CharacterRelationships cr
|
FROM dbo.CharacterRelationships cr
|
||||||
INNER JOIN dbo.Characters a ON a.CharacterID = cr.CharacterAID
|
INNER JOIN dbo.Characters a ON a.CharacterID = cr.CharacterAID
|
||||||
INNER JOIN dbo.Characters bch ON bch.CharacterID = cr.CharacterBID
|
INNER JOIN dbo.Characters bch ON bch.CharacterID = cr.CharacterBID
|
||||||
@ -315,7 +345,7 @@ BEGIN
|
|||||||
SELECT l.LocationID, l.ProjectID, l.ParentLocationID, parent.LocationName AS ParentLocationName,
|
SELECT l.LocationID, l.ProjectID, l.ParentLocationID, parent.LocationName AS ParentLocationName,
|
||||||
l.LocationName, l.LocationTypeID, lt.TypeName AS LocationTypeName, l.Description,
|
l.LocationName, l.LocationTypeID, lt.TypeName AS LocationTypeName, l.Description,
|
||||||
l.LocationName AS LocationPath, CAST(0 AS int) AS Depth, l.ShowInQuickAddBar,
|
l.LocationName AS LocationPath, CAST(0 AS int) AS Depth, l.ShowInQuickAddBar,
|
||||||
l.CreatedDate, l.UpdatedDate, l.IsArchived
|
l.CreatedDate, l.UpdatedDate, l.IsArchived, l.ArchivedDate, l.ArchivedReason
|
||||||
FROM dbo.Locations l
|
FROM dbo.Locations l
|
||||||
LEFT JOIN dbo.Locations parent ON parent.LocationID = l.ParentLocationID
|
LEFT JOIN dbo.Locations parent ON parent.LocationID = l.ParentLocationID
|
||||||
LEFT JOIN dbo.LocationTypes lt ON lt.LocationTypeID = l.LocationTypeID
|
LEFT JOIN dbo.LocationTypes lt ON lt.LocationTypeID = l.LocationTypeID
|
||||||
@ -426,7 +456,8 @@ BEGIN
|
|||||||
ORDER BY b.SortOrder, c.SortOrder;
|
ORDER BY b.SortOrder, c.SortOrder;
|
||||||
|
|
||||||
SELECT sc.ScenarioID, sc.ProjectID, sc.BookID, sc.ScenarioName, sc.Description, sc.BaseCreatedDate,
|
SELECT sc.ScenarioID, sc.ProjectID, sc.BookID, sc.ScenarioName, sc.Description, sc.BaseCreatedDate,
|
||||||
sc.IsAppliedToMain, sc.IsArchived, sc.CreatedDate, sc.UpdatedDate, p.ProjectName, b.BookTitle,
|
sc.IsAppliedToMain, sc.IsArchived, sc.ArchivedDate, sc.ArchivedReason,
|
||||||
|
sc.CreatedDate, sc.UpdatedDate, p.ProjectName, b.BookTitle,
|
||||||
(SELECT COUNT(1) FROM dbo.ScenarioSceneOrder sso WHERE sso.ScenarioID = sc.ScenarioID) AS SceneCount,
|
(SELECT COUNT(1) FROM dbo.ScenarioSceneOrder sso WHERE sso.ScenarioID = sc.ScenarioID) AS SceneCount,
|
||||||
(SELECT COUNT(1) FROM dbo.ScenarioWarnings sw WHERE sw.ScenarioID = sc.ScenarioID) AS WarningCount
|
(SELECT COUNT(1) FROM dbo.ScenarioWarnings sw WHERE sw.ScenarioID = sc.ScenarioID) AS WarningCount
|
||||||
FROM dbo.Scenarios sc
|
FROM dbo.Scenarios sc
|
||||||
@ -454,5 +485,34 @@ BEGIN
|
|||||||
INNER JOIN dbo.Scenarios sc ON sc.ScenarioID = sw.ScenarioID
|
INNER JOIN dbo.Scenarios sc ON sc.ScenarioID = sw.ScenarioID
|
||||||
WHERE sc.ProjectID = @ProjectID
|
WHERE sc.ProjectID = @ProjectID
|
||||||
ORDER BY sw.CreatedDate DESC, sw.ScenarioWarningID;
|
ORDER BY sw.CreatedDate DESC, sw.ScenarioWarningID;
|
||||||
|
|
||||||
|
SELECT GenreMetricPresetID, PresetKey, PresetName, Description, SortOrder, IsActive
|
||||||
|
FROM dbo.GenreMetricPresets
|
||||||
|
ORDER BY SortOrder, PresetName;
|
||||||
|
|
||||||
|
SELECT gpmm.GenreMetricPresetID, gmp.PresetKey, gmp.PresetName,
|
||||||
|
gpmm.MetricTypeID, mt.MetricName, gpmm.SortOrder
|
||||||
|
FROM dbo.GenreMetricPresetMetrics gpmm
|
||||||
|
INNER JOIN dbo.GenreMetricPresets gmp ON gmp.GenreMetricPresetID = gpmm.GenreMetricPresetID
|
||||||
|
INNER JOIN dbo.SceneMetricTypes mt ON mt.MetricTypeID = gpmm.MetricTypeID
|
||||||
|
WHERE EXISTS
|
||||||
|
(
|
||||||
|
SELECT 1
|
||||||
|
FROM dbo.SceneMetricTypes projectMetric
|
||||||
|
WHERE projectMetric.MetricTypeID = gpmm.MetricTypeID
|
||||||
|
AND
|
||||||
|
(
|
||||||
|
projectMetric.ProjectID IS NULL
|
||||||
|
OR projectMetric.ProjectID = @ProjectID
|
||||||
|
OR EXISTS
|
||||||
|
(
|
||||||
|
SELECT 1
|
||||||
|
FROM dbo.ProjectTimelineMetricSettings ptms
|
||||||
|
WHERE ptms.ProjectID = @ProjectID
|
||||||
|
AND ptms.MetricTypeID = projectMetric.MetricTypeID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ORDER BY gmp.SortOrder, gpmm.SortOrder, mt.MetricName;
|
||||||
END;
|
END;
|
||||||
GO
|
GO
|
||||||
|
|||||||
@ -95,3 +95,41 @@ BEGIN
|
|||||||
WHERE rp.ProjectRestorePointID = @ProjectRestorePointID;
|
WHERE rp.ProjectRestorePointID = @ProjectRestorePointID;
|
||||||
END;
|
END;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ProjectRestorePoint_Count
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT COUNT(1)
|
||||||
|
FROM dbo.ProjectRestorePoints
|
||||||
|
WHERE ProjectID = @ProjectID;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ProjectRestorePoint_Delete
|
||||||
|
@ProjectRestorePointID int,
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.ProjectRestoreHistory', N'U') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
UPDATE dbo.ProjectRestoreHistory
|
||||||
|
SET SourceRestorePointID = NULL
|
||||||
|
WHERE SourceRestorePointID = @ProjectRestorePointID;
|
||||||
|
|
||||||
|
UPDATE dbo.ProjectRestoreHistory
|
||||||
|
SET SafetyRestorePointID = NULL
|
||||||
|
WHERE SafetyRestorePointID = @ProjectRestorePointID;
|
||||||
|
END;
|
||||||
|
|
||||||
|
DELETE FROM dbo.ProjectRestorePoints
|
||||||
|
WHERE ProjectRestorePointID = @ProjectRestorePointID
|
||||||
|
AND ProjectID = @ProjectID;
|
||||||
|
|
||||||
|
SELECT CAST(CASE WHEN @@ROWCOUNT = 1 THEN 1 ELSE 0 END AS bit);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|||||||
@ -9,8 +9,8 @@ BEGIN
|
|||||||
(
|
(
|
||||||
ProjectRestoreHistoryID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ProjectRestoreHistory PRIMARY KEY,
|
ProjectRestoreHistoryID int IDENTITY(1,1) NOT NULL CONSTRAINT PK_ProjectRestoreHistory PRIMARY KEY,
|
||||||
ProjectID int NOT NULL,
|
ProjectID int NOT NULL,
|
||||||
SourceRestorePointID int NOT NULL,
|
SourceRestorePointID int NULL,
|
||||||
SafetyRestorePointID int NOT NULL,
|
SafetyRestorePointID int NULL,
|
||||||
RestoredDateUtc datetime2 NOT NULL CONSTRAINT DF_ProjectRestoreHistory_RestoredDateUtc DEFAULT SYSUTCDATETIME(),
|
RestoredDateUtc datetime2 NOT NULL CONSTRAINT DF_ProjectRestoreHistory_RestoredDateUtc DEFAULT SYSUTCDATETIME(),
|
||||||
PerformedBy nvarchar(100) NULL,
|
PerformedBy nvarchar(100) NULL,
|
||||||
Notes nvarchar(max) NULL,
|
Notes nvarchar(max) NULL,
|
||||||
@ -21,6 +21,28 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID(N'dbo.ProjectRestoreHistory', N'U') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_ProjectRestoreHistory_SourceRestorePoint' AND parent_object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory'))
|
||||||
|
ALTER TABLE dbo.ProjectRestoreHistory DROP CONSTRAINT FK_ProjectRestoreHistory_SourceRestorePoint;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_ProjectRestoreHistory_SafetyRestorePoint' AND parent_object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory'))
|
||||||
|
ALTER TABLE dbo.ProjectRestoreHistory DROP CONSTRAINT FK_ProjectRestoreHistory_SafetyRestorePoint;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory') AND name = N'SourceRestorePointID' AND is_nullable = 0)
|
||||||
|
ALTER TABLE dbo.ProjectRestoreHistory ALTER COLUMN SourceRestorePointID int NULL;
|
||||||
|
|
||||||
|
IF EXISTS (SELECT 1 FROM sys.columns WHERE object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory') AND name = N'SafetyRestorePointID' AND is_nullable = 0)
|
||||||
|
ALTER TABLE dbo.ProjectRestoreHistory ALTER COLUMN SafetyRestorePointID int NULL;
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_ProjectRestoreHistory_SourceRestorePoint' AND parent_object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory'))
|
||||||
|
ALTER TABLE dbo.ProjectRestoreHistory ADD CONSTRAINT FK_ProjectRestoreHistory_SourceRestorePoint FOREIGN KEY(SourceRestorePointID) REFERENCES dbo.ProjectRestorePoints(ProjectRestorePointID);
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = N'FK_ProjectRestoreHistory_SafetyRestorePoint' AND parent_object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory'))
|
||||||
|
ALTER TABLE dbo.ProjectRestoreHistory ADD CONSTRAINT FK_ProjectRestoreHistory_SafetyRestorePoint FOREIGN KEY(SafetyRestorePointID) REFERENCES dbo.ProjectRestorePoints(ProjectRestorePointID);
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_ProjectRestoreHistory_ProjectDate' AND object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory'))
|
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = N'IX_ProjectRestoreHistory_ProjectDate' AND object_id = OBJECT_ID(N'dbo.ProjectRestoreHistory'))
|
||||||
CREATE INDEX IX_ProjectRestoreHistory_ProjectDate ON dbo.ProjectRestoreHistory(ProjectID, RestoredDateUtc DESC);
|
CREATE INDEX IX_ProjectRestoreHistory_ProjectDate ON dbo.ProjectRestoreHistory(ProjectID, RestoredDateUtc DESC);
|
||||||
GO
|
GO
|
||||||
@ -117,8 +139,8 @@ GO
|
|||||||
|
|
||||||
CREATE OR ALTER PROCEDURE dbo.ProjectRestore_ApplyFromJson
|
CREATE OR ALTER PROCEDURE dbo.ProjectRestore_ApplyFromJson
|
||||||
@ProjectID int,
|
@ProjectID int,
|
||||||
@SourceRestorePointID int,
|
@SourceRestorePointID int = NULL,
|
||||||
@SafetyRestorePointID int,
|
@SafetyRestorePointID int = NULL,
|
||||||
@JsonData nvarchar(max)
|
@JsonData nvarchar(max)
|
||||||
AS
|
AS
|
||||||
BEGIN
|
BEGIN
|
||||||
@ -231,7 +253,9 @@ BEGIN
|
|||||||
Description = src.Description,
|
Description = src.Description,
|
||||||
CreatedDate = src.CreatedDate,
|
CreatedDate = src.CreatedDate,
|
||||||
UpdatedDate = SYSUTCDATETIME(),
|
UpdatedDate = SYSUTCDATETIME(),
|
||||||
IsArchived = src.IsArchived
|
IsArchived = src.IsArchived,
|
||||||
|
ArchivedDate = src.ArchivedDate,
|
||||||
|
ArchivedReason = src.ArchivedReason
|
||||||
FROM dbo.Projects p
|
FROM dbo.Projects p
|
||||||
CROSS APPLY OPENJSON(@JsonData, '$.Project')
|
CROSS APPLY OPENJSON(@JsonData, '$.Project')
|
||||||
WITH
|
WITH
|
||||||
@ -240,7 +264,9 @@ BEGIN
|
|||||||
ProjectName nvarchar(200) '$.ProjectName',
|
ProjectName nvarchar(200) '$.ProjectName',
|
||||||
Description nvarchar(max) '$.Description',
|
Description nvarchar(max) '$.Description',
|
||||||
CreatedDate datetime2 '$.CreatedDate',
|
CreatedDate datetime2 '$.CreatedDate',
|
||||||
IsArchived bit '$.IsArchived'
|
IsArchived bit '$.IsArchived',
|
||||||
|
ArchivedDate datetime2 '$.ArchivedDate',
|
||||||
|
ArchivedReason nvarchar(1000) '$.ArchivedReason'
|
||||||
) src
|
) src
|
||||||
WHERE p.ProjectID = @ProjectID AND src.ProjectID = @ProjectID;
|
WHERE p.ProjectID = @ProjectID AND src.ProjectID = @ProjectID;
|
||||||
|
|
||||||
@ -298,9 +324,144 @@ BEGIN
|
|||||||
CLOSE EnableCursor;
|
CLOSE EnableCursor;
|
||||||
DEALLOCATE EnableCursor;
|
DEALLOCATE EnableCursor;
|
||||||
|
|
||||||
|
IF @SourceRestorePointID IS NOT NULL AND @SafetyRestorePointID IS NOT NULL
|
||||||
|
BEGIN
|
||||||
INSERT INTO dbo.ProjectRestoreHistory (ProjectID, SourceRestorePointID, SafetyRestorePointID, PerformedBy, Notes)
|
INSERT INTO dbo.ProjectRestoreHistory (ProjectID, SourceRestorePointID, SafetyRestorePointID, PerformedBy, Notes)
|
||||||
VALUES (@ProjectID, @SourceRestorePointID, @SafetyRestorePointID, SUSER_SNAME(), N'Project restored from saved restore point.');
|
VALUES (@ProjectID, @SourceRestorePointID, @SafetyRestorePointID, SUSER_SNAME(), N'Project restored from saved restore point.');
|
||||||
|
END;
|
||||||
|
|
||||||
COMMIT TRANSACTION;
|
COMMIT TRANSACTION;
|
||||||
END;
|
END;
|
||||||
GO
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ProjectBackup_GetIdentityMaxValues
|
||||||
|
@TableNames nvarchar(max)
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT t.name AS TableName,
|
||||||
|
ISNULL(CONVERT(int, IDENT_CURRENT(N'dbo.' + t.name)), 0) AS MaxID
|
||||||
|
FROM sys.tables t
|
||||||
|
INNER JOIN string_split(@TableNames, N',') requested ON requested.value = t.name
|
||||||
|
WHERE SCHEMA_NAME(t.schema_id) = N'dbo';
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ProjectBackup_GetRelationshipImportLookups
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT RevisionStatusID AS LookupID, StatusName AS LookupName FROM dbo.RevisionStatuses ORDER BY SortOrder, StatusName;
|
||||||
|
SELECT TimeModeID AS LookupID, TimeModeName AS LookupName FROM dbo.TimeModes ORDER BY SortOrder, TimeModeName;
|
||||||
|
SELECT DurationUnitID AS LookupID, DurationUnitName AS LookupName FROM dbo.DurationUnits ORDER BY SortOrder, DurationUnitName;
|
||||||
|
SELECT TimeConfidenceID AS LookupID, TimeConfidenceName AS LookupName FROM dbo.TimeConfidences ORDER BY SortOrder, TimeConfidenceName;
|
||||||
|
SELECT LocationTypeID AS LookupID, TypeName AS LookupName FROM dbo.LocationTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
|
||||||
|
SELECT LocationRelationshipTypeID AS LookupID, TypeName AS LookupName FROM dbo.LocationRelationshipTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
|
||||||
|
SELECT PlotLineTypeID AS LookupID, TypeName AS LookupName FROM dbo.PlotLineTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
|
||||||
|
SELECT ThreadTypeID AS LookupID, TypeName AS LookupName FROM dbo.ThreadTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
|
||||||
|
SELECT ThreadStatusID AS LookupID, StatusName AS LookupName FROM dbo.ThreadStatuses WHERE IsActive = 1 ORDER BY SortOrder, StatusName;
|
||||||
|
SELECT ThreadEventTypeID AS LookupID, TypeName AS LookupName FROM dbo.ThreadEventTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
|
||||||
|
SELECT AssetKindID AS LookupID, KindName AS LookupName FROM dbo.AssetKinds WHERE IsActive = 1 ORDER BY SortOrder, KindName;
|
||||||
|
SELECT AssetStateID AS LookupID, StateName AS LookupName FROM dbo.AssetStates WHERE IsActive = 1 AND ProjectID IS NULL ORDER BY SortOrder, StateName;
|
||||||
|
SELECT AssetEventTypeID AS LookupID, TypeName AS LookupName FROM dbo.AssetEventTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
|
||||||
|
SELECT AssetDependencyTypeID AS LookupID, TypeName AS LookupName FROM dbo.AssetDependencyTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
|
||||||
|
SELECT AssetCustodyEventTypeID AS LookupID, TypeName AS LookupName FROM dbo.AssetCustodyEventTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
|
||||||
|
SELECT CustodyRoleID AS LookupID, RoleName AS LookupName FROM dbo.CustodyRoles WHERE IsActive = 1 ORDER BY SortOrder, RoleName;
|
||||||
|
SELECT CharacterRoleInSceneTypeID AS LookupID, TypeName AS LookupName FROM dbo.CharacterRoleInSceneTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
|
||||||
|
SELECT PresenceTypeID AS LookupID, TypeName AS LookupName FROM dbo.PresenceTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
|
||||||
|
SELECT KnowledgeStateID AS LookupID, StateName AS LookupName FROM dbo.KnowledgeStates WHERE IsActive = 1 ORDER BY SortOrder, StateName;
|
||||||
|
SELECT SceneDependencyTypeID AS LookupID, TypeName AS LookupName FROM dbo.SceneDependencyTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
|
||||||
|
SELECT SceneNoteTypeID AS LookupID, TypeName AS LookupName FROM dbo.SceneNoteTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
|
||||||
|
SELECT SceneAttachmentTypeID AS LookupID, TypeName AS LookupName FROM dbo.SceneAttachmentTypes WHERE IsActive = 1 ORDER BY SortOrder, TypeName;
|
||||||
|
SELECT ScenePurposeTypeID AS LookupID, PurposeName AS LookupName FROM dbo.ScenePurposeTypes ORDER BY SortOrder, PurposeName;
|
||||||
|
SELECT MetricTypeID AS LookupID, MetricName AS LookupName FROM dbo.SceneMetricTypes WHERE ProjectID IS NULL ORDER BY SortOrder, MetricName;
|
||||||
|
|
||||||
|
SELECT rt.RelationshipTypeID, rt.TypeName, rt.IsPermanentDefault, rt.SortOrder, rt.IsActive,
|
||||||
|
rt.RelationshipCategoryID, rc.CategoryName AS RelationshipCategoryName, COALESCE(rc.SortOrder, 900) AS CategorySortOrder
|
||||||
|
FROM dbo.RelationshipTypes rt
|
||||||
|
LEFT JOIN dbo.RelationshipCategories rc ON rc.RelationshipCategoryID = rt.RelationshipCategoryID
|
||||||
|
WHERE rt.IsActive = 1
|
||||||
|
ORDER BY COALESCE(rc.SortOrder, 900), rt.SortOrder, rt.TypeName;
|
||||||
|
|
||||||
|
SELECT RelationshipStateID, StateName, SortOrder, IsActive
|
||||||
|
FROM dbo.RelationshipStates
|
||||||
|
WHERE IsActive = 1
|
||||||
|
ORDER BY SortOrder, StateName;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE dbo.ProjectBackup_RelationshipImportDiagnostics
|
||||||
|
@ProjectID int
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
(SELECT COUNT(1)
|
||||||
|
FROM dbo.Characters c
|
||||||
|
WHERE c.ProjectID = @ProjectID) AS CharacterCount,
|
||||||
|
(SELECT COUNT(1)
|
||||||
|
FROM dbo.CharacterRelationships cr
|
||||||
|
WHERE cr.ProjectID = @ProjectID) AS RelationshipCount,
|
||||||
|
(SELECT COUNT(1)
|
||||||
|
FROM dbo.RelationshipEvents re
|
||||||
|
INNER JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID
|
||||||
|
WHERE cr.ProjectID = @ProjectID) AS RelationshipEventCount,
|
||||||
|
(SELECT COUNT(1)
|
||||||
|
FROM dbo.CharacterRelationships cr
|
||||||
|
INNER JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID AND ca.ProjectID = @ProjectID AND ca.IsArchived = 0
|
||||||
|
INNER JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID AND cb.ProjectID = @ProjectID AND cb.IsArchived = 0
|
||||||
|
INNER JOIN dbo.RelationshipTypes rt ON rt.RelationshipTypeID = cr.RelationshipTypeID AND rt.IsActive = 1
|
||||||
|
LEFT JOIN dbo.RelationshipCategories rc ON rc.RelationshipCategoryID = rt.RelationshipCategoryID
|
||||||
|
WHERE cr.ProjectID = @ProjectID
|
||||||
|
AND cr.IsArchived = 0) AS MapRelationshipCount,
|
||||||
|
(SELECT COUNT(1)
|
||||||
|
FROM dbo.CharacterRelationships cr
|
||||||
|
LEFT JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID AND ca.ProjectID = @ProjectID
|
||||||
|
WHERE cr.ProjectID = @ProjectID
|
||||||
|
AND ca.CharacterID IS NULL) AS MissingSourceCharacterCount,
|
||||||
|
(SELECT COUNT(1)
|
||||||
|
FROM dbo.CharacterRelationships cr
|
||||||
|
LEFT JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID AND cb.ProjectID = @ProjectID
|
||||||
|
WHERE cr.ProjectID = @ProjectID
|
||||||
|
AND cb.CharacterID IS NULL) AS MissingTargetCharacterCount,
|
||||||
|
(SELECT COUNT(1)
|
||||||
|
FROM dbo.CharacterRelationships cr
|
||||||
|
LEFT JOIN dbo.RelationshipTypes rt ON rt.RelationshipTypeID = cr.RelationshipTypeID
|
||||||
|
WHERE cr.ProjectID = @ProjectID
|
||||||
|
AND rt.RelationshipTypeID IS NULL) AS MissingRelationshipTypeCount,
|
||||||
|
(SELECT COUNT(1)
|
||||||
|
FROM dbo.CharacterRelationships cr
|
||||||
|
LEFT JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = cr.InitialRelationshipStateID
|
||||||
|
WHERE cr.ProjectID = @ProjectID
|
||||||
|
AND cr.InitialRelationshipStateID IS NOT NULL
|
||||||
|
AND rs.RelationshipStateID IS NULL) AS MissingInitialStateCount,
|
||||||
|
(SELECT COUNT(1)
|
||||||
|
FROM dbo.RelationshipEvents re
|
||||||
|
LEFT JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID AND cr.ProjectID = @ProjectID
|
||||||
|
WHERE cr.CharacterRelationshipID IS NULL) AS MissingEventRelationshipCount,
|
||||||
|
(SELECT COUNT(1)
|
||||||
|
FROM dbo.RelationshipEvents re
|
||||||
|
INNER JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID AND cr.ProjectID = @ProjectID
|
||||||
|
LEFT JOIN dbo.Scenes s ON s.SceneID = re.SceneID
|
||||||
|
WHERE s.SceneID IS NULL) AS MissingEventSceneCount,
|
||||||
|
(SELECT COUNT(1)
|
||||||
|
FROM dbo.RelationshipEvents re
|
||||||
|
INNER JOIN dbo.CharacterRelationships cr ON cr.CharacterRelationshipID = re.CharacterRelationshipID AND cr.ProjectID = @ProjectID
|
||||||
|
LEFT JOIN dbo.RelationshipStates rs ON rs.RelationshipStateID = re.RelationshipStateID
|
||||||
|
WHERE rs.RelationshipStateID IS NULL) AS MissingEventStateCount,
|
||||||
|
(SELECT COUNT(1)
|
||||||
|
FROM dbo.CharacterRelationships cr
|
||||||
|
WHERE cr.ProjectID = @ProjectID
|
||||||
|
AND cr.IsArchived = 1) AS ArchivedRelationshipCount,
|
||||||
|
(SELECT COUNT(1)
|
||||||
|
FROM dbo.CharacterRelationships cr
|
||||||
|
INNER JOIN dbo.Characters ca ON ca.CharacterID = cr.CharacterAID AND ca.ProjectID = @ProjectID
|
||||||
|
INNER JOIN dbo.Characters cb ON cb.CharacterID = cr.CharacterBID AND cb.ProjectID = @ProjectID
|
||||||
|
WHERE cr.ProjectID = @ProjectID
|
||||||
|
AND cr.IsArchived = 0
|
||||||
|
AND (ca.IsArchived = 1 OR cb.IsArchived = 1)) AS ArchivedCharacterEndpointCount;
|
||||||
|
END;
|
||||||
|
GO
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Tools</p>
|
<p class="eyebrow">Tools</p>
|
||||||
<h1>JSON Import <help-icon key="imports.overview" /></h1>
|
<h1>JSON Import <help-icon key="imports.overview" /></h1>
|
||||||
<p class="lead-text">Paste or upload a Phase 1G package, preview it, dry-run if needed, then import a new project.</p>
|
<p class="lead-text">Paste or upload a legacy JSON package or a PlotWeaver backup file, preview it, dry-run if needed, then import a new project.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-row">
|
<div class="button-row">
|
||||||
<a class="btn btn-outline-primary" asp-action="Sample">Download sample JSON</a>
|
<a class="btn btn-outline-primary" asp-action="Sample">Download sample JSON</a>
|
||||||
@ -39,13 +39,13 @@
|
|||||||
<form asp-action="Preview" method="post" enctype="multipart/form-data">
|
<form asp-action="Preview" method="post" enctype="multipart/form-data">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<label asp-for="JsonFile" class="form-label">Upload JSON file</label>
|
<label asp-for="JsonFile" class="form-label">Upload JSON or PlotWeaver backup file</label>
|
||||||
<input asp-for="JsonFile" class="form-control" type="file" accept=".json,application/json" />
|
<input asp-for="JsonFile" class="form-control" type="file" accept=".json,.plotweaver,application/json" />
|
||||||
<div class="form-text">Uploading a file replaces the pasted text for this preview.</div>
|
<div class="form-text">Uploading a file replaces the pasted text for this preview.</div>
|
||||||
<div class="form-text" data-upload-status></div>
|
<div class="form-text" data-upload-status></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<label asp-for="JsonText" class="form-label">JSON package</label>
|
<label asp-for="JsonText" class="form-label">JSON package or backup content</label>
|
||||||
<textarea asp-for="JsonText" class="form-control import-json-input" rows="22" spellcheck="false"></textarea>
|
<textarea asp-for="JsonText" class="form-control import-json-input" rows="22" spellcheck="false"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Restore Points";
|
ViewData["Title"] = "Restore Points";
|
||||||
var defaultRestorePointName = $"Manual restore point - {DateTime.Now:yyyy-MM-dd HH:mm}";
|
var defaultRestorePointName = $"Manual restore point - {DateTime.Now:yyyy-MM-dd HH:mm}";
|
||||||
|
var restorePointLimitReached = Model.RestorePoints.Count >= 10;
|
||||||
|
const string restorePointLimitMessage = "You have reached the limit of 10 restore points for this project. Delete an old restore point before creating a new one. You can download a restore point first if you want to keep a local backup copy.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@functions {
|
@functions {
|
||||||
@ -50,18 +52,22 @@
|
|||||||
<h2>Create restore point</h2>
|
<h2>Create restore point</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form asp-action="Create" method="post" class="row g-3">
|
@if (restorePointLimitReached)
|
||||||
|
{
|
||||||
|
<div class="alert alert-warning">@restorePointLimitMessage</div>
|
||||||
|
}
|
||||||
|
<form asp-action="Create" method="post" class="row g-3" data-confirm-message="Create restore point? This will save the current state of this project as a restore point. You can return to it later if you need to undo major changes." data-confirm-button-text="Create Restore Point" data-confirm-button-class="btn-primary">
|
||||||
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<label class="form-label" for="restorePointName">Name</label>
|
<label class="form-label" for="restorePointName">Name</label>
|
||||||
<input class="form-control" id="restorePointName" name="restorePointName" maxlength="255" placeholder="@defaultRestorePointName" />
|
<input class="form-control" id="restorePointName" name="restorePointName" maxlength="255" placeholder="@defaultRestorePointName" disabled="@restorePointLimitReached" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<label class="form-label" for="notes">Notes</label>
|
<label class="form-label" for="notes">Notes</label>
|
||||||
<input class="form-control" id="notes" name="notes" maxlength="1000" placeholder="Optional reason or reminder" />
|
<input class="form-control" id="notes" name="notes" maxlength="1000" placeholder="Optional reason or reminder" disabled="@restorePointLimitReached" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2 d-flex align-items-end">
|
<div class="col-md-2 d-flex align-items-end">
|
||||||
<button class="btn btn-primary w-100" type="submit">Create Restore Point</button>
|
<button class="btn btn-primary w-100" type="submit" disabled="@restorePointLimitReached">Create Restore Point</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
@ -98,6 +104,7 @@
|
|||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<a class="btn btn-outline-secondary btn-sm" asp-action="Download" asp-route-projectId="@Model.Project.ProjectID" asp-route-id="@restorePoint.ProjectRestorePointID">Download</a>
|
<a class="btn btn-outline-secondary btn-sm" asp-action="Download" asp-route-projectId="@Model.Project.ProjectID" asp-route-id="@restorePoint.ProjectRestorePointID">Download</a>
|
||||||
<button class="btn btn-outline-danger btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#restorePointModal-@restorePoint.ProjectRestorePointID">Restore</button>
|
<button class="btn btn-outline-danger btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#restorePointModal-@restorePoint.ProjectRestorePointID">Restore</button>
|
||||||
|
<button class="btn btn-outline-danger btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#deleteRestorePointModal-@restorePoint.ProjectRestorePointID">Delete</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@ -130,6 +137,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="deleteRestorePointModal-@restorePoint.ProjectRestorePointID" tabindex="-1" aria-labelledby="deleteRestorePointModalLabel-@restorePoint.ProjectRestorePointID" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2 class="modal-title fs-5" id="deleteRestorePointModalLabel-@restorePoint.ProjectRestorePointID">Delete Restore Point</h2>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>This will permanently delete this restore point. You will not be able to restore from it after deletion.</p>
|
||||||
|
<p class="mb-0">You can download the restore point first if you want to keep a local backup copy.</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<form asp-action="Delete" method="post" data-confirm-skip="true">
|
||||||
|
<input type="hidden" name="projectId" value="@Model.Project.ProjectID" />
|
||||||
|
<input type="hidden" name="id" value="@restorePoint.ProjectRestorePointID" />
|
||||||
|
<button class="btn btn-danger" type="submit">Delete Restore Point</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user