// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; using Horde.Build.Acls; using Horde.Build.Server; using Horde.Build.Utilities; using MongoDB.Bson.Serialization.Attributes; using MongoDB.Driver; namespace Horde.Build.Projects { using ProjectId = StringId; /// /// Wraps functionality for manipulating projects /// public class ProjectCollection : IProjectCollection { /// /// Represents a project /// class ProjectDocument : IProject { public const int DefaultOrder = 128; [BsonRequired, BsonId] public ProjectId Id { get; set; } [BsonRequired] public string Name { get; set; } public string? ConfigPath { get; set; } public string? ConfigRevision { get; set; } public int Order { get; set; } = DefaultOrder; public Acl? Acl { get; set; } public List Categories { get; set; } = new List(); [BsonIgnoreIfDefault, BsonDefaultValue(false)] public bool Deleted { get; set; } IReadOnlyList IProject.Categories => Categories; [BsonConstructor] private ProjectDocument() { Name = null!; } public ProjectDocument(ProjectId id, string name) { Id = id; Name = name; } } /// /// Logo for a project /// class ProjectLogoDocument : IProjectLogo { public ProjectId Id { get; set; } public string Path { get; set; } = String.Empty; public string Revision { get; set; } = String.Empty; public string MimeType { get; set; } = String.Empty; public byte[] Data { get; set; } = Array.Empty(); } /// /// Projection of a project definition to just include permissions info /// [SuppressMessage("Design", "CA1812: Class is never instantiated")] class ProjectPermissions : IProjectPermissions { /// /// ACL for the project /// public Acl? Acl { get; set; } /// /// Projection to extract the permissions info from the project /// public static readonly ProjectionDefinition Projection = Builders.Projection.Include(x => x.Acl); } /// /// Collection of project documents /// readonly IMongoCollection _projects; /// /// Collection of project logo documents /// readonly IMongoCollection _projectLogos; /// /// Constructor /// /// The database service instance public ProjectCollection(MongoService mongoService) { _projects = mongoService.GetCollection("Projects"); _projectLogos = mongoService.GetCollection("ProjectLogos"); } /// public async Task AddOrUpdateAsync(ProjectId id, string configPath, string revision, int order, ProjectConfig config) { ProjectDocument newProject = new ProjectDocument(id, config.Name); newProject.ConfigPath = configPath; newProject.ConfigRevision = revision; newProject.Order = order; newProject.Categories = config.Categories.ConvertAll(x => new StreamCategory(x)); newProject.Acl = Acl.Merge(new Acl(), config.Acl); await _projects.FindOneAndReplaceAsync(x => x.Id == id, newProject, new FindOneAndReplaceOptions { IsUpsert = true }); return newProject; } /// public async Task> FindAllAsync() { List results = await _projects.Find(x => !x.Deleted).ToListAsync(); return results.OrderBy(x => x.Order).ThenBy(x => x.Name).Select(x => x).ToList(); } /// public async Task GetAsync(ProjectId projectId) { return await _projects.Find(x => x.Id == projectId).FirstOrDefaultAsync(); } /// public async Task GetLogoAsync(ProjectId projectId) { return await _projectLogos.Find(x => x.Id == projectId).FirstOrDefaultAsync(); } /// public async Task SetLogoAsync(ProjectId projectId, string logoPath, string logoRevision, string mimeType, byte[] data) { ProjectLogoDocument logo = new ProjectLogoDocument(); logo.Id = projectId; logo.Path = logoPath; logo.Revision = logoRevision; logo.MimeType = mimeType; logo.Data = data; await _projectLogos.ReplaceOneAsync(x => x.Id == projectId, logo, new ReplaceOptions { IsUpsert = true }); } /// public async Task GetPermissionsAsync(ProjectId projectId) { return await _projects.Find(x => x.Id == projectId).Project(ProjectPermissions.Projection).FirstOrDefaultAsync(); } /// public async Task DeleteAsync(ProjectId projectId) { await _projects.UpdateOneAsync(x => x.Id == projectId, Builders.Update.Set(x => x.Deleted, true)); } } }