Organize workspace: Frontend, Backend, and Tests in one repo

This commit is contained in:
2026-03-04 22:04:07 +00:00
parent 236780cf41
commit 0d517b198d
1719 changed files with 688496 additions and 449 deletions

View File

@@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Mvc;
using Seasoned.Backend.Services;
using Seasoned.Backend.DTOs;
namespace Seasoned.Backend.Controllers;
[ApiController]
[Route("api/[controller]")]
public class RecipeController : ControllerBase
{
private readonly IRecipeService _recipeService;
// Dependency Injection: The service is "injected" here
public RecipeController(IRecipeService recipeService)
{
_recipeService = recipeService;
}
[HttpPost("upload")]
public async Task<ActionResult<RecipeResponseDto>> UploadRecipe([FromForm] IFormFile image)
{
if (image == null || image.Length == 0)
{
return BadRequest("No image uploaded.");
}
var result = await _recipeService.ParseRecipeImageAsync(image);
return Ok(result);
}
}

View File

@@ -0,0 +1,9 @@
namespace Seasoned.Backend.DTOs;
public class RecipeResponseDto
{
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public List<string> Ingredients { get; set; } = new();
public List<string> Instructions { get; set; } = new();
}

View File

@@ -0,0 +1,29 @@
using Seasoned.Backend.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IRecipeService, RecipeService>();
builder.Services.AddControllers();
builder.Services.AddOpenApi();
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
var app = builder.Build();
app.UseCors("AllowAll");
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.MapControllers();
app.Run();

View File

@@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5243",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>71e44981-0c1f-4d90-8e87-317af8bd03ce</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.13" />
<PackageReference Include="Mscc.GenerativeAI" Version="2.2.8" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
@Seasoned.Backend_HostAddress = http://localhost:5243
GET {{Seasoned.Backend_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@@ -0,0 +1,8 @@
using Seasoned.Backend.DTOs;
namespace Seasoned.Backend.Services;
public interface IRecipeService
{
Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image);
}

View File

@@ -0,0 +1,49 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
using Microsoft.AspNetCore.Http;
using System.IO;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
// 1. Better Prompt: Tell Gemini exactly what the JSON should look like
var prompt = @"Extract the recipe from this image.
Return a JSON object with exactly these fields:
{
""title"": ""string"",
""description"": ""string"",
""ingredients"": [""string""],
""instructions"": [""string""]
}";
// 2. Set the Response MIME Type to application/json
var config = new GenerationConfig { ResponseMimeType = "application/json" };
var request = new GenerateContentRequest(prompt, config);
request.AddMedia(base64Image, "image/png");
var response = await model.GenerateContent(request);
// 3. Use System.Text.Json to turn that string back into our DTO
var options = new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var result = System.Text.Json.JsonSerializer.Deserialize<RecipeResponseDto>(response.Text, options);
return result ?? new RecipeResponseDto { Title = "Error parsing JSON" };
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}