Switch from token to cookie auth
This commit is contained in:
@@ -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")
|
||||||
@@ -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),
|
||||||
@@ -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")
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user