Organize workspace: Frontend, Backend, and Tests in one repo
This commit is contained in:
30
Seasoned.Backend/Controllers/RecipeController.cs
Normal file
30
Seasoned.Backend/Controllers/RecipeController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
9
Seasoned.Backend/DTOs/RecipeResponseDto.cs
Normal file
9
Seasoned.Backend/DTOs/RecipeResponseDto.cs
Normal 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();
|
||||
}
|
||||
29
Seasoned.Backend/Program.cs
Normal file
29
Seasoned.Backend/Program.cs
Normal 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();
|
||||
14
Seasoned.Backend/Properties/launchSettings.json
Normal file
14
Seasoned.Backend/Properties/launchSettings.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Seasoned.Backend/Seasoned.Backend.csproj
Normal file
15
Seasoned.Backend/Seasoned.Backend.csproj
Normal 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>
|
||||
6
Seasoned.Backend/Seasoned.Backend.http
Normal file
6
Seasoned.Backend/Seasoned.Backend.http
Normal file
@@ -0,0 +1,6 @@
|
||||
@Seasoned.Backend_HostAddress = http://localhost:5243
|
||||
|
||||
GET {{Seasoned.Backend_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
8
Seasoned.Backend/Services/IRecipeService.cs
Normal file
8
Seasoned.Backend/Services/IRecipeService.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Seasoned.Backend.DTOs;
|
||||
|
||||
namespace Seasoned.Backend.Services;
|
||||
|
||||
public interface IRecipeService
|
||||
{
|
||||
Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image);
|
||||
}
|
||||
49
Seasoned.Backend/Services/RecipeService.cs
Normal file
49
Seasoned.Backend/Services/RecipeService.cs
Normal 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" };
|
||||
}
|
||||
}
|
||||
8
Seasoned.Backend/appsettings.Development.json
Normal file
8
Seasoned.Backend/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Seasoned.Backend/appsettings.json
Normal file
9
Seasoned.Backend/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Reference in New Issue
Block a user