Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions backend/src/main/java/com/mealchemy/auth/model/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mealchemy.auth.model;

public class Role {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mealchemy.auth.model;

public class UserProfile {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mealchemy.auth.repository;

public class UserProfileRepository {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mealchemy.pantry.dto;

public class PantryIngredientResponse {

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.mealchemy.pantry.repository;

public class PantryRepository {
public class PantryIngredientRepository {

}
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mealchemy.vault.dto;

public class VaultFolderRequest {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mealchemy.vault.dto;

public class VaultFolderResponse {

}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mealchemy.vault.model;

public class VaultFolder {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mealchemy.vault.model;

public class VaultFolderRecipe {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mealchemy.vault.repository;

public class VaultFolderRecipeRepository {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mealchemy.vault.repository;

public class VaultFolderRepository {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mealchemy.recipe;

public class RecipeControllerTest {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.mealchemy.recipe;

public class RecipeServiceTest {

}
Empty file.
39 changes: 39 additions & 0 deletions database/V10__create_recipes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
-- =============================================================================
-- V10__create_recipes.sql
--
-- Core recipe metadata. Each recipe is owned by a user.
-- Ingredients live in recipe_ingredients (V12).
-- Steps live in recipe_steps (V13).
-- Vault assignment is handled via vault_folder_recipes (V11).
-- =============================================================================

CREATE TABLE recipes (
recipe_id SERIAL PRIMARY KEY,
owner_id INT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
title VARCHAR(200) NOT NULL,

Check warning on line 13 in database/V10__create_recipes.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use VARCHAR2 instead of VARCHAR.

See more on https://sonarcloud.io/project/issues?id=COS301-SE-2026_Mealchemy&issues=AZ4zSdoXh_oryJbHVjUf&open=AZ4zSdoXh_oryJbHVjUf&pullRequest=19
description TEXT,
cuisine_type cuisine_type_enum,
prep_time_mins SMALLINT,
cooking_time_mins SMALLINT,
serving_size SMALLINT,

-- URLs to Firebase Storage objects.
-- Null until the user uploads media.
photo_url TEXT,
video_url TEXT,

-- Default false. This flag is checked by the backend when global vault is implemented
is_community_published BOOLEAN NOT NULL DEFAULT FALSE,

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Index for fast lookup of all recipes by a user.
CREATE INDEX idx_recipes_owner_id ON recipes(owner_id);

COMMENT ON TABLE recipes IS 'Core recipe metadata. Ingredients and steps are stored in their own tables.';
COMMENT ON COLUMN recipes.photo_url IS 'URL to Firebase Storage object. Null until uploaded.';
COMMENT ON COLUMN recipes.video_url IS 'URL to Firebase Storage object. Null until uploaded.';
COMMENT ON COLUMN recipes.is_community_published IS 'Default false. Activates when the global vault is implemented.';
COMMENT ON COLUMN recipes.updated_at IS 'Updated when user edits recipe';
22 changes: 22 additions & 0 deletions database/V11__create_vault_folder_recipes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-- =============================================================================
-- V11__create_vault_folder_recipes.sql
--
-- Many-to-many between vault_folders and recipes - one folder can have many recipes.
-- A recipe can appear in multiple folders (Saved under Breakfast and Meal Prep)
-- =============================================================================

CREATE TABLE vault_folder_recipes (
id SERIAL PRIMARY KEY,
folder_id INT NOT NULL REFERENCES vault_folders(folder_id) ON DELETE CASCADE,
recipe_id INT NOT NULL REFERENCES recipes(recipe_id) ON DELETE CASCADE,
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

-- Prevent a recipe being added to the same folder twice.
CONSTRAINT vault_folder_recipes_unique UNIQUE (folder_id, recipe_id)
);

-- Indexes for fast lookup from either direction of the relationship.
CREATE INDEX idx_vfr_folder_id ON vault_folder_recipes(folder_id);
CREATE INDEX idx_vfr_recipe_id ON vault_folder_recipes(recipe_id);

COMMENT ON TABLE vault_folder_recipes IS 'Many-to-many between vault_folders and recipes. A recipe can exist in multiple folders.';
28 changes: 28 additions & 0 deletions database/V12__create_recipe_ingredients.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-- =============================================================================
-- V12__create_recipe_ingredients.sql
--
-- Each row is one ingredient line in a recipe, eg.) "200g chicken breast"
--
-- name_raw stores the ingredient exactly as the user typed it - avoids restricting the user
-- at upload time, and enables fuzzy matching against pantry_ingredients
-- =============================================================================

CREATE TABLE recipe_ingredients (
ingredient_id SERIAL PRIMARY KEY,
recipe_id INT NOT NULL REFERENCES recipes(recipe_id) ON DELETE CASCADE,
name_raw VARCHAR(200) NOT NULL,

Check warning on line 13 in database/V12__create_recipe_ingredients.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use VARCHAR2 instead of VARCHAR.

See more on https://sonarcloud.io/project/issues?id=COS301-SE-2026_Mealchemy&issues=AZ4zSdn8h_oryJbHVjUZ&open=AZ4zSdn8h_oryJbHVjUZ&pullRequest=19
quantity DECIMAL(10,3),
unit VARCHAR(30),

Check warning on line 15 in database/V12__create_recipe_ingredients.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use VARCHAR2 instead of VARCHAR.

See more on https://sonarcloud.io/project/issues?id=COS301-SE-2026_Mealchemy&issues=AZ4zSdn8h_oryJbHVjUa&open=AZ4zSdn8h_oryJbHVjUa&pullRequest=19

-- Controls the display order of ingredients in the recipe view
-- Frontend must take this into account
sort_order SMALLINT NOT NULL DEFAULT 0
);

-- Index for fast retrieval of all ingredients for a given recipe.
CREATE INDEX idx_recipe_ingredients_recipe_id ON recipe_ingredients(recipe_id);

COMMENT ON TABLE recipe_ingredients IS 'One row per ingredient line per recipe. Name stored as free text for fuzzy pantry matching.';
COMMENT ON COLUMN recipe_ingredients.name_raw IS 'Ingredient name as entered by the user. Used for fuzzy matching against pantry in a future use case.';
COMMENT ON COLUMN recipe_ingredients.unit IS 'eg.) g, ml, cups, tbsp. Null if not specified.';
COMMENT ON COLUMN recipe_ingredients.sort_order IS 'Display order. Frontend must sort by this field when rendering ingredients.';
24 changes: 24 additions & 0 deletions database/V13__create_recipe_steps.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- =============================================================================
-- V13__create_recipe_steps.sql
--
-- Each row is one numbered step in a recipe's method
--
-- Steps are stored as individual rows rather than a single text blob so that:
-- 1. The frontend can render a step-by-step view with navigation
-- 2. The voice-controlled cooking feature can read each step aloud individually
-- =============================================================================

CREATE TABLE recipe_steps (
step_id SERIAL PRIMARY KEY,
recipe_id INT NOT NULL REFERENCES recipes(recipe_id) ON DELETE CASCADE,
step_nr SMALLINT NOT NULL,
content TEXT NOT NULL,

CONSTRAINT recipe_steps_unique UNIQUE (recipe_id, step_nr) -- Recipe step numbers cannot be repeated
);

-- Index for fast retrieval of all steps for a given recipe.
CREATE INDEX idx_recipe_steps_recipe_id ON recipe_steps(recipe_id);

COMMENT ON TABLE recipe_steps IS 'One row per step per recipe. Stored separately to support step-by-step display and voice-controlled cooking.';
COMMENT ON COLUMN recipe_steps.step_nr IS 'Step number within the recipe (1, 2, 3...). Must be unique per recipe.';
33 changes: 33 additions & 0 deletions database/V14__create_pantry_ingredients.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-- =============================================================================
-- V14__create_pantry_ingredients.sql

-- Each row is one ingredient a user has manually added to their pantry
-- price_paid: stored in ZAR cents as INTEGER to avoid floating-point errors (R19.99 is stored as 1999.)
--
-- potentially add expiry date
-- =============================================================================

CREATE TABLE pantry_ingredients (
p_ingredient_id SERIAL PRIMARY KEY,
user_id INT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
name VARCHAR(200) NOT NULL,

Check warning on line 13 in database/V14__create_pantry_ingredients.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use VARCHAR2 instead of VARCHAR.

See more on https://sonarcloud.io/project/issues?id=COS301-SE-2026_Mealchemy&issues=AZ4zSdomh_oryJbHVjUl&open=AZ4zSdomh_oryJbHVjUl&pullRequest=19
category pantry_category_enum,
quantity DECIMAL(10,3),
unit VARCHAR(30),

Check warning on line 16 in database/V14__create_pantry_ingredients.sql

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use VARCHAR2 instead of VARCHAR.

See more on https://sonarcloud.io/project/issues?id=COS301-SE-2026_Mealchemy&issues=AZ4zSdomh_oryJbHVjUm&open=AZ4zSdomh_oryJbHVjUm&pullRequest=19
is_out_of_stock BOOLEAN NOT NULL DEFAULT FALSE,

price_paid_zar_cents INTEGER, -- Stored in ZAR cents. R19.99 = 1999.

created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Index for fast retrieval of all pantry items for a given user.
CREATE INDEX idx_pantry_ingredients_user_id ON pantry_ingredients(user_id);

COMMENT ON TABLE pantry_ingredients IS 'Ingredients manually added to a user pantry.';
COMMENT ON COLUMN pantry_ingredients.name IS 'Ingredient name as entered by the user.';
COMMENT ON COLUMN pantry_ingredients.unit IS 'eg.) g, ml, units, tbsp. Null if not specified.';
COMMENT ON COLUMN pantry_ingredients.is_out_of_stock IS 'True when the user marks an item as depleted. Default false.';
COMMENT ON COLUMN pantry_ingredients.price_paid_zar_cents IS 'ZAR cents. R19.99 stored as 1999. Rename to price_paid_zar_cents before production.';
COMMENT ON COLUMN pantry_ingredients.updated_at IS 'Updated when user makes a pantry update.';
17 changes: 17 additions & 0 deletions database/V15__seed_roles.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- =============================================================================
-- V15__seed_roles.sql
-- Seeds the two required roles: 'user' (default) and 'admin'.
--
-- Replace with a proper admin assignment flow later on. (Manage roles without direct database access)
-- =============================================================================

INSERT INTO roles (role_name, permissions)
VALUES
(
'user',
'{"can_upload_recipes": true, "can_manage_own_pantry": true, "can_manage_own_profile": true, "can_publish_to_community": false}'::jsonb
),
(
'admin',
'{"can_upload_recipes": true, "can_manage_own_pantry": true, "can_manage_own_profile": true, "can_publish_to_community": true, "can_delete_community_recipes": true, "can_manage_users": true}'::jsonb
);
15 changes: 15 additions & 0 deletions database/V16__seed_global_vault.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- =============================================================================
-- V16__seed_global_vault.sql
--
-- Seeds the single global vault used by the community discovery page
--
-- The global vault:
-- 1. Has no owner (owner_id = NULL)
-- 2. Is of type 'global'
-- 3. Must only ever be one instance (Singleton)
--
-- Created at database setup and never deleted.
-- =============================================================================

INSERT INTO vaults (owner_id, vault_type, name)
VALUES (NULL, 'global', 'Global');
41 changes: 41 additions & 0 deletions database/V17__create_update_triggers.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
-- =============================================================================
-- V17__create_updated_at_triggers.sql
--
-- PostgreSQL trigger that automatically sets updated_at = NOW() on every UPDATE, for all tables that have an updated_at column.
--
-- Without it, updated_at columns in table would never change
-- The application layer doesn't need to set updated_at manually
-- =============================================================================

-- Shared trigger function - used by all triggers below.
CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- user_profile
CREATE TRIGGER trg_user_profile_updated_at
BEFORE UPDATE ON user_profile
FOR EACH ROW
EXECUTE FUNCTION set_updated_at();

-- user_preferences
CREATE TRIGGER trg_user_preferences_updated_at
BEFORE UPDATE ON user_preferences
FOR EACH ROW
EXECUTE FUNCTION set_updated_at();

-- recipes
CREATE TRIGGER trg_recipes_updated_at
BEFORE UPDATE ON recipes
FOR EACH ROW
EXECUTE FUNCTION set_updated_at();

-- pantry_ingredients
CREATE TRIGGER trg_pantry_ingredients_updated_at
BEFORE UPDATE ON pantry_ingredients
FOR EACH ROW
EXECUTE FUNCTION set_updated_at();
37 changes: 37 additions & 0 deletions database/V18__seed_admin.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
-- =============================================================================
-- V18__seed_admin.sql
--
-- Seeds the default admin account
-- Only the admin is seeded - other users will register through app (so passwords are correctly salted and hashed)
--
-- Hash generated with BCrypt cost factor 12.
-- Change password after registration is up and running
-- =============================================================================

WITH inserted_user AS (
INSERT INTO users (email, password_hash, role_id)
VALUES (
'admin@mealchemy.com',
'$2b$12$FCqc84bIoMfMKxomm9E66OVKA.VPdvbQm6QwJc3k5G1.JQ6GNlE5m', -- NOSONAR
(SELECT role_id FROM roles WHERE role_name = 'admin')
)
RETURNING user_id
),
inserted_profile AS (
INSERT INTO user_profile (user_id, display_name, preferred_unit, equipment)
SELECT
user_id,
'Admin',
'metric',
'["oven", "airfryer", "blender"]'::jsonb
FROM inserted_user
RETURNING user_id
)
INSERT INTO user_preferences (user_id, dietary_restrictions, allergies, disliked_ingredients, flavour_profile)
SELECT
user_id,
'[]'::jsonb,
'[]'::jsonb,
'[]'::jsonb,
'["italian", "asian", "mediterranean", "mexican"]'::jsonb
FROM inserted_user;
19 changes: 19 additions & 0 deletions database/V19__seed_admin_vault_and_folders.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- =============================================================================
-- V19__seed_admin_vault_and_folders.sql
--
-- Creates the admin's private vault and a General folder inside it
-- All 5 mock recipes will be placed in the General folder
-- =============================================================================

WITH private_vault AS (
INSERT INTO vaults (owner_id, vault_type, name)
VALUES (
(SELECT user_id FROM users WHERE email = 'admin@mealchemy.com'),
'private',
'My Vault'
)
RETURNING vault_id
)
INSERT INTO vault_folders (vault_id, name)
SELECT vault_id, 'General'
FROM private_vault;
18 changes: 18 additions & 0 deletions database/V20__seed_recipes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- =============================================================================
-- V20__seed_recipes.sql
--
-- Seeds 5 recipes owned by the admin account.
--
-- Ingredients, Steps, Folder Links in migration files to follow
-- =============================================================================

WITH admin_user AS (
SELECT user_id FROM users WHERE email = 'admin@mealchemy.com'
)

INSERT INTO recipes (owner_id, title, description, cuisine_type, prep_time_mins, cooking_time_mins, serving_size)
SELECT user_id, 'Honey Sriracha Chicken and Broccoli', 'A quick and healthy meal prep bowl with crispy oven-baked chicken tossed in a sweet and spicy honey sriracha sauce, served over brown rice with roasted broccoli.', 'asian', 10, 20, 8 FROM admin_user UNION ALL
SELECT user_id, 'Caprese Pasta Salad', 'A fresh and vibrant cold pasta salad with cherry tomatoes, mini mozzarella, red onion, and basil, dressed in a bright lemon and balsamic vinaigrette with pecorino.', 'italian', 15, 15, 6 FROM admin_user UNION ALL
SELECT user_id, 'Penne Alla Vodka', 'A classic Italian-American pasta with a rich, creamy tomato and vodka sauce finished with Parmesan and fresh herbs. Comforting and deeply flavourful.', 'italian', 5, 25, 8 FROM admin_user UNION ALL
SELECT user_id, 'Lemon Herb Salmon with Roasted Vegetables', 'Tender oven-roasted salmon fillets on a bed of colourful roasted vegetables, finished with a garlic, lemon, and herb dressing. Light, healthy, and ready in 30 minutes.', 'mediterranean', 10, 20, 4 FROM admin_user UNION ALL
SELECT user_id, 'Black Bean and Corn Burrito Bowls', 'A hearty and wholesome vegetarian meal prep bowl with spiced black beans and corn over brown rice, topped with avocado, fresh coriander, and sour cream.', 'mexican', 10, 15, 4 FROM admin_user;
Loading
Loading