diff --git a/deploy/portainer-stack.yml b/deploy/portainer-stack.yml
index 252767a..f16e389 100644
--- a/deploy/portainer-stack.yml
+++ b/deploy/portainer-stack.yml
@@ -1,6 +1,6 @@
services:
beacon-server:
- image: beacon-server:0.1.10
+ image: beacon-server:0.1.11
restart: always
environment:
ASPNETCORE_ENVIRONMENT: Production
@@ -59,7 +59,7 @@ services:
/bin/sh -c '(while sleep 86400; do nginx -s reload; done) & exec nginx -g "daemon off;"'
beacon-backup:
- image: beacon-backup:0.1.10
+ image: beacon-backup:0.1.11
restart: always
environment:
BACKUP_INTERVAL_SECONDS: "86400"
diff --git a/src/Beacon.Plugin/Beacon.Plugin.csproj b/src/Beacon.Plugin/Beacon.Plugin.csproj
index 76e2b92..7c1c611 100644
--- a/src/Beacon.Plugin/Beacon.Plugin.csproj
+++ b/src/Beacon.Plugin/Beacon.Plugin.csproj
@@ -4,7 +4,7 @@
Beacon
Beacon
- 0.1.10.0
+ 0.1.11.0
enable
diff --git a/src/Beacon.Plugin/UI/ProfileLabels.cs b/src/Beacon.Plugin/UI/ProfileLabels.cs
index d83edd2..8a3fa10 100644
--- a/src/Beacon.Plugin/UI/ProfileLabels.cs
+++ b/src/Beacon.Plugin/UI/ProfileLabels.cs
@@ -190,7 +190,7 @@ public static string LastSeen(DateTimeOffset? lastActive)
< 90 => "Seen within the hour",
< 60 * 12 => "Seen today",
< 60 * 36 => "Seen yesterday",
- < 60 * 24 * 7 => $"Seen {(int)span.TotalDays} days ago",
+ < 60 * 24 * 7 => (int)span.TotalDays == 1 ? "Seen yesterday" : $"Seen {(int)span.TotalDays} days ago",
< 60 * 24 * 14 => "Seen last week",
< 60 * 24 * 60 => $"Seen {(int)(span.TotalDays / 7)} weeks ago",
_ => "Not seen in months",
diff --git a/src/Beacon.Server/Endpoints/ImageEndpoints.cs b/src/Beacon.Server/Endpoints/ImageEndpoints.cs
index 8a895ff..d61672e 100644
--- a/src/Beacon.Server/Endpoints/ImageEndpoints.cs
+++ b/src/Beacon.Server/Endpoints/ImageEndpoints.cs
@@ -155,9 +155,29 @@ private static async Task ServeAsync(
BeaconDbContext db,
CancellationToken ct)
{
+ // Beacon screenshots and profile gallery images share one store on disk but keep separate
+ // metadata tables. Looking in only the screenshot table is what made every profile portrait
+ // return 404 while its bytes sat on disk the whole time.
var record = await db.Images.FirstOrDefaultAsync(i => i.Id == imageId, ct);
- if (record is null)
- return Results.NotFound();
+
+ string contentType;
+ DateTimeOffset createdAt;
+
+ if (record is not null)
+ {
+ contentType = record.ContentType;
+ createdAt = record.CreatedAt;
+ }
+ else
+ {
+ var gallery = await db.ProfileImages.FirstOrDefaultAsync(i => i.ImageId == imageId, ct);
+ if (gallery is null)
+ return Results.NotFound();
+
+ // The gallery stores no content type because everything in the store is written as WebP.
+ contentType = "image/webp";
+ createdAt = gallery.CreatedAt;
+ }
var wantsPng = string.Equals(format, "png", StringComparison.OrdinalIgnoreCase);
@@ -168,16 +188,16 @@ private static async Task ServeAsync(
if (path is null || !File.Exists(path))
return Results.NotFound();
- var contentType = wantsPng ? "image/png" : record.ContentType;
+ var served = wantsPng ? "image/png" : contentType;
var variant = $"{(thumb ? "t" : "f")}{(wantsPng ? "p" : "w")}";
// Image ids are never reused and the bytes never change, so this can be cached hard.
// The tag includes the variant so a PNG and a WebP of the same image never collide in a cache.
return Results.File(
path,
- contentType,
+ served,
enableRangeProcessing: true,
- lastModified: record.CreatedAt,
+ lastModified: createdAt,
entityTag: new Microsoft.Net.Http.Headers.EntityTagHeaderValue($"\"{imageId:N}{variant}\""));
}