1805 lines
66 KiB
C#
1805 lines
66 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using NTDLS.DelegateThreadPooling;
|
|
using NTDLS.Helpers;
|
|
using System.Reflection;
|
|
using System.Security.Claims;
|
|
using TightWiki.Caching;
|
|
using TightWiki.Engine.Implementation.Utility;
|
|
using TightWiki.Engine.Library.Interfaces;
|
|
using TightWiki.Library;
|
|
using TightWiki.Models;
|
|
using TightWiki.Models.DataModels;
|
|
using TightWiki.Models.ViewModels.Admin;
|
|
using TightWiki.Models.ViewModels.Page;
|
|
using TightWiki.Models.ViewModels.Profile;
|
|
using TightWiki.Models.ViewModels.Shared;
|
|
using TightWiki.Models.ViewModels.Utility;
|
|
using TightWiki.Repository;
|
|
using static TightWiki.Library.Constants;
|
|
using Constants = TightWiki.Library.Constants;
|
|
|
|
namespace TightWiki.Controllers
|
|
{
|
|
[Authorize]
|
|
[Route("[controller]")]
|
|
public class AdminController(ITightEngine tightEngine, SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager)
|
|
: WikiControllerBase(signInManager, userManager)
|
|
{
|
|
#region Metrics.
|
|
|
|
[Authorize]
|
|
[HttpGet("Database")]
|
|
public ActionResult Database()
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
SessionState.Page.Name = $"Database";
|
|
|
|
var versions = SpannedRepository.GetDatabaseVersions();
|
|
var pageCounts = SpannedRepository.GetDatabasePageCounts();
|
|
var pageSizes = SpannedRepository.GetDatabasePageSizes();
|
|
|
|
var info = new List<DatabaseInfo>();
|
|
|
|
foreach (var version in versions)
|
|
{
|
|
var pageCount = pageCounts.FirstOrDefault(o => o.Name == version.Name).PageCount;
|
|
var pageSize = pageSizes.FirstOrDefault(o => o.Name == version.Name).PageSize;
|
|
|
|
info.Add(new DatabaseInfo
|
|
{
|
|
Name = version.Name,
|
|
Version = version.Version,
|
|
PageCount = pageCount,
|
|
PageSize = pageSize,
|
|
DatabaseSize = pageCount * pageSize
|
|
});
|
|
}
|
|
|
|
var model = new DatabaseViewModel()
|
|
{
|
|
Info = info
|
|
};
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("Database/{databaseAction}/{database}")]
|
|
public ActionResult Database(ConfirmActionViewModel model, string databaseAction, string database)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
SessionState.Page.Name = $"Database";
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
try
|
|
{
|
|
switch (databaseAction)
|
|
{
|
|
case "Optimize":
|
|
{
|
|
var resultText = SpannedRepository.OptimizeDatabase(database);
|
|
return NotifyOfSuccess($"操作成功 {resultText}", model.YesRedirectURL);
|
|
}
|
|
case "Vacuum":
|
|
{
|
|
var resultText = SpannedRepository.OptimizeDatabase(database);
|
|
return NotifyOfSuccess($"操作成功 {resultText}", model.YesRedirectURL);
|
|
}
|
|
case "Verify":
|
|
{
|
|
var resultText = SpannedRepository.OptimizeDatabase(database);
|
|
return NotifyOfSuccess($"验证完成 {resultText}", model.YesRedirectURL);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return NotifyOfError($"操作失败: {ex.Message}.", model.YesRedirectURL);
|
|
}
|
|
|
|
return NotifyOfError($"Unknown database action: '{databaseAction}'.", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Metrics.
|
|
|
|
[Authorize]
|
|
[HttpGet("Metrics")]
|
|
public ActionResult Metrics()
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
SessionState.Page.Name = $"Metrics";
|
|
|
|
var version = string.Join('.', (Assembly.GetExecutingAssembly()
|
|
.GetName().Version?.ToString() ?? "0.0.0.0").Split('.').Take(3)); //Major.Minor.Patch
|
|
|
|
var model = new MetricsViewModel()
|
|
{
|
|
Metrics = ConfigurationRepository.GetWikiDatabaseMetrics(),
|
|
ApplicationVersion = version
|
|
};
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("PurgeCompilationStatistics")]
|
|
public ActionResult PurgeCompilationStatistics(ConfirmActionViewModel model)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
StatisticsRepository.PurgeCompilationStatistics();
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("PurgeMemoryCache")]
|
|
public ActionResult PurgeMemoryCache(ConfirmActionViewModel model)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
WikiCache.Clear();
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Compilation Statistics.
|
|
|
|
[Authorize]
|
|
[HttpGet("CompilationStatistics")]
|
|
public ActionResult CompilationStatistics()
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
SessionState.Page.Name = $"Compilations Statistics";
|
|
|
|
var pageNumber = GetQueryValue("page", 1);
|
|
var orderBy = GetQueryValue("OrderBy");
|
|
var orderByDirection = GetQueryValue("OrderByDirection");
|
|
|
|
var model = new PageCompilationStatisticsViewModel()
|
|
{
|
|
Statistics = StatisticsRepository.GetCompilationStatisticsPaged(pageNumber, orderBy, orderByDirection),
|
|
};
|
|
|
|
model.PaginationPageCount = (model.Statistics.FirstOrDefault()?.PaginationPageCount ?? 0);
|
|
|
|
model.Statistics.ForEach(o =>
|
|
{
|
|
o.LatestBuild = SessionState.LocalizeDateTime(o.LatestBuild);
|
|
});
|
|
|
|
return View(model);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Moderate.
|
|
|
|
[Authorize]
|
|
[HttpGet("Moderate")]
|
|
public ActionResult Moderate()
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
SessionState.Page.Name = $"Page Moderation";
|
|
|
|
var instruction = GetQueryValue("Instruction");
|
|
if (instruction != null)
|
|
{
|
|
var model = new PageModerateViewModel()
|
|
{
|
|
Pages = PageRepository.GetAllPagesByInstructionPaged(GetQueryValue("page", 1), instruction),
|
|
Instruction = instruction,
|
|
Instructions = typeof(WikiInstruction).GetProperties().Select(o => o.Name).ToList()
|
|
};
|
|
|
|
model.PaginationPageCount = (model.Pages.FirstOrDefault()?.PaginationPageCount ?? 0);
|
|
|
|
if (model.Pages != null && model.Pages.Count > 0)
|
|
{
|
|
model.Pages.ForEach(o =>
|
|
{
|
|
o.CreatedDate = SessionState.LocalizeDateTime(o.CreatedDate);
|
|
o.ModifiedDate = SessionState.LocalizeDateTime(o.ModifiedDate);
|
|
});
|
|
}
|
|
|
|
return View(model);
|
|
}
|
|
|
|
return View(new PageModerateViewModel()
|
|
{
|
|
Pages = new(),
|
|
Instruction = string.Empty,
|
|
Instructions = typeof(WikiInstruction).GetProperties().Select(o => o.Name).ToList()
|
|
});
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Missing Pages.
|
|
|
|
[Authorize]
|
|
[HttpGet("MissingPages")]
|
|
public ActionResult MissingPages()
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
SessionState.Page.Name = $"Missing Pages";
|
|
|
|
var pageNumber = GetQueryValue("page", 1);
|
|
var orderBy = GetQueryValue("OrderBy");
|
|
var orderByDirection = GetQueryValue("OrderByDirection");
|
|
|
|
var model = new MissingPagesViewModel()
|
|
{
|
|
Pages = PageRepository.GetMissingPagesPaged(pageNumber, orderBy, orderByDirection)
|
|
};
|
|
|
|
model.PaginationPageCount = (model.Pages.FirstOrDefault()?.PaginationPageCount ?? 0);
|
|
|
|
return View(model);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Namespaces.
|
|
|
|
[Authorize]
|
|
[HttpGet("Namespaces")]
|
|
public ActionResult Namespaces()
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
SessionState.Page.Name = $"Namespaces";
|
|
|
|
var pageNumber = GetQueryValue("page", 1);
|
|
var orderBy = GetQueryValue("OrderBy");
|
|
var orderByDirection = GetQueryValue("OrderByDirection");
|
|
|
|
var model = new NamespacesViewModel()
|
|
{
|
|
Namespaces = PageRepository.GetAllNamespacesPaged(pageNumber, orderBy, orderByDirection),
|
|
};
|
|
|
|
model.PaginationPageCount = (model.Namespaces.FirstOrDefault()?.PaginationPageCount ?? 0);
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("Namespace/{namespaceName?}")]
|
|
public ActionResult Namespace(string? namespaceName = null)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
SessionState.Page.Name = $"Namespace";
|
|
|
|
var pageNumber = GetQueryValue("page", 1);
|
|
var orderBy = GetQueryValue("OrderBy");
|
|
var orderByDirection = GetQueryValue("OrderByDirection");
|
|
|
|
var model = new NamespaceViewModel()
|
|
{
|
|
Pages = PageRepository.GetAllNamespacePagesPaged(pageNumber, namespaceName ?? string.Empty, orderBy, orderByDirection),
|
|
Namespace = namespaceName ?? string.Empty
|
|
};
|
|
|
|
model.PaginationPageCount = (model.Pages.FirstOrDefault()?.PaginationPageCount ?? 0);
|
|
|
|
if (model.Pages != null && model.Pages.Count > 0)
|
|
{
|
|
model.Pages.ForEach(o =>
|
|
{
|
|
o.CreatedDate = SessionState.LocalizeDateTime(o.CreatedDate);
|
|
o.ModifiedDate = SessionState.LocalizeDateTime(o.ModifiedDate);
|
|
});
|
|
}
|
|
|
|
return View(model);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Pages.
|
|
|
|
[Authorize]
|
|
[HttpGet("Pages")]
|
|
public ActionResult Pages()
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
SessionState.Page.Name = $"Pages";
|
|
|
|
var searchString = GetQueryValue("SearchString");
|
|
var orderBy = GetQueryValue("OrderBy");
|
|
var orderByDirection = GetQueryValue("OrderByDirection");
|
|
|
|
var model = new PagesViewModel()
|
|
{
|
|
Pages = PageRepository.GetAllPagesPaged(GetQueryValue("page", 1), orderBy, orderByDirection, Utility.SplitToTokens(searchString)),
|
|
SearchString = searchString ?? string.Empty
|
|
};
|
|
|
|
model.PaginationPageCount = (model.Pages.FirstOrDefault()?.PaginationPageCount ?? 0);
|
|
|
|
if (model.Pages != null && model.Pages.Count > 0)
|
|
{
|
|
model.Pages.ForEach(o =>
|
|
{
|
|
o.CreatedDate = SessionState.LocalizeDateTime(o.CreatedDate);
|
|
o.ModifiedDate = SessionState.LocalizeDateTime(o.ModifiedDate);
|
|
});
|
|
}
|
|
|
|
return View(model);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Revisions.
|
|
|
|
[Authorize]
|
|
[HttpPost("RevertPageRevision/{givenCanonical}/{revision:int}")]
|
|
public ActionResult Revert(string givenCanonical, int revision, ConfirmActionViewModel model)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
var pageNavigation = NamespaceNavigation.CleanAndValidate(givenCanonical);
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
var page = PageRepository.GetPageRevisionByNavigation(pageNavigation, revision).EnsureNotNull();
|
|
|
|
int currentPageRevision = PageRepository.GetCurrentPageRevision(page.Id);
|
|
if (revision >= currentPageRevision)
|
|
{
|
|
return NotifyOfError("操作失败");
|
|
}
|
|
|
|
Engine.Implementation.Helpers.UpsertPage(tightEngine, page, SessionState);
|
|
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("DeletedPageRevisions/{pageId:int}")]
|
|
public ActionResult DeletedPageRevisions(int pageId)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
var pageNumber = GetQueryValue("page", 1);
|
|
var orderBy = GetQueryValue("OrderBy");
|
|
var orderByDirection = GetQueryValue("OrderByDirection");
|
|
|
|
var model = new DeletedPagesRevisionsViewModel()
|
|
{
|
|
Revisions = PageRepository.GetDeletedPageRevisionsByIdPaged(pageId, pageNumber, orderBy, orderByDirection)
|
|
};
|
|
|
|
var page = PageRepository.GetLimitedPageInfoByIdAndRevision(pageId);
|
|
if (page == null)
|
|
{
|
|
return NotifyOfError("找不到指定页面");
|
|
}
|
|
|
|
model.Name = page.Name;
|
|
model.Namespace = page.Namespace;
|
|
model.Navigation = page.Navigation;
|
|
model.PageId = pageId;
|
|
model.PaginationPageCount = (model.Revisions.FirstOrDefault()?.PaginationPageCount ?? 0);
|
|
|
|
model.Revisions.ForEach(o =>
|
|
{
|
|
o.DeletedDate = SessionState.LocalizeDateTime(o.DeletedDate);
|
|
});
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("DeletedPageRevision/{pageId:int}/{revision:int}")]
|
|
public ActionResult DeletedPageRevision(int pageId, int revision)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
var model = new DeletedPageRevisionViewModel();
|
|
|
|
var page = PageRepository.GetDeletedPageRevisionById(pageId, revision);
|
|
|
|
if (page != null)
|
|
{
|
|
var state = tightEngine.Transform(SessionState, page);
|
|
model.PageId = pageId;
|
|
model.Revision = pageId;
|
|
model.Body = state.HtmlResult;
|
|
model.DeletedDate = SessionState.LocalizeDateTime(page.DeletedDate);
|
|
model.DeletedByUserName = page.DeletedByUserName;
|
|
}
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("PageRevisions/{givenCanonical}")]
|
|
public ActionResult PageRevisions(string givenCanonical)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
var pageNavigation = NamespaceNavigation.CleanAndValidate(givenCanonical);
|
|
|
|
var pageNumber = GetQueryValue("page", 1);
|
|
var orderBy = GetQueryValue("OrderBy");
|
|
var orderByDirection = GetQueryValue("OrderByDirection");
|
|
|
|
var model = new PageRevisionsViewModel()
|
|
{
|
|
Revisions = PageRepository.GetPageRevisionsInfoByNavigationPaged(pageNavigation, pageNumber, orderBy, orderByDirection)
|
|
};
|
|
|
|
model.PaginationPageCount = (model.Revisions.FirstOrDefault()?.PaginationPageCount ?? 0);
|
|
|
|
model.Revisions.ForEach(o =>
|
|
{
|
|
o.CreatedDate = SessionState.LocalizeDateTime(o.CreatedDate);
|
|
o.ModifiedDate = SessionState.LocalizeDateTime(o.ModifiedDate);
|
|
});
|
|
|
|
foreach (var p in model.Revisions)
|
|
{
|
|
var thisRev = PageRepository.GetPageRevisionByNavigation(p.Navigation, p.Revision);
|
|
var prevRev = PageRepository.GetPageRevisionByNavigation(p.Navigation, p.Revision - 1);
|
|
p.ChangeSummary = Differentiator.GetComparisonSummary(thisRev?.Body ?? "", prevRev?.Body ?? "");
|
|
}
|
|
|
|
if (model.Revisions != null && model.Revisions.Count > 0)
|
|
{
|
|
SessionState.SetPageId(model.Revisions.First().PageId);
|
|
}
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("DeletePageRevision/{givenCanonical}/{revision:int}")]
|
|
public ActionResult DeletePageRevision(ConfirmActionViewModel model, string givenCanonical, int revision)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
var pageNavigation = NamespaceNavigation.CleanAndValidate(givenCanonical);
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
var page = PageRepository.GetPageInfoByNavigation(pageNavigation);
|
|
if (page == null)
|
|
{
|
|
return NotifyOfError("无法找到指定页面");
|
|
}
|
|
|
|
int revisionCount = PageRepository.GetPageRevisionCountByPageId(page.Id);
|
|
if (revisionCount <= 1)
|
|
{
|
|
return NotifyOfError("唯一修订无法删除");
|
|
}
|
|
|
|
//If we are deleting the latest revision, then we need to grab the previous
|
|
// version and make it the latest then delete the specified revision.
|
|
if (revision >= page.Revision)
|
|
{
|
|
int previousRevision = PageRepository.GetPagePreviousRevision(page.Id, revision);
|
|
var previousPageRevision = PageRepository.GetPageRevisionByNavigation(pageNavigation, previousRevision).EnsureNotNull();
|
|
Engine.Implementation.Helpers.UpsertPage(tightEngine, previousPageRevision, SessionState);
|
|
}
|
|
|
|
PageRepository.MovePageRevisionToDeletedById(page.Id, revision, SessionState.Profile.EnsureNotNull().UserId);
|
|
|
|
return NotifyOfSuccess("操作成功页面修订已移至删除队列", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Deleted Pages.
|
|
|
|
[Authorize]
|
|
[HttpGet("DeletedPage/{pageId}")]
|
|
public ActionResult DeletedPage(int pageId)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
var model = new DeletedPageViewModel();
|
|
|
|
var page = PageRepository.GetDeletedPageById(pageId);
|
|
|
|
if (page != null)
|
|
{
|
|
var state = tightEngine.Transform(SessionState, page);
|
|
model.PageId = pageId;
|
|
model.Body = state.HtmlResult;
|
|
model.DeletedDate = SessionState.LocalizeDateTime(page.ModifiedDate);
|
|
model.DeletedByUserName = page.DeletedByUserName;
|
|
}
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("DeletedPages")]
|
|
public ActionResult DeletedPages()
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
var searchString = GetQueryValue("SearchString", string.Empty);
|
|
var pageNumber = GetQueryValue("page", 1);
|
|
var orderBy = GetQueryValue("OrderBy");
|
|
var orderByDirection = GetQueryValue("OrderByDirection");
|
|
|
|
var model = new DeletedPagesViewModel()
|
|
{
|
|
Pages = PageRepository.GetAllDeletedPagesPaged(pageNumber, orderBy, orderByDirection, Utility.SplitToTokens(searchString)),
|
|
SearchString = searchString
|
|
};
|
|
|
|
model.PaginationPageCount = (model.Pages.FirstOrDefault()?.PaginationPageCount ?? 0);
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("RebuildAllPages")]
|
|
public ActionResult RebuildAllPages(ConfirmActionViewModel model)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
foreach (var page in PageRepository.GetAllPages())
|
|
{
|
|
Engine.Implementation.Helpers.RefreshPageMetadata(tightEngine, page, SessionState);
|
|
}
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("PreCacheAllPages")]
|
|
public ActionResult PreCacheAllPages(ConfirmActionViewModel model)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
int threadCount = Environment.ProcessorCount > 1 ? Environment.ProcessorCount / 2 : Environment.ProcessorCount;
|
|
|
|
var pool = new DelegateThreadPool(threadCount, 0);
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
var workload = pool.CreateChildPool();
|
|
|
|
foreach (var page in PageRepository.GetAllPages())
|
|
{
|
|
workload.Enqueue(() =>
|
|
{
|
|
string queryKey = string.Empty;
|
|
foreach (var query in Request.Query)
|
|
{
|
|
queryKey += $"{query.Key}:{query.Value}";
|
|
}
|
|
|
|
var cacheKey = WikiCacheKeyFunction.Build(WikiCache.Category.Page, [page.Navigation, page.Revision, queryKey]);
|
|
if (WikiCache.Contains(cacheKey) == false)
|
|
{
|
|
var state = tightEngine.Transform(SessionState, page, page.Revision);
|
|
page.Body = state.HtmlResult;
|
|
|
|
if (state.ProcessingInstructions.Contains(WikiInstruction.NoCache) == false)
|
|
{
|
|
WikiCache.Put(cacheKey, state.HtmlResult); //This is cleared with the call to Cache.ClearCategory($"Page:{page.Navigation}");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
workload.WaitForCompletion();
|
|
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("TruncatePageRevisions")]
|
|
public ActionResult TruncatePageRevisions(ConfirmActionViewModel model)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
PageRepository.TruncateAllPageRevisions("YES");
|
|
WikiCache.Clear();
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("PurgeDeletedPageRevisions/{pageId:int}")]
|
|
public ActionResult PurgeDeletedPageRevisions(ConfirmActionViewModel model, int pageId)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
PageRepository.PurgeDeletedPageRevisionsByPageId(pageId);
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("PurgeDeletedPageRevision/{pageId:int}/{revision:int}")]
|
|
public ActionResult PurgeDeletedPageRevision(ConfirmActionViewModel model, int pageId, int revision)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
PageRepository.PurgeDeletedPageRevisionByPageIdAndRevision(pageId, revision);
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("RestoreDeletedPageRevision/{pageId:int}/{revision:int}")]
|
|
public ActionResult RestoreDeletedPageRevision(ConfirmActionViewModel model, int pageId, int revision)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
PageRepository.RestoreDeletedPageRevisionByPageIdAndRevision(pageId, revision);
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("PurgeDeletedPages")]
|
|
public ActionResult PurgeDeletedPages(ConfirmActionViewModel model)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
PageRepository.PurgeDeletedPages();
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("PurgeDeletedPage/{pageId:int}")]
|
|
public ActionResult PurgeDeletedPage(ConfirmActionViewModel model, int pageId)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
PageRepository.PurgeDeletedPageByPageId(pageId);
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("DeletePage/{pageId:int}")]
|
|
public ActionResult DeletePage(ConfirmActionViewModel model, int pageId)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
PageRepository.MovePageToDeletedById(pageId, SessionState.Profile.EnsureNotNull().UserId);
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("RestoreDeletedPage/{pageId:int}")]
|
|
public ActionResult RestoreDeletedPage(ConfirmActionViewModel model, int pageId)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
PageRepository.RestoreDeletedPageByPageId(pageId);
|
|
var page = PageRepository.GetLatestPageRevisionById(pageId);
|
|
if (page != null)
|
|
{
|
|
Engine.Implementation.Helpers.RefreshPageMetadata(tightEngine, page, SessionState);
|
|
}
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Files.
|
|
|
|
[Authorize]
|
|
[HttpGet("OrphanedPageAttachments")]
|
|
public ActionResult OrphanedPageAttachments()
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
SessionState.Page.Name = $"Orphaned Page Attachments";
|
|
|
|
var pageNumber = GetQueryValue("page", 1);
|
|
var orderBy = GetQueryValue("OrderBy");
|
|
var orderByDirection = GetQueryValue("OrderByDirection");
|
|
|
|
var model = new OrphanedPageAttachmentsViewModel()
|
|
{
|
|
Files = PageFileRepository.GetOrphanedPageAttachmentsPaged(pageNumber, orderBy, orderByDirection),
|
|
};
|
|
|
|
model.PaginationPageCount = (model.Files.FirstOrDefault()?.PaginationPageCount ?? 0);
|
|
|
|
/* Localization:
|
|
if (model.Files != null && model.Files.Count > 0)
|
|
{
|
|
model.Files.ForEach(o =>
|
|
{
|
|
o.CreatedDate = SessionState.LocalizeDateTime(o.CreatedDate);
|
|
o.ModifiedDate = SessionState.LocalizeDateTime(o.ModifiedDate);
|
|
});
|
|
}
|
|
*/
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("PurgeOrphanedAttachments")]
|
|
public ActionResult PurgeOrphanedAttachments(ConfirmActionViewModel model)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
PageFileRepository.PurgeOrphanedPageAttachments();
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
|
|
[Authorize]
|
|
[HttpPost("PurgeOrphanedAttachment/{pageFileId:int}/{revision:int}")]
|
|
public ActionResult PurgeOrphanedAttachment(ConfirmActionViewModel model, int pageFileId, int revision)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
PageFileRepository.PurgeOrphanedPageAttachment(pageFileId, revision);
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Menu Items.
|
|
|
|
[Authorize]
|
|
[HttpGet("MenuItems")]
|
|
public ActionResult MenuItems()
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
//var pageNumber = GetQueryValue("page", 1);
|
|
var orderBy = GetQueryValue("OrderBy");
|
|
var orderByDirection = GetQueryValue("OrderByDirection");
|
|
|
|
var model = new MenuItemsViewModel()
|
|
{
|
|
Items = ConfigurationRepository.GetAllMenuItems(orderBy, orderByDirection)
|
|
};
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("MenuItem/{id:int?}")]
|
|
public ActionResult MenuItem(int? id)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
SessionState.Page.Name = $"Menu Item";
|
|
|
|
if (id != null)
|
|
{
|
|
var menuItem = ConfigurationRepository.GetMenuItemById((int)id);
|
|
return View(menuItem.ToViewModel());
|
|
}
|
|
else
|
|
{
|
|
var model = new MenuItemViewModel
|
|
{
|
|
Link = "/"
|
|
};
|
|
return View(model);
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Save site menu item.
|
|
/// </summary>
|
|
/// <param name="profile"></param>
|
|
/// <returns></returns>
|
|
[Authorize]
|
|
[HttpPost("MenuItem/{id:int?}")]
|
|
public ActionResult MenuItem(int? id, MenuItemViewModel model)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return View(model);
|
|
}
|
|
|
|
if (ConfigurationRepository.GetAllMenuItems().Where(o => o.Name.Equals(model.Name, StringComparison.InvariantCultureIgnoreCase) && o.Id != model.Id).Any())
|
|
{
|
|
ModelState.AddModelError("Name", $"菜单名 '{model.Name}' 已被使用");
|
|
return View(model);
|
|
}
|
|
|
|
if (id.DefaultWhenNull(0) == 0)
|
|
{
|
|
model.Id = ConfigurationRepository.InsertMenuItem(model.ToDataModel());
|
|
ModelState.Clear();
|
|
|
|
return NotifyOfSuccess("创建成功", $"/Admin/MenuItem/{model.Id}");
|
|
}
|
|
else
|
|
{
|
|
ConfigurationRepository.UpdateMenuItemById(model.ToDataModel());
|
|
}
|
|
|
|
model.SuccessMessage = "操作成功!";
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("DeleteMenuItem/{id}")]
|
|
public ActionResult DeleteMenuItem(int id)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
var model = ConfigurationRepository.GetMenuItemById(id);
|
|
SessionState.Page.Name = $"{model.Name} Delete";
|
|
|
|
return View(model.ToViewModel());
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("DeleteMenuItem/{id}")]
|
|
public ActionResult DeleteMenuItem(MenuItemViewModel model)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
bool confirmAction = bool.Parse(GetFormValue("IsActionConfirmed").EnsureNotNull());
|
|
if (confirmAction == true)
|
|
{
|
|
ConfigurationRepository.DeleteMenuItemById(model.Id);
|
|
|
|
return NotifyOfSuccess("删除成功", $"/Admin/MenuItems");
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}/Admin/MenuItem/{model.Id}");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Roles.
|
|
|
|
[Authorize]
|
|
[HttpGet("Role/{navigation}")]
|
|
public ActionResult Role(string navigation)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
SessionState.Page.Name = $"Roles";
|
|
|
|
navigation = Navigation.Clean(navigation);
|
|
|
|
var role = UsersRepository.GetRoleByName(navigation);
|
|
|
|
var model = new RoleViewModel()
|
|
{
|
|
Id = role.Id,
|
|
Name = role.Name,
|
|
Users = UsersRepository.GetProfilesByRoleIdPaged(role.Id, GetQueryValue("page", 1))
|
|
};
|
|
|
|
model.PaginationPageCount = (model.Users.FirstOrDefault()?.PaginationPageCount ?? 0);
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("Roles")]
|
|
public ActionResult Roles()
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
var orderBy = GetQueryValue("OrderBy");
|
|
var orderByDirection = GetQueryValue("OrderByDirection");
|
|
|
|
var model = new RolesViewModel()
|
|
{
|
|
Roles = UsersRepository.GetAllRoles(orderBy, orderByDirection)
|
|
};
|
|
|
|
return View(model);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Accounts
|
|
|
|
[Authorize]
|
|
[HttpGet("Account/{navigation}")]
|
|
public ActionResult Account(string navigation)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
var model = new Models.ViewModels.Admin.AccountProfileViewModel()
|
|
{
|
|
AccountProfile = Models.ViewModels.Admin.AccountProfileAccountViewModel.FromDataModel(
|
|
UsersRepository.GetAccountProfileByNavigation(Navigation.Clean(navigation))),
|
|
Credential = new CredentialViewModel(),
|
|
Themes = ConfigurationRepository.GetAllThemes(),
|
|
TimeZones = TimeZoneItem.GetAll(),
|
|
Countries = CountryItem.GetAll(),
|
|
Languages = LanguageItem.GetAll(),
|
|
Roles = UsersRepository.GetAllRoles()
|
|
};
|
|
|
|
model.AccountProfile.CreatedDate = SessionState.LocalizeDateTime(model.AccountProfile.CreatedDate);
|
|
model.AccountProfile.ModifiedDate = SessionState.LocalizeDateTime(model.AccountProfile.ModifiedDate);
|
|
|
|
return View(model);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Save user profile.
|
|
/// </summary>
|
|
/// <param name="profile"></param>
|
|
/// <returns></returns>
|
|
[Authorize]
|
|
[HttpPost("Account/{navigation}")]
|
|
public ActionResult Account(string navigation, Models.ViewModels.Admin.AccountProfileViewModel model)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
model.Themes = ConfigurationRepository.GetAllThemes();
|
|
model.TimeZones = TimeZoneItem.GetAll();
|
|
model.Countries = CountryItem.GetAll();
|
|
model.Languages = LanguageItem.GetAll();
|
|
model.Roles = UsersRepository.GetAllRoles();
|
|
model.AccountProfile.Navigation = NamespaceNavigation.CleanAndValidate(model.AccountProfile.AccountName.ToLower());
|
|
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return View(model);
|
|
}
|
|
|
|
var user = UserManager.FindByIdAsync(model.AccountProfile.UserId.ToString()).Result.EnsureNotNull();
|
|
|
|
if (model.Credential.Password != CredentialViewModel.NOTSET && model.Credential.Password == model.Credential.ComparePassword)
|
|
{
|
|
try
|
|
{
|
|
var token = UserManager.GeneratePasswordResetTokenAsync(user).Result.EnsureNotNull();
|
|
var result = UserManager.ResetPasswordAsync(user, token, model.Credential.Password).Result.EnsureNotNull();
|
|
if (!result.Succeeded)
|
|
{
|
|
throw new Exception(string.Join("<br />\r\n", result.Errors.Select(o => o.Description)));
|
|
}
|
|
|
|
if (model.AccountProfile.AccountName.Equals(Constants.DEFAULTACCOUNT, StringComparison.CurrentCultureIgnoreCase))
|
|
{
|
|
UsersRepository.SetAdminPasswordIsChanged();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ModelState.AddModelError("Credential.Password", ex.Message);
|
|
return View(model);
|
|
}
|
|
}
|
|
|
|
var profile = UsersRepository.GetAccountProfileByUserId(model.AccountProfile.UserId);
|
|
if (!profile.Navigation.Equals(model.AccountProfile.Navigation, StringComparison.CurrentCultureIgnoreCase))
|
|
{
|
|
if (UsersRepository.DoesProfileAccountExist(model.AccountProfile.AccountName))
|
|
{
|
|
ModelState.AddModelError("AccountProfile.AccountName", "用户名已经被使用");
|
|
return View(model);
|
|
}
|
|
}
|
|
|
|
if (!profile.EmailAddress.Equals(model.AccountProfile.EmailAddress, StringComparison.CurrentCultureIgnoreCase))
|
|
{
|
|
if (UsersRepository.DoesEmailAddressExist(model.AccountProfile.EmailAddress))
|
|
{
|
|
ModelState.AddModelError("AccountProfile.EmailAddress", "该邮箱已经存在");
|
|
return View(model);
|
|
}
|
|
}
|
|
|
|
var file = Request.Form.Files["Avatar"];
|
|
if (file != null && file.Length > 0)
|
|
{
|
|
if (GlobalConfiguration.AllowableImageTypes.Contains(file.ContentType.ToLower()) == false)
|
|
{
|
|
model.ErrorMessage += "无法保存附件图片, 不允许的格式.\r\n";
|
|
}
|
|
else if (file.Length > GlobalConfiguration.MaxAvatarFileSize)
|
|
{
|
|
model.ErrorMessage += "无法保存附件图片, 图片过大.\r\n";
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
var imageBytes = Utility.ConvertHttpFileToBytes(file);
|
|
var image = SixLabors.ImageSharp.Image.Load(new MemoryStream(imageBytes));
|
|
UsersRepository.UpdateProfileAvatar(profile.UserId, imageBytes, file.ContentType.ToLower());
|
|
}
|
|
catch
|
|
{
|
|
model.ErrorMessage += "无法保存附件图片.\r\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
profile.AccountName = model.AccountProfile.AccountName;
|
|
profile.Navigation = NamespaceNavigation.CleanAndValidate(model.AccountProfile.AccountName);
|
|
profile.Biography = model.AccountProfile.Biography;
|
|
profile.ModifiedDate = DateTime.UtcNow;
|
|
UsersRepository.UpdateProfile(profile);
|
|
|
|
var claims = new List<Claim>
|
|
{
|
|
new (ClaimTypes.Role, model.AccountProfile.Role),
|
|
new ("timezone", model.AccountProfile.TimeZone),
|
|
new (ClaimTypes.Country, model.AccountProfile.Country),
|
|
new ("language", model.AccountProfile.Language),
|
|
new ("firstname", model.AccountProfile.FirstName ?? ""),
|
|
new ("lastname", model.AccountProfile.LastName ?? ""),
|
|
new ("theme", model.AccountProfile.Theme ?? ""),
|
|
};
|
|
SecurityRepository.UpsertUserClaims(UserManager, user, claims);
|
|
|
|
//If we are changing the currently logged in user, then make sure we take some extra actions so we can see the changes immediately.
|
|
if (SessionState.Profile?.UserId == model.AccountProfile.UserId)
|
|
{
|
|
SignInManager.RefreshSignInAsync(user);
|
|
|
|
WikiCache.ClearCategory(WikiCacheKey.Build(WikiCache.Category.User, [profile.Navigation]));
|
|
WikiCache.ClearCategory(WikiCacheKey.Build(WikiCache.Category.User, [profile.UserId]));
|
|
|
|
//This is not 100% necessary, I just want to prevent the user from needing to refresh to view the new theme.
|
|
SessionState.UserTheme = ConfigurationRepository.GetAllThemes().SingleOrDefault(o => o.Name == model.AccountProfile.Theme) ?? GlobalConfiguration.SystemTheme;
|
|
}
|
|
|
|
//Allow the administrator to confirm/unconfirm the email address.
|
|
bool emailConfirmChanged = profile.EmailConfirmed != model.AccountProfile.EmailConfirmed;
|
|
if (emailConfirmChanged)
|
|
{
|
|
user.EmailConfirmed = model.AccountProfile.EmailConfirmed;
|
|
var updateResult = UserManager.UpdateAsync(user).Result;
|
|
if (!updateResult.Succeeded)
|
|
{
|
|
throw new Exception(string.Join("<br />\r\n", updateResult.Errors.Select(o => o.Description)));
|
|
}
|
|
}
|
|
|
|
if (!profile.EmailAddress.Equals(model.AccountProfile.EmailAddress, StringComparison.CurrentCultureIgnoreCase))
|
|
{
|
|
bool wasEmailAlreadyConfirmed = user.EmailConfirmed;
|
|
|
|
var setEmailResult = UserManager.SetEmailAsync(user, model.AccountProfile.EmailAddress).Result;
|
|
if (!setEmailResult.Succeeded)
|
|
{
|
|
throw new Exception(string.Join("<br />\r\n", setEmailResult.Errors.Select(o => o.Description)));
|
|
}
|
|
|
|
var setUserNameResult = UserManager.SetUserNameAsync(user, model.AccountProfile.EmailAddress).Result;
|
|
if (!setUserNameResult.Succeeded)
|
|
{
|
|
throw new Exception(string.Join("<br />\r\n", setUserNameResult.Errors.Select(o => o.Description)));
|
|
}
|
|
|
|
//If the email address was already confirmed, just keep the status. Afterall, this is an admin making the change.
|
|
if (wasEmailAlreadyConfirmed && emailConfirmChanged == false)
|
|
{
|
|
user.EmailConfirmed = true;
|
|
var updateResult = UserManager.UpdateAsync(user).Result;
|
|
if (!updateResult.Succeeded)
|
|
{
|
|
throw new Exception(string.Join("<br />\r\n", updateResult.Errors.Select(o => o.Description)));
|
|
}
|
|
}
|
|
}
|
|
|
|
model.SuccessMessage = "操作成功!";
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("AddAccount")]
|
|
public ActionResult AddAccount()
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
var membershipConfig = ConfigurationRepository.GetConfigurationEntryValuesByGroupName("Membership");
|
|
var defaultSignupRole = membershipConfig.Value<string>("Default Signup Role").EnsureNotNull();
|
|
var customizationConfig = ConfigurationRepository.GetConfigurationEntryValuesByGroupName("Customization");
|
|
|
|
var model = new Models.ViewModels.Admin.AccountProfileViewModel()
|
|
{
|
|
AccountProfile = new Models.ViewModels.Admin.AccountProfileAccountViewModel
|
|
{
|
|
AccountName = string.Empty,
|
|
Country = customizationConfig.Value<string>("Default Country", string.Empty),
|
|
TimeZone = customizationConfig.Value<string>("Default TimeZone", string.Empty),
|
|
Language = customizationConfig.Value<string>("Default Language", string.Empty),
|
|
Role = defaultSignupRole
|
|
},
|
|
Themes = ConfigurationRepository.GetAllThemes(),
|
|
Credential = new CredentialViewModel(),
|
|
TimeZones = TimeZoneItem.GetAll(),
|
|
Countries = CountryItem.GetAll(),
|
|
Languages = LanguageItem.GetAll(),
|
|
Roles = UsersRepository.GetAllRoles()
|
|
};
|
|
|
|
return View(model);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new user profile.
|
|
/// </summary>
|
|
/// <param name="profile"></param>
|
|
/// <returns></returns>
|
|
[Authorize]
|
|
[HttpPost("AddAccount")]
|
|
public ActionResult AddAccount(Models.ViewModels.Admin.AccountProfileViewModel model)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
model.Themes = ConfigurationRepository.GetAllThemes();
|
|
model.TimeZones = TimeZoneItem.GetAll();
|
|
model.Countries = CountryItem.GetAll();
|
|
model.Languages = LanguageItem.GetAll();
|
|
model.Roles = UsersRepository.GetAllRoles();
|
|
model.AccountProfile.Navigation = NamespaceNavigation.CleanAndValidate(model.AccountProfile.AccountName?.ToLower());
|
|
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return View(model);
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(model.AccountProfile.AccountName))
|
|
{
|
|
ModelState.AddModelError("AccountProfile.AccountName", "用户名为必填项");
|
|
return View(model);
|
|
}
|
|
|
|
if (UsersRepository.DoesProfileAccountExist(model.AccountProfile.AccountName))
|
|
{
|
|
ModelState.AddModelError("AccountProfile.AccountName", "用户名已经在使用");
|
|
return View(model);
|
|
}
|
|
|
|
if (UsersRepository.DoesEmailAddressExist(model.AccountProfile.EmailAddress))
|
|
{
|
|
ModelState.AddModelError("AccountProfile.EmailAddress", "邮箱已经被使用");
|
|
return View(model);
|
|
}
|
|
|
|
Guid? userId;
|
|
|
|
try
|
|
{
|
|
//Define the new user:
|
|
var identityUser = new IdentityUser(model.AccountProfile.EmailAddress)
|
|
{
|
|
Email = model.AccountProfile.EmailAddress,
|
|
EmailConfirmed = true
|
|
};
|
|
|
|
//Create the new user:
|
|
var creationResult = UserManager.CreateAsync(identityUser, model.Credential.Password).Result;
|
|
if (!creationResult.Succeeded)
|
|
{
|
|
model.ErrorMessage = string.Join("<br />\r\n", creationResult.Errors.Select(o => o.Description));
|
|
return View(model);
|
|
}
|
|
identityUser = UserManager.FindByEmailAsync(model.AccountProfile.EmailAddress).Result.EnsureNotNull();
|
|
|
|
userId = Guid.Parse(identityUser.Id);
|
|
|
|
//Insert the claims.
|
|
var claims = new List<Claim>
|
|
{
|
|
new (ClaimTypes.Role, model.AccountProfile.Role),
|
|
new ("timezone", model.AccountProfile.TimeZone),
|
|
new (ClaimTypes.Country, model.AccountProfile.Country),
|
|
new ("language", model.AccountProfile.Language),
|
|
new ("firstname", model.AccountProfile.FirstName ?? ""),
|
|
new ("lastname", model.AccountProfile.LastName ?? ""),
|
|
new ("theme", model.AccountProfile.Theme ?? ""),
|
|
};
|
|
SecurityRepository.UpsertUserClaims(UserManager, identityUser, claims);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return NotifyOfError(ex.Message);
|
|
}
|
|
|
|
UsersRepository.CreateProfile((Guid)userId, model.AccountProfile.AccountName);
|
|
var profile = UsersRepository.GetAccountProfileByUserId((Guid)userId);
|
|
|
|
profile.AccountName = model.AccountProfile.AccountName;
|
|
profile.Navigation = NamespaceNavigation.CleanAndValidate(model.AccountProfile.AccountName);
|
|
profile.Biography = model.AccountProfile.Biography;
|
|
profile.ModifiedDate = DateTime.UtcNow;
|
|
UsersRepository.UpdateProfile(profile);
|
|
|
|
var file = Request.Form.Files["Avatar"];
|
|
if (file != null && file.Length > 0)
|
|
{
|
|
if (GlobalConfiguration.AllowableImageTypes.Contains(file.ContentType.ToLower()) == false)
|
|
{
|
|
model.ErrorMessage += "无法保存附件图片, 不支持的格式.\r\n";
|
|
}
|
|
else if (file.Length > GlobalConfiguration.MaxAvatarFileSize)
|
|
{
|
|
model.ErrorMessage += "无法保存附件图片, 图片过大.\r\n";
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
var imageBytes = Utility.ConvertHttpFileToBytes(file);
|
|
var image = SixLabors.ImageSharp.Image.Load(new MemoryStream(imageBytes));
|
|
UsersRepository.UpdateProfileAvatar(profile.UserId, imageBytes, file.ContentType.ToLower());
|
|
}
|
|
catch
|
|
{
|
|
model.ErrorMessage += "无法保存附件图片.";
|
|
}
|
|
}
|
|
}
|
|
|
|
return NotifyOf("创建成功", model.ErrorMessage, $"/Admin/Account/{profile.Navigation}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("Accounts")]
|
|
public ActionResult Accounts()
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
var pageNumber = GetQueryValue("page", 1);
|
|
var orderBy = GetQueryValue("OrderBy");
|
|
var orderByDirection = GetQueryValue("OrderByDirection");
|
|
var searchString = GetQueryValue("SearchString") ?? string.Empty;
|
|
|
|
var model = new AccountsViewModel()
|
|
{
|
|
Users = UsersRepository.GetAllUsersPaged(pageNumber, orderBy, orderByDirection, searchString),
|
|
SearchString = searchString
|
|
};
|
|
|
|
model.PaginationPageCount = (model.Users.FirstOrDefault()?.PaginationPageCount ?? 0);
|
|
|
|
if (model.Users != null && model.Users.Count > 0)
|
|
{
|
|
model.Users.ForEach(o =>
|
|
{
|
|
o.CreatedDate = SessionState.LocalizeDateTime(o.CreatedDate);
|
|
o.ModifiedDate = SessionState.LocalizeDateTime(o.ModifiedDate);
|
|
});
|
|
}
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("DeleteAccount/{navigation}")]
|
|
public ActionResult DeleteAccount(string navigation, DeleteAccountViewModel model)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
var profile = UsersRepository.GetAccountProfileByNavigation(navigation);
|
|
|
|
bool confirmAction = bool.Parse(GetFormValue("IsActionConfirmed").EnsureNotNull());
|
|
if (confirmAction == true && profile != null)
|
|
{
|
|
var user = UserManager.FindByIdAsync(profile.UserId.ToString()).Result;
|
|
if (user == null)
|
|
{
|
|
return NotFound("404 404 404");
|
|
}
|
|
|
|
var result = UserManager.DeleteAsync(user).Result;
|
|
if (!result.Succeeded)
|
|
{
|
|
throw new Exception(string.Join("<br />\r\n", result.Errors.Select(o => o.Description)));
|
|
}
|
|
|
|
UsersRepository.AnonymizeProfile(profile.UserId);
|
|
WikiCache.ClearCategory(WikiCacheKey.Build(WikiCache.Category.User, [profile.Navigation]));
|
|
|
|
if (profile.UserId == SessionState.Profile?.UserId)
|
|
{
|
|
//We're deleting our own account. Oh boy...
|
|
SignInManager.SignOutAsync();
|
|
|
|
return NotifyOfSuccess("操作成功", $"/Profile/Deleted");
|
|
}
|
|
|
|
return NotifyOfSuccess("操作成功", $"/Admin/Accounts");
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}/Admin/Account/{navigation}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("DeleteAccount/{navigation}")]
|
|
public ActionResult DeleteAccount(string navigation)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
SessionState.Page.Name = $"Delete Profile";
|
|
|
|
var profile = UsersRepository.GetAccountProfileByNavigation(navigation);
|
|
|
|
var model = new DeleteAccountViewModel()
|
|
{
|
|
AccountName = profile.AccountName
|
|
};
|
|
|
|
if (profile != null)
|
|
{
|
|
SessionState.Page.Name = $"Delete {profile.AccountName}";
|
|
}
|
|
|
|
return View(model);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Config.
|
|
|
|
[Authorize]
|
|
[HttpGet("Config")]
|
|
public ActionResult Config()
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
var model = new ConfigurationViewModel()
|
|
{
|
|
Themes = ConfigurationRepository.GetAllThemes(),
|
|
Roles = UsersRepository.GetAllRoles(),
|
|
TimeZones = TimeZoneItem.GetAll(),
|
|
Countries = CountryItem.GetAll(),
|
|
Languages = LanguageItem.GetAll(),
|
|
Nest = ConfigurationRepository.GetConfigurationNest()
|
|
};
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("Config")]
|
|
public ActionResult Config(ConfigurationViewModel model)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return View(model);
|
|
}
|
|
|
|
try
|
|
{
|
|
model = new ConfigurationViewModel()
|
|
{
|
|
Themes = ConfigurationRepository.GetAllThemes(),
|
|
Roles = UsersRepository.GetAllRoles(),
|
|
TimeZones = TimeZoneItem.GetAll(),
|
|
Countries = CountryItem.GetAll(),
|
|
Languages = LanguageItem.GetAll(),
|
|
Nest = ConfigurationRepository.GetConfigurationNest(),
|
|
};
|
|
|
|
var flatConfig = ConfigurationRepository.GetFlatConfiguration();
|
|
|
|
foreach (var fc in flatConfig)
|
|
{
|
|
var parent = model.Nest.Single(o => o.Name == fc.GroupName);
|
|
var child = parent.Entries.Single(o => o.Name == fc.EntryName);
|
|
|
|
var value = GetFormValue($"{fc.GroupId}:{fc.EntryId}", string.Empty);
|
|
|
|
//We keep the value in model.Nest.Entries.Value so that the page will reflect the new settings after post.
|
|
child.Value = value;
|
|
|
|
if (fc.IsRequired && string.IsNullOrEmpty(value))
|
|
{
|
|
model.ErrorMessage = $"{fc.GroupName} : {fc.EntryName} is required.";
|
|
return View(model);
|
|
}
|
|
|
|
if ($"{fc.GroupName}:{fc.EntryName}" == "Customization:Theme")
|
|
{
|
|
//This is not 100% necessary, I just want to prevent the user from needing to refresh to view the new theme.
|
|
GlobalConfiguration.SystemTheme = ConfigurationRepository.GetAllThemes().Single(o => o.Name == value);
|
|
if (string.IsNullOrEmpty(SessionState.Profile?.Theme))
|
|
{
|
|
SessionState.UserTheme = GlobalConfiguration.SystemTheme;
|
|
}
|
|
}
|
|
|
|
if (fc.IsEncrypted)
|
|
{
|
|
value = Security.Helpers.EncryptString(Security.Helpers.MachineKey, value);
|
|
}
|
|
|
|
ConfigurationRepository.SaveConfigurationEntryValueByGroupAndEntry(fc.GroupName, fc.EntryName, value);
|
|
}
|
|
|
|
WikiCache.ClearCategory(WikiCache.Category.Configuration);
|
|
|
|
model.SuccessMessage = "操作成功!";
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return NotifyOfError(ex.Message);
|
|
}
|
|
|
|
return View(model);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Emojis.
|
|
|
|
[Authorize]
|
|
[HttpGet("Emojis")]
|
|
public ActionResult Emojis()
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
SessionState.Page.Name = $"Emojis";
|
|
|
|
var pageNumber = GetQueryValue("page", 1);
|
|
var orderBy = GetQueryValue("OrderBy");
|
|
var orderByDirection = GetQueryValue("OrderByDirection");
|
|
var searchString = GetQueryValue("SearchString") ?? string.Empty;
|
|
|
|
var model = new EmojisViewModel()
|
|
{
|
|
Emojis = EmojiRepository.GetAllEmojisPaged(pageNumber, orderBy, orderByDirection, Utility.SplitToTokens(searchString)),
|
|
SearchString = searchString
|
|
};
|
|
|
|
model.PaginationPageCount = (model.Emojis.FirstOrDefault()?.PaginationPageCount ?? 0);
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("Emoji/{name}")]
|
|
public ActionResult Emoji(string name)
|
|
{
|
|
SessionState.RequireModeratePermission();
|
|
|
|
var emoji = EmojiRepository.GetEmojiByName(name);
|
|
|
|
var model = new EmojiViewModel
|
|
{
|
|
Emoji = emoji ?? new Emoji(),
|
|
Categories = string.Join(",", EmojiRepository.GetEmojiCategoriesByName(name).Select(o => o.Category).ToList()),
|
|
OriginalName = emoji?.Name ?? string.Empty
|
|
};
|
|
|
|
return View(model);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update an existing emoji.
|
|
/// </summary>
|
|
/// <param name="profile"></param>
|
|
/// <returns></returns>
|
|
[Authorize]
|
|
[HttpPost("Emoji/{name}")]
|
|
public ActionResult Emoji(EmojiViewModel model)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return View(model);
|
|
}
|
|
|
|
bool nameChanged = false;
|
|
|
|
if (!model.OriginalName.Equals(model.Emoji.Name, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
nameChanged = true;
|
|
var checkName = EmojiRepository.GetEmojiByName(model.Emoji.Name.ToLowerInvariant());
|
|
if (checkName != null)
|
|
{
|
|
ModelState.AddModelError("Emoji.Name", "Emoji名称已经被使用");
|
|
return View(model);
|
|
}
|
|
}
|
|
|
|
var emoji = new UpsertEmoji
|
|
{
|
|
Id = model.Emoji.Id,
|
|
Name = model.Emoji.Name.ToLowerInvariant(),
|
|
Categories = Utility.SplitToTokens($"{model.Categories} {model.Emoji.Name} {Text.SeperateCamelCase(model.Emoji.Name)}")
|
|
};
|
|
|
|
var file = Request.Form.Files["ImageData"];
|
|
if (file != null && file.Length > 0)
|
|
{
|
|
if (file.Length > GlobalConfiguration.MaxEmojiFileSize)
|
|
{
|
|
model.ErrorMessage += "无法保存附件图片, 图片过大.";
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
emoji.ImageData = Utility.ConvertHttpFileToBytes(file);
|
|
_ = SixLabors.ImageSharp.Image.Load(new MemoryStream(emoji.ImageData));
|
|
emoji.MimeType = file.ContentType;
|
|
}
|
|
catch
|
|
{
|
|
model.ErrorMessage += "无法保存附件图片.";
|
|
}
|
|
}
|
|
}
|
|
|
|
emoji.Id = EmojiRepository.UpsertEmoji(emoji);
|
|
model.OriginalName = model.Emoji.Name;
|
|
model.SuccessMessage = "操作成功!";
|
|
model.Emoji.Id = (int)emoji.Id;
|
|
ModelState.Clear();
|
|
|
|
ConfigurationRepository.ReloadEmojis();
|
|
|
|
if (nameChanged)
|
|
{
|
|
return NotifyOfSuccess("操作成功", $"/Admin/Emoji/{Navigation.Clean(emoji.Name)}");
|
|
}
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("AddEmoji")]
|
|
public ActionResult AddEmoji()
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
var model = new AddEmojiViewModel()
|
|
{
|
|
Name = string.Empty,
|
|
OriginalName = string.Empty,
|
|
Categories = string.Empty
|
|
};
|
|
|
|
return View(model);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Save user profile.
|
|
/// </summary>
|
|
/// <param name="profile"></param>
|
|
/// <returns></returns>
|
|
[Authorize]
|
|
[HttpPost("AddEmoji")]
|
|
public ActionResult AddEmoji(AddEmojiViewModel model)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return View(model);
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(model.OriginalName) == true || !model.OriginalName.Equals(model.Name, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
var checkName = EmojiRepository.GetEmojiByName(model.Name.ToLower());
|
|
if (checkName != null)
|
|
{
|
|
ModelState.AddModelError("Name", "Emoji名已经存在");
|
|
return View(model);
|
|
}
|
|
}
|
|
|
|
var emoji = new UpsertEmoji
|
|
{
|
|
Id = model.Id,
|
|
Name = model.Name.ToLowerInvariant(),
|
|
Categories = Utility.SplitToTokens($"{model.Categories} {model.Name} {Text.SeperateCamelCase(model.Name)}")
|
|
};
|
|
|
|
var file = Request.Form.Files["ImageData"];
|
|
if (file != null && file.Length > 0)
|
|
{
|
|
if (file.Length > GlobalConfiguration.MaxEmojiFileSize)
|
|
{
|
|
ModelState.AddModelError("Name", "无法保存附件图片, 图片过大.");
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
emoji.ImageData = Utility.ConvertHttpFileToBytes(file);
|
|
var image = SixLabors.ImageSharp.Image.Load(new MemoryStream(emoji.ImageData));
|
|
emoji.MimeType = file.ContentType;
|
|
}
|
|
catch
|
|
{
|
|
ModelState.AddModelError("Name", "无法保存附件图片.");
|
|
}
|
|
}
|
|
}
|
|
|
|
EmojiRepository.UpsertEmoji(emoji);
|
|
|
|
return NotifyOfSuccess("操作成功", $"/Admin/Emoji/{Navigation.Clean(emoji.Name)}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("DeleteEmoji/{name}")]
|
|
public ActionResult DeleteEmoji(string name, EmojiViewModel model)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
var emoji = EmojiRepository.GetEmojiByName(name);
|
|
|
|
bool confirmAction = bool.Parse(GetFormValue("IsActionConfirmed").EnsureNotNull());
|
|
if (confirmAction == true && emoji != null)
|
|
{
|
|
EmojiRepository.DeleteById(emoji.Id);
|
|
|
|
return NotifyOfSuccess("操作成功", $"/Admin/Emojis");
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}/Admin/Emoji/{name}");
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("DeleteEmoji/{name}")]
|
|
public ActionResult DeleteEmoji(string name)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
SessionState.Page.Name = $"Delete Emoji";
|
|
|
|
var emoji = EmojiRepository.GetEmojiByName(name);
|
|
|
|
var model = new EmojiViewModel()
|
|
{
|
|
OriginalName = emoji?.Name ?? string.Empty
|
|
};
|
|
|
|
if (emoji != null)
|
|
{
|
|
SessionState.Page.Name = $"Delete {emoji.Name}";
|
|
}
|
|
|
|
return View(model);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Exceptions.
|
|
|
|
[Authorize]
|
|
[HttpGet("Exceptions")]
|
|
public ActionResult Exceptions()
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
SessionState.Page.Name = $"Exceptions";
|
|
|
|
var pageNumber = GetQueryValue("page", 1);
|
|
var orderBy = GetQueryValue("OrderBy");
|
|
var orderByDirection = GetQueryValue("OrderByDirection");
|
|
|
|
var model = new ExceptionsViewModel()
|
|
{
|
|
Exceptions = ExceptionRepository.GetAllExceptionsPaged(pageNumber, orderBy, orderByDirection)
|
|
};
|
|
|
|
model.PaginationPageCount = (model.Exceptions.FirstOrDefault()?.PaginationPageCount ?? 0);
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpGet("Exception/{id}")]
|
|
public ActionResult Exception(int id)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
SessionState.Page.Name = $"Exception";
|
|
|
|
var model = new ExceptionViewModel()
|
|
{
|
|
Exception = ExceptionRepository.GetExceptionById(id)
|
|
};
|
|
|
|
return View(model);
|
|
}
|
|
|
|
[Authorize]
|
|
[HttpPost("PurgeExceptions")]
|
|
public ActionResult PurgeExceptions(ConfirmActionViewModel model)
|
|
{
|
|
SessionState.RequireAdminPermission();
|
|
|
|
if (model.UserSelection == true)
|
|
{
|
|
ExceptionRepository.PurgeExceptions();
|
|
return NotifyOfSuccess("操作成功", model.YesRedirectURL);
|
|
}
|
|
|
|
return Redirect($"{GlobalConfiguration.BasePath}{model.NoRedirectURL}");
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|