Add Family Pages

Swapped out version listing for project family listing on home page
refactor-intermediate-models
BuildFeed Bot 2018-02-05 22:24:29 +00:00
parent d51cbd21f9
commit b5a3e016ee
8 changed files with 428 additions and 185 deletions

View File

@ -11,13 +11,30 @@ namespace BuildFeed.Model
{ {
public partial class BuildRepository public partial class BuildRepository
{ {
public Task<ProjectFamily[]> SelectAllFamilies(int limit = -1, int skip = 0) => Task.Run(() => Enum.GetValues(typeof(ProjectFamily)) as ProjectFamily[]); public Task<ProjectFamily[]> SelectAllFamilies(int limit = -1, int skip = 0) => Task.Run(() =>
{
Array values = Enum.GetValues(typeof(ProjectFamily));
if (values.Length == 0)
{
return Array.Empty<ProjectFamily>();
}
var valuesWithoutNone = new ProjectFamily[values.Length - 1];
for (int i = 0, j = values.Length - 1; j > 0; j--, i++)
{
valuesWithoutNone[i] = (ProjectFamily)values.GetValue(j);
}
return valuesWithoutNone;
});
public Task<long> SelectAllFamiliesCount() => Task.Run(() => Enum.GetValues(typeof(ProjectFamily)).LongLength); public Task<long> SelectAllFamiliesCount() => Task.Run(() => Enum.GetValues(typeof(ProjectFamily)).LongLength);
public async Task<List<Build>> SelectFamily(ProjectFamily family, int limit = -1, int skip = 0) public async Task<List<Build>> SelectFamily(ProjectFamily family, int limit = -1, int skip = 0)
{ {
IFindFluent<Build, Build> query = _buildCollection.Find(new BsonDocument(nameof(Build.Family), family)).Sort(sortByOrder).Skip(skip); var query = _buildCollection.Find(new BsonDocument(nameof(Build.Family), family))
.Sort(sortByOrder)
.Skip(skip);
if (limit > 0) if (limit > 0)
{ {
@ -27,11 +44,12 @@ namespace BuildFeed.Model
return await query.ToListAsync(); return await query.ToListAsync();
} }
public async Task<long> SelectFamilyCount(ProjectFamily family) => await _buildCollection.CountAsync(new BsonDocument(nameof(Build.Family), family)); public async Task<long> SelectFamilyCount(ProjectFamily family)
=> await _buildCollection.CountAsync(new BsonDocument(nameof(Build.Family), family));
public async Task<List<FamilyOverview>> SelectFamilyOverviews() public async Task<List<FamilyOverview>> SelectFamilyOverviews()
{ {
IAggregateFluent<BsonDocument> families = _buildCollection.Aggregate() var families = _buildCollection.Aggregate()
.Sort(sortByOrder) .Sort(sortByOrder)
.Group(new BsonDocument .Group(new BsonDocument
{ {
@ -41,7 +59,7 @@ namespace BuildFeed.Model
}) })
.Sort(new BsonDocument("_id", -1)); .Sort(new BsonDocument("_id", -1));
List<BsonDocument> result = await families.ToListAsync(); var result = await families.ToListAsync();
return (from o in result return (from o in result
select BsonSerializer.Deserialize<FamilyOverview>(o)).ToList(); select BsonSerializer.Deserialize<FamilyOverview>(o)).ToList();

View File

@ -37,7 +37,7 @@ namespace BuildFeed.Model
public MetaItem() public MetaItem()
{ {
MongoClientSettings settings = new MongoClientSettings var settings = new MongoClientSettings
{ {
Server = new MongoServerAddress(MongoConfig.Host, MongoConfig.Port) Server = new MongoServerAddress(MongoConfig.Host, MongoConfig.Port)
}; };
@ -48,17 +48,16 @@ namespace BuildFeed.Model
MongoCredential.CreateCredential(MongoConfig.Database, MongoConfig.Username, MongoConfig.Password); MongoCredential.CreateCredential(MongoConfig.Database, MongoConfig.Username, MongoConfig.Password);
} }
MongoClient dbClient = new MongoClient(settings); var dbClient = new MongoClient(settings);
_metaCollection = dbClient.GetDatabase(MongoConfig.Database).GetCollection<MetaItemModel>(META_COLLECTION_NAME); _metaCollection = dbClient.GetDatabase(MongoConfig.Database)
.GetCollection<MetaItemModel>(META_COLLECTION_NAME);
_bModel = new BuildRepository(); _bModel = new BuildRepository();
} }
[DataObjectMethod(DataObjectMethodType.Select, false)] [DataObjectMethod(DataObjectMethodType.Select, false)]
public async Task<IEnumerable<MetaItemModel>> Select() public async Task<IEnumerable<MetaItemModel>> Select()
{ => await _metaCollection.Find(new BsonDocument()).ToListAsync();
return await _metaCollection.Find(new BsonDocument()).ToListAsync();
}
[DataObjectMethod(DataObjectMethodType.Select, true)] [DataObjectMethod(DataObjectMethodType.Select, true)]
public async Task<IEnumerable<MetaItemModel>> SelectByType(MetaType type) public async Task<IEnumerable<MetaItemModel>> SelectByType(MetaType type)
@ -69,15 +68,16 @@ namespace BuildFeed.Model
[DataObjectMethod(DataObjectMethodType.Select, false)] [DataObjectMethod(DataObjectMethodType.Select, false)]
public async Task<MetaItemModel> SelectById(MetaItemKey id) public async Task<MetaItemModel> SelectById(MetaItemKey id)
{ {
return await _metaCollection.Find(f => f.Id.Type == id.Type && f.Id.Value == id.Value).SingleOrDefaultAsync(); return await _metaCollection.Find(f => f.Id.Type == id.Type && f.Id.Value == id.Value)
.SingleOrDefaultAsync();
} }
[DataObjectMethod(DataObjectMethodType.Select, false)] [DataObjectMethod(DataObjectMethodType.Select, false)]
public async Task<IEnumerable<string>> SelectUnusedLabs() public async Task<IEnumerable<string>> SelectUnusedLabs()
{ {
string[] labs = await _bModel.SelectAllLabs(); var labs = await _bModel.SelectAllLabs();
List<MetaItemModel> usedLabs = await _metaCollection.Find(f => f.Id.Type == MetaType.Lab).ToListAsync(); var usedLabs = await _metaCollection.Find(f => f.Id.Type == MetaType.Lab).ToListAsync();
return from l in labs return from l in labs
where usedLabs.All(ul => ul.Id.Value != l) where usedLabs.All(ul => ul.Id.Value != l)
@ -87,9 +87,9 @@ namespace BuildFeed.Model
[DataObjectMethod(DataObjectMethodType.Select, false)] [DataObjectMethod(DataObjectMethodType.Select, false)]
public async Task<IEnumerable<string>> SelectUnusedVersions() public async Task<IEnumerable<string>> SelectUnusedVersions()
{ {
BuildVersion[] versions = await _bModel.SelectAllVersions(); var versions = await _bModel.SelectAllVersions();
List<MetaItemModel> usedVersions = await _metaCollection.Find(f => f.Id.Type == MetaType.Version).ToListAsync(); var usedVersions = await _metaCollection.Find(f => f.Id.Type == MetaType.Version).ToListAsync();
return from v in versions return from v in versions
where usedVersions.All(ul => ul.Id.Value != v.ToString()) where usedVersions.All(ul => ul.Id.Value != v.ToString())
@ -99,15 +99,27 @@ namespace BuildFeed.Model
[DataObjectMethod(DataObjectMethodType.Select, false)] [DataObjectMethod(DataObjectMethodType.Select, false)]
public async Task<IEnumerable<string>> SelectUnusedYears() public async Task<IEnumerable<string>> SelectUnusedYears()
{ {
int[] years = await _bModel.SelectAllYears(); var years = await _bModel.SelectAllYears();
List<MetaItemModel> usedYears = await _metaCollection.Find(f => f.Id.Type == MetaType.Year).ToListAsync(); var usedYears = await _metaCollection.Find(f => f.Id.Type == MetaType.Year).ToListAsync();
return from y in years return from y in years
where usedYears.All(ul => ul.Id.Value != y.ToString()) where usedYears.All(ul => ul.Id.Value != y.ToString())
select y.ToString(); select y.ToString();
} }
[DataObjectMethod(DataObjectMethodType.Select, false)]
public async Task<IEnumerable<string>> SelectUnusedFamilies()
{
var families = await _bModel.SelectAllFamilies();
var usedFamilies = await _metaCollection.Find(f => f.Id.Type == MetaType.Family).ToListAsync();
return from y in families
where usedFamilies.All(ul => ul.Id.Value != y.ToString())
select y.ToString();
}
[DataObjectMethod(DataObjectMethodType.Insert, true)] [DataObjectMethod(DataObjectMethodType.Insert, true)]
public async Task Insert(MetaItemModel item) public async Task Insert(MetaItemModel item)
{ {
@ -144,15 +156,12 @@ namespace BuildFeed.Model
public MetaItemKey(string id) public MetaItemKey(string id)
{ {
string[] items = id.Split(':'); var items = id.Split(':');
Type = (MetaType)Enum.Parse(typeof(MetaType), items[0]); Type = (MetaType)Enum.Parse(typeof(MetaType), items[0]);
Value = items[1]; Value = items[1];
} }
public override string ToString() public override string ToString() => $"{Type}:{Value}";
{
return $"{Type}:{Value}";
}
} }
public enum MetaType public enum MetaType
@ -160,6 +169,7 @@ namespace BuildFeed.Model
Lab, Lab,
Version, Version,
Source, Source,
Year Year,
Family
} }
} }

View File

@ -20,9 +20,7 @@ namespace BuildFeed.Admin.Controllers
} }
[Route("")] [Route("")]
public async Task<ActionResult> Index() public async Task<ActionResult> Index() => View(new MetaListing
{
return View(new MetaListing
{ {
CurrentItems = from i in await _mModel.Select() CurrentItems = from i in await _mModel.Select()
group i by i.Id.Type group i by i.Id.Type
@ -46,7 +44,8 @@ namespace BuildFeed.Admin.Controllers
Type = MetaType.Version, Type = MetaType.Version,
Value = v Value = v
} }
}).Concat(from y in await _mModel.SelectUnusedYears() })
.Concat(from y in await _mModel.SelectUnusedYears()
select new MetaItemModel select new MetaItemModel
{ {
Id = new MetaItemKey Id = new MetaItemKey
@ -55,17 +54,23 @@ namespace BuildFeed.Admin.Controllers
Value = y Value = y
} }
}) })
.Concat(from y in await _mModel.SelectUnusedFamilies()
select new MetaItemModel
{
Id = new MetaItemKey
{
Type = MetaType.Family,
Value = y
}
})
group i by i.Id.Type group i by i.Id.Type
into b into b
orderby b.Key.ToString() orderby b.Key.ToString()
select b select b
}); });
}
[Route("create/{type}/{value}")] [Route("create/{type}/{value}")]
public ActionResult Create(MetaType type, string value) public ActionResult Create(MetaType type, string value) => View(new MetaItemModel
{
return View(new MetaItemModel
{ {
Id = new MetaItemKey Id = new MetaItemKey
{ {
@ -73,7 +78,6 @@ namespace BuildFeed.Admin.Controllers
Value = value Value = value
} }
}); });
}
[HttpPost] [HttpPost]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
@ -90,15 +94,12 @@ namespace BuildFeed.Admin.Controllers
} }
[Route("edit/{type}/{value}")] [Route("edit/{type}/{value}")]
public async Task<ActionResult> Edit(MetaType type, string value) public async Task<ActionResult> Edit(MetaType type, string value) => View(nameof(Create),
{
return View(nameof(Create),
await _mModel.SelectById(new MetaItemKey await _mModel.SelectById(new MetaItemKey
{ {
Type = type, Type = type,
Value = value Value = value
})); }));
}
[HttpPost] [HttpPost]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]

