From bc24b136ad8098eeb9477e598b978b3dc2c15134 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 13 Jun 2016 11:38:55 +0100 Subject: [PATCH 1/5] Corrected typo in `PackageDetails` class name --- OurUmbraco/OurUmbraco.csproj | 2 +- .../Controllers/PackageRepositoryController.cs | 3 +++ .../{PacakgeDetails.cs => PackageDetails.cs} | 18 +++++++++++++----- 3 files changed, 17 insertions(+), 6 deletions(-) rename OurUmbraco/Repository/Models/{PacakgeDetails.cs => PackageDetails.cs} (73%) diff --git a/OurUmbraco/OurUmbraco.csproj b/OurUmbraco/OurUmbraco.csproj index 8c2c9a0d..848098cc 100644 --- a/OurUmbraco/OurUmbraco.csproj +++ b/OurUmbraco/OurUmbraco.csproj @@ -624,7 +624,7 @@ - + diff --git a/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs b/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs index a86baba0..e1be5bb9 100644 --- a/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs +++ b/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs @@ -219,6 +219,9 @@ namespace OurUmbraco.Repository.Controllers public IEnumerable GetCategories() { + // [LK:2016-06-13@CGRT16] We're hardcoding the categories as the 'icon' isn't + // content-manageable (yet). There is a media-picker icon, but that's for a different use. + // When the time comes, we can switch to query for the Category nodes directly. return new[] { new Models.Category diff --git a/OurUmbraco/Repository/Models/PacakgeDetails.cs b/OurUmbraco/Repository/Models/PackageDetails.cs similarity index 73% rename from OurUmbraco/Repository/Models/PacakgeDetails.cs rename to OurUmbraco/Repository/Models/PackageDetails.cs index 55bfd0a5..61940a90 100644 --- a/OurUmbraco/Repository/Models/PacakgeDetails.cs +++ b/OurUmbraco/Repository/Models/PackageDetails.cs @@ -1,24 +1,32 @@ using System; using System.Collections.Generic; -using System.Runtime.Serialization; namespace OurUmbraco.Repository.Models { - public class PacakgeDetails : Package + public class PackageDetails : Package { - public PacakgeDetails() + public PackageDetails() { - Images = new List(); + Images = new List(); Compatibility = new List(); ExternalSources = new List(); } + public string Description { get; set; } + public List Images { get; set; } + public List Compatibility { get; set; } + public List ExternalSources { get; set; } + + public string LicenseName { get; set; } + public string LicenseUrl { get; set; } - public string NetVersion { get; set; } + + public string NetVersion { get; set; } + public string ZipUrl { get; set; } } } \ No newline at end of file From 060fb89496c11137c3bdb56788fe0aab3ca02269 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 13 Jun 2016 11:39:25 +0100 Subject: [PATCH 2/5] Minor whitespace --- OurUmbraco/Repository/RepoStartupHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OurUmbraco/Repository/RepoStartupHandler.cs b/OurUmbraco/Repository/RepoStartupHandler.cs index 946e538f..825aa0e0 100644 --- a/OurUmbraco/Repository/RepoStartupHandler.cs +++ b/OurUmbraco/Repository/RepoStartupHandler.cs @@ -11,7 +11,7 @@ namespace OurUmbraco.Repository RouteTable.Routes.MapHttpRoute( name: "PackageRepositoryApi", routeTemplate: "webapi/packages/v1/{id}", - defaults: new { id = RouteParameter.Optional, controller = "PackageRepository"}, + defaults: new { id = RouteParameter.Optional, controller = "PackageRepository", action = "GetPopular" }, constraints: new { } ); From e78de056f237dc66d6a47908f9bfb5a1d00bf381 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 13 Jun 2016 12:11:21 +0100 Subject: [PATCH 3/5] Added package repo "Service". Amended legacy `Util` and `WikiFile` code to accommodate newer queries. --- OurUmbraco/Our/Utils.cs | 27 +- OurUmbraco/OurUmbraco.csproj | 1 + .../PackageRepositoryController.cs | 12 +- .../Repository/PackageRepositoryService.cs | 233 ++++++++++++++++++ OurUmbraco/Repository/RepoStartupHandler.cs | 2 +- OurUmbraco/Wiki/BusinessLogic/WikiFile.cs | 5 + 6 files changed, 266 insertions(+), 14 deletions(-) create mode 100644 OurUmbraco/Repository/PackageRepositoryService.cs diff --git a/OurUmbraco/Our/Utils.cs b/OurUmbraco/Our/Utils.cs index ee8ec079..dfa9ee7e 100644 --- a/OurUmbraco/Our/Utils.cs +++ b/OurUmbraco/Our/Utils.cs @@ -214,13 +214,17 @@ namespace OurUmbraco.Our Data.SqlHelper.CreateParameter("@memberId", memberId)) > 0)); } - public static string GetMemberAvatar(IPublishedContent member, int avatarSize) + public static string GetMemberAvatar(IPublishedContent member, int avatarSize, bool getRawUrl = false) { var memberAvatarPath = MemberAvatarPath(member); if (string.IsNullOrWhiteSpace(memberAvatarPath) == false) - return MemberAvatarPath(member) == string.Empty ? GetGravatar(member.GetPropertyValue("Email").ToString(), avatarSize, member.Name) : GetLocalAvatar(member.GetPropertyValue("avatar").ToString(), avatarSize, member.Name); + { + return MemberAvatarPath(member) == string.Empty + ? GetGravatar(member.GetPropertyValue("Email").ToString(), avatarSize, member.Name, getRawUrl) + : GetLocalAvatar(member.GetPropertyValue("avatar").ToString(), avatarSize, member.Name, getRawUrl); + } - return GetGravatar(member.GetPropertyValue("Email").ToString(), avatarSize, member.Name); + return GetGravatar(member.GetPropertyValue("Email").ToString(), avatarSize, member.Name, getRawUrl); } private static string MemberAvatarPath(IPublishedContent member) @@ -288,18 +292,25 @@ namespace OurUmbraco.Our return null; } - public static string GetGravatar(string email, int size, string memberName) + public static string GetGravatar(string email, int size, string memberName, bool getRawUrl = false) { var emailId = email.ToLower(); var hash = FormsAuthentication.HashPasswordForStoringInConfigFile(emailId, "MD5").ToLower(); - return string.Format("\"{2}\"", hash, size, memberName); + var url = string.Format("https://www.gravatar.com/avatar/{0}?s={1}&d=mm&r=g&d=retro", hash, size); + + return !getRawUrl + ? string.Format("\"{1}\"", url, memberName) + : url; } - public static string GetLocalAvatar(string imgPath, int minSize, string memberName) + public static string GetLocalAvatar(string imgPath, int minSize, string memberName, bool getRawUrl = false) { - return string.Format("\"{4}\"", - imgPath.Replace(" ", "%20"), minSize, (minSize * 2), (minSize * 3), memberName); + var url = string.Format("{0}?width={1}&height={1}&mode=crop", imgPath.Replace(" ", "%20"), minSize); + + return !getRawUrl + ? string.Format("\"{4}\"", imgPath.Replace(" ", "%20"), minSize, (minSize * 2), (minSize * 3), memberName) + : url; } } diff --git a/OurUmbraco/OurUmbraco.csproj b/OurUmbraco/OurUmbraco.csproj index 848098cc..e1645f58 100644 --- a/OurUmbraco/OurUmbraco.csproj +++ b/OurUmbraco/OurUmbraco.csproj @@ -630,6 +630,7 @@ + diff --git a/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs b/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs index e1be5bb9..7a628cd1 100644 --- a/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs +++ b/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs @@ -19,12 +19,12 @@ namespace OurUmbraco.Repository.Controllers /// [EnableCors(origins: "*", headers: "*", methods: "*")] [JsonCamelCaseFormatter] - public class PackageRepositoryController : ApiController + public class PackageRepositoryController : UmbracoApiControllerBase { - private Models.PacakgeDetails GetTestDetails() + private Models.PackageDetails GetTestDetails() { - return new Models.PacakgeDetails + return new Models.PackageDetails { Category = "Collaboration", Excerpt = "You will make me be president", @@ -293,9 +293,11 @@ namespace OurUmbraco.Repository.Controllers }; } - public Models.PacakgeDetails GetDetails(Guid id) + public Models.PackageDetails GetDetails(Guid id) { - return GetTestDetails(); + var service = new PackageRepositoryService(Umbraco, Members, DatabaseContext); + + return service.GetDetails(id); } } diff --git a/OurUmbraco/Repository/PackageRepositoryService.cs b/OurUmbraco/Repository/PackageRepositoryService.cs new file mode 100644 index 00000000..9ef3de0f --- /dev/null +++ b/OurUmbraco/Repository/PackageRepositoryService.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using OurUmbraco.Forum.Extensions; +using OurUmbraco.Our; +using OurUmbraco.Project.Services; +using OurUmbraco.Repository.Models; +using OurUmbraco.Wiki.BusinessLogic; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web; +using Umbraco.Web.Security; + +namespace OurUmbraco.Repository +{ + internal class PackageRepositoryService + { + private DatabaseContext DatabaseContext; + private MembershipHelper MembershipHelper; + private UmbracoHelper UmbracoHelper; + + const string BASE_URL = "https://our.umbraco.org"; + + public PackageRepositoryService(UmbracoHelper umbracoHelper, MembershipHelper membershipHelper, DatabaseContext databaseContext) + { + UmbracoHelper = umbracoHelper; + MembershipHelper = membershipHelper; + DatabaseContext = databaseContext; + } + + public IEnumerable GetCategories() + { + var xpath = "//ProjectGroup[@isDoc]"; + var helper = new UmbracoHelper(UmbracoContext.Current); + + var items = helper.TypedContentAtXPath(xpath); + + return items + .OrderBy(x => x.SortOrder) + .Select(x => new Models.Category + { + Icon = "", + Name = x.Name + }); + } + + public Models.PackageDetails GetDetails(Guid id) + { + // [LK:2016-06-13@CGRT16] We're using XPath as we experienced issues with query Examine for GUIDs, + // (it might worth but we were up against the clock). + // The XPath 'translate' is being used to force the 'packageGuid' to be lowercase for comparison. + var xpath = string.Format("//Project[@isDoc and translate(packageGuid,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz') = '{0}']", id.ToString("D").ToLowerInvariant()); + var item = UmbracoHelper.TypedContentSingleAtXPath(xpath); + + return MapContentToPackageDetails(id, item); + } + + private Models.PackageDetails MapContentToPackageDetails(Guid id, IPublishedContent content) + { + var wikiFiles = WikiFile.CurrentFiles(content.Id); + + return new Models.PackageDetails + { + Category = content.Parent.Name, + Excerpt = GetPackageExcerpt(content, 10), + Downloads = Utils.GetProjectTotalDownloadCount(content.Id), + Id = id, + Likes = Utils.GetProjectTotalVotes(content.Id), + Name = content.Name, + Icon = GetThumbnailUrl(BASE_URL + content.GetPropertyValue("defaultScreenshotPath"), 154, 281), + Created = content.CreateDate, + Compatibility = GetPackageCompatibility(content), + NetVersion = content.GetPropertyValue("dotNetVersion"), + LatestVersion = content.GetPropertyValue("version"), + LicenseName = content.GetPropertyValue("licenseName"), + LicenseUrl = content.GetPropertyValue("licenseUrl"), + MinimumVersion = GetMinimumVersion(content, wikiFiles), + OwnerInfo = GetPackageOwnerInfo(content), + Description = content.GetPropertyValue("description"), + Images = GetPackageImages(wikiFiles.Where(x => x.FileType.InvariantEquals("screenshot")), 154, 281), + ExternalSources = GetExternalSources(content), + Url = string.Concat(BASE_URL, content.Url), + ZipUrl = string.Concat(BASE_URL, "/FileDownload?id=", content.GetPropertyValue("file")) + }; + } + + private List GetPackageCompatibility(IPublishedContent content) + { + var service = new VersionCompatibilityService(DatabaseContext); + var report = service.GetCompatibilityReport(content.Id); + + if (report == null || !report.Any()) + return null; + + return report + .Select(x => new PackageCompatibility + { + Percentage = x.Percentage, + Version = x.Version + }) + .ToList(); + } + + private string GetMinimumVersion(IPublishedContent content, IEnumerable packages) + { + var currentVersion = content.GetPropertyValue("file"); + var latest = packages.FirstOrDefault(x => x.Id == currentVersion); + + return latest.Version.Version; + } + + private PackageOwnerInfo GetPackageOwnerInfo(IPublishedContent content) + { + var ownerId = content.GetPropertyValue("owner"); + var owner = MembershipHelper.GetById(ownerId); + + var ownerInfo = new PackageOwnerInfo + { + Karma = owner.Karma(), + Owner = owner.Name, + OwnerAvatar = Utils.GetMemberAvatar(owner, 200, true) + }; + + if (content.GetPropertyValue("openForCollab", false)) + { + var service = new ContributionService(DatabaseContext); + var contributors = service.GetContributors(content.Id).ToList(); + + if (contributors != null && contributors.Any()) + { + var names = new List(); + + foreach (var contributor in contributors) + { + var member = MembershipHelper.GetById(contributor.MemberId); + if (member != null) + { + names.Add(member.Name); + } + } + + ownerInfo.Contributors = names.ToArray(); + } + } + + return ownerInfo; + } + + private List GetPackageImages(IEnumerable images, int thumbnailHeight, int thumbnailWidth) + { + var items = new List(); + + foreach (var image in images) + { + var url = string.Concat(BASE_URL, image.Path); + + items.Add(new PackageImage + { + Source = url, + Thumbnail = GetThumbnailUrl(url, thumbnailHeight, thumbnailWidth) + }); + } + + return items; + } + + private string GetThumbnailUrl(string url, int height, int width) + { + return string.Format("{0}?bgcolor=fff&height={1}&width={2}&format=png", url, height, width); + } + + private List GetExternalSources(IPublishedContent content) + { + var items = new List(); + + if (content.HasValue("websiteUrl")) + { + items.Add(new ExternalSource + { + Name = "Project website", + Url = content.GetPropertyValue("websiteUrl") + }); + } + + if (content.HasValue("sourceUrl")) + { + items.Add(new ExternalSource + { + Name = "Source code", + Url = content.GetPropertyValue("sourceUrl") + }); + } + + if (content.HasValue("demoUrl")) + { + items.Add(new ExternalSource + { + Name = "Demonstration", + Url = content.GetPropertyValue("demoUrl") + }); + } + + if (content.HasValue("supportUrl")) + { + items.Add(new ExternalSource + { + Name = "Issue tracker", + Url = content.GetPropertyValue("supportUrl") + }); + } + + return items; + } + + private string GetPackageExcerpt(IPublishedContent content, int count = 35) + { + var input = content.GetPropertyValue("description"); + + if (string.IsNullOrWhiteSpace(input)) + return null; + + var words = input + .StripHtml() + .StripNewLines() + .Replace(" ", " ") + .Split(new[] { ' ' }) + .Take(count) + .ToList(); + + return string.Concat(string.Join(" ", words), "..."); + } + } +} \ No newline at end of file diff --git a/OurUmbraco/Repository/RepoStartupHandler.cs b/OurUmbraco/Repository/RepoStartupHandler.cs index 825aa0e0..ffd7496d 100644 --- a/OurUmbraco/Repository/RepoStartupHandler.cs +++ b/OurUmbraco/Repository/RepoStartupHandler.cs @@ -11,7 +11,7 @@ namespace OurUmbraco.Repository RouteTable.Routes.MapHttpRoute( name: "PackageRepositoryApi", routeTemplate: "webapi/packages/v1/{id}", - defaults: new { id = RouteParameter.Optional, controller = "PackageRepository", action = "GetPopular" }, + defaults: new { id = RouteParameter.Optional, controller = "PackageRepository" }, constraints: new { } ); diff --git a/OurUmbraco/Wiki/BusinessLogic/WikiFile.cs b/OurUmbraco/Wiki/BusinessLogic/WikiFile.cs index 2586411f..d9761e2e 100644 --- a/OurUmbraco/Wiki/BusinessLogic/WikiFile.cs +++ b/OurUmbraco/Wiki/BusinessLogic/WikiFile.cs @@ -386,6 +386,11 @@ namespace OurUmbraco.Wiki.BusinessLogic { if (UmbracoVersion.AvailableVersions().ContainsKey(ver)) umbracoVersions.Add(UmbracoVersion.AvailableVersions()[ver]); + + // [LK:2016-06-13@CGRT16] Added to support v7.5+ package repository REST API + System.Version v; + if (System.Version.TryParse(ver, out v)) + umbracoVersions.Add(new UmbracoVersion { Version = v.ToString(3) }); } return umbracoVersions; From 6239d78858b8f13900be3e0acb1948e20c6a3ce9 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 13 Jun 2016 17:00:57 +0100 Subject: [PATCH 4/5] [WIP] Started to implement the "Package Search" feature --- OurUmbraco/OurUmbraco.csproj | 2 +- .../PackageRepositoryController.cs | 232 ++---------------- OurUmbraco/Repository/Models/Package.cs | 16 +- .../Repository/Models/PackageDetails.cs | 21 +- .../PackageRepositoryService.cs | 77 ++++-- 5 files changed, 112 insertions(+), 236 deletions(-) rename OurUmbraco/Repository/{ => Services}/PackageRepositoryService.cs (73%) diff --git a/OurUmbraco/OurUmbraco.csproj b/OurUmbraco/OurUmbraco.csproj index e1645f58..0122101b 100644 --- a/OurUmbraco/OurUmbraco.csproj +++ b/OurUmbraco/OurUmbraco.csproj @@ -630,7 +630,7 @@ - + diff --git a/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs b/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs index 7a628cd1..7fafb463 100644 --- a/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs +++ b/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs @@ -3,11 +3,12 @@ using System.Collections.Generic; using System.Linq; using System.Web.Http; using System.Web.Http.Cors; -using Newtonsoft.Json.Serialization; using OurUmbraco.Repository.Models; +using OurUmbraco.Repository.Services; using Umbraco.Core; -using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; +using System.Net.Http; +using System.Net; namespace OurUmbraco.Repository.Controllers { @@ -20,203 +21,14 @@ namespace OurUmbraco.Repository.Controllers [EnableCors(origins: "*", headers: "*", methods: "*")] [JsonCamelCaseFormatter] public class PackageRepositoryController : UmbracoApiControllerBase - { + { + private readonly PackageRepositoryService Service; - private Models.PackageDetails GetTestDetails() + public PackageRepositoryController() { - return new Models.PackageDetails - { - Category = "Collaboration", - Excerpt = "You will make me be president", - Downloads = 123, - Id = Guid.NewGuid(), - Likes = 444, - Name = "The Donald", - Icon = "https://our.umbraco.org/media/wiki/150283/635768313097111400_usightlylogopng.png?bgcolor=fff&height=154&width=281&format=png", - Created = DateTime.Now, - Compatibility = new List - { - new PackageCompatibility - { - Percentage = 90, - Version = "7.5.0" - }, - new PackageCompatibility - { - Percentage = 100, - Version = "7.4.0" - } - }, - NetVersion = "4.5.0", - LatestVersion = "1.2.0", - LicenseName = "MIT", - LicenseUrl = "https://opensource.org/licenses/MIT", - MinimumVersion = "7.4.0", - OwnerInfo = new PackageOwnerInfo - { - Contributors = new[] - { - "Lee Kelleher", - "Matt Brailsford" - }, - Karma = 1000000, - Owner = "Shannon Deminick", - OwnerAvatar = "https://our.umbraco.org/media/upload/d476d257-a494-46d9-9a00-56c2f94a55c8/our-profile.jpg?width=200&height=200&mode=crop" - }, - Description = "

This package is so great, I mean, it's really really good

It has lots of words because I know words, I have the best words

", - Images = new List - { - new PackageImage - { - Thumbnail = "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", - Source = "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" - }, - new PackageImage - { - Thumbnail = "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", - Source = "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" - }, - new PackageImage - { - Thumbnail = "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", - Source = "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" - }, - new PackageImage - { - Thumbnail = "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", - Source = "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" - } - }, - ExternalSources = new List - { - new ExternalSource - { - Name = "Source code", - Url = "https://github.com/merchello/Merchello" - }, - new ExternalSource - { - Name = "Issue tracker", - Url = "http://issues.merchello.com/youtrack/oauth?state=%2Fyoutrack%2FrootGo" - } - } - }; + Service = new PackageRepositoryService(Umbraco, Members, DatabaseContext); } - private IEnumerable GetTestData() - { - return new[] - { - new Models.Package - { - Category = "Collaboration", - Excerpt = "You will make me be president", - Downloads = 123, - Id = Guid.NewGuid(), - Likes = 444, - Name = "The Donald", - Icon = "https://our.umbraco.org/media/wiki/150283/635768313097111400_usightlylogopng.png?bgcolor=fff&height=154&width=281&format=png", - LatestVersion = "1.2.0", - MinimumVersion = "7.4.0", - OwnerInfo = new PackageOwnerInfo - { - Contributors = new[] - { - "Lee Kelleher", - "Matt Brailsford" - }, - Karma = 1000000, - Owner = "Shannon Deminick", - OwnerAvatar = "https://our.umbraco.org/media/upload/d476d257-a494-46d9-9a00-56c2f94a55c8/our-profile.jpg?width=200&height=200&mode=crop" - } - }, - new Models.Package - { - Category = "Starter Kits", - Excerpt = "Another great package", - Downloads = 123, - Id = Guid.NewGuid(), - Likes = 444, - Name = "Kill IE6", - Icon = "https://our.umbraco.org/media/wiki/9138/634697622367666000_offroadcode-100x100.png?bgcolor=fff&height=154&width=281&format=png", - OwnerInfo = new PackageOwnerInfo - { - Contributors = new[] - { - "Lee Kelleher", - "Matt Brailsford" - }, - Karma = 1000000, - Owner = "Shannon Deminick", - OwnerAvatar = "https://our.umbraco.org/media/upload/d476d257-a494-46d9-9a00-56c2f94a55c8/our-profile.jpg?width=200&height=200&mode=crop" - } - }, - new Models.Package - { - Category = "Umbraco Pro", - Excerpt = "A package for doing great things", - Downloads = 123, - Id = Guid.NewGuid(), - Likes = 4, - Name = "Great!", - Icon = "https://our.umbraco.org/media/wiki/50703/634782902373558000_cogworks.jpg?bgcolor=fff&height=154&width=281&format=png", - OwnerInfo = new PackageOwnerInfo - { - Contributors = new[] - { - "Lee Kelleher", - "Matt Brailsford" - }, - Karma = 1000000, - Owner = "Shannon Deminick", - OwnerAvatar = "https://our.umbraco.org/media/upload/d476d257-a494-46d9-9a00-56c2f94a55c8/our-profile.jpg?width=200&height=200&mode=crop" - } - }, - new Models.Package - { - Category = "Collaboration", - Excerpt = "Super crazy awesome, this thing is just amazing", - Downloads = 13323, - Id = Guid.NewGuid(), - Likes = 44334, - Name = "Amazeballs", - Icon = "https://our.umbraco.org/media/wiki/154472/635997115126742822_logopng.png?bgcolor=fff&height=154&width=281&format=png", - OwnerInfo = new PackageOwnerInfo - { - Contributors = new[] - { - "Lee Kelleher", - "Matt Brailsford" - }, - Karma = 1000000, - Owner = "Shannon Deminick", - OwnerAvatar = "https://our.umbraco.org/media/upload/d476d257-a494-46d9-9a00-56c2f94a55c8/our-profile.jpg?width=200&height=200&mode=crop" - } - }, - new Models.Package - { - Category = "Starter Kits", - Excerpt = "A classic, you just can't get enough of this one", - Downloads = 12, - Id = Guid.NewGuid(), - Likes = 433, - Name = "uBeMeAndIBeU", - Icon = "https://our.umbraco.org/media/wiki/152476/635917291068518788_pipeline-crm-logopng.png?bgcolor=fff&height=154&width=281&format=png", - OwnerInfo = new PackageOwnerInfo - { - Contributors = new[] - { - "Lee Kelleher", - "Matt Brailsford" - }, - Karma = 1000000, - Owner = "Shannon Deminick", - OwnerAvatar = "https://our.umbraco.org/media/upload/d476d257-a494-46d9-9a00-56c2f94a55c8/our-profile.jpg?width=200&height=200&mode=crop" - } - } - }; - } - public IEnumerable GetCategories() { // [LK:2016-06-13@CGRT16] We're hardcoding the categories as the 'icon' isn't @@ -238,7 +50,7 @@ namespace OurUmbraco.Repository.Controllers { Icon = "icon-brackets", Name = "Developer tools" - }, + }, new Models.Category { Icon = "icon-wand", @@ -258,14 +70,14 @@ namespace OurUmbraco.Repository.Controllers } [HttpGet] - public PagedPackages Search(int pageIndex, int pageSize, - string category = null, - string query = null, - PackageSortOrder order = PackageSortOrder.Latest ) + public PagedPackages Search( + int pageIndex, + int pageSize, + string category = null, + string query = null, + PackageSortOrder order = PackageSortOrder.Latest) { - var packages = GetTestData() - .Where(x => string.IsNullOrWhiteSpace(category) || x.Category == category) - .Where(x => string.IsNullOrWhiteSpace(query) || x.Name.InvariantContains(query)); + var packages = Service.GetPackages(category, query, order); //TODO: This will be interesting - not sure if we are using Examine for searching but if we are // and if the query is not empty, then we should order by score, @@ -292,13 +104,17 @@ namespace OurUmbraco.Repository.Controllers Total = sorted.Length }; } - + public Models.PackageDetails GetDetails(Guid id) { - var service = new PackageRepositoryService(Umbraco, Members, DatabaseContext); + var package = Service.GetDetails(id); - return service.GetDetails(id); + if (package == null) + { + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + } + + return package; } - } -} +} \ No newline at end of file diff --git a/OurUmbraco/Repository/Models/Package.cs b/OurUmbraco/Repository/Models/Package.cs index a95389d5..1cc39bf9 100644 --- a/OurUmbraco/Repository/Models/Package.cs +++ b/OurUmbraco/Repository/Models/Package.cs @@ -1,7 +1,4 @@ using System; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OurUmbraco.Repository.Models { @@ -11,16 +8,27 @@ namespace OurUmbraco.Repository.Models { OwnerInfo = new PackageOwnerInfo(); } + public DateTime Created { get; set; } + public Guid Id { get; set; } + public string Name { get; set; } + public string Excerpt { get; set; } + public string Icon { get; set; } + public string Url { get; set; } + public int Downloads { get; set; } + public int Likes { get; set; } + public string Category { get; set; } + public string LatestVersion { get; set; } + public PackageOwnerInfo OwnerInfo { get; set; } /// @@ -31,4 +39,4 @@ namespace OurUmbraco.Repository.Models /// public string MinimumVersion { get; set; } } -} +} \ No newline at end of file diff --git a/OurUmbraco/Repository/Models/PackageDetails.cs b/OurUmbraco/Repository/Models/PackageDetails.cs index 61940a90..4cb952f5 100644 --- a/OurUmbraco/Repository/Models/PackageDetails.cs +++ b/OurUmbraco/Repository/Models/PackageDetails.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace OurUmbraco.Repository.Models { @@ -12,14 +11,28 @@ namespace OurUmbraco.Repository.Models ExternalSources = new List(); } + public PackageDetails(Package package) + : base() + { + this.Category = package.Category; + this.Excerpt = package.Excerpt; + this.Downloads = package.Downloads; + this.Id = package.Id; + this.Likes = package.Likes; + this.Name = package.Name; + this.Icon = package.Icon; + this.LatestVersion = package.LatestVersion; + this.MinimumVersion = package.MinimumVersion; + this.OwnerInfo = package.OwnerInfo; + } + public string Description { get; set; } public List Images { get; set; } public List Compatibility { get; set; } - public List ExternalSources { get; set; } - + public List ExternalSources { get; set; } public string LicenseName { get; set; } diff --git a/OurUmbraco/Repository/PackageRepositoryService.cs b/OurUmbraco/Repository/Services/PackageRepositoryService.cs similarity index 73% rename from OurUmbraco/Repository/PackageRepositoryService.cs rename to OurUmbraco/Repository/Services/PackageRepositoryService.cs index 9ef3de0f..80ba71a8 100644 --- a/OurUmbraco/Repository/PackageRepositoryService.cs +++ b/OurUmbraco/Repository/Services/PackageRepositoryService.cs @@ -4,6 +4,7 @@ using System.Linq; using OurUmbraco.Forum.Extensions; using OurUmbraco.Our; using OurUmbraco.Project.Services; +using OurUmbraco.Repository.Controllers; using OurUmbraco.Repository.Models; using OurUmbraco.Wiki.BusinessLogic; using Umbraco.Core; @@ -11,13 +12,13 @@ using Umbraco.Core.Models; using Umbraco.Web; using Umbraco.Web.Security; -namespace OurUmbraco.Repository +namespace OurUmbraco.Repository.Services { internal class PackageRepositoryService { - private DatabaseContext DatabaseContext; - private MembershipHelper MembershipHelper; - private UmbracoHelper UmbracoHelper; + private readonly DatabaseContext DatabaseContext; + private readonly MembershipHelper MembershipHelper; + private readonly UmbracoHelper UmbracoHelper; const string BASE_URL = "https://our.umbraco.org"; @@ -44,6 +45,14 @@ namespace OurUmbraco.Repository }); } + public IEnumerable GetPackages( + string category = null, + string query = null, + PackageSortOrder order = PackageSortOrder.Latest) + { + return UmbracoHelper.TypedContent(9325, 8985, 145710, 10020).Select(x => MapContentToPackage(x)); + } + public Models.PackageDetails GetDetails(Guid id) { // [LK:2016-06-13@CGRT16] We're using XPath as we experienced issues with query Examine for GUIDs, @@ -52,38 +61,51 @@ namespace OurUmbraco.Repository var xpath = string.Format("//Project[@isDoc and translate(packageGuid,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz') = '{0}']", id.ToString("D").ToLowerInvariant()); var item = UmbracoHelper.TypedContentSingleAtXPath(xpath); - return MapContentToPackageDetails(id, item); + if (item == null) + return null; + + return MapContentToPackageDetails(item); } - private Models.PackageDetails MapContentToPackageDetails(Guid id, IPublishedContent content) + private Models.Package MapContentToPackage(IPublishedContent content) { var wikiFiles = WikiFile.CurrentFiles(content.Id); - return new Models.PackageDetails + return new Models.Package { Category = content.Parent.Name, Excerpt = GetPackageExcerpt(content, 10), Downloads = Utils.GetProjectTotalDownloadCount(content.Id), - Id = id, + Id = content.GetPropertyValue("packageGuid"), Likes = Utils.GetProjectTotalVotes(content.Id), Name = content.Name, Icon = GetThumbnailUrl(BASE_URL + content.GetPropertyValue("defaultScreenshotPath"), 154, 281), - Created = content.CreateDate, - Compatibility = GetPackageCompatibility(content), - NetVersion = content.GetPropertyValue("dotNetVersion"), LatestVersion = content.GetPropertyValue("version"), - LicenseName = content.GetPropertyValue("licenseName"), - LicenseUrl = content.GetPropertyValue("licenseUrl"), MinimumVersion = GetMinimumVersion(content, wikiFiles), - OwnerInfo = GetPackageOwnerInfo(content), - Description = content.GetPropertyValue("description"), - Images = GetPackageImages(wikiFiles.Where(x => x.FileType.InvariantEquals("screenshot")), 154, 281), - ExternalSources = GetExternalSources(content), - Url = string.Concat(BASE_URL, content.Url), - ZipUrl = string.Concat(BASE_URL, "/FileDownload?id=", content.GetPropertyValue("file")) + OwnerInfo = GetPackageOwnerInfo(content) }; } + private Models.PackageDetails MapContentToPackageDetails(IPublishedContent content) + { + var package = MapContentToPackage(content); + var packageDetails = new PackageDetails(package); + var wikiFiles = WikiFile.CurrentFiles(content.Id); + + packageDetails.Created = content.CreateDate; + packageDetails.Compatibility = GetPackageCompatibility(content); + packageDetails.NetVersion = content.GetPropertyValue("dotNetVersion"); + packageDetails.LicenseName = content.GetPropertyValue("licenseName"); + packageDetails.LicenseUrl = content.GetPropertyValue("licenseUrl"); + packageDetails.Description = content.GetPropertyValue("description"); + packageDetails.Images = GetPackageImages(wikiFiles.Where(x => x.FileType.InvariantEquals("screenshot")), 154, 281); + packageDetails.ExternalSources = GetExternalSources(content); + packageDetails.Url = string.Concat(BASE_URL, content.Url); + packageDetails.ZipUrl = string.Concat(BASE_URL, "/FileDownload?id=", content.GetPropertyValue("file")); + + return packageDetails; + } + private List GetPackageCompatibility(IPublishedContent content) { var service = new VersionCompatibilityService(DatabaseContext); @@ -106,6 +128,23 @@ namespace OurUmbraco.Repository var currentVersion = content.GetPropertyValue("file"); var latest = packages.FirstOrDefault(x => x.Id == currentVersion); + if (string.IsNullOrWhiteSpace(latest.Version.Version) || latest.Version.Version.InvariantEquals("nan")) + return null; + + if (latest.Version.Version.InvariantStartsWith("v")) + { + var legacyFormat = latest.Version.Version; + + if (legacyFormat.InvariantStartsWith("v4")) + { + return string.Concat(legacyFormat.Replace("v4", "4."), ".0"); + } + else + { + return string.Join(".", legacyFormat.ToCharArray().Skip(1)); + } + } + return latest.Version.Version; } From a8ca4fc0f5cba3e32998f524da4cf2091468e24b Mon Sep 17 00:00:00 2001 From: leekelleher Date: Mon, 13 Jun 2016 19:40:37 +0100 Subject: [PATCH 5/5] Implement the rest of the "Package Search" feature --- .../MarketPlace/Providers/KarmaProvider.cs | 2 +- .../PackageRepositoryController.cs | 27 +---- .../Repository/Models/PackageDetails.cs | 23 ++-- .../Services/PackageRepositoryService.cs | 106 ++++++++++++++++-- 4 files changed, 111 insertions(+), 47 deletions(-) diff --git a/OurUmbraco/MarketPlace/Providers/KarmaProvider.cs b/OurUmbraco/MarketPlace/Providers/KarmaProvider.cs index 632b336b..fc7bd89d 100644 --- a/OurUmbraco/MarketPlace/Providers/KarmaProvider.cs +++ b/OurUmbraco/MarketPlace/Providers/KarmaProvider.cs @@ -19,7 +19,7 @@ namespace OurUmbraco.MarketPlace.Providers { var karmaList = new List(); - using (var reader = Data.SqlHelper.ExecuteReader("SELECT id as ProjectId, SUM(points) Points FROM powersProject GROUP BY id")) + using (var reader = Data.SqlHelper.ExecuteReader("SELECT id as ProjectId, SUM(points) Points FROM powersProject GROUP BY id ORDER BY SUM(points) DESC")) { while (reader.Read()) { diff --git a/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs b/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs index 7fafb463..2005b3c1 100644 --- a/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs +++ b/OurUmbraco/Repository/Controllers/PackageRepositoryController.cs @@ -77,32 +77,7 @@ namespace OurUmbraco.Repository.Controllers string query = null, PackageSortOrder order = PackageSortOrder.Latest) { - var packages = Service.GetPackages(category, query, order); - - //TODO: This will be interesting - not sure if we are using Examine for searching but if we are - // and if the query is not empty, then we should order by score, - // otherwise if there is no query we will order by the 'order' parameter - Models.Package[] sorted; - if (string.IsNullOrWhiteSpace(query)) - { - //TODO: order by score if possible - sorted = packages.OrderBy(x => x.Created).ToArray(); - } - else if (order == PackageSortOrder.Latest) - { - sorted = packages.OrderBy(x => x.Created).ToArray(); - } - else - { - //TODO: Also included downloads somehow? - sorted = packages.OrderBy(x => x.Likes).ToArray(); - } - - return new PagedPackages - { - Packages = sorted.Skip(pageIndex * pageSize).Take(pageSize), - Total = sorted.Length - }; + return Service.GetPackages(pageIndex, pageSize, category, query, order); } public Models.PackageDetails GetDetails(Guid id) diff --git a/OurUmbraco/Repository/Models/PackageDetails.cs b/OurUmbraco/Repository/Models/PackageDetails.cs index 4cb952f5..8f85678e 100644 --- a/OurUmbraco/Repository/Models/PackageDetails.cs +++ b/OurUmbraco/Repository/Models/PackageDetails.cs @@ -14,16 +14,19 @@ namespace OurUmbraco.Repository.Models public PackageDetails(Package package) : base() { - this.Category = package.Category; - this.Excerpt = package.Excerpt; - this.Downloads = package.Downloads; - this.Id = package.Id; - this.Likes = package.Likes; - this.Name = package.Name; - this.Icon = package.Icon; - this.LatestVersion = package.LatestVersion; - this.MinimumVersion = package.MinimumVersion; - this.OwnerInfo = package.OwnerInfo; + if (package != null) + { + this.Category = package.Category; + this.Excerpt = package.Excerpt; + this.Downloads = package.Downloads; + this.Id = package.Id; + this.Likes = package.Likes; + this.Name = package.Name; + this.Icon = package.Icon; + this.LatestVersion = package.LatestVersion; + this.MinimumVersion = package.MinimumVersion; + this.OwnerInfo = package.OwnerInfo; + } } public string Description { get; set; } diff --git a/OurUmbraco/Repository/Services/PackageRepositoryService.cs b/OurUmbraco/Repository/Services/PackageRepositoryService.cs index 80ba71a8..272e56f8 100644 --- a/OurUmbraco/Repository/Services/PackageRepositoryService.cs +++ b/OurUmbraco/Repository/Services/PackageRepositoryService.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using Examine; using OurUmbraco.Forum.Extensions; +using OurUmbraco.MarketPlace.Providers; using OurUmbraco.Our; using OurUmbraco.Project.Services; using OurUmbraco.Repository.Controllers; @@ -45,12 +48,89 @@ namespace OurUmbraco.Repository.Services }); } - public IEnumerable GetPackages( + public PagedPackages GetPackages( + int pageIndex, + int pageSize, string category = null, string query = null, PackageSortOrder order = PackageSortOrder.Latest) { - return UmbracoHelper.TypedContent(9325, 8985, 145710, 10020).Select(x => MapContentToPackage(x)); + var items = Enumerable.Empty(); + + if (string.IsNullOrWhiteSpace(category) && string.IsNullOrWhiteSpace(query)) + { + if (order == PackageSortOrder.Latest) + { + // [LK:2016-06-13@CGRT16] This feels hacky, but unsure how else to get + // a list of all the newly created projects. + var xpath = "//Project[@isDoc and projectLive = 1 and approved = 1 and notAPackage = 0]"; + items = UmbracoHelper + .TypedContentAtXPath(xpath) + .OrderByDescending(x => x.CreateDate); + } + else + { + // [LK:2016-06-13@CGRT16] Attempting to reuse legacy code + var karmaProvider = new KarmaProvider(); + var projectsByKarma = karmaProvider.GetProjectsKarmaList(); + + items = UmbracoHelper + .TypedContent(projectsByKarma.Select(x => x.ProjectId)) + .Where(x => + x.GetPropertyValue("projectLive") && + x.GetPropertyValue("approved") && + !x.GetPropertyValue("notAPackage")); + } + } + else + { + var q = new StringBuilder(); + + if (!string.IsNullOrWhiteSpace(category)) + { + q.AppendFormat("+categoryFolder: \"{0}\" ", category); + } + + if (!string.IsNullOrWhiteSpace(query)) + { + q.AppendFormat("+nodeName:{0}", query); + } + + var searcher = ExamineManager.Instance.SearchProviderCollection["projectSearcher"]; + var criteria = searcher.CreateSearchCriteria().RawQuery(q.ToString()); + + items = UmbracoHelper.TypedSearch(criteria, searcher); + } + + if (items == null) + { + return null; + } + + var packages = items + .Skip(pageIndex * pageSize) + .Take(pageSize) + .Select(x => MapContentToPackage(x)); + + IEnumerable sorted; + + if (order == PackageSortOrder.Latest) + { + sorted = packages + .OrderByDescending(x => x.Created); + } + else + { + sorted = packages + .OrderByDescending(x => x.Downloads) + .ThenByDescending(x => x.Likes); + } + + return new PagedPackages + { + Packages = packages, + Total = items.Count() + }; } public Models.PackageDetails GetDetails(Guid id) @@ -58,7 +138,7 @@ namespace OurUmbraco.Repository.Services // [LK:2016-06-13@CGRT16] We're using XPath as we experienced issues with query Examine for GUIDs, // (it might worth but we were up against the clock). // The XPath 'translate' is being used to force the 'packageGuid' to be lowercase for comparison. - var xpath = string.Format("//Project[@isDoc and translate(packageGuid,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz') = '{0}']", id.ToString("D").ToLowerInvariant()); + var xpath = string.Format("//Project[@isDoc and projectLive = 1 and approved = 1 and translate(packageGuid,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz') = '{0}']", id.ToString("D").ToLowerInvariant()); var item = UmbracoHelper.TypedContentSingleAtXPath(xpath); if (item == null) @@ -69,30 +149,37 @@ namespace OurUmbraco.Repository.Services private Models.Package MapContentToPackage(IPublishedContent content) { + if (content == null) + return null; + var wikiFiles = WikiFile.CurrentFiles(content.Id); return new Models.Package { Category = content.Parent.Name, - Excerpt = GetPackageExcerpt(content, 10), + Created = content.CreateDate, + Excerpt = GetPackageExcerpt(content, 12), Downloads = Utils.GetProjectTotalDownloadCount(content.Id), Id = content.GetPropertyValue("packageGuid"), Likes = Utils.GetProjectTotalVotes(content.Id), Name = content.Name, - Icon = GetThumbnailUrl(BASE_URL + content.GetPropertyValue("defaultScreenshotPath"), 154, 281), + Icon = GetThumbnailUrl(BASE_URL + content.GetPropertyValue("defaultScreenshotPath", "/css/img/package2.png"), 154, 281), LatestVersion = content.GetPropertyValue("version"), - MinimumVersion = GetMinimumVersion(content, wikiFiles), - OwnerInfo = GetPackageOwnerInfo(content) + MinimumVersion = GetMinimumVersion(content, wikiFiles.Where(x => x.FileType.InvariantEquals("package"))), + OwnerInfo = GetPackageOwnerInfo(content), + Url = string.Concat(BASE_URL, content.Url) }; } private Models.PackageDetails MapContentToPackageDetails(IPublishedContent content) { + if (content == null) + return null; + var package = MapContentToPackage(content); var packageDetails = new PackageDetails(package); var wikiFiles = WikiFile.CurrentFiles(content.Id); - packageDetails.Created = content.CreateDate; packageDetails.Compatibility = GetPackageCompatibility(content); packageDetails.NetVersion = content.GetPropertyValue("dotNetVersion"); packageDetails.LicenseName = content.GetPropertyValue("licenseName"); @@ -100,7 +187,6 @@ namespace OurUmbraco.Repository.Services packageDetails.Description = content.GetPropertyValue("description"); packageDetails.Images = GetPackageImages(wikiFiles.Where(x => x.FileType.InvariantEquals("screenshot")), 154, 281); packageDetails.ExternalSources = GetExternalSources(content); - packageDetails.Url = string.Concat(BASE_URL, content.Url); packageDetails.ZipUrl = string.Concat(BASE_URL, "/FileDownload?id=", content.GetPropertyValue("file")); return packageDetails; @@ -128,7 +214,7 @@ namespace OurUmbraco.Repository.Services var currentVersion = content.GetPropertyValue("file"); var latest = packages.FirstOrDefault(x => x.Id == currentVersion); - if (string.IsNullOrWhiteSpace(latest.Version.Version) || latest.Version.Version.InvariantEquals("nan")) + if (latest == null || string.IsNullOrWhiteSpace(latest.Version.Version) || latest.Version.Version.InvariantEquals("nan")) return null; if (latest.Version.Version.InvariantStartsWith("v"))