Skip to content
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
21 changes: 17 additions & 4 deletions AurionCal.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,25 @@
</ItemGroup>

<ItemGroup>
<Content Update="Templates\\**\\*">
<Compile Remove="AurionCal.Tests/**" />
<Content Remove="AurionCal.Tests/**" />
<None Remove="AurionCal.Tests/**" />
<EmbeddedResource Remove="AurionCal.Tests/**" />
</ItemGroup>

<ItemGroup>
<!--
RazorLight templates are compiled at runtime, not at build time.
Removing .cshtml files from Content prevents the SDK Razor source generator
from picking them up, which would conflict with RazorLight's own Razor engine
and produce duplicate assembly attributes (CS0579) and CS8785 warnings.
-->
<Content Remove="Templates\**\*.cshtml" />
<None Include="Templates\**\*.cshtml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>

<None Update="Templates\\**\\*">
</None>
<None Update="Templates\**\*.cs">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</None>
Expand Down
3 changes: 2 additions & 1 deletion Endpoints/GetCalendarFeedEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public override async Task HandleAsync(GetCalendarFeedRequest r, CancellationTok
.Include(u => u.RefreshStatus)
.FirstOrDefaultAsync(u => u.Id == r.UserId, c);


if (user == null || user.CalendarToken != r.Token)
{
await Send.NotFoundAsync(c);
Expand Down Expand Up @@ -58,7 +59,7 @@ public override async Task HandleAsync(GetCalendarFeedRequest r, CancellationTok
}).ToList() ?? [];
});

var feed = calendarService.GenerateCalendarFeed(planningEvents);
var feed = calendarService.GenerateCalendarFeed(planningEvents, user.ExamAccommodations);

HttpContext.Response.Headers.Append("Content-Disposition", "attachment; filename=\"Planning Junia.ics\"");
HttpContext.Response.ContentType = "text/calendar";
Expand Down
4 changes: 3 additions & 1 deletion Endpoints/GetUserProfileEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class UserProfileResponse
public string Email { get; set; } = string.Empty;
public string CalendarFeedUrl { get; set; } = string.Empty;
public DateTime? LastUpdated { get; set; }
public bool ExamAccommodations { get; set; }
}

public class GetUserProfileEndpoint(ApplicationDbContext db, IConfiguration config) : EndpointWithoutRequest<UserProfileResponse>
Expand Down Expand Up @@ -49,7 +50,8 @@ public override async Task HandleAsync(CancellationToken ct)
UserId = user.Id,
Email = user.JuniaEmail,
CalendarFeedUrl = calendarUrl,
LastUpdated = user.LastUpdate ?? null
LastUpdated = user.LastUpdate ?? null,
ExamAccommodations = user.ExamAccommodations
};

await Send.OkAsync(response, ct);
Expand Down
41 changes: 41 additions & 0 deletions Endpoints/SetExamAccommodationsEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Security.Claims;
using AurionCal.Api.Contexts;
using FastEndpoints;
using Microsoft.EntityFrameworkCore;

namespace AurionCal.Api.Endpoints;

public class SetExamAccommodationsRequest
{
public bool Enabled { get; set; }
}

public class SetExamAccommodationsEndpoint(ApplicationDbContext db) : Endpoint<SetExamAccommodationsRequest>
{
public override void Configure()
{
Patch("/api/user/exam-accommodations");
Claims("UserId");
}

public override async Task HandleAsync(SetExamAccommodationsRequest r, CancellationToken ct)
{
var userIdValue = User.FindFirstValue("UserId");
if (string.IsNullOrWhiteSpace(userIdValue) || !Guid.TryParse(userIdValue, out var userId))
{
await Send.UnauthorizedAsync(ct);
return;
}

var user = await db.Users.FirstOrDefaultAsync(u => u.Id == userId, ct);
if (user is null)
{
await Send.NotFoundAsync(ct);
return;
}

user.ExamAccommodations = r.Enabled;
await db.SaveChangesAsync(ct);
await Send.OkAsync(ct);
}
}
1 change: 1 addition & 0 deletions Entities/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public class User
public DateTime? LastUpdate { get; set; }
public virtual List<CalendarEvent> Planning { get; set; }
public Guid CalendarToken { get; set; }
public bool ExamAccommodations { get; set; }
public virtual UserRefreshStatus? RefreshStatus { get; set; }
}
1 change: 1 addition & 0 deletions Enums/CourseTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public static class RawCourseTypes
public const string CoursTp = "TP";
public const string Projet = "PROJET";
public const string Epreuve = "est-epreuve";
public const string EpreuveAlt = "EXAM_SURV";
public const string AutoAppr = "AUTO_APPR";
public const string Reunion = "REUNION";
public const string Conference = "CONF";
Expand Down
150 changes: 150 additions & 0 deletions Migrations/20260622000000_AddExamAccommodations.Designer.cs

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

29 changes: 29 additions & 0 deletions Migrations/20260622000000_AddExamAccommodations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace AurionCal.Api.Migrations
{
/// <inheritdoc />
public partial class AddExamAccommodations : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "ExamAccommodations",
table: "Users",
type: "boolean",
nullable: false,
defaultValue: false);
}

/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ExamAccommodations",
table: "Users");
}
}
}
3 changes: 3 additions & 0 deletions Migrations/ApplicationDbContextModelSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property<Guid>("CalendarToken")
.HasColumnType("uuid");

b.Property<bool>("ExamAccommodations")
.HasColumnType("boolean");

b.Property<string>("JuniaEmail")
.IsRequired()
.HasColumnType("text");
Expand Down
4 changes: 2 additions & 2 deletions Services/CalendarService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,14 +175,14 @@ private static TimeSpan ComputeBackoff(int consecutiveFailures)
private static string Truncate(string value, int max)
=> value.Length <= max ? value : value[..max];

public string GenerateCalendarFeed(IEnumerable<CalendarEvent> planningEvents)
public string GenerateCalendarFeed(IEnumerable<CalendarEvent> planningEvents, bool examAccommodations = false)
{
var calendar = new Calendar();
calendar.AddTimeZone(new VTimeZone("Europe/Paris"));

foreach (var evt in planningEvents)
{
calendar.Events.Add(CalendarEventFormatter.ToIcalEvent(evt));
calendar.Events.Add(CalendarEventFormatter.ToIcalEvent(evt, examAccommodations));
}

return new CalendarSerializer().SerializeToString(calendar);
Expand Down
8 changes: 3 additions & 5 deletions Services/CourseTypeMappings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public static class CourseTypeMappings
[RawCourseTypes.CoursTp] = CourseType.CoursTp,
[RawCourseTypes.Projet] = CourseType.Projet,
[RawCourseTypes.Epreuve] = CourseType.Epreuve,
[RawCourseTypes.EpreuveAlt] = CourseType.Epreuve,
[RawCourseTypes.AutoAppr] = CourseType.AutoAppr,
[RawCourseTypes.Reunion] = CourseType.Reunion,
[RawCourseTypes.Conference] = CourseType.Conference,
Expand Down Expand Up @@ -40,16 +41,13 @@ public static CourseType Parse(string? rawType)

var normalized = Normalize(rawType);

if (RawToEnum.TryGetValue(normalized, out var found))
return found;

if (RawToEnum.TryGetValue(rawType, out found))
if (RawToEnum.TryGetValue(normalized, out var found) || RawToEnum.TryGetValue(rawType, out found))
return found;

return CourseType.Unknown;
}

public static string ToDisplayName(CourseType type)
private static string ToDisplayName(CourseType type)
{
return EnumToDisplay.TryGetValue(type, out var display)
? display
Expand Down
Loading