View File

@ -484,6 +484,7 @@
<Content Include="Views\account\validate-success.cshtml" /> <Content Include="Views\account\validate-success.cshtml" />
<Content Include="Views\account\validate-failure.cshtml" /> <Content Include="Views\account\validate-failure.cshtml" />
<None Include="tsconfig.json" /> <None Include="tsconfig.json" />
<Content Include="Views\front\ViewFamily.cshtml" />
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />
<ItemGroup> <ItemGroup>

View File

@ -69,6 +69,24 @@ namespace BuildFeed.Code
return result ?? o.ToString(); return result ?? o.ToString();
} }
public static string GetDisplayDescriptionForEnum(object o)
{
string result = null;
DisplayAttribute display = o.GetType()
.GetMember(o.ToString())
.First()
.GetCustomAttributes(false)
.OfType<DisplayAttribute>()
.LastOrDefault();
if (display != null)
{
result = display.GetDescription() ?? display.GetName();
}
return result ?? o.ToString();
}
public static string ToLongDateWithoutDay(this DateTime dt) public static string ToLongDateWithoutDay(this DateTime dt)
{ {
string s = CultureInfo.CurrentUICulture.DateTimeFormat.LongDatePattern; string s = CultureInfo.CurrentUICulture.DateTimeFormat.LongDatePattern;

View File

@ -32,31 +32,32 @@ namespace BuildFeed.Controllers
} }
[Route("", Order = 1)] [Route("", Order = 1)]
#if !DEBUG #if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")] [OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")]
[OutputCachePush(Order = 2)] [OutputCachePush(Order = 2)]
#endif #endif
public async Task<ActionResult> Index() public async Task<ActionResult> Index()
{ {
ViewBag.Versions = await _bModel.SelectAllVersions(); ViewBag.Versions = await _bModel.SelectAllFamilies();
ViewBag.Years = await _bModel.SelectAllYears(); ViewBag.Years = await _bModel.SelectAllYears();
ViewBag.Sources = await _bModel.SelectAllSources(); ViewBag.Sources = await _bModel.SelectAllSources();
Dictionary<ProjectFamily, FrontPage> items = await _bModel.SelectFrontPage(); var items = await _bModel.SelectFrontPage();
return View(nameof(Index), items); return View(nameof(Index), items);
} }
[Route("page-{page:int:min(1)}/", Order = 0)] [Route("page-{page:int:min(1)}/", Order = 0)]
#if !DEBUG #if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "page", VaryByCustom = "userName;lang;theme")] [OutputCache(Duration = 600, VaryByParam = "page", VaryByCustom = "userName;lang;theme")]
[OutputCachePush(Order = 2)] [OutputCachePush(Order = 2)]
#endif #endif
public async Task<ActionResult> IndexPage(int page) public async Task<ActionResult> IndexPage(int page)
{ {
FrontBuildGroup[] buildGroups = await _bModel.SelectAllGroups(PAGE_SIZE, (page - 1) * PAGE_SIZE); var buildGroups = await _bModel.SelectAllGroups(PAGE_SIZE, (page - 1) * PAGE_SIZE);
ViewBag.PageNumber = page; ViewBag.PageNumber = page;
ViewBag.PageCount = Math.Ceiling(Convert.ToDouble(await _bModel.SelectAllGroupsCount()) / Convert.ToDouble(PAGE_SIZE)); ViewBag.PageCount =
Math.Ceiling(Convert.ToDouble(await _bModel.SelectAllGroupsCount()) / Convert.ToDouble(PAGE_SIZE));
if (ViewBag.PageNumber > ViewBag.PageCount) if (ViewBag.PageNumber > ViewBag.PageCount)
{ {
@ -69,13 +70,13 @@ namespace BuildFeed.Controllers
[Route("group/{major}.{minor}.{number}.{revision}/", Order = 1)] [Route("group/{major}.{minor}.{number}.{revision}/", Order = 1)]
[Route("group/{major}.{minor}.{number}/", Order = 5)] [Route("group/{major}.{minor}.{number}/", Order = 5)]
// for when there is no revision // for when there is no revision
#if !DEBUG #if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")] [OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")]
[OutputCachePush(Order = 2)] [OutputCachePush(Order = 2)]
#endif #endif
public async Task<ActionResult> ViewGroup(uint major, uint minor, uint number, uint? revision = null) public async Task<ActionResult> ViewGroup(uint major, uint minor, uint number, uint? revision = null)
{ {
BuildGroup bg = new BuildGroup var bg = new BuildGroup
{ {
Major = major, Major = major,
Minor = minor, Minor = minor,
@ -83,7 +84,7 @@ namespace BuildFeed.Controllers
Revision = revision Revision = revision
}; };
List<Build> builds = await _bModel.SelectGroup(bg); var builds = await _bModel.SelectGroup(bg);
return builds.Count == 1 return builds.Count == 1
? RedirectToAction(nameof(ViewBuild), ? RedirectToAction(nameof(ViewBuild),
@ -95,10 +96,10 @@ namespace BuildFeed.Controllers
} }
[Route("build/{id:guid}/", Name = "Build")] [Route("build/{id:guid}/", Name = "Build")]
#if !DEBUG #if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")] [OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")]
[OutputCachePush(Order = 2)] [OutputCachePush(Order = 2)]
#endif #endif
public async Task<ActionResult> ViewBuild(Guid id) public async Task<ActionResult> ViewBuild(Guid id)
{ {
Build b = await _bModel.SelectById(id); Build b = await _bModel.SelectById(id);
@ -127,10 +128,10 @@ namespace BuildFeed.Controllers
} }
[Route("twitter/{id:guid}/", Name = "Twitter")] [Route("twitter/{id:guid}/", Name = "Twitter")]
#if !DEBUG #if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")] [OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")]
[CustomContentType(ContentType = "image/png", Order = 2)] [CustomContentType(ContentType = "image/png", Order = 2)]
#endif #endif
public async Task<ActionResult> TwitterCard(Guid id) public async Task<ActionResult> TwitterCard(Guid id)
{ {
Build b = await _bModel.SelectById(id); Build b = await _bModel.SelectById(id);
@ -161,11 +162,16 @@ namespace BuildFeed.Controllers
} }
int left = 40; int left = 40;
using (GraphicsPath gp = new GraphicsPath()) using (var gp = new GraphicsPath())
{ {
foreach (char c in "BUILDFEED") foreach (char c in "BUILDFEED")
{ {
gp.AddString(c.ToString(), new FontFamily("Segoe UI Semibold"), 0, 32, new Point(left, 32), StringFormat.GenericTypographic); gp.AddString(c.ToString(),
new FontFamily("Segoe UI Semibold"),
0,
32,
new Point(left, 32),
StringFormat.GenericTypographic);
RectangleF bounds = gp.GetBounds(); RectangleF bounds = gp.GetBounds();
left = Convert.ToInt32(bounds.Width); left = Convert.ToInt32(bounds.Width);
@ -175,9 +181,14 @@ namespace BuildFeed.Controllers
gr.FillPath(Brushes.White, gp); gr.FillPath(Brushes.White, gp);
} }
using (GraphicsPath gp = new GraphicsPath()) using (var gp = new GraphicsPath())
{ {
gp.AddString(b.Number.ToString(), new FontFamily("Segoe UI Light"), 0, 260, new Point(32, 114), StringFormat.GenericTypographic); gp.AddString(b.Number.ToString(),
new FontFamily("Segoe UI Light"),
0,
260,
new Point(32, 114),
StringFormat.GenericTypographic);
RectangleF bounds = gp.GetBounds(); RectangleF bounds = gp.GetBounds();
left = Convert.ToInt32(bounds.Width); left = Convert.ToInt32(bounds.Width);
@ -185,24 +196,56 @@ namespace BuildFeed.Controllers
if (b.Revision.HasValue) if (b.Revision.HasValue)
{ {
gp.AddString($".{b.Revision}", new FontFamily("Segoe UI Light"), 0, 160, new Point(left, 220), StringFormat.GenericTypographic); gp.AddString($".{b.Revision}",
new FontFamily("Segoe UI Light"),
0,
160,
new Point(left, 220),
StringFormat.GenericTypographic);
} }
gr.DrawPath(new Pen(new SolidBrush(Color.FromArgb(0x24, 0x24, 0x23)), 4), gp); gr.DrawPath(new Pen(new SolidBrush(Color.FromArgb(0x24, 0x24, 0x23)), 4), gp);
gr.FillPath(Brushes.White, gp); gr.FillPath(Brushes.White, gp);
} }
using (GraphicsPath gp = new GraphicsPath()) using (var gp = new GraphicsPath())
{ {
gp.AddString($"{MvcExtensions.GetDisplayTextForEnum(b.Family)} (NT {b.MajorVersion}.{b.MinorVersion})", new FontFamily("Segoe UI Light"), 0, 48, new Point(40, 80), StringFormat.GenericTypographic); gp.AddString(
$"{MvcExtensions.GetDisplayTextForEnum(b.Family)} (NT {b.MajorVersion}.{b.MinorVersion})",
new FontFamily("Segoe UI Light"),
0,
48,
new Point(40, 80),
StringFormat.GenericTypographic);
gp.AddString(char.ConvertFromUtf32(0xf126), new FontFamily("FontAwesome"), 0, 28, new Point(46, 468), StringFormat.GenericTypographic); gp.AddString(char.ConvertFromUtf32(0xf126),
gp.AddString(b.Lab, new FontFamily("Segoe UI Light"), 0, 40, new Point(88, 450), StringFormat.GenericTypographic); new FontFamily("FontAwesome"),
0,
28,
new Point(46, 468),
StringFormat.GenericTypographic);
gp.AddString(b.Lab,
new FontFamily("Segoe UI Light"),
0,
40,
new Point(88, 450),
StringFormat.GenericTypographic);
if (b.BuildTime.HasValue) if (b.BuildTime.HasValue)
{ {
gp.AddString(char.ConvertFromUtf32(0xf017), new FontFamily("FontAwesome"), 0, 28, new Point(40, 538), StringFormat.GenericTypographic); gp.AddString(char.ConvertFromUtf32(0xf017),
gp.AddString($"{b.BuildTime.Value.ToShortTimeString()} on {b.BuildTime.Value.ToLongDateString()}", new FontFamily("Segoe UI Light"), 0, 40, new Point(88, 520), StringFormat.GenericTypographic); new FontFamily("FontAwesome"),
0,
28,
new Point(40, 538),
StringFormat.GenericTypographic);
gp.AddString(
$"{b.BuildTime.Value.ToShortTimeString()} on {b.BuildTime.Value.ToLongDateString()}",
new FontFamily("Segoe UI Light"),
0,
40,
new Point(88, 520),
StringFormat.GenericTypographic);
} }
gr.FillPath(Brushes.White, gp); gr.FillPath(Brushes.White, gp);
@ -232,21 +275,59 @@ namespace BuildFeed.Controllers
}); });
} }
[Route("lab/{lab}/", Order = 1, Name = "Lab Root")] [Route("family/{family}/", Order = 1, Name = "Family Root")]
#if !DEBUG #if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")] [OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")]
[OutputCachePush(Order = 2)] [OutputCachePush(Order = 2)]
#endif #endif
public async Task<ActionResult> ViewFamily(ProjectFamily family)
{
return await ViewFamilyPage(family, 1);
}
[Route("family/{family}/page-{page:int:min(2)}/", Order = 0)]
#if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "page", VaryByCustom = "userName;lang;theme")]
[OutputCachePush(Order = 2)]
#endif
public async Task<ActionResult> ViewFamilyPage(ProjectFamily family, int page)
{
ViewBag.MetaItem = await _mModel.SelectById(new MetaItemKey
{
Type = MetaType.Family,
Value = family.ToString()
});
ViewBag.ItemId = MvcExtensions.GetDisplayDescriptionForEnum(family);
var builds = await _bModel.SelectFamily(family, PAGE_SIZE, (page - 1) * PAGE_SIZE);
ViewBag.PageNumber = page;
ViewBag.PageCount =
Math.Ceiling(Convert.ToDouble(await _bModel.SelectFamilyCount(family)) / Convert.ToDouble(PAGE_SIZE));
if (ViewBag.PageNumber > ViewBag.PageCount)
{
return new HttpNotFoundResult();
}
return View("viewFamily", builds);
}
[Route("lab/{lab}/", Order = 1, Name = "Lab Root")]
#if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")]
[OutputCachePush(Order = 2)]
#endif
public async Task<ActionResult> ViewLab(string lab) public async Task<ActionResult> ViewLab(string lab)
{ {
return await ViewLabPage(lab, 1); return await ViewLabPage(lab, 1);
} }
[Route("lab/{lab}/page-{page:int:min(2)}/", Order = 0)] [Route("lab/{lab}/page-{page:int:min(2)}/", Order = 0)]
#if !DEBUG #if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "page", VaryByCustom = "userName;lang;theme")] [OutputCache(Duration = 600, VaryByParam = "page", VaryByCustom = "userName;lang;theme")]
[OutputCachePush(Order = 2)] [OutputCachePush(Order = 2)]
#endif #endif
public async Task<ActionResult> ViewLabPage(string lab, int page) public async Task<ActionResult> ViewLabPage(string lab, int page)
{ {
ViewBag.MetaItem = await _mModel.SelectById(new MetaItemKey ViewBag.MetaItem = await _mModel.SelectById(new MetaItemKey
@ -255,11 +336,12 @@ namespace BuildFeed.Controllers
Value = lab Value = lab
}); });
List<Build> builds = await _bModel.SelectLab(lab, PAGE_SIZE, (page - 1) * PAGE_SIZE); var builds = await _bModel.SelectLab(lab, PAGE_SIZE, (page - 1) * PAGE_SIZE);
ViewBag.ItemId = builds.FirstOrDefault()?.Lab; ViewBag.ItemId = builds.FirstOrDefault()?.Lab;
ViewBag.PageNumber = page; ViewBag.PageNumber = page;
ViewBag.PageCount = Math.Ceiling(Convert.ToDouble(await _bModel.SelectLabCount(lab)) / Convert.ToDouble(PAGE_SIZE)); ViewBag.PageCount =
Math.Ceiling(Convert.ToDouble(await _bModel.SelectLabCount(lab)) / Convert.ToDouble(PAGE_SIZE));
if (ViewBag.PageNumber > ViewBag.PageCount) if (ViewBag.PageNumber > ViewBag.PageCount)
{ {
@ -270,20 +352,20 @@ namespace BuildFeed.Controllers
} }
[Route("source/{source}/", Order = 1, Name = "Source Root")] [Route("source/{source}/", Order = 1, Name = "Source Root")]
#if !DEBUG #if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")] [OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")]
[OutputCachePush(Order = 2)] [OutputCachePush(Order = 2)]
#endif #endif
public async Task<ActionResult> ViewSource(TypeOfSource source) public async Task<ActionResult> ViewSource(TypeOfSource source)
{ {
return await ViewSourcePage(source, 1); return await ViewSourcePage(source, 1);
} }
[Route("source/{source}/page-{page:int:min(2)}/", Order = 0)] [Route("source/{source}/page-{page:int:min(2)}/", Order = 0)]
#if !DEBUG #if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "page", VaryByCustom = "userName;lang;theme")] [OutputCache(Duration = 600, VaryByParam = "page", VaryByCustom = "userName;lang;theme")]
[OutputCachePush(Order = 2)] [OutputCachePush(Order = 2)]
#endif #endif
public async Task<ActionResult> ViewSourcePage(TypeOfSource source, int page) public async Task<ActionResult> ViewSourcePage(TypeOfSource source, int page)
{ {
ViewBag.MetaItem = await _mModel.SelectById(new MetaItemKey ViewBag.MetaItem = await _mModel.SelectById(new MetaItemKey
@ -293,10 +375,11 @@ namespace BuildFeed.Controllers
}); });
ViewBag.ItemId = MvcExtensions.GetDisplayTextForEnum(source); ViewBag.ItemId = MvcExtensions.GetDisplayTextForEnum(source);
List<Build> builds = await _bModel.SelectSource(source, PAGE_SIZE, (page - 1) * PAGE_SIZE); var builds = await _bModel.SelectSource(source, PAGE_SIZE, (page - 1) * PAGE_SIZE);
ViewBag.PageNumber = page; ViewBag.PageNumber = page;
ViewBag.PageCount = Math.Ceiling(Convert.ToDouble(await _bModel.SelectSourceCount(source)) / Convert.ToDouble(PAGE_SIZE)); ViewBag.PageCount = Math.Ceiling(Convert.ToDouble(await _bModel.SelectSourceCount(source))
/ Convert.ToDouble(PAGE_SIZE));
if (ViewBag.PageNumber > ViewBag.PageCount) if (ViewBag.PageNumber > ViewBag.PageCount)
{ {
@ -307,20 +390,20 @@ namespace BuildFeed.Controllers
} }
[Route("year/{year}/", Order = 1, Name = "Year Root")] [Route("year/{year}/", Order = 1, Name = "Year Root")]
#if !DEBUG #if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")] [OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")]
[OutputCachePush(Order = 2)] [OutputCachePush(Order = 2)]
#endif #endif
public async Task<ActionResult> ViewYear(int year) public async Task<ActionResult> ViewYear(int year)
{ {
return await ViewYearPage(year, 1); return await ViewYearPage(year, 1);
} }
[Route("year/{year}/page-{page:int:min(2)}/", Order = 0)] [Route("year/{year}/page-{page:int:min(2)}/", Order = 0)]
#if !DEBUG #if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "page", VaryByCustom = "userName;lang;theme")] [OutputCache(Duration = 600, VaryByParam = "page", VaryByCustom = "userName;lang;theme")]
[OutputCachePush(Order = 2)] [OutputCachePush(Order = 2)]
#endif #endif
public async Task<ActionResult> ViewYearPage(int year, int page) public async Task<ActionResult> ViewYearPage(int year, int page)
{ {
ViewBag.MetaItem = await _mModel.SelectById(new MetaItemKey ViewBag.MetaItem = await _mModel.SelectById(new MetaItemKey
@ -330,7 +413,7 @@ namespace BuildFeed.Controllers
}); });
ViewBag.ItemId = year.ToString(); ViewBag.ItemId = year.ToString();
List<Build> builds = await _bModel.SelectYear(year, PAGE_SIZE, (page - 1) * PAGE_SIZE); var builds = await _bModel.SelectYear(year, PAGE_SIZE, (page - 1) * PAGE_SIZE);
ViewBag.PageNumber = page; ViewBag.PageNumber = page;
ViewBag.PageCount = Math.Ceiling(await _bModel.SelectYearCount(year) / Convert.ToDouble(PAGE_SIZE)); ViewBag.PageCount = Math.Ceiling(await _bModel.SelectYearCount(year) / Convert.ToDouble(PAGE_SIZE));
@ -344,20 +427,20 @@ namespace BuildFeed.Controllers
} }
[Route("version/{major}.{minor}/", Order = 1, Name = "Version Root")] [Route("version/{major}.{minor}/", Order = 1, Name = "Version Root")]
#if !DEBUG #if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")] [OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")]
[OutputCachePush(Order = 2)] [OutputCachePush(Order = 2)]
#endif #endif
public async Task<ActionResult> ViewVersion(uint major, uint minor) public async Task<ActionResult> ViewVersion(uint major, uint minor)
{ {
return await ViewVersionPage(major, minor, 1); return await ViewVersionPage(major, minor, 1);
} }
[Route("version/{major}.{minor}/page-{page:int:min(2)}/", Order = 0)] [Route("version/{major}.{minor}/page-{page:int:min(2)}/", Order = 0)]
#if !DEBUG #if !DEBUG
[OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")] [OutputCache(Duration = 600, VaryByParam = "none", VaryByCustom = "userName;lang;theme")]
[OutputCachePush(Order = 2)] [OutputCachePush(Order = 2)]
#endif #endif
public async Task<ActionResult> ViewVersionPage(uint major, uint minor, int page) public async Task<ActionResult> ViewVersionPage(uint major, uint minor, int page)
{ {
string valueString = $"{major}.{minor}"; string valueString = $"{major}.{minor}";
@ -368,10 +451,11 @@ namespace BuildFeed.Controllers
}); });
ViewBag.ItemId = valueString; ViewBag.ItemId = valueString;
List<Build> builds = await _bModel.SelectVersion(major, minor, PAGE_SIZE, (page - 1) * PAGE_SIZE); var builds = await _bModel.SelectVersion(major, minor, PAGE_SIZE, (page - 1) * PAGE_SIZE);
ViewBag.PageNumber = page; ViewBag.PageNumber = page;
ViewBag.PageCount = Math.Ceiling(Convert.ToDouble(await _bModel.SelectVersionCount(major, minor)) / Convert.ToDouble(PAGE_SIZE)); ViewBag.PageCount = Math.Ceiling(Convert.ToDouble(await _bModel.SelectVersionCount(major, minor))
/ Convert.ToDouble(PAGE_SIZE));
if (ViewBag.PageNumber > ViewBag.PageCount) if (ViewBag.PageNumber > ViewBag.PageCount)
{ {
@ -385,7 +469,7 @@ namespace BuildFeed.Controllers
[Authorize(Roles = "Administrators,Editors")] [Authorize(Roles = "Administrators,Editors")]
public ActionResult AddBuild() public ActionResult AddBuild()
{ {
Build b = new Build var b = new Build
{ {
SourceType = TypeOfSource.PrivateLeak SourceType = TypeOfSource.PrivateLeak
}; };
@ -417,7 +501,7 @@ namespace BuildFeed.Controllers
build.RegenerateCachedProperties(); build.RegenerateCachedProperties();
BuildDetails bi = new BuildDetails var bi = new BuildDetails
{ {
MajorVersion = build.MajorVersion, MajorVersion = build.MajorVersion,
MinorVersion = build.MinorVersion, MinorVersion = build.MinorVersion,
@ -449,8 +533,9 @@ namespace BuildFeed.Controllers
return View("EditBuild", build); return View("EditBuild", build);
} }
OneSignalClient osc = new OneSignalClient(ConfigurationManager.AppSettings["push:OneSignalApiKey"]); var osc = new OneSignalClient(ConfigurationManager.AppSettings["push:OneSignalApiKey"]);
osc.PushNewBuild(build, $"https://buildfeed.net{Url.Action(nameof(ViewBuild), new { id = build.Id })}?utm_source=notification&utm_campaign=new_build"); osc.PushNewBuild(build,
$"https://buildfeed.net{Url.Action(nameof(ViewBuild), new { id = build.Id })}?utm_source=notification&utm_campaign=new_build");
return RedirectToAction(nameof(ViewBuild), return RedirectToAction(nameof(ViewBuild),
new new
@ -458,15 +543,13 @@ namespace BuildFeed.Controllers
id = build.Id id = build.Id
}); });
} }
return View("EditBuild", build); return View("EditBuild", build);
} }
[Route("bulk/")] [Route("bulk/")]
[Authorize(Roles = "Administrators")] [Authorize(Roles = "Administrators")]
public ActionResult AddBulk() public ActionResult AddBulk() => View();
{
return View();
}
[Route("bulk/")] [Route("bulk/")]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
@ -474,7 +557,7 @@ namespace BuildFeed.Controllers
[HttpPost] [HttpPost]
public async Task<ActionResult> AddBulk(FormCollection values) public async Task<ActionResult> AddBulk(FormCollection values)
{ {
OneSignalClient osc = new OneSignalClient(ConfigurationManager.AppSettings["push:OneSignalApiKey"]); var osc = new OneSignalClient(ConfigurationManager.AppSettings["push:OneSignalApiKey"]);
var success = new List<Build>(); var success = new List<Build>();
var failed = new List<string>(); var failed = new List<string>();
bool notify = bool.Parse(values[nameof(BulkAddition.SendNotifications)].Split(',')[0]); bool notify = bool.Parse(values[nameof(BulkAddition.SendNotifications)].Split(',')[0]);
@ -487,12 +570,13 @@ namespace BuildFeed.Controllers
}, },
StringSplitOptions.RemoveEmptyEntries)) StringSplitOptions.RemoveEmptyEntries))
{ {
Match m = Regex.Match(line, @"(([\d]{1,2})\.([\d]{1,2})\.)?([\d]{4,5})(\.([\d]{1,5}))?(\.| \()([a-zA-Z][a-zA-Z0-9._\(\)-]+?)\.(\d\d\d\d\d\d-\d\d\d\d)\)?"); Match m = Regex.Match(line,
@"(([\d]{1,2})\.([\d]{1,2})\.)?([\d]{4,5})(\.([\d]{1,5}))?(\.| \()([a-zA-Z][a-zA-Z0-9._\(\)-]+?)\.(\d\d\d\d\d\d-\d\d\d\d)\)?");
if (m.Success) if (m.Success)
{ {
try try
{ {
Build b = new Build var b = new Build
{ {
MajorVersion = uint.Parse(m.Groups[2].Value), MajorVersion = uint.Parse(m.Groups[2].Value),
MinorVersion = uint.Parse(m.Groups[3].Value), MinorVersion = uint.Parse(m.Groups[3].Value),
@ -503,14 +587,17 @@ namespace BuildFeed.Controllers
Lab = m.Groups[8].Value, Lab = m.Groups[8].Value,
BuildTime = string.IsNullOrEmpty(m.Groups[9].Value) BuildTime = string.IsNullOrEmpty(m.Groups[9].Value)
? null ? null
: DateTime.SpecifyKind(DateTime.ParseExact(m.Groups[9].Value, "yyMMdd-HHmm", CultureInfo.CurrentCulture.DateTimeFormat), DateTimeKind.Utc) as DateTime?, : DateTime.SpecifyKind(DateTime.ParseExact(m.Groups[9].Value,
"yyMMdd-HHmm",
CultureInfo.CurrentCulture.DateTimeFormat),
DateTimeKind.Utc) as DateTime?,
Added = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc), Added = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc),
Modified = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc), Modified = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc),
SourceType = TypeOfSource.PrivateLeak SourceType = TypeOfSource.PrivateLeak
}; };
b.RegenerateCachedProperties(); b.RegenerateCachedProperties();
BuildDetails bi = new BuildDetails var bi = new BuildDetails
{ {
MajorVersion = b.MajorVersion, MajorVersion = b.MajorVersion,
MinorVersion = b.MinorVersion, MinorVersion = b.MinorVersion,
@ -544,7 +631,8 @@ namespace BuildFeed.Controllers
if (notify) if (notify)
{ {
osc.PushNewBuild(b, $"https://buildfeed.net{Url.Action(nameof(ViewBuild), new { id = b.Id })}?utm_source=notification&utm_campaign=new_build"); osc.PushNewBuild(b,
$"https://buildfeed.net{Url.Action(nameof(ViewBuild), new { id = b.Id })}?utm_source=notification&utm_campaign=new_build");
} }
success.Add(b); success.Add(b);
@ -599,7 +687,7 @@ namespace BuildFeed.Controllers
build.LeakDate = DateTime.SpecifyKind(build.LeakDate.Value, DateTimeKind.Utc); build.LeakDate = DateTime.SpecifyKind(build.LeakDate.Value, DateTimeKind.Utc);
} }
BuildDetails bi = new BuildDetails var bi = new BuildDetails
{ {
MajorVersion = build.MajorVersion, MajorVersion = build.MajorVersion,
MinorVersion = build.MinorVersion, MinorVersion = build.MinorVersion,
@ -640,6 +728,7 @@ namespace BuildFeed.Controllers
id = build.Id id = build.Id
}); });
} }
return View(build); return View(build);
} }

