Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/show last opened files #15

Merged
merged 3 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 20 additions & 15 deletions src/IPA.Bcfier.App/Controllers/BcfConversionController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,39 +24,44 @@ public BcfConversionController(ElectronWindowProvider electronWindowProvider)
[ProducesResponseType((int)HttpStatusCode.NoContent)]
[ProducesResponseType(typeof(ApiError), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(BcfFileWrapper), (int)HttpStatusCode.OK)]
public async Task<IActionResult> ImportBcfFileAsync()
public async Task<IActionResult> ImportBcfFileAsync([FromQuery] string? filePath)
{
var electronWindow = _electronWindowProvider.BrowserWindow;
if (electronWindow == null)
if (string.IsNullOrWhiteSpace(filePath))
{
return BadRequest();
}
var electronWindow = _electronWindowProvider.BrowserWindow;
if (electronWindow == null)
{
return BadRequest();
}

var fileSelectionResult = await Electron.Dialog.ShowOpenDialogAsync(electronWindow, new OpenDialogOptions
{
Filters = new []
var fileSelectionResult = await Electron.Dialog.ShowOpenDialogAsync(electronWindow, new OpenDialogOptions
{
Filters = new[]
{
new FileFilter
{
Name = "BCF File",
Extensions = new string[] { "bcf", "bcfzip" }
}
}
});
});

if (fileSelectionResult == null)
{
return NoContent();
if (fileSelectionResult == null)
{
return NoContent();
}

filePath = fileSelectionResult.First();
}

try
{
using var bcfFileStream = System.IO.File.OpenRead(fileSelectionResult.First());
var bcfFileName = Path.GetFileName(fileSelectionResult.FirstOrDefault());
using var bcfFileStream = System.IO.File.OpenRead(filePath);
var bcfFileName = Path.GetFileName(filePath);
var bcfResult = await new BcfImportService().ImportBcfFileAsync(bcfFileStream, bcfFileName ?? "issue.bcf");
return Ok(new BcfFileWrapper
{
FileName = fileSelectionResult.First(),
FileName = filePath,
BcfFile = bcfResult
});
}
Expand Down
76 changes: 76 additions & 0 deletions src/IPA.Bcfier.App/Controllers/LastOpenedFilesController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using IPA.Bcfier.App.Data;
using IPA.Bcfier.App.Models.Controllers.LastOpenedFiles;
using IPA.Bcfier.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
using System.Net;

namespace IPA.Bcfier.App.Controllers
{
[ApiController]
[Route("api/last-opened-files")]
public class LastOpenedFilesController : ControllerBase
{
private readonly BcfierDbContext _context;
private readonly SettingsService _settingsService;

public LastOpenedFilesController(BcfierDbContext context,
SettingsService settingsService)
{
_context = context;
_settingsService = settingsService;
}

[HttpGet("")]
[ProducesResponseType(typeof(LastOpenedFilesWrapperGet), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetLastOpenedFilesAsync([FromQuery]Guid? projectId)
{
var userName = (await _settingsService.LoadSettingsAsync()).Username;

var lastOpenedFiles = await _context
.LastOpenedUserFiles
.Where(louf => louf.UserName == userName && louf.ProjectId == projectId)
.OrderByDescending(louf => louf.OpenedAtAtUtc)
.Select(louf => new LastOpenedFileGet
{
FileName = louf.FilePath,
OpenedAtUtc = louf.OpenedAtAtUtc
})
.Take(10)
.ToListAsync();

return Ok(new LastOpenedFilesWrapperGet
{
LastOpenedFiles = lastOpenedFiles
});
}

[HttpPut("")]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
public async Task<IActionResult> SetFileAsLastOpened([FromQuery]Guid? projectId, [FromQuery, Required]string filePath)
{
var userName = (await _settingsService.LoadSettingsAsync()).Username;
var existingEntry = await _context.LastOpenedUserFiles
.FirstOrDefaultAsync(louf => louf.ProjectId == projectId
&& louf.UserName == userName
&& louf.FilePath == filePath);
if (existingEntry != null)
{
existingEntry.OpenedAtAtUtc = DateTimeOffset.UtcNow;
}
else
{
_context.LastOpenedUserFiles.Add(new Data.Models.LastOpenedUserFile
{
ProjectId = projectId,
UserName = userName,
FilePath = filePath
});
}

await _context.SaveChangesAsync();
return NoContent();
}
}
}
36 changes: 34 additions & 2 deletions src/IPA.Bcfier.App/Data/BcfierDbContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using IPA.Bcfier.App.Data.Models;
using Microsoft.EntityFrameworkCore;
using System.Security.Cryptography.X509Certificates;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace IPA.Bcfier.App.Data
{
Expand All @@ -13,5 +14,36 @@ public BcfierDbContext(DbContextOptions<BcfierDbContext> options) : base(options
public DbSet<Project> Projects { get; set; }

public DbSet<ProjectUser> ProjectUsers { get; set; }

public DbSet<LastOpenedUserFile> LastOpenedUserFiles { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

LastOpenedUserFile.OnModelCreating(modelBuilder);

if (Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite")
{
// SQLite does not have proper support for DateTimeOffset via Entity Framework Core, see the limitations
// here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations
// To work around this, when the Sqlite database provider is used, all model properties of type DateTimeOffset
// use the DateTimeOffsetToBinaryConverter
// Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754
// This only supports millisecond precision, but should be sufficient for most use cases.
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset)
|| p.PropertyType == typeof(DateTimeOffset?));
foreach (var property in properties)
{
modelBuilder
.Entity(entityType.Name)
.Property(property.Name)
.HasConversion(new DateTimeOffsetToBinaryConverter());
}
}
}
}
}
}
}
28 changes: 28 additions & 0 deletions src/IPA.Bcfier.App/Data/Models/LastOpenedUserFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;

namespace IPA.Bcfier.App.Data.Models
{
public class LastOpenedUserFile
{
public Guid Id { get; set; }

public Guid? ProjectId { get; set; }

public Project? Project { get; set; }

public DateTimeOffset OpenedAtAtUtc { get; set; } = DateTimeOffset.UtcNow;

[Required]
public string FilePath { get; set; } = string.Empty;

[Required]
public string UserName { get; set; } = string.Empty;

public static void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<LastOpenedUserFile>()
.HasIndex(x => x.UserName);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading