# AWS S3

## Description

AWS S3 can be used as the backing implementation for Flex file operations. Your application code should depend on `IFlexFileStore`, while Flex provides the provider bridge and wiring.

## Important concepts

* **`IFlexFileStore` is the contract**: app code uploads/downloads using the shared interface rather than AWS SDK types.
* **Files are streamed**: uploads use a `Stream`; downloads return a `Stream?` (when a file doesn’t exist, you should handle `null`).
* **Provider bridge**: the AWS implementation is exposed via an `IFlexFileStoreBridge` internally, but most consumers only need `IFlexFileStore`.

## Configuration in DI

Add the provider in your DI composition root (commonly in `EndPoints/...CommonConfigs/OtherApplicationServicesConfig.cs` or wherever you centralize registrations).

```csharp
// using Sumeru.Flex; // IFlexFileStore

public static class OtherApplicationServicesConfig
{
	public static IServiceCollection AddOtherApplicationServices(
		this IServiceCollection services,
		IConfiguration configuration)
	{
		// Registers AWS S3 as the IFlexFileStore bridge.
		// Flex auto-wires generated Queries/Handlers that *use* IFlexFileStore.
		services.AddFlexAwsS3FileStore(configuration);

		return services;
	}
}
```

## appsettings.json

Configuration is read from `FlexBase:DataStores:File:AwsS3`.

```json
{
  "FlexBase": {
	"DataStores": {
	  "File": {
		"AwsS3": {
		  "AccessKeyId": "...",
		  "SecretAccessKey": "...",
		  "Region": "us-east-1",
		  "BucketName": "your-bucket-name",
		  "ServiceUrl": null
		}
	  }
	}
  }
}
```

## Examples (template-based)

These examples mirror the generated Query and PostBusHandler templates. You do **not** register these types manually—Flex discovers and wires generated Queries/Handlers automatically.

### Upload file (PostBusHandler)

```csharp
using Microsoft.Extensions.Logging;
using Sumeru.Flex;
using System.IO;
using System.Threading.Tasks;

namespace {YourApplication}.Handlers.Files;

public partial class UploadFileHandler : IAmFlexCommandHandler<UploadFileCommand>
{
	protected string EventCondition = "";

	protected readonly ILogger<UploadFileHandler> _logger;
	protected readonly IFlexHost _flexHost;
	protected readonly IFlexFileStore _fileStore;

	public UploadFileHandler(ILogger<UploadFileHandler> logger, IFlexHost flexHost, IFlexFileStore fileStore)
	{
		_logger = logger;
		_flexHost = flexHost;
		_fileStore = fileStore;
	}

	public virtual async Task Execute(UploadFileCommand cmd, IFlexServiceBusContext serviceBusContext)
	{
		_logger.LogDebug("Starting Upload for {EntityType}", nameof(FileModel));

		var appContext = cmd.Dto.GetAppContext(); // do not remove this line

		var model = _flexHost.GetDomainModel<FileModel>().Create(cmd);

		var fileId = await _fileStore.UploadAsync(
			cmd.Dto.Stream,
			cmd.Dto.FileName,
			cmd.Dto.ContentType,
			cmd.Dto.Metadata);

		// Example:
		// EventCondition = UploadFileEvents.CONDITION_ONSUCCESS;
		await this.Fire(EventCondition, serviceBusContext);

		_logger.LogDebug("Completed Upload for {EntityType} {FileId}, Event: {EventCondition}", nameof(FileModel), fileId, EventCondition);
	}
}

public class UploadFileCommand : FlexCommandBridge<UploadFileDto> { }

public class UploadFileDto : DtoBridge
{
	public Stream Stream { get; set; }
	public string FileName { get; set; }
	public string ContentType { get; set; }
	public IDictionary<string, string>? Metadata { get; set; }
}

public class FileModel : FlexDomainModelBridge { }
```

### Download file (Query)

```csharp
using Microsoft.Extensions.Logging;
using Sumeru.Flex;
using System.IO;
using System.Threading.Tasks;

namespace {YourApplication}.Queries.Files;

public class DownloadFileQuery : FlexiQueryBridgeAsync<FileDownloadOutput>
{
	protected readonly ILogger<DownloadFileQuery> _logger;
	protected readonly IFlexHost _flexHost;
	protected readonly IFlexFileStore _fileStore;
	protected DownloadFileParams _params;

	public DownloadFileQuery(ILogger<DownloadFileQuery> logger, IFlexHost flexHost, IFlexFileStore fileStore)
	{
		_logger = logger;
		_flexHost = flexHost;
		_fileStore = fileStore;
	}

	public virtual DownloadFileQuery AssignParameters(DownloadFileParams @params)
	{
		_params = @params;
		return this;
	}

	public virtual async Task<FileDownloadOutput?> Fetch()
	{
		var appContext = _params.GetAppContext();

		var stream = await _fileStore.DownloadAsync(_params.FileId);
		if (stream == null)
		{
			return default;
		}

		return new FileDownloadOutput
		{
			FileId = _params.FileId,
			Stream = stream
		};
	}
}

public class DownloadFileParams : DtoBridge
{
	public string FileId { get; set; }
}

public class FileDownloadOutput : DtoBridge
{
	public string FileId { get; set; }
	public Stream Stream { get; set; }
	public string? FileName { get; set; }
	public string? ContentType { get; set; }
}
```

## Library-specific considerations

* **Credentials**: prefer environment/secret manager injection; avoid committing `AccessKeyId` / `SecretAccessKey` to source control.
* **Bucket policy**: the executing identity typically needs `s3:GetObject`, `s3:PutObject`, and `s3:ListBucket`.
* **`ServiceUrl`**: useful for S3-compatible endpoints (for example LocalStack or MinIO).
