Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9b528c6
git version release
Jun 12, 2025
31f06c1
#141, Add zip download feature for selected media objects
kolev-sf Jun 13, 2025
cf437ee
Merge pull request #99 from scalefocus/fix/gitversion_release
cdjambov Jun 13, 2025
597f118
Merge pull request #100 from scalefocus/work/kolev-sf/ticket_141_down…
kolev-sf Jun 13, 2025
f36bf8f
fix healthchecks
borissiljan Jun 16, 2025
65f1db7
fix the tests
borissiljan Jun 16, 2025
3013f5b
Merge pull request #102 from scalefocus/bugfix/fix-healthchecks
borissiljan Jun 16, 2025
a9c209e
Fix the trashendpoint return with lastid set and page count set
Jun 16, 2025
03982ca
Revert "Fix the trashendpoint return with lastid set and page count set"
Jun 16, 2025
b6965cd
Fix the trashendpoint return with lastid set and page count set
Jun 16, 2025
33e40d5
Merge pull request #103 from scalefocus/fix/tuslinks
cdjambov Jun 16, 2025
f91b03c
fix git version yaml
Jun 17, 2025
9f38805
another attempt to fix the TUS location header
Jun 17, 2025
db60e1d
try fix the tests
Jun 17, 2025
aa408d0
fix the tests
Jun 17, 2025
5130760
revert GitVersion.yaml changes
Jun 17, 2025
8ccb8a8
Inherit the TusCreation attribute
Jun 17, 2025
aaa50e6
fix docker tag version
borissiljan Jun 17, 2025
222a027
Merge pull request #104 from scalefocus/fix/tuslinks
cdjambov Jun 17, 2025
b3796e2
Merge pull request #105 from scalefocus/bugfix/docker-tag-fix
borissiljan Jun 17, 2025
d591612
fix this already
Jun 17, 2025
e2367e9
Merge pull request #106 from scalefocus/fix/GitVersion
cdjambov Jun 17, 2025
65bd03a
an identation error
Jun 17, 2025
7b0e183
Merge pull request #107 from scalefocus/fix/GitVersion
borissiljan Jun 17, 2025
050ea59
Merge pull request #109 from scalefocus/develop
kolev-sf Jun 19, 2025
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
8 changes: 7 additions & 1 deletion .github/workflows/build_to_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ jobs:
- name: Get version suffix
run: |
version_suffix_string=""
delimiter=""
branch_name="${GITHUB_REF##*/}"

if [ "$branch_name" = "develop" ]; then
Expand All @@ -66,6 +67,11 @@ jobs:
version_suffix_string="beta"
fi

if [ -n "$version_suffix_string" ]; then
delimiter="-"
fi

echo "DOCKER_TAG=${{ steps.gitversion.outputs.MajorMinorPatch }}${delimiter}${version_suffix_string}" >> "$GITHUB_ENV"
echo "VERSION_SUFFIX=$version_suffix_string" >> "$GITHUB_ENV"

- name: Build docker image
Expand All @@ -75,6 +81,6 @@ jobs:
--no-cache \
--build-arg VERSION="${{ steps.gitversion.outputs.MajorMinorPatch }}" \
--build-arg VERSION_SUFFIX="${{ env.VERSION_SUFFIX }}" \
-t scalefocusad/photopixels-backend-net:"${{ steps.gitversion.outputs.MajorMinorPatch }}-${{ env.VERSION_SUFFIX }}" \
-t scalefocusad/photopixels-backend-net:"${{ env.DOCKER_TAG }}" \
-f ./src/SF.PhotoPixels.API/Dockerfile . \
--push
18 changes: 17 additions & 1 deletion GitVersion.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ strategies:
- Mainline
branches:
main:
mode: ContinuousDelivery
label: ''
increment: Patch
prevent-increment:
Expand All @@ -14,6 +13,8 @@ branches:
regex: ^master$|^main$
source-branches:
- develop
is-source-branch-for:
- release
tracks-release-branches: false
is-release-branch: false
is-main-branch: true
Expand All @@ -29,6 +30,21 @@ branches:
regex: ^dev(elop)?(ment)?$
is-source-branch-for:
- main
- release
tracks-release-branches: true
is-release-branch: false
is-main-branch: false

release:
label: ''
increment: Patch
prevent-increment:
when-current-commit-tagged: true
track-merge-target: true
track-merge-message: true
regex: ^release\/(v)?\d+\.\d+\.\d+$ # Regex for branches like release/1.0.0 or release/v1.0.0
is-source-branch-for:
- main
tracks-release-branches: false
is-release-branch: true
is-main-branch: false
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ protected async Task<string> PostTusWhiteImage()

var response = await _httpClient.SendAsync(message);

