From 54bd26f587b2692b46609e96040accb5e00e354c Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Tue, 14 Jan 2020 16:33:50 -0700 Subject: [PATCH 1/3] Dedupe lists with different versions in metrics --- Wabbajack.BuildServer/Models/Metric.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Wabbajack.BuildServer/Models/Metric.cs b/Wabbajack.BuildServer/Models/Metric.cs index 61355831..796af29e 100644 --- a/Wabbajack.BuildServer/Models/Metric.cs +++ b/Wabbajack.BuildServer/Models/Metric.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; @@ -24,6 +25,7 @@ namespace Wabbajack.BuildServer.Models public static async Task> Report(DBContext db, string grouping) { + var regex = new Regex("\\d+\\."); var data = await db.Metrics.AsQueryable() .Where(m => m.MetricsKey != null) .Where(m => m.Action == grouping) @@ -40,7 +42,7 @@ namespace Wabbajack.BuildServer.Models var results = data .Where(d => !Guid.TryParse(d.Subject, out var _)) - .GroupBy(d => d.Subject) + .GroupBy(d => regex.Split(d.Subject).First()) .Select(by_series => { var by_day = by_series.GroupBy(d => d.Timestamp.ToString("yyyy-MM-dd")) From 6a0688d37abafff4d137d79cdb7590df3218d7ab Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Wed, 15 Jan 2020 22:06:25 -0700 Subject: [PATCH 2/3] Start on file upload support --- .../ApiKeyAuthenticationHandler.cs | 87 +++++++++++++++++++ Wabbajack.BuildServer/AppSettings.cs | 2 + .../Controllers/Heartbeat.cs | 9 ++ Wabbajack.BuildServer/Controllers/Jobs.cs | 2 + .../Controllers/UploadedFiles.cs | 74 ++++++++++++++++ Wabbajack.BuildServer/Extensions.cs | 20 +++++ Wabbajack.BuildServer/GraphQL/Query.cs | 7 ++ .../GraphQL/UploadedFileType.cs | 22 +++++ Wabbajack.BuildServer/JobManager.cs | 2 + Wabbajack.BuildServer/Models/ApiKey.cs | 23 +++++ Wabbajack.BuildServer/Models/DBContext.cs | 4 +- .../Models/Jobs/UpdateModLists.cs | 2 +- Wabbajack.BuildServer/Models/UploadedFile.cs | 39 +++++++++ Wabbajack.BuildServer/Program.cs | 6 +- Wabbajack.BuildServer/Startup.cs | 20 +++++ .../Wabbajack.BuildServer.csproj | 5 ++ Wabbajack.BuildServer/appsettings.json | 7 +- .../ModListRegistry/ModListMetadata.cs | 2 + Wabbajack.Lib/Wabbajack.Lib.csproj | 3 + 19 files changed, 331 insertions(+), 5 deletions(-) create mode 100644 Wabbajack.BuildServer/ApiKeyAuthenticationHandler.cs create mode 100644 Wabbajack.BuildServer/Controllers/UploadedFiles.cs create mode 100644 Wabbajack.BuildServer/GraphQL/UploadedFileType.cs create mode 100644 Wabbajack.BuildServer/Models/ApiKey.cs create mode 100644 Wabbajack.BuildServer/Models/UploadedFile.cs diff --git a/Wabbajack.BuildServer/ApiKeyAuthenticationHandler.cs b/Wabbajack.BuildServer/ApiKeyAuthenticationHandler.cs new file mode 100644 index 00000000..1c54073f --- /dev/null +++ b/Wabbajack.BuildServer/ApiKeyAuthenticationHandler.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Wabbajack.BuildServer.Models; + +namespace Wabbajack.BuildServer +{ + + public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions + { + public const string DefaultScheme = "API Key"; + public string Scheme => DefaultScheme; + public string AuthenticationType = DefaultScheme; + } + + public class ApiKeyAuthenticationHandler : AuthenticationHandler + { + private const string ProblemDetailsContentType = "application/problem+json"; + private readonly DBContext _db; + private const string ApiKeyHeaderName = "X-Api-Key"; + + public ApiKeyAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock, + DBContext db) : base(options, logger, encoder, clock) + { + _db = db; + } + + protected override async Task HandleAuthenticateAsync() + { + if (!Request.Headers.TryGetValue(ApiKeyHeaderName, out var apiKeyHeaderValues)) + { + return AuthenticateResult.NoResult(); + } + + var providedApiKey = apiKeyHeaderValues.FirstOrDefault(); + + if (apiKeyHeaderValues.Count == 0 || string.IsNullOrWhiteSpace(providedApiKey)) + { + return AuthenticateResult.NoResult(); + } + + var existingApiKey = await ApiKey.Get(_db, providedApiKey); + + if (existingApiKey != null) + { + var claims = new List {new Claim(ClaimTypes.Name, existingApiKey.Owner)}; + + claims.AddRange(existingApiKey.Roles.Select(role => new Claim(ClaimTypes.Role, role))); + + var identity = new ClaimsIdentity(claims, Options.AuthenticationType); + var identities = new List {identity}; + var principal = new ClaimsPrincipal(identities); + var ticket = new AuthenticationTicket(principal, Options.Scheme); + + return AuthenticateResult.Success(ticket); + } + + return AuthenticateResult.Fail("Invalid API Key provided."); + } + + protected override async Task HandleChallengeAsync(AuthenticationProperties properties) + { + Response.StatusCode = 401; + Response.ContentType = ProblemDetailsContentType; + await Response.WriteAsync("Unauthorized"); + } + + protected override async Task HandleForbiddenAsync(AuthenticationProperties properties) + { + Response.StatusCode = 403; + Response.ContentType = ProblemDetailsContentType; + await Response.WriteAsync("forbidden"); + } + } +} diff --git a/Wabbajack.BuildServer/AppSettings.cs b/Wabbajack.BuildServer/AppSettings.cs index fc579416..b57ea38e 100644 --- a/Wabbajack.BuildServer/AppSettings.cs +++ b/Wabbajack.BuildServer/AppSettings.cs @@ -11,5 +11,7 @@ namespace Wabbajack.BuildServer public string DownloadDir { get; set; } public string ArchiveDir { get; set; } + + public bool MinimalMode { get; set; } } } diff --git a/Wabbajack.BuildServer/Controllers/Heartbeat.cs b/Wabbajack.BuildServer/Controllers/Heartbeat.cs index 3fcd4a56..73c12362 100644 --- a/Wabbajack.BuildServer/Controllers/Heartbeat.cs +++ b/Wabbajack.BuildServer/Controllers/Heartbeat.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Wabbajack.BuildServer.Models; @@ -27,5 +28,13 @@ namespace Wabbajack.BuildServer.Controllers { return DateTime.Now - _startTime; } + + [HttpGet("only-authenticated")] + [Authorize] + public IActionResult OnlyAuthenticated() + { + var message = $"Hello from {nameof(OnlyAuthenticated)}"; + return new ObjectResult(message); + } } } diff --git a/Wabbajack.BuildServer/Controllers/Jobs.cs b/Wabbajack.BuildServer/Controllers/Jobs.cs index 4079aada..098f1772 100644 --- a/Wabbajack.BuildServer/Controllers/Jobs.cs +++ b/Wabbajack.BuildServer/Controllers/Jobs.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using MongoDB.Driver; @@ -10,6 +11,7 @@ using Wabbajack.BuildServer.Models.JobQueue; namespace Wabbajack.BuildServer.Controllers { + [Authorize] [ApiController] [Route("/jobs")] public class Jobs : AControllerBase diff --git a/Wabbajack.BuildServer/Controllers/UploadedFiles.cs b/Wabbajack.BuildServer/Controllers/UploadedFiles.cs new file mode 100644 index 00000000..31d55591 --- /dev/null +++ b/Wabbajack.BuildServer/Controllers/UploadedFiles.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using Nettle; +using Wabbajack.BuildServer.Models; +using Wabbajack.Common; + +namespace Wabbajack.BuildServer.Controllers +{ + public class UploadedFiles : AControllerBase + { + public UploadedFiles(ILogger logger, DBContext db) : base(logger, db) + { + + } + + [HttpPost] + [Authorize] + [Route("upload_file")] + public async Task UploadFile(IList files) + { + var user = User.FindFirstValue(ClaimTypes.Name); + foreach (var file in files) + await UploadedFile.Ingest(Db, file, user); + + return Ok(); + } + + private static readonly Func HandleGetListTemplate = NettleEngine.GetCompiler().Compile(@" + + + {{each $.files }} + + {{/each}} +
{{$.Name}}{{$.Size}}{{$.Date}}{{$.Uploader}}
+ + "); + + [HttpGet] + [Route("uploaded_files")] + public async Task UploadedFilesGet() + { + var files = await Db.UploadedFiles.AsQueryable().OrderByDescending(f => f.UploadDate).ToListAsync(); + var response = HandleGetListTemplate(new + { + files = files.Select(file => new + { + Link = file.Uri, + Size = file.Size.ToFileSizeString(), + file.Name, + Date = file.UploadDate, + file.Uploader + }) + + }); + return new ContentResult + { + ContentType = "text/html", + StatusCode = (int) HttpStatusCode.OK, + Content = response + }; + } + } +} diff --git a/Wabbajack.BuildServer/Extensions.cs b/Wabbajack.BuildServer/Extensions.cs index a5c5d550..aa8026ec 100644 --- a/Wabbajack.BuildServer/Extensions.cs +++ b/Wabbajack.BuildServer/Extensions.cs @@ -1,12 +1,15 @@ using System; +using System.IO; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Logging; using MongoDB.Driver; using MongoDB.Driver.Linq; using Wabbajack.Common; +using File = Alphaleonis.Win32.Filesystem.File; namespace Wabbajack.BuildServer { @@ -24,5 +27,22 @@ namespace Wabbajack.BuildServer manager.StartJobRunners(); } + + public static async Task CopyFileAsync(string sourcePath, string destinationPath) + { + using (Stream source = File.OpenRead(sourcePath)) + { + using(Stream destination = File.Create(destinationPath)) + { + await source.CopyToAsync(destination); + } + } + } + + + public static AuthenticationBuilder AddApiKeySupport(this AuthenticationBuilder authenticationBuilder, Action options) + { + return authenticationBuilder.AddScheme(ApiKeyAuthenticationOptions.DefaultScheme, options); + } } } diff --git a/Wabbajack.BuildServer/GraphQL/Query.cs b/Wabbajack.BuildServer/GraphQL/Query.cs index 5b7582be..b61a9815 100644 --- a/Wabbajack.BuildServer/GraphQL/Query.cs +++ b/Wabbajack.BuildServer/GraphQL/Query.cs @@ -53,6 +53,13 @@ namespace Wabbajack.BuildServer.GraphQL var data = await db.Jobs.AsQueryable().Where(j => j.Id == id).ToListAsync(); return data; }); + + FieldAsync>("uploadedFiles", + resolve: async context => + { + var data = await db.UploadedFiles.AsQueryable().ToListAsync(); + return data; + }); FieldAsync>("dailyUniqueMetrics", arguments: new QueryArguments( diff --git a/Wabbajack.BuildServer/GraphQL/UploadedFileType.cs b/Wabbajack.BuildServer/GraphQL/UploadedFileType.cs new file mode 100644 index 00000000..8399beff --- /dev/null +++ b/Wabbajack.BuildServer/GraphQL/UploadedFileType.cs @@ -0,0 +1,22 @@ +using GraphQL.Types; +using Wabbajack.BuildServer.Models; + +namespace Wabbajack.BuildServer.GraphQL +{ + public class UploadedFileType : ObjectGraphType + { + public UploadedFileType() + { + Name = "UploadedFile"; + Description = "A file uploaded for hosting on Wabbajack's static file hosting"; + Field(x => x.Id, type: typeof(IdGraphType)).Description("Unique Id of the Job"); + Field(x => x.Name).Description("Non-unique name of the file"); + Field(x => x.MungedName, type: typeof(IdGraphType)).Description("Unique file name"); + Field(x => x.UploadDate, type: typeof(DateGraphType)).Description("Date of the file upload"); + Field(x => x.Uploader, type: typeof(IdGraphType)).Description("Uploader of the file"); + Field(x => x.Uri, type: typeof(UriGraphType)).Description("URI of the file"); + Field(x => x.Hash).Description("xxHash64 of the file"); + Field(x => x.Size).Description("Size of the file"); + } + } +} diff --git a/Wabbajack.BuildServer/JobManager.cs b/Wabbajack.BuildServer/JobManager.cs index 6bd1ce36..a2e64f8d 100644 --- a/Wabbajack.BuildServer/JobManager.cs +++ b/Wabbajack.BuildServer/JobManager.cs @@ -26,6 +26,7 @@ namespace Wabbajack.BuildServer public void StartJobRunners() { + if (Settings.MinimalMode) return; for (var idx = 0; idx < 2; idx++) { Task.Run(async () => @@ -67,6 +68,7 @@ namespace Wabbajack.BuildServer public async Task JobScheduler() { + if (Settings.MinimalMode) return; Utils.LogMessages.Subscribe(msg => Logger.Log(LogLevel.Information, msg.ToString())); while (true) { diff --git a/Wabbajack.BuildServer/Models/ApiKey.cs b/Wabbajack.BuildServer/Models/ApiKey.cs new file mode 100644 index 00000000..2fe63b7a --- /dev/null +++ b/Wabbajack.BuildServer/Models/ApiKey.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using MongoDB.Driver; +using MongoDB.Driver.Linq; + +namespace Wabbajack.BuildServer.Models +{ + public class ApiKey + { + public string Id { get; set; } + + public string Key { get; set; } + public string Owner { get; set; } + + public List CanUploadLists { get; set; } + public List Roles { get; set; } + + public static async Task Get(DBContext db, string key) + { + return await db.ApiKeys.AsQueryable().Where(k => k.Key == key).FirstOrDefaultAsync(); + } + } +} diff --git a/Wabbajack.BuildServer/Models/DBContext.cs b/Wabbajack.BuildServer/Models/DBContext.cs index 128825c9..d3fb92f3 100644 --- a/Wabbajack.BuildServer/Models/DBContext.cs +++ b/Wabbajack.BuildServer/Models/DBContext.cs @@ -27,11 +27,13 @@ namespace Wabbajack.BuildServer.Models public IMongoCollection Metrics => Client.GetCollection(_settings.Collections["Metrics"]); public IMongoCollection IndexedFiles => Client.GetCollection(_settings.Collections["IndexedFiles"]); public IMongoCollection>> NexusUpdates => Client.GetCollection>>(_settings.Collections["NexusUpdates"]); + + public IMongoCollection ApiKeys => Client.GetCollection(_settings.Collections["ApiKeys"]); + public IMongoCollection UploadedFiles => Client.GetCollection(_settings.Collections["UploadedFiles"]); public IMongoCollection> NexusModFiles => Client.GetCollection>( _settings.Collections["NexusModFiles"]); - private IMongoDatabase Client => new MongoClient($"mongodb://{_settings.Host}").GetDatabase(_settings.Database); } public class Settings diff --git a/Wabbajack.BuildServer/Models/Jobs/UpdateModLists.cs b/Wabbajack.BuildServer/Models/Jobs/UpdateModLists.cs index 43549101..badba9b3 100644 --- a/Wabbajack.BuildServer/Models/Jobs/UpdateModLists.cs +++ b/Wabbajack.BuildServer/Models/Jobs/UpdateModLists.cs @@ -26,7 +26,7 @@ namespace Wabbajack.BuildServer.Models.Jobs var whitelists = new ValidateModlist(queue); await whitelists.LoadListsFromGithub(); - foreach (var list in modlists) + foreach (var list in modlists.Skip(8).Take(1)) { try { diff --git a/Wabbajack.BuildServer/Models/UploadedFile.cs b/Wabbajack.BuildServer/Models/UploadedFile.cs new file mode 100644 index 00000000..9edd0426 --- /dev/null +++ b/Wabbajack.BuildServer/Models/UploadedFile.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using MongoDB.Bson.Serialization.Attributes; +using Wabbajack.Common; +using Path = Alphaleonis.Win32.Filesystem.Path; + +namespace Wabbajack.BuildServer.Models +{ + public class UploadedFile + { + public string Id { get; set; } + public string Name { get; set; } + public long Size { get; set; } + public string Hash { get; set; } + public string Uploader { get; set; } + public DateTime UploadDate { get; set; } = DateTime.UtcNow; + + [BsonIgnore] + public string MungedName => $"{Path.GetFileNameWithoutExtension(Name)}-{Id}-{Path.GetExtension(Name)}"; + + [BsonIgnore] public object Uri => $"https://static.wabbajack.org/files/{MungedName}"; + + public static async Task Ingest(DBContext db, IFormFile src, string uploader) + { + var record = new UploadedFile {Uploader = uploader, Name = src.FileName, Id = Guid.NewGuid().ToString()}; + var dest_path = + $@"public\\files\\{Path.GetFileNameWithoutExtension(src.FileName)}-{record.Id}{Path.GetExtension(src.FileName)}"; + + using (var stream = File.OpenWrite(dest_path)) + await src.CopyToAsync(stream); + record.Size = new FileInfo(dest_path).Length; + record.Hash = await dest_path.FileHashAsync(); + await db.UploadedFiles.InsertOneAsync(record); + return record; + } + } +} diff --git a/Wabbajack.BuildServer/Program.cs b/Wabbajack.BuildServer/Program.cs index f38d2517..b0cb70a4 100644 --- a/Wabbajack.BuildServer/Program.cs +++ b/Wabbajack.BuildServer/Program.cs @@ -21,7 +21,11 @@ namespace Wabbajack.BuildServer .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseUrls("http://*:5000"); - webBuilder.UseStartup(); + webBuilder.UseStartup() + .UseKestrel(options => + { + options.Limits.MaxRequestBodySize = null; + }); }); } } diff --git a/Wabbajack.BuildServer/Startup.cs b/Wabbajack.BuildServer/Startup.cs index fc7f46d7..f3a821a1 100644 --- a/Wabbajack.BuildServer/Startup.cs +++ b/Wabbajack.BuildServer/Startup.cs @@ -12,6 +12,12 @@ using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.AzureAD.UI; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -45,6 +51,19 @@ namespace Wabbajack.BuildServer { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Wabbajack Build API", Version = "v1"}); }); + + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = ApiKeyAuthenticationOptions.DefaultScheme; + options.DefaultChallengeScheme = ApiKeyAuthenticationOptions.DefaultScheme; + }) + .AddApiKeySupport(options => {}); + + services.Configure(x => + { + x.ValueLengthLimit = int.MaxValue; + x.MultipartBodyLengthLimit = int.MaxValue; + }); services.AddSingleton(); services.AddSingleton(); @@ -69,6 +88,7 @@ namespace Wabbajack.BuildServer app.UseDeveloperExceptionPage(); } + app.UseGraphiQl(); app.UseDeveloperExceptionPage(); app.UseStaticFiles(); diff --git a/Wabbajack.BuildServer/Wabbajack.BuildServer.csproj b/Wabbajack.BuildServer/Wabbajack.BuildServer.csproj index 4fc8332a..9f684b90 100644 --- a/Wabbajack.BuildServer/Wabbajack.BuildServer.csproj +++ b/Wabbajack.BuildServer/Wabbajack.BuildServer.csproj @@ -13,6 +13,7 @@ + @@ -74,4 +75,8 @@ + + + + diff --git a/Wabbajack.BuildServer/appsettings.json b/Wabbajack.BuildServer/appsettings.json index 9e7d3582..f7b96535 100644 --- a/Wabbajack.BuildServer/appsettings.json +++ b/Wabbajack.BuildServer/appsettings.json @@ -24,12 +24,15 @@ "JobQueue": "job_queue", "DownloadStates": "download_states", "IndexedFiles": "indexed_files", - "Metrics": "metrics" + "Metrics": "metrics", + "ApiKeys": "api_keys", + "UploadedFiles": "uploaded_files" } }, "WabbajackSettings": { "DownloadDir": "c:\\tmp\\downloads", - "ArchiveDir": "c:\\archives" + "ArchiveDir": "c:\\archives", + "MinimalMode": true }, "AllowedHosts": "*" } diff --git a/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs b/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs index f9cb3d70..31332ef4 100644 --- a/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs +++ b/Wabbajack.Lib/ModListRegistry/ModListMetadata.cs @@ -4,6 +4,7 @@ using System.Drawing; using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using MongoDB.Bson.Serialization.Attributes; using Newtonsoft.Json; using Wabbajack.Common; using File = System.IO.File; @@ -39,6 +40,7 @@ namespace Wabbajack.Lib.ModListRegistry [JsonIgnore] public ModlistSummary ValidationSummary { get; set; } = new ModlistSummary(); + [BsonIgnoreExtraElements] public class LinksObject { [JsonProperty("image")] diff --git a/Wabbajack.Lib/Wabbajack.Lib.csproj b/Wabbajack.Lib/Wabbajack.Lib.csproj index ba6bc79f..dcbd419f 100644 --- a/Wabbajack.Lib/Wabbajack.Lib.csproj +++ b/Wabbajack.Lib/Wabbajack.Lib.csproj @@ -39,6 +39,9 @@ 2.1.0 + + 2.10.0 + 11.1.6 From 476d6363db6c5bc14cb45ab49d9d700def438306 Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Wed, 15 Jan 2020 22:32:30 -0700 Subject: [PATCH 3/3] GraphQL client support for querying uploaded files --- Wabbajack.Lib/GraphQL/DTOs/UploadedFile.cs | 16 +++++++ Wabbajack.Lib/GraphQL/GraphQLService.cs | 53 ++++++++++++++++++++++ Wabbajack.Lib/Wabbajack.Lib.csproj | 3 ++ 3 files changed, 72 insertions(+) create mode 100644 Wabbajack.Lib/GraphQL/DTOs/UploadedFile.cs create mode 100644 Wabbajack.Lib/GraphQL/GraphQLService.cs diff --git a/Wabbajack.Lib/GraphQL/DTOs/UploadedFile.cs b/Wabbajack.Lib/GraphQL/DTOs/UploadedFile.cs new file mode 100644 index 00000000..f1d54b23 --- /dev/null +++ b/Wabbajack.Lib/GraphQL/DTOs/UploadedFile.cs @@ -0,0 +1,16 @@ +using System; + +namespace Wabbajack.Lib.GraphQL.DTOs +{ + public class UploadedFile + { + public string Id { get; set; } + public string Name { get; set; } + public string MungedName { get; set; } + public DateTime UploadDate { get; set; } + public string Uploader { get; set; } + public Uri Uri { get; set; } + public string Hash { get; set; } + public long Size { get; set; } + } +} diff --git a/Wabbajack.Lib/GraphQL/GraphQLService.cs b/Wabbajack.Lib/GraphQL/GraphQLService.cs new file mode 100644 index 00000000..40d7e2d5 --- /dev/null +++ b/Wabbajack.Lib/GraphQL/GraphQLService.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; +using GraphQL.Client; +using GraphQL.Client.Http; +using GraphQL.Common.Request; +using Wabbajack.Common; +using Wabbajack.Lib.GraphQL.DTOs; +using Path = Alphaleonis.Win32.Filesystem.Path; + +namespace Wabbajack.Lib.GraphQL +{ + public class GraphQLService + { + public static readonly Uri BaseURL = new Uri("https://build.wabbajack.org/graphql"); + public static readonly Uri UploadURL = new Uri("https://build.wabbajack.org/upload_file"); + + public async Task> GetUploadedFiles() + { + var client = new GraphQLHttpClient(BaseURL); + var query = new GraphQLRequest + { + Query = @" + query uploadedFilesQuery { + uploadedFiles { + id + name + hash + uri + uploader + uploadDate + } + }" + }; + var result = await client.SendQueryAsync(query); + return result.GetDataFieldAs>("uploadedFiles"); + } + + public async Task UploadFile(string filename) + { + using (var stream = new StatusFileStream(File.OpenRead(filename), $"Uploading {Path.GetFileName(filename)}")) + { + var client = new HttpClient(); + client.DefaultRequestHeaders.Add("X-API-KEY", "TODO"); + var form = new MultipartFormDataContent {{new StreamContent(stream), "file"}}; + var response = await client.PostAsync(UploadURL, form); + return response.IsSuccessStatusCode; + } + } + } +} diff --git a/Wabbajack.Lib/Wabbajack.Lib.csproj b/Wabbajack.Lib/Wabbajack.Lib.csproj index dcbd419f..fe642cb4 100644 --- a/Wabbajack.Lib/Wabbajack.Lib.csproj +++ b/Wabbajack.Lib/Wabbajack.Lib.csproj @@ -27,6 +27,9 @@ 2.2.2.1 + + 2.0.0-alpha.3 + 1.11.17