View File

@ -0,0 +1,107 @@
@using BuildFeed.Code
@using BuildFeed.Controllers
@using BuildFeed.Model
@using Humanizer
@model IEnumerable<Build>
@{
ViewBag.Title = string.Format("{0} {1} | {2}", ViewBag.ItemId, ViewBag.PageNumber == 1
? ""
: string.Format(VariantTerms.Common_TitlePage, ViewBag.PageNumber), InvariantTerms.SiteName);
}
@section head
{
@if (ViewBag.MetaItem != null)
{
<meta name="description" content="@ViewBag.MetaItem.MetaDescription" />
<meta property="og:description" content="@ViewBag.MetaItem.MetaDescription" />
}
@if (ViewBag.PageNumber != 1)
{
<meta name="robots" content="noindex, follow" />
}
}
<h1>@ViewBag.ItemId</h1>
@if (ViewBag.MetaItem != null && !string.IsNullOrWhiteSpace(ViewBag.MetaItem.PageContent))
{
<h3>@VariantTerms.Front_About</h3>
@Html.Raw(ViewBag.MetaItem.PageContent)
}
<h3>@VariantTerms.Front_Share</h3>
<div class="addthis_sharing_toolbox"></div>
<br />
<h3>@VariantTerms.Front_Listing</h3>
<div class="build-group-listing">
@foreach (Build build in Model)
{
<div class="build-group">
<h3 class="build-group-title" title="@build.AlternateBuildString">
<a href="@Url.Action("ViewBuild", new
{
id = build.Id
})">
@($"{build.MajorVersion}.{build.MinorVersion}.{build.Number}.{build.Revision}")
</a>
</h3>
@if (!string.IsNullOrEmpty(build.Lab))
{
<p class="no-wrapping build-group-p" title="@build.Lab">
<i class="fa fa-code-fork fa-fw"></i> @build.Lab</p>
}
@if (build.BuildTime.HasValue)
{
<p class="build-group-p">
<span title="@build.BuildTime.Value.Humanize()">
<i class="fa fa-calendar fa-fw"></i> @build.BuildTime.Value.ToLongDateWithoutDay()</span>
</p>
<p class="build-group-p">
<span title="@build.BuildTime.Value.Humanize()">
<i class="fa fa-clock-o fa-fw"></i> @build.BuildTime.Value.ToShortTimeString()</span>
</p>
}
@if (build.IsLeaked)
{
<p class="build-group-p">
<span>
<i class="fa fa-unlock-alt fa-fw"></i> @VariantTerms.Front_Public</span>
</p>
}
else
{
<p class="build-group-p">
<span>
<i class="fa fa-lock fa-fw"></i> @VariantTerms.Front_Private</span>
</p>
}
@if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators"))
{
<p>
<a href="@Url.Action(nameof(FrontController.EditBuild), new
{
id = build.Id
})" class="button edit-button">
@VariantTerms.Front_Edit
</a>
&nbsp;
@if (Roles.IsUserInRole("Administrators"))
{
<a href="@Url.Action(nameof(FrontController.DeleteBuild), new
{
id = build.Id
})" class="button delete-button">
@VariantTerms.Front_Delete
</a>
}
</p>
}
</div>
}
<div class="build-group-empty"></div>
<div class="build-group-empty"></div>
<div class="build-group-empty"></div>
<div class="build-group-empty"></div>
<div class="build-group-empty"></div>
</div>
@PaginationHelpers.PaginationBlock((int)ViewBag.PageNumber, (int)ViewBag.PageCount, "ViewFamily", ViewContext.RouteData.Values)

View File

@ -102,15 +102,14 @@
<div class="group-input-button"> <div class="group-input-button">
<select id="search-version"> <select id="search-version">
@{ @{
BuildVersion[] versions = ViewBag.Versions; ProjectFamily[] versions = ViewBag.Versions;
foreach (BuildVersion version in versions) foreach (ProjectFamily version in versions)
{ {
<option value="@Url.Action(nameof(FrontController.ViewVersion), "Front", new <option value="@Url.Action(nameof(FrontController.ViewFamily), "Front", new
{ {
major = version.Major, family = version
minor = version.Minor
})"> })">
@version.ToString() @MvcExtensions.GetDisplayDescriptionForEnum(version)
</option> </option>
} }
} }