Add project files.
This commit is contained in:
11
OfflineDemoApi/Data/ISqlDataAccess.cs
Normal file
11
OfflineDemoApi/Data/ISqlDataAccess.cs
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
|
||||
namespace OfflineDemoApi.Data
|
||||
{
|
||||
public interface ISqlDataAccess
|
||||
{
|
||||
Task<List<T>> LoadData<T, U>(string storedProcedure, U parameters, string connectionStringName);
|
||||
Task SaveData<T>(string storedProcedure, T parameters, string connectionStringName);
|
||||
Task<T> SaveDataScalar<T, U>(string storedProcedure, U parameters, string connectionStringName);
|
||||
}
|
||||
}
|
||||
58
OfflineDemoApi/Data/SqlDataAccess.cs
Normal file
58
OfflineDemoApi/Data/SqlDataAccess.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using Dapper;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System.Data;
|
||||
|
||||
namespace OfflineDemoApi.Data;
|
||||
|
||||
public class SqlDataAccess : ISqlDataAccess
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
|
||||
public SqlDataAccess(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public async Task<List<T>> LoadData<T, U>(string storedProcedure,
|
||||
U parameters,
|
||||
string connectionStringName)
|
||||
{
|
||||
string connectionString = _config.GetConnectionString(connectionStringName) ??
|
||||
throw new KeyNotFoundException("Did not find the connection string specified");
|
||||
|
||||
using IDbConnection connection = new SqlConnection(connectionString);
|
||||
|
||||
List<T> output = (await connection.QueryAsync<T>(
|
||||
storedProcedure,
|
||||
parameters,
|
||||
commandType: CommandType.StoredProcedure)).ToList();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public async Task<T> SaveDataScalar<T, U>(string storedProcedure,
|
||||
U parameters,
|
||||
string connectionStringName)
|
||||
{
|
||||
string connectionString = _config.GetConnectionString(connectionStringName) ??
|
||||
throw new KeyNotFoundException("Did not find the connection string specified");
|
||||
|
||||
using IDbConnection connection = new SqlConnection(connectionString);
|
||||
|
||||
T? output = await connection.ExecuteScalarAsync<T>(storedProcedure, parameters, commandType: CommandType.StoredProcedure);
|
||||
|
||||
return output ?? throw new ArgumentNullException("The return value was null, which is an invalid result.");
|
||||
}
|
||||
|
||||
public async Task SaveData<T>(string storedProcedure,
|
||||
T parameters,
|
||||
string connectionStringName)
|
||||
{
|
||||
string connectionString = _config.GetConnectionString(connectionStringName) ??
|
||||
throw new KeyNotFoundException("Did not find the connection string specified");
|
||||
|
||||
using IDbConnection connection = new SqlConnection(connectionString);
|
||||
|
||||
await connection.ExecuteAsync(storedProcedure, parameters, commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
7
OfflineDemoApi/Models/AzureStorageConfig.cs
Normal file
7
OfflineDemoApi/Models/AzureStorageConfig.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace OfflineDemoApi.Models;
|
||||
|
||||
public class AzureStorageConfig
|
||||
{
|
||||
public string? ConnectionString { get; set; }
|
||||
public string? ContainerName { get; set; }
|
||||
}
|
||||
12
OfflineDemoApi/Models/ShortsModel.cs
Normal file
12
OfflineDemoApi/Models/ShortsModel.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace OfflineDemoApi.Models;
|
||||
|
||||
public class ShortsModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Hashtags { get; set; }
|
||||
public string Mp4FileUrl { get; set; }
|
||||
public string ImageFileUrl { get; set; }
|
||||
public bool IsUploaded { get; set; } = false;
|
||||
}
|
||||
18
OfflineDemoApi/OfflineDemoApi.csproj
Normal file
18
OfflineDemoApi/OfflineDemoApi.csproj
Normal file
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>9343a209-53dc-41cc-b263-bce9f7c1f999</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
145
OfflineDemoApi/Program.cs
Normal file
145
OfflineDemoApi/Program.cs
Normal file
@ -0,0 +1,145 @@
|
||||
using Azure.Storage.Blobs;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using OfflineDemoApi.Data;
|
||||
using OfflineDemoApi.Models;
|
||||
using System;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
// Bind Azure Storage configuration
|
||||
var azureStorageConfig = builder.Configuration.GetSection("AzureStorage").Get<AzureStorageConfig>();
|
||||
|
||||
// Ensure the blob container exists
|
||||
var blobServiceClient = new BlobServiceClient(azureStorageConfig.ConnectionString);
|
||||
var blobContainerClient = blobServiceClient.GetBlobContainerClient(azureStorageConfig.ContainerName);
|
||||
await blobContainerClient.CreateIfNotExistsAsync();
|
||||
await blobContainerClient.SetAccessPolicyAsync(Azure.Storage.Blobs.Models.PublicAccessType.None);
|
||||
|
||||
builder.Services.AddSingleton(blobContainerClient);
|
||||
|
||||
// Configure Kestrel limits
|
||||
builder.WebHost.ConfigureKestrel(options =>
|
||||
{
|
||||
options.Limits.MaxRequestBodySize = 524288000*2; // Example: 500 MB
|
||||
options.Limits.MaxRequestBufferSize = 524288000 * 2; // Example: 100 MB
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton<ISqlDataAccess, SqlDataAccess>();
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowAnyOrigin", policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod();
|
||||
});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseCors("AllowAnyOrigin");
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.MapGet("/", () => {
|
||||
return "Hello World";
|
||||
});
|
||||
|
||||
app.MapGet("/api/shorts", async (ISqlDataAccess sql) =>
|
||||
{
|
||||
return await sql.LoadData<ShortsModel, dynamic>("spShorts_GetAll", new { }, "sql");
|
||||
});
|
||||
|
||||
app.MapGet("/api/file", async (IConfiguration config, string url) =>
|
||||
{
|
||||
var blobUri = new Uri(url);
|
||||
var storageAccountKey = config.GetValue<string>("AzureStorage:StorageAccountKey");
|
||||
var storageAccountName = config.GetValue<string>("AzureStorage:StorageAccountName");
|
||||
|
||||
var credential = new Azure.Storage.StorageSharedKeyCredential(storageAccountName, storageAccountKey);
|
||||
var blobClient = new BlobClient(blobUri, credential);
|
||||
|
||||
var downloadResponse = await blobClient.DownloadStreamingAsync();
|
||||
return Results.File(
|
||||
downloadResponse.Value.Content,
|
||||
downloadResponse.Value.Details.ContentType,
|
||||
fileDownloadName: blobUri.Segments.Last()
|
||||
);
|
||||
});
|
||||
|
||||
app.MapPost("/api/upload", async (HttpRequest request,
|
||||
BlobContainerClient containerClient,
|
||||
ISqlDataAccess sql) =>
|
||||
{
|
||||
if (!request.HasFormContentType || request.Form.Files.Count == 0)
|
||||
{
|
||||
return Results.BadRequest("Invalid form submission. Files are required.");
|
||||
}
|
||||
|
||||
var form = await request.ReadFormAsync();
|
||||
|
||||
// Extract form data
|
||||
var title = form["Title"].ToString();
|
||||
var description = form["Description"].ToString();
|
||||
var hashtags = form["Hashtags"].ToString();
|
||||
var mp4File = form.Files["Mp4File"];
|
||||
var imageFile = form.Files["ImageFile"];
|
||||
|
||||
if (mp4File == null || imageFile == null)
|
||||
{
|
||||
return Results.BadRequest("Both MP4 and image files are required.");
|
||||
}
|
||||
|
||||
// Validate file size
|
||||
const long maxFileSize = 800L * 1024 * 1024; // 800MB
|
||||
if (mp4File.Length > maxFileSize)
|
||||
{
|
||||
return Results.BadRequest("MP4 file exceeds the 800MB limit.");
|
||||
}
|
||||
|
||||
var createNewParameters = new { title, description, hashtags };
|
||||
int id = await sql.SaveDataScalar<int, dynamic>("spShorts_CreateNew", createNewParameters, "sql");
|
||||
|
||||
// Rename files
|
||||
string? mp4FileName = $"{id}.mp4";
|
||||
|
||||
// TODO - Fix this extension lookup
|
||||
string? imageFileName = $"{id}.{imageFile.FileName.Split('.')[1]}";
|
||||
|
||||
// Upload MP4 file
|
||||
var mp4BlobClient = containerClient.GetBlobClient($"shorts/{mp4FileName}");
|
||||
await using (var mp4Stream = mp4File.OpenReadStream())
|
||||
{
|
||||
await mp4BlobClient.UploadAsync(mp4Stream, true);
|
||||
}
|
||||
|
||||
// Upload Image file
|
||||
var imageBlobClient = containerClient.GetBlobClient($"images/{imageFileName}");
|
||||
await using (var imageStream = imageFile.OpenReadStream())
|
||||
{
|
||||
await imageBlobClient.UploadAsync(imageStream, true);
|
||||
}
|
||||
|
||||
// Update SQL with the uploaded files
|
||||
string? mp4FileUrl = mp4BlobClient.Uri.ToString();
|
||||
string? imageFileUrl = imageBlobClient.Uri.ToString();
|
||||
|
||||
var addUploadedFilesParameters = new { id, mp4FileUrl, imageFileUrl };
|
||||
|
||||
await sql.SaveData("spShorts_AddUploadedFiles", addUploadedFilesParameters, "sql");
|
||||
|
||||
return Results.Ok(new { Message = "Files uploaded to Azure Blob Storage successfully." });
|
||||
});
|
||||
|
||||
app.Run();
|
||||
@ -0,0 +1,173 @@
|
||||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"metadata": {
|
||||
"_dependencyType": "compute.function.linux.appService"
|
||||
},
|
||||
"parameters": {
|
||||
"resourceGroupName": {
|
||||
"type": "string",
|
||||
"defaultValue": "offline-demo-sql",
|
||||
"metadata": {
|
||||
"description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking."
|
||||
}
|
||||
},
|
||||
"resourceGroupLocation": {
|
||||
"type": "string",
|
||||
"defaultValue": "southcentralus",
|
||||
"metadata": {
|
||||
"description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support."
|
||||
}
|
||||
},
|
||||
"resourceName": {
|
||||
"type": "string",
|
||||
"defaultValue": "offlinedemoapi",
|
||||
"metadata": {
|
||||
"description": "Name of the main resource to be created by this template."
|
||||
}
|
||||
},
|
||||
"resourceLocation": {
|
||||
"type": "string",
|
||||
"defaultValue": "[parameters('resourceGroupLocation')]",
|
||||
"metadata": {
|
||||
"description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there."
|
||||
}
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"type": "Microsoft.Resources/resourceGroups",
|
||||
"name": "[parameters('resourceGroupName')]",
|
||||
"location": "[parameters('resourceGroupLocation')]",
|
||||
"apiVersion": "2019-10-01"
|
||||
},
|
||||
{
|
||||
"type": "Microsoft.Resources/deployments",
|
||||
"name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]",
|
||||
"resourceGroup": "[parameters('resourceGroupName')]",
|
||||
"apiVersion": "2019-10-01",
|
||||
"dependsOn": [
|
||||
"[parameters('resourceGroupName')]"
|
||||
],
|
||||
"properties": {
|
||||
"mode": "Incremental",
|
||||
"expressionEvaluationOptions": {
|
||||
"scope": "inner"
|
||||
},
|
||||
"parameters": {
|
||||
"resourceGroupName": {
|
||||
"value": "[parameters('resourceGroupName')]"
|
||||
},
|
||||
"resourceGroupLocation": {
|
||||
"value": "[parameters('resourceGroupLocation')]"
|
||||
},
|
||||
"resourceName": {
|
||||
"value": "[parameters('resourceName')]"
|
||||
},
|
||||
"resourceLocation": {
|
||||
"value": "[parameters('resourceLocation')]"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"resourceGroupName": {
|
||||
"type": "string"
|
||||
},
|
||||
"resourceGroupLocation": {
|
||||
"type": "string"
|
||||
},
|
||||
"resourceName": {
|
||||
"type": "string"
|
||||
},
|
||||
"resourceLocation": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"storage_name": "[toLower(concat('storage', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId))))]",
|
||||
"appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]",
|
||||
"storage_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Storage/storageAccounts/', variables('storage_name'))]",
|
||||
"appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]",
|
||||
"function_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/sites/', parameters('resourceName'))]"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"location": "[parameters('resourceLocation')]",
|
||||
"name": "[parameters('resourceName')]",
|
||||
"type": "Microsoft.Web/sites",
|
||||
"apiVersion": "2015-08-01",
|
||||
"tags": {
|
||||
"[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty"
|
||||
},
|
||||
"dependsOn": [
|
||||
"[variables('appServicePlan_ResourceId')]",
|
||||
"[variables('storage_ResourceId')]"
|
||||
],
|
||||
"kind": "functionapp",
|
||||
"properties": {
|
||||
"name": "[parameters('resourceName')]",
|
||||
"kind": "functionapp",
|
||||
"httpsOnly": true,
|
||||
"reserved": false,
|
||||
"serverFarmId": "[variables('appServicePlan_ResourceId')]",
|
||||
"siteConfig": {
|
||||
"alwaysOn": true,
|
||||
"linuxFxVersion": "dotnet|3.1"
|
||||
}
|
||||
},
|
||||
"identity": {
|
||||
"type": "SystemAssigned"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"name": "appsettings",
|
||||
"type": "config",
|
||||
"apiVersion": "2015-08-01",
|
||||
"dependsOn": [
|
||||
"[variables('function_ResourceId')]"
|
||||
],
|
||||
"properties": {
|
||||
"AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storage_name'), ';AccountKey=', listKeys(variables('storage_ResourceId'), '2017-10-01').keys[0].value, ';EndpointSuffix=', 'core.windows.net')]",
|
||||
"FUNCTIONS_EXTENSION_VERSION": "~3",
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"location": "[parameters('resourceGroupLocation')]",
|
||||
"name": "[variables('storage_name')]",
|
||||
"type": "Microsoft.Storage/storageAccounts",
|
||||
"apiVersion": "2017-10-01",
|
||||
"tags": {
|
||||
"[concat('hidden-related:', concat('/providers/Microsoft.Web/sites/', parameters('resourceName')))]": "empty"
|
||||
},
|
||||
"properties": {
|
||||
"supportsHttpsTrafficOnly": true
|
||||
},
|
||||
"sku": {
|
||||
"name": "Standard_LRS"
|
||||
},
|
||||
"kind": "Storage"
|
||||
},
|
||||
{
|
||||
"location": "[parameters('resourceGroupLocation')]",
|
||||
"name": "[variables('appServicePlan_name')]",
|
||||
"type": "Microsoft.Web/serverFarms",
|
||||
"apiVersion": "2015-02-01",
|
||||
"kind": "linux",
|
||||
"properties": {
|
||||
"name": "[variables('appServicePlan_name')]",
|
||||
"sku": "Standard",
|
||||
"workerSizeId": "0",
|
||||
"reserved": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
23
OfflineDemoApi/Properties/launchSettings.json
Normal file
23
OfflineDemoApi/Properties/launchSettings.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5057",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7099;http://localhost:5057",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
OfflineDemoApi/appsettings.Development.json
Normal file
8
OfflineDemoApi/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
18
OfflineDemoApi/appsettings.json
Normal file
18
OfflineDemoApi/appsettings.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"AzureStorage": {
|
||||
"ConnectionString": "",
|
||||
"ContainerName": "shorts",
|
||||
"StorageAccountName": "",
|
||||
"StorageAccountKey": ""
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"sql": ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user