public async Task <CreateReleaseResult> Handle(CreateReleaseCommand request, CancellationToken cancellationToken)
        {
            if (!_versionParser.TryParse(request.Version, out var version))
            {
                throw new ValidationException("Invalid version format");
            }

            var app = await _db.Apps.SingleOrDefaultAsync(e => e.Uuid == request.AppId);

            if (app == null)
            {
                throw new AppException(AppErrorCode.NotFound, "Application was not found");
            }

            var release = new Release {
                Uuid                      = Uuid.NewUuid(),
                CreatedAt                 = _clock.UtcNowMilliseconds,
                Version                   = request.Version,
                SortableVersion           = _sortableVersionPrinter.Print(version),
                UniqueSortableVersionHack = version.IsPrerelease ? null : string.Empty,
                Title                     = request.Title,
                Description               = request.Description,
                Commit                    = request.Commit,
                Prerelease                = version.IsPrerelease,
                Published                 = false
            };

            app.Releases.Add(release);

            return(new CreateReleaseResult {
                Id = release.Uuid
            });
        }
        public async Task <IEnumerable <GetReleasesListResultItem> > Handle(GetReleasesListQuery request, CancellationToken cancellationToken)
        {
            var app = await _db.Apps.SingleOrDefaultAsync(e => e.Name == request.App);

            if (app == null)
            {
                throw new AppException(AppErrorCode.NotFound, "Application was not found");
            }

            bool includeAssets = false;

            foreach (var include in request.Include)
            {
                if (string.IsNullOrEmpty(include))
                {
                    continue;
                }

                switch (include)
                {
                case "assets":
                    includeAssets = true;
                    break;

                default:
                    throw new ValidationException("Invalid include identifier");
                }
            }

            var releasesDbQuery = _db.Releases.Where(e => e.AppId == app.Id && e.Published);

            if (includeAssets)
            {
                releasesDbQuery = releasesDbQuery.Include(e => e.Assets);
            }

            Domain.Versioning.Version sinceVersion = null;

            if (!string.IsNullOrEmpty(request.Filter.SinceRelease))
            {
                if (!_versionParser.TryParse(request.Filter.SinceRelease, out sinceVersion, false))
                {
                    throw new ValidationException("Invalid version format");
                }
            }

            bool hasPrereleaseBuildMetadata = request.Filter.Prerelease && sinceVersion != null && sinceVersion.BuildIdentifiers.Any();

            if (sinceVersion != null)
            {
                var sortableVersion = _sortableVersionPrinter.Print(sinceVersion);

                // If build identifiers were included in the version then we need to do some more investigation to find it in the database
                if (hasPrereleaseBuildMetadata)
                {
                    releasesDbQuery = releasesDbQuery.Where(e => e.SortableVersion.CompareTo(sortableVersion) >= 0);
                }
                else
                {
                    releasesDbQuery = releasesDbQuery.Where(e => e.SortableVersion.CompareTo(sortableVersion) > 0);
                }
            }

            if (!request.Filter.Prerelease)
            {
                releasesDbQuery = releasesDbQuery.Where(e => !e.Prerelease);
            }

            releasesDbQuery = releasesDbQuery
                              .OrderByDescending(e => e.SortableVersion)
                              .ThenByDescending(e => e.CreatedAt);

            // We cannot limit here with prereleases since we need to check the returned releases more thoroughly and then limit
            if (!request.Filter.Prerelease)
            {
                if (request.Filter.Limit > 0)
                {
                    releasesDbQuery = releasesDbQuery.Take(request.Filter.Limit);
                }
            }

            var releases = await releasesDbQuery.ToArrayAsync();

            // When searching for a version that contains build metadata, we have a special case because prereleases can have the same sortable version,
            // varying only by build metadata, which may return multiple results from the database query with the same sortable version.
            // This can make it hard or even impossible to know exactly where in the history the version being searched for is located.
            // Without any more information to go on than the version itself, we need to look for an exact match and assume that any version after the matched
            // version's created time is newer.
            // If we can't find an exact match then there is currently no way to know which versions are newer so we just return the latest one.
            // If no build metadata is specified then we'll do regular version matching.

            if (hasPrereleaseBuildMetadata)
            {
                int index = Array.FindIndex(releases, 0, releases.Length, release => release.Version == request.Filter.SinceRelease);
                if (index != -1)
                {
                    // Take the newer versions and make sure to exclude the one we searched for
                    var temp = releases.Take(index);

                    // Limit the results here for prereleases since we could not do it earlier when querying the database
                    if (request.Filter.Limit > 0)
                    {
                        temp = temp.Take(request.Filter.Limit);
                    }

                    releases = temp.ToArray();
                }
                else if (releases.Any())
                {
                    releases = releases.Take(1).ToArray();
                }
            }

            return(releases.Select(release => new GetReleasesListResultItem {
                CreatedAt = release.CreatedAt,
                Version = release.Version,
                Title = release.Title,
                Description = release.Description,
                Commit = release.Commit,
                Prerelease = release.Prerelease,
                Assets = includeAssets ? getAssets(app, release, request.AssetTag) : null
            }));
        }