Initial commit

This commit is contained in:
2026-06-02 10:51:14 +02:00
committed by kawa
commit d19092c84e
182 changed files with 13757 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
namespace SharepointToolbox.Web.Core.Models;
public class AppConfiguration
{
public string DataFolder { get; set; } = "/data";
public string ExportsFolder { get; set; } = "/data/exports";
}
+9
View File
@@ -0,0 +1,9 @@
namespace SharepointToolbox.Web.Core.Models;
public class AppSettings
{
public string DataFolder { get; set; } = string.Empty;
public string Lang { get; set; } = "en";
public bool AutoTakeOwnership { get; set; } = false;
public string Theme { get; set; } = "System";
}
+11
View File
@@ -0,0 +1,11 @@
namespace SharepointToolbox.Web.Core.Models;
public class AppUser
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Email { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public UserRole Role { get; set; } = UserRole.TechN0;
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset? LastLogin { get; set; }
}
+14
View File
@@ -0,0 +1,14 @@
namespace SharepointToolbox.Web.Core.Models;
public class AuditEntry
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.UtcNow;
public string UserEmail { get; set; } = string.Empty;
public string UserDisplay { get; set; } = string.Empty;
public UserRole UserRole { get; set; }
public string Action { get; set; } = string.Empty;
public string ClientName { get; set; } = string.Empty;
public List<string> Sites { get; set; } = new();
public string Details { get; set; } = string.Empty;
}
+6
View File
@@ -0,0 +1,6 @@
namespace SharepointToolbox.Web.Core.Models;
public class BrandingSettings
{
public LogoData? MspLogo { get; set; }
}
+11
View File
@@ -0,0 +1,11 @@
using CsvHelper.Configuration.Attributes;
namespace SharepointToolbox.Web.Core.Models;
public class BulkMemberRow
{
[Name("GroupName")] public string GroupName { get; set; } = string.Empty;
[Name("GroupUrl")] public string GroupUrl { get; set; } = string.Empty;
[Name("Email")] public string Email { get; set; } = string.Empty;
[Name("Role")] public string Role { get; set; } = string.Empty;
}
+29
View File
@@ -0,0 +1,29 @@
namespace SharepointToolbox.Web.Core.Models;
public class BulkItemResult<T>
{
public T Item { get; }
public bool IsSuccess { get; }
public string? ErrorMessage { get; }
public DateTime Timestamp { get; }
private BulkItemResult(T item, bool success, string? error)
{
Item = item; IsSuccess = success; ErrorMessage = error; Timestamp = DateTime.UtcNow;
}
public static BulkItemResult<T> Success(T item) => new(item, true, null);
public static BulkItemResult<T> Failed(T item, string error) => new(item, false, error);
}
public class BulkOperationSummary<T>
{
public IReadOnlyList<BulkItemResult<T>> Results { get; }
public int TotalCount => Results.Count;
public int SuccessCount => Results.Count(r => r.IsSuccess);
public int FailedCount => Results.Count(r => !r.IsSuccess);
public bool HasFailures => FailedCount > 0;
public IReadOnlyList<BulkItemResult<T>> FailedItems => Results.Where(r => !r.IsSuccess).ToList();
public BulkOperationSummary(IReadOnlyList<BulkItemResult<T>> results) { Results = results; }
}
+13
View File
@@ -0,0 +1,13 @@
using CsvHelper.Configuration.Attributes;
namespace SharepointToolbox.Web.Core.Models;
public class BulkSiteRow
{
[Name("Name")] public string Name { get; set; } = string.Empty;
[Name("Alias")] public string Alias { get; set; } = string.Empty;
[Name("Type")] public string Type { get; set; } = string.Empty;
[Name("Template")] public string Template { get; set; } = string.Empty;
[Name("Owners")] public string Owners { get; set; } = string.Empty;
[Name("Members")] public string Members { get; set; } = string.Empty;
}
+3
View File
@@ -0,0 +1,3 @@
namespace SharepointToolbox.Web.Core.Models;
public enum ConflictPolicy { Skip, Overwrite, Rename }
@@ -0,0 +1,18 @@
namespace SharepointToolbox.Web.Core.Models;
public record ConsolidatedPermissionEntry(
string UserDisplayName,
string UserLogin,
string PermissionLevel,
AccessType AccessType,
string GrantedThrough,
bool IsHighPrivilege,
bool IsExternalUser,
IReadOnlyList<LocationInfo> Locations,
string? TargetUrl = null,
string? TargetLabel = null,
string? SharingLinkType = null
)
{
public int LocationCount => Locations.Count;
}
+22
View File
@@ -0,0 +1,22 @@
namespace SharepointToolbox.Web.Core.Models;
public class CsvValidationRow<T>
{
public T? Record { get; }
public bool IsValid => Errors.Count == 0;
public List<string> Errors { get; }
public string? RawRecord { get; }
public CsvValidationRow(T record, List<string> errors)
{
Record = record; Errors = errors;
}
private CsvValidationRow(string rawRecord, string parseError)
{
Record = default; RawRecord = rawRecord; Errors = new List<string> { parseError };
}
public static CsvValidationRow<T> ParseError(string? rawRecord, string error)
=> new(rawRecord ?? string.Empty, error);
}
+8
View File
@@ -0,0 +1,8 @@
namespace SharepointToolbox.Web.Core.Models;
public class DuplicateGroup
{
public string GroupKey { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public List<DuplicateItem> Items { get; set; } = new();
}
+15
View File
@@ -0,0 +1,15 @@
namespace SharepointToolbox.Web.Core.Models;
public class DuplicateItem
{
public string Name { get; set; } = string.Empty;
public string Path { get; set; } = string.Empty;
public string Library { get; set; } = string.Empty;
public long? SizeBytes { get; set; }
public DateTime? Created { get; set; }
public DateTime? Modified { get; set; }
public int? FolderCount { get; set; }
public int? FileCount { get; set; }
public string SiteUrl { get; set; } = string.Empty;
public string SiteTitle { get; set; } = string.Empty;
}
+12
View File
@@ -0,0 +1,12 @@
namespace SharepointToolbox.Web.Core.Models;
public record DuplicateScanOptions(
string Mode = "Files",
bool MatchSize = true,
bool MatchCreated = false,
bool MatchModified = false,
bool MatchSubfolderCount = false,
bool MatchFileCount = false,
bool IncludeSubsites = false,
string? Library = null
);
+11
View File
@@ -0,0 +1,11 @@
namespace SharepointToolbox.Web.Core.Models;
public record FileTypeMetric(
string Extension,
long TotalSizeBytes,
int FileCount)
{
public string DisplayLabel => string.IsNullOrEmpty(Extension)
? "No Extension"
: Extension.TrimStart('.').ToUpperInvariant();
}
+17
View File
@@ -0,0 +1,17 @@
using CsvHelper.Configuration.Attributes;
namespace SharepointToolbox.Web.Core.Models;
public class FolderStructureRow
{
[Name("Level1")] public string Level1 { get; set; } = string.Empty;
[Name("Level2")] public string Level2 { get; set; } = string.Empty;
[Name("Level3")] public string Level3 { get; set; } = string.Empty;
[Name("Level4")] public string Level4 { get; set; } = string.Empty;
public string BuildPath()
{
var parts = new[] { Level1, Level2, Level3, Level4 }.Where(s => !string.IsNullOrWhiteSpace(s));
return string.Join("/", parts);
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace SharepointToolbox.Web.Core.Models;
public record GraphDirectoryUser(
string DisplayName,
string UserPrincipalName,
string? Mail,
string? Department,
string? JobTitle,
string? UserType);
+9
View File
@@ -0,0 +1,9 @@
namespace SharepointToolbox.Web.Core.Models;
public record LocationInfo(
string SiteUrl,
string SiteTitle,
string ObjectTitle,
string ObjectUrl,
string ObjectType
);
+7
View File
@@ -0,0 +1,7 @@
namespace SharepointToolbox.Web.Core.Models;
public record LogoData
{
public string Base64 { get; init; } = string.Empty;
public string MimeType { get; init; } = string.Empty;
}
+7
View File
@@ -0,0 +1,7 @@
namespace SharepointToolbox.Web.Core.Models;
public record OperationProgress(int Current, int Total, string Message, bool IsIndeterminate = false)
{
public static OperationProgress Indeterminate(string message) =>
new(0, 0, message, IsIndeterminate: true);
}
+17
View File
@@ -0,0 +1,17 @@
namespace SharepointToolbox.Web.Core.Models;
public record PermissionEntry(
string ObjectType,
string Title,
string Url,
bool HasUniquePermissions,
string Users,
string UserLogins,
string PermissionLevels,
string GrantedThrough,
string PrincipalType,
bool WasAutoElevated = false,
string? TargetUrl = null,
string? TargetLabel = null,
string? SharingLinkType = null
);
+33
View File
@@ -0,0 +1,33 @@
namespace SharepointToolbox.Web.Core.Models;
public record PermissionSummary(
string Label,
RiskLevel RiskLevel,
int Count,
int DistinctUsers
);
public static class PermissionSummaryBuilder
{
private static readonly Dictionary<RiskLevel, string> Labels = new()
{
[RiskLevel.High] = "High Risk",
[RiskLevel.Medium] = "Medium Risk",
[RiskLevel.Low] = "Low Risk",
[RiskLevel.ReadOnly] = "Read Only",
};
public static IReadOnlyList<PermissionSummary> Build(IEnumerable<SimplifiedPermissionEntry> entries)
{
var grouped = entries.GroupBy(e => e.RiskLevel).ToDictionary(g => g.Key, g => g.ToList());
return Enum.GetValues<RiskLevel>().Select(level =>
{
var items = grouped.GetValueOrDefault(level, new List<SimplifiedPermissionEntry>());
var distinctUsers = items
.SelectMany(e => e.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries))
.Select(u => u.Trim()).Where(u => u.Length > 0)
.Distinct(StringComparer.OrdinalIgnoreCase).Count();
return new PermissionSummary(Labels[level], level, items.Count, distinctUsers);
}).ToList();
}
}
+3
View File
@@ -0,0 +1,3 @@
namespace SharepointToolbox.Web.Core.Models;
public record ReportBranding(LogoData? MspLogo, LogoData? ClientLogo);
+3
View File
@@ -0,0 +1,3 @@
namespace SharepointToolbox.Web.Core.Models;
public record ResolvedMember(string DisplayName, string Login);
+9
View File
@@ -0,0 +1,9 @@
namespace SharepointToolbox.Web.Core.Models;
public enum RiskLevel
{
High,
Medium,
Low,
ReadOnly
}
+8
View File
@@ -0,0 +1,8 @@
namespace SharepointToolbox.Web.Core.Models;
public record ScanOptions(
bool IncludeInherited = false,
bool ScanFolders = true,
int FolderDepth = 1,
bool IncludeSubsites = false
);
+15
View File
@@ -0,0 +1,15 @@
namespace SharepointToolbox.Web.Core.Models;
public record SearchOptions(
string[] Extensions,
string? Regex,
DateTime? CreatedAfter,
DateTime? CreatedBefore,
DateTime? ModifiedAfter,
DateTime? ModifiedBefore,
string? CreatedBy,
string? ModifiedBy,
string? Library,
int MaxResults,
string SiteUrl
);
+13
View File
@@ -0,0 +1,13 @@
namespace SharepointToolbox.Web.Core.Models;
public class SearchResult
{
public string Title { get; set; } = string.Empty;
public string Path { get; set; } = string.Empty;
public string FileExtension { get; set; } = string.Empty;
public DateTime? Created { get; set; }
public DateTime? LastModified { get; set; }
public string Author { get; set; } = string.Empty;
public string ModifiedBy { get; set; } = string.Empty;
public long SizeBytes { get; set; }
}
+11
View File
@@ -0,0 +1,11 @@
namespace SharepointToolbox.Web.Core.Models;
/// <summary>Held in ProtectedSessionStorage — never persisted to disk.</summary>
public class SessionTokens
{
public string RefreshToken { get; set; } = string.Empty;
public string TenantId { get; set; } = string.Empty;
public string ClientId { get; set; } = string.Empty;
public string SpHost { get; set; } = string.Empty;
public string UserPrincipalName { get; set; } = string.Empty;
}
+35
View File
@@ -0,0 +1,35 @@
using SharepointToolbox.Web.Core.Helpers;
namespace SharepointToolbox.Web.Core.Models;
public class SimplifiedPermissionEntry
{
public PermissionEntry Inner { get; }
public string SimplifiedLabels { get; }
public RiskLevel RiskLevel { get; }
public IReadOnlyList<PermissionLevelMapping.MappingResult> Mappings { get; }
public string ObjectType => Inner.ObjectType;
public string Title => Inner.Title;
public string Url => Inner.Url;
public bool HasUniquePermissions => Inner.HasUniquePermissions;
public string Users => Inner.Users;
public string UserLogins => Inner.UserLogins;
public string PermissionLevels => Inner.PermissionLevels;
public string GrantedThrough => Inner.GrantedThrough;
public string PrincipalType => Inner.PrincipalType;
public string? TargetUrl => Inner.TargetUrl;
public string? TargetLabel => Inner.TargetLabel;
public string? SharingLinkType => Inner.SharingLinkType;
public SimplifiedPermissionEntry(PermissionEntry entry)
{
Inner = entry;
Mappings = PermissionLevelMapping.GetMappings(entry.PermissionLevels);
SimplifiedLabels = PermissionLevelMapping.GetSimplifiedLabels(entry.PermissionLevels);
RiskLevel = PermissionLevelMapping.GetHighestRisk(entry.PermissionLevels);
}
public static IReadOnlyList<SimplifiedPermissionEntry> WrapAll(IEnumerable<PermissionEntry> entries)
=> entries.Select(e => new SimplifiedPermissionEntry(e)).ToList();
}
+38
View File
@@ -0,0 +1,38 @@
namespace SharepointToolbox.Web.Core.Models;
public record SiteInfo(string Url, string Title)
{
public long StorageUsedMb { get; init; }
public long StorageQuotaMb { get; init; }
public string Template { get; init; } = string.Empty;
public SiteKind Kind => SiteKindHelper.FromTemplate(Template);
}
public enum SiteKind
{
Unknown,
TeamSite,
CommunicationSite,
Classic
}
public static class SiteKindHelper
{
public static SiteKind FromTemplate(string template)
{
if (string.IsNullOrEmpty(template)) return SiteKind.Unknown;
if (template.StartsWith("GROUP#", StringComparison.OrdinalIgnoreCase)) return SiteKind.TeamSite;
if (template.StartsWith("SITEPAGEPUBLISHING#", StringComparison.OrdinalIgnoreCase)) return SiteKind.CommunicationSite;
if (template.StartsWith("STS#", StringComparison.OrdinalIgnoreCase)) return SiteKind.Classic;
return SiteKind.Unknown;
}
public static string DisplayName(SiteKind kind) => kind switch
{
SiteKind.TeamSite => "Team site",
SiteKind.CommunicationSite => "Communication site",
SiteKind.Classic => "Classic site",
_ => "Other"
};
}
+27
View File
@@ -0,0 +1,27 @@
namespace SharepointToolbox.Web.Core.Models;
public class SiteTemplate
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Name { get; set; } = string.Empty;
public string SourceUrl { get; set; } = string.Empty;
public DateTime CapturedAt { get; set; }
public string SiteType { get; set; } = string.Empty;
public SiteTemplateOptions Options { get; set; } = new();
public TemplateSettings? Settings { get; set; }
public TemplateLogo? Logo { get; set; }
public List<TemplateLibraryInfo> Libraries { get; set; } = new();
public List<TemplatePermissionGroup> PermissionGroups { get; set; } = new();
}
public class TemplateSettings
{
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public int Language { get; set; }
}
public class TemplateLogo
{
public string LogoUrl { get; set; } = string.Empty;
}
+10
View File
@@ -0,0 +1,10 @@
namespace SharepointToolbox.Web.Core.Models;
public class SiteTemplateOptions
{
public bool CaptureLibraries { get; set; } = true;
public bool CaptureFolders { get; set; } = true;
public bool CapturePermissionGroups { get; set; } = true;
public bool CaptureLogo { get; set; } = true;
public bool CaptureSettings { get; set; } = true;
}
+17
View File
@@ -0,0 +1,17 @@
namespace SharepointToolbox.Web.Core.Models;
public class StorageNode
{
public string Name { get; set; } = string.Empty;
public string Url { get; set; } = string.Empty;
public string SiteTitle { get; set; } = string.Empty;
public string Library { get; set; } = string.Empty;
public long TotalSizeBytes { get; set; }
public long FileStreamSizeBytes { get; set; }
public long VersionSizeBytes => Math.Max(0L, TotalSizeBytes - FileStreamSizeBytes);
public long TotalFileCount { get; set; }
public DateTime? LastModified { get; set; }
public int IndentLevel { get; set; }
public StorageNodeKind Kind { get; set; } = StorageNodeKind.Library;
public List<StorageNode> Children { get; set; } = new();
}
+11
View File
@@ -0,0 +1,11 @@
namespace SharepointToolbox.Web.Core.Models;
public enum StorageNodeKind
{
Library,
HiddenLibrary,
PreservationHold,
ListAttachments,
RecycleBin,
Subsite
}
+11
View File
@@ -0,0 +1,11 @@
namespace SharepointToolbox.Web.Core.Models;
public record StorageScanOptions(
bool PerLibrary = true,
bool IncludeSubsites = false,
int FolderDepth = 0,
bool IncludeHiddenLibraries = true,
bool IncludePreservationHold = true,
bool IncludeListAttachments = true,
bool IncludeRecycleBin = true
);
+10
View File
@@ -0,0 +1,10 @@
using SharepointToolbox.Web.Core.Helpers;
namespace SharepointToolbox.Web.Core.Models;
public record SystemGroupTarget(
SystemGroupKind Kind,
string Label,
string Url,
string? LinkType = null
);
+8
View File
@@ -0,0 +1,8 @@
namespace SharepointToolbox.Web.Core.Models;
public class TemplateFolderInfo
{
public string Name { get; set; } = string.Empty;
public string RelativePath { get; set; } = string.Empty;
public List<TemplateFolderInfo> Children { get; set; } = new();
}
+9
View File
@@ -0,0 +1,9 @@
namespace SharepointToolbox.Web.Core.Models;
public class TemplateLibraryInfo
{
public string Name { get; set; } = string.Empty;
public string BaseType { get; set; } = string.Empty;
public int BaseTemplate { get; set; }
public List<TemplateFolderInfo> Folders { get; set; } = new();
}
+8
View File
@@ -0,0 +1,8 @@
namespace SharepointToolbox.Web.Core.Models;
public class TemplatePermissionGroup
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public List<string> RoleDefinitions { get; set; } = new();
}
+18
View File
@@ -0,0 +1,18 @@
namespace SharepointToolbox.Web.Core.Models;
public class TenantProfile
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Name { get; set; } = string.Empty;
/// <summary>https://contoso.sharepoint.com</summary>
public string TenantUrl { get; set; } = string.Empty;
/// <summary>Azure AD tenant GUID or domain (e.g. contoso.onmicrosoft.com). Required for app-only Graph calls.</summary>
public string TenantId { get; set; } = string.Empty;
/// <summary>Azure AD app registration client (application) ID.</summary>
public string ClientId { get; set; } = string.Empty;
public LogoData? ClientLogo { get; set; }
}
+16
View File
@@ -0,0 +1,16 @@
namespace SharepointToolbox.Web.Core.Models;
public class TransferJob
{
public string SourceSiteUrl { get; set; } = string.Empty;
public string SourceLibrary { get; set; } = string.Empty;
public string SourceFolderPath { get; set; } = string.Empty;
public string DestinationSiteUrl { get; set; } = string.Empty;
public string DestinationLibrary { get; set; } = string.Empty;
public string DestinationFolderPath { get; set; } = string.Empty;
public TransferMode Mode { get; set; } = TransferMode.Copy;
public ConflictPolicy ConflictPolicy { get; set; } = ConflictPolicy.Skip;
public IReadOnlyList<string> SelectedFilePaths { get; set; } = Array.Empty<string>();
public bool IncludeSourceFolder { get; set; }
public bool CopyFolderContents { get; set; } = true;
}
+3
View File
@@ -0,0 +1,3 @@
namespace SharepointToolbox.Web.Core.Models;
public enum TransferMode { Copy, Move }
+26
View File
@@ -0,0 +1,26 @@
namespace SharepointToolbox.Web.Core.Models;
public enum AccessType
{
Direct,
Group,
Inherited
}
public record UserAccessEntry(
string UserDisplayName,
string UserLogin,
string SiteUrl,
string SiteTitle,
string ObjectType,
string ObjectTitle,
string ObjectUrl,
string PermissionLevel,
AccessType AccessType,
string GrantedThrough,
bool IsHighPrivilege,
bool IsExternalUser,
string? TargetUrl = null,
string? TargetLabel = null,
string? SharingLinkType = null
);
+8
View File
@@ -0,0 +1,8 @@
namespace SharepointToolbox.Web.Core.Models;
public enum UserRole
{
TechN0 = 0, // Read-only
TechN1 = 1, // Read/Write
Admin = 2 // Read/Write + account management + client profiles
}
+9
View File
@@ -0,0 +1,9 @@
namespace SharepointToolbox.Web.Core.Models;
public record VersionCleanupOptions(
IReadOnlyList<string> LibraryTitles,
int KeepLast,
bool KeepFirst)
{
public static VersionCleanupOptions Default => new(Array.Empty<string>(), 5, false);
}
+14
View File
@@ -0,0 +1,14 @@
namespace SharepointToolbox.Web.Core.Models;
public class VersionCleanupResult
{
public string SiteUrl { get; init; } = string.Empty;
public string Library { get; init; } = string.Empty;
public string FileServerRelativeUrl { get; init; } = string.Empty;
public string FileName { get; init; } = string.Empty;
public int VersionsBefore { get; init; }
public int VersionsDeleted { get; init; }
public int VersionsRemaining { get; init; }
public long BytesFreed { get; init; }
public string? Error { get; init; }
}