return response.Headers.GetValues("Location").First().Substring(11);
return response.Headers.GetValues("Location").First().Replace("send_data", string.Empty).TrimStart('/');
}
private async Task CleanUpDirectories()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using FluentAssertions;
using System.Net;
using System.Net;
using FluentAssertions;
using Xunit;

namespace SF.PhotoPixels.API.Integration.Tests.ResumableEndpoints;
Expand All @@ -20,7 +20,7 @@ public async Task DeleteUpload_WithValidId_ShouldReturnNoContent()
var message = new HttpRequestMessage()
{
Method = HttpMethod.Delete,
RequestUri = new Uri($"/send_data/{id}", UriKind.Relative)
RequestUri = new Uri($"send_data/{id}", UriKind.Relative)
};

message.Headers.Add("Tus-Resumable", "1.0.0");
Expand All @@ -39,7 +39,7 @@ public async Task DeleteUpload_WithNonExistingId_ShouldReturnNotFound()
var message = new HttpRequestMessage()
{
Method = HttpMethod.Delete,
RequestUri = new Uri($"/send_data/{Guid.NewGuid():N}", UriKind.Relative)
RequestUri = new Uri($"send_data/{Guid.NewGuid():N}", UriKind.Relative)
};

message.Headers.Add("Tus-Resumable", "1.0.0");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,22 @@ public async Task Health_WithAdminAuth_ShouldReturnOk()
}

[Fact]
public async Task Health_WithNoAuth_ShouldReturnUnauthorized()
public async Task Health_WithNoAuth_ShouldReturnOk()
{
var response = await _httpClient.GetAsync("/health");

response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
response.StatusCode.Should().Be(HttpStatusCode.OK);
}

