Switch from token to cookie auth

This commit is contained in:
2026-03-11 18:58:55 +00:00
parent 01f42c22d6
commit b3355831d8
9 changed files with 46 additions and 59 deletions

View File

@@ -13,8 +13,8 @@ using Seasoned.Backend.Data;
namespace Seasoned.Backend.Migrations namespace Seasoned.Backend.Migrations
{ {
[DbContext(typeof(ApplicationDbContext))] [DbContext(typeof(ApplicationDbContext))]
[Migration("20260306062007_InitialCreate")] [Migration("20260311160009_AddRecipeFields")]
partial class InitialCreate partial class AddRecipeFields
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -234,7 +234,8 @@ namespace Seasoned.Backend.Migrations
b.Property<DateTime>("CreatedAt") b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<string>("Description") b.Property<string>("Icon")
.IsRequired()
.HasColumnType("text"); .HasColumnType("text");
b.PrimitiveCollection<List<string>>("Ingredients") b.PrimitiveCollection<List<string>>("Ingredients")

View File

@@ -8,7 +8,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Seasoned.Backend.Migrations namespace Seasoned.Backend.Migrations
{ {
/// <inheritdoc /> /// <inheritdoc />
public partial class InitialCreate : Migration public partial class AddRecipeFields : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
@@ -168,7 +168,7 @@ namespace Seasoned.Backend.Migrations
Id = table.Column<int>(type: "integer", nullable: false) Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Title = table.Column<string>(type: "text", nullable: false), Title = table.Column<string>(type: "text", nullable: false),
Description = table.Column<string>(type: "text", nullable: true), Icon = table.Column<string>(type: "text", nullable: false),
Ingredients = table.Column<List<string>>(type: "text[]", nullable: false), Ingredients = table.Column<List<string>>(type: "text[]", nullable: false),
Instructions = table.Column<List<string>>(type: "text[]", nullable: false), Instructions = table.Column<List<string>>(type: "text[]", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),

View File

@@ -231,7 +231,8 @@ namespace Seasoned.Backend.Migrations
b.Property<DateTime>("CreatedAt") b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<string>("Description") b.Property<string>("Icon")
.IsRequired()
.HasColumnType("text"); .HasColumnType("text");
b.PrimitiveCollection<List<string>>("Ingredients") b.PrimitiveCollection<List<string>>("Ingredients")

View File

@@ -5,6 +5,9 @@ using Microsoft.EntityFrameworkCore;
using Seasoned.Backend.Data; using Seasoned.Backend.Data;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using DotNetEnv;
Env.Load("../.env");
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -49,7 +52,7 @@ var app = builder.Build();
using (var scope = app.Services.CreateScope()) using (var scope = app.Services.CreateScope())
{ {
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
db.Database.EnsureCreated(); db.Database.Migrate();
} }
app.UseDefaultFiles(); app.UseDefaultFiles();

View File

@@ -8,6 +8,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="dotenv.net" Version="4.0.1" />
<PackageReference Include="DotNetEnv" Version="3.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.2" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.2" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.13" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.13" />
<PackageReference Include="Mscc.GenerativeAI" Version="2.2.8" /> <PackageReference Include="Mscc.GenerativeAI" Version="2.2.8" />

View File

@@ -42,12 +42,12 @@ public class RecipeService : IRecipeService
""instructions"": [""string"", ""string""] ""instructions"": [""string"", ""string""]
}"; }";
var config = new GenerationConfig { var generationConfig = new GenerationConfig {
ResponseMimeType = "application/json", ResponseMimeType = "application/json",
Temperature = 0.1f Temperature = 0.1f
}; };
var request = new GenerateContentRequest(prompt, config); var request = new GenerateContentRequest(prompt, generationConfig);
await Task.Run(() => request.AddMedia(base64Image, image.ContentType ?? "image/png")); await Task.Run(() => request.AddMedia(base64Image, image.ContentType ?? "image/png"));

View File

@@ -197,25 +197,17 @@ const showDetails = ref(false)
const selectedRecipe = ref(null) const selectedRecipe = ref(null)
const isEditing = ref(false) const isEditing = ref(false)
const originalRecipe = ref(null) const originalRecipe = ref(null)
const config = useRuntimeConfig()
onMounted(async () => { onMounted(async () => {
await fetchRecipes() await fetchRecipes()
}) })
const fetchRecipes = async () => { const fetchRecipes = async () => {
const token = useCookie('seasoned_token').value
|| (import.meta.client ? localStorage.getItem('token') : null)
if (!token) {
return navigateTo('/login')
}
try { try {
loading.value = true loading.value = true
const data = await $fetch(`${config.public.apiBase}api/recipe/my-collection`, { const data = await $fetch(`${config.public.apiBase}api/recipe/my-collection`, {
headers: { credentials: 'include'
'Authorization': `Bearer ${token}`
}
}) })
recipes.value = data recipes.value = data
} catch (err) { } catch (err) {
@@ -234,34 +226,38 @@ const openRecipe = (recipe) => {
} }
const editRecipe = (recipe) => { const editRecipe = (recipe) => {
originalRecipe.value = JSON.parse(JSON.stringify(recipe))
selectedRecipe.value = { ...recipe } selectedRecipe.value = { ...recipe }
originalRecipe.value = { ...recipe }
isEditing.value = true isEditing.value = true
showDetails.value = true showDetails.value = true
} }
const closeDetails = () => { const closeDetails = () => {
if (isEditing.value && originalRecipe.value) {
const index = recipes.value.findIndex(r => r.id === originalRecipe.value.id)
if (index !== -1) {
recipes.value[index] = originalRecipe.value
}
}
showDetails.value = false showDetails.value = false
isEditing.value = false isEditing.value = false
originalRecipe.value = null
} }
const saveChanges = async () => { const saveChanges = async () => {
const token = useCookie('seasoned_token').value
try { try {
await $fetch(`${config.public.apiBase}api/recipe/update/${selectedRecipe.value.id}`, { await $fetch(`${config.public.apiBase}api/recipe/update/${selectedRecipe.value.id}`, {
method: 'PUT', method: 'PUT',
headers: { 'Authorization': `Bearer ${token}` }, credentials: 'include',
body: selectedRecipe.value body: selectedRecipe.value
}) })
await fetchRecipes() await fetchRecipes()
closeDetails()
isEditing.value = false
showDetails.value = false
} catch (e) { } catch (e) {
console.error("Failed to update recipe:", e) console.error("Failed to update recipe:", e)
alert("Could not save changes. Please try again.") alert("Could not save changes. Your session might have expired.")
} }
} }
@@ -297,12 +293,13 @@ const saveChanges = async () => {
//showDetails.value = false //showDetails.value = false
//} //}
const getRecipeIcon = (title) => { const getRecipeIcon = (recipe) => {
const t = title.toLowerCase()
if (recipe.icon) return recipe.icon if (recipe.icon) return recipe.icon
const t = (recipe.title || '').toLowerCase()
if (t.includes('cake') || t.includes('cookie') || t.includes('dessert')) return 'mdi-cookie' if (t.includes('cake') || t.includes('cookie') || t.includes('dessert')) return 'mdi-cookie'
if (t.includes('soup') || t.includes('stew')) return 'mdi-bowl-mix' if (t.includes('soup') || t.includes('stew')) return 'mdi-bowl-mix'
if (t.includes('drink') || t.includes('cocktail')) return 'mdi-glass-cocktail' if (t.includes('drink') || t.includes('cocktail')) return 'mdi-glass-cocktail'
return 'mdi-silverware-fork-knife' return 'mdi-silverware-fork-knife'
} }
</script> </script>

View File

@@ -144,11 +144,15 @@ const isDragging = ref(false)
const saving = ref(false) const saving = ref(false)
const hasSaved = ref(false) const hasSaved = ref(false)
const isAuthenticated = () => { const isAuthenticated = async () => {
if (import.meta.client) { try {
return !!localStorage.getItem('token'); await $fetch('/api/auth/manage/info', {
credentials: 'include'
})
return true
} catch {
return false
} }
return false;
} }
const handleViewCollection = () => { const handleViewCollection = () => {
@@ -196,7 +200,6 @@ const uploadImage = async () => {
const saveToCollection = async () => { const saveToCollection = async () => {
if (!recipe.value || hasSaved.value) return; if (!recipe.value || hasSaved.value) return;
// 1. Get the token (same logic as gallery)
const token = useCookie('seasoned_token').value const token = useCookie('seasoned_token').value
|| (import.meta.client ? localStorage.getItem('token') : null) || (import.meta.client ? localStorage.getItem('token') : null)
@@ -210,9 +213,6 @@ const saveToCollection = async () => {
try { try {
await $fetch(`${config.public.apiBase}api/recipe/save`, { await $fetch(`${config.public.apiBase}api/recipe/save`, {
method: 'POST', method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: recipe.value body: recipe.value
}); });
hasSaved.value = true; hasSaved.value = true;

View File

@@ -72,7 +72,7 @@ const handleAuth = async () => {
const endpoint = isLogin.value ? 'api/auth/login' : 'api/auth/register' const endpoint = isLogin.value ? 'api/auth/login' : 'api/auth/register'
const url = isLogin.value const url = isLogin.value
? `${config.public.apiBase}${endpoint}?useCookies=false` ? `${config.public.apiBase}${endpoint}?useCookies=true`
: `${config.public.apiBase}${endpoint}` : `${config.public.apiBase}${endpoint}`
try { try {
@@ -80,33 +80,16 @@ const handleAuth = async () => {
method: 'POST', method: 'POST',
body: { body: {
email: email.value, email: email.value,
userName: email.value,
password: password.value password: password.value
} }
}) })
if (isLogin.value) { if (isLogin.value) {
if (response.accessToken) {
const tokenCookie = useCookie('seasoned_token', { maxAge: response.expiresIn })
tokenCookie.value = response.accessToken
if (import.meta.client) {
localStorage.setItem('token', response.accessToken)
}
navigateTo('/gallery') navigateTo('/gallery')
}
} else {
alert("Account created successfully! Please sign in to open your ledger.")
isLogin.value = true
} }
} catch (err) { } catch (err) {
const errorDetail = err.data?.errors alert("Authentication failed. Check your credentials.")
? Object.values(err.data.errors).flat().join('\n')
: "Check your credentials and try again."
alert(`Authentication failed:\n${errorDetail}`)
} }
} }
</script> </script>