[Fact]
public async Task Health_WithContributorAuth_ShouldReturnForbidden()
public async Task Health_WithContributorAuth_ShouldReturnOk()
{
await AuthenticateAsSeededAdminAsync();
await SeedDefaultContributorAsync();
await AuthenticateAsDefaultContributorAsync();

var response = await _httpClient.GetAsync("/health");

response.StatusCode.Should().Be(HttpStatusCode.Forbidden);
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Ardalis.ApiEndpoints;
using Mediator;
using Microsoft.AspNetCore.Mvc;
using SF.PhotoPixels.Application.Query.PhotoStorage.LoadMedia;
using Swashbuckle.AspNetCore.Annotations;

namespace SF.PhotoPixels.API.Endpoints.PhotosEndpoints;

public class DownloadObjects : EndpointBaseAsync.WithRequest<DownloadObjectsRequest>.WithoutResult
{
private readonly IMediator _mediator;

public DownloadObjects(IMediator mediator)
{
_mediator = mediator;
}

[HttpPost("/object/downloadZip")]
[SwaggerOperation(
Summary = "Download selected photos/videos as a zip file",
Description = "Download photos/videos from the server as a zip file",
Tags = new[] { "Object operations" }),
]
public override async Task<ActionResult> HandleAsync([FromBody] DownloadObjectsRequest request, CancellationToken cancellationToken = default)
{
var result = await _mediator.Send(request, cancellationToken);

return result.Match<ActionResult>(
zip => File(zip, "application/zip", "files.zip"),
notFound => new NoContentResult()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Ardalis.ApiEndpoints;
using JasperFx.Core;
using Mediator;
using Microsoft.AspNetCore.Mvc;
using SF.PhotoPixels.Application.Commands.Tus;
Expand All @@ -18,7 +19,7 @@ public CreateUploadEndpoint(IMediator mediator)
}

[UpdateMetadata]
[TusCreation("create_upload")]
[TusCreationPhotopixels("create_upload")]
[SwaggerOperation(
Summary = "Create and get info on a new upload resource",
Tags = ["Tus"]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public GetUserUploadsEndpoint(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet("/resumable_uploads")]
[HttpGet("resumable_uploads")]
[SwaggerOperation(
Summary = "Get User current resumable uploads",
Tags = ["Tus"]),
Expand Down
58 changes: 36 additions & 22 deletions src/SF.PhotoPixels.API/Extensions/AppConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using Serilog.Debugging;
using Serilog.Sinks.Grafana.Loki;
using Serilog;
using HealthChecks.Network.Core;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Diagnostics;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using FFMpegCore;
using Microsoft.Extensions.Hosting;
using HealthChecks.Network.Core;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Options;
using Serilog;
using Serilog.Debugging;
using Serilog.Sinks.Grafana.Loki;
using SF.PhotoPixels.Infrastructure.Options;

namespace SF.PhotoPixels.API.Extensions
{
Expand All @@ -26,6 +27,7 @@ public static TelemetryConfiguration ConfigureTelemetry(this WebApplicationBuild
AppName = appName,
};
builder.Configuration.Bind(TelemetryConfiguration.SectionName, telemetryConfiguration);

return telemetryConfiguration;
}

Expand Down Expand Up @@ -59,17 +61,29 @@ public static void ConfigureLogging(this WebApplicationBuilder builder, Telemetr

public static void ConfigureHealthChecks(this WebApplicationBuilder builder)
{
var smtpOptions = builder.Configuration.GetSection("EmailConfiguration");
var smtpOptions = builder.Configuration.GetSection("EmailConfiguration").Get<SmtpOptions>();

builder.Services.AddHealthChecks()
var healthCheckBuilder = builder.Services.AddHealthChecks()
.AddNpgSql(builder.Configuration.GetConnectionString("PhotosMetadata"))
.AddPingHealthCheck(options => options.AddHost("localhost", 1000))
.AddPingHealthCheck(options => options.AddHost("localhost", 1000));

if (smtpOptions is null)
{
return;
}

if (!smtpOptions.IsValidConfiguration())
{
return;
}

healthCheckBuilder
.AddSmtpHealthCheck(setup =>
{
setup.Host = smtpOptions.GetValue<string>("Host");
setup.Port = smtpOptions.GetValue<int>("Port");
setup.ConnectionType = smtpOptions.GetValue<bool>("UseSsl") == true ? SmtpConnectionType.SSL : SmtpConnectionType.TLS;
setup.LoginWith(smtpOptions.GetValue<string>("Username"), smtpOptions.GetValue<string>("Password"));
setup.Host = smtpOptions.Host;
setup.Port = smtpOptions.Port ?? 22;
setup.ConnectionType = smtpOptions.UseSsl ? SmtpConnectionType.SSL : SmtpConnectionType.TLS;
setup.LoginWith(smtpOptions.Username, smtpOptions.Password);
});
}

Expand Down Expand Up @@ -107,12 +121,13 @@ public static string GetRootDirectory()
public static void ConfigureWebServersUpperLimitOptions(this WebApplicationBuilder builder)
{
var upperLimit = builder.Configuration.GetValue<int>("UploadFileUpperLimit");

if (upperLimit == 0)
{
upperLimit = defaultUpperLimit; // default value 50 MB
}

builder.Services.Configure<Microsoft.AspNetCore.Http.Features.FormOptions>(options =>
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = upperLimit;
});
Expand Down Expand Up @@ -153,13 +168,12 @@ public static void ConfigureFFmpegVideoSupport(this WebApplication app)
}

// configure ffmpeg global options
var binaryFolder = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ?
"/bin"
var binaryFolder = RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
? "/bin"
: ffmpegWindowsBinaryFolder;

var temporaryFilesFolder = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ?
"/tmp" :
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "sf-photos", "temp");
var temporaryFilesFolder = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/tmp" : Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "sf-photos", "temp");

if (!Directory.Exists(temporaryFilesFolder))
{
Directory.CreateDirectory(temporaryFilesFolder);
Expand All @@ -173,4 +187,4 @@ public static void ConfigureFFmpegVideoSupport(this WebApplication app)
GlobalFFOptions.Configure(ffOptions);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.DependencyInjection;
using SolidTUS.Constants;
using SolidTUS.Models;
using SolidTUS.ProtocolFlows;
using SolidTUS.ProtocolHandlers;
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Routing;
using Endpoints = System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Routing.EndpointDataSource>;

using static Microsoft.AspNetCore.Http.HttpMethods;
using SolidTUS.Functional.Models;
namespace SolidTUS.Attributes;

/// <summary>
/// Identifies an action that supports TUS resource creation.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class TusCreationPhotopixelsAttribute : TusCreationAttribute
{
/// <summary>
/// Instantiate a new object of <see cref="TusCreationAttribute"/>
/// </summary>
public TusCreationPhotopixelsAttribute()
{
}

/// <summary>
/// Instantiate a new <see cref="TusCreationAttribute"/> creation endpoint handler.
/// </summary>
/// <param name="template">The route template</param>
public TusCreationPhotopixelsAttribute([StringSyntax("Route")] string template) : base(template)
{

}

public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
await base.OnActionExecutionAsync(context, next);
}

/// <inheritdoc />
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
//base.OnResultExecutionAsync(context, next);
// Before sending headers -->
context.HttpContext.Response.OnStarting(state =>
{
var ctx = (ResultExecutingContext)state;
var isPost = IsPost(ctx.HttpContext.Request.Method);
var response = ctx.HttpContext.Response;
var isSuccess = response.StatusCode >= 200 && response.StatusCode < 300;
if (isPost && isSuccess)
{
ctx.HttpContext.Response.StatusCode = 201;
string location = ctx.HttpContext.Response.Headers["Location"]!;
ctx.HttpContext.Response.Headers["Location"] = location.TrimStart('/');
}
return Task.CompletedTask;
}, context);

await next();
}
}
Loading
Loading