Index: itignore
===================================================================
--- .gitignore	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,31 +1,0 @@
-# Java / Maven
-target/
-!.mvn/wrapper/maven-wrapper.jar
-*.class
-
-# IntelliJ
-.idea/
-*.iml
-*.iws
-out/
-
-# Node
-node_modules/
-dist/
-.vite/
-
-# Environment
-.env
-.env.local
-
-# OS
-.DS_Store
-Thumbs.db
-
-# Logs
-*.log
-
-# Credentials and local tooling - never commit
-tunnel_scripta.cmd
-*.ppk
-plink.exe
Index: TP_SMOKE_TEST.http
===================================================================
--- HTTP_SMOKE_TEST.http	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,104 +1,0 @@
-### Nutritioneer - click the green arrows in IntelliJ, top to bottom.
-### The token from step 2 is captured into a variable and reused.
-
-@host = http://localhost:8080
-
-### 1. Register a fresh account (409 if you run it twice - that is correct)
-POST {{host}}/api/auth/register
-Content-Type: application/json
-
-{ "email": "test.user@example.com", "username": "test_user", "password": "testPass@1" }
-
-### 2. Log in as a seeded account
-# @name login
-POST {{host}}/api/auth/login
-Content-Type: application/json
-
-{ "email": "marija.trajkovska@gmail.com", "password": "testPass@1" }
-
-> {% client.global.set("token", response.body.token); %}
-
-### 3. The public feed
-GET {{host}}/api/posts/feed
-Authorization: Bearer {{token}}
-
-### 4. UC1001 - create a recipe. No kcal is sent; the server derives it.
-# @name newRecipe
-POST {{host}}/api/recipes
-Authorization: Bearer {{token}}
-Content-Type: application/json
-
-{
-  "name": "Test Bean Stew",
-  "guide": "1. Boil the beans.\n2. Fry the onion.\n3. Combine and bake.",
-  "servings": 4,
-  "ingredients": [
-    { "ingredientName": "Dry white beans", "quantity": 400 },
-    { "ingredientName": "Onion", "quantity": 150 },
-    { "ingredientName": "Olive oil", "quantity": 30 }
-  ],
-  "restrictionIds": []
-}
-
-> {% client.global.set("recipeId", response.body.id); %}
-
-### 5. UC1004 - share it. Running this twice returns 409 (uq_post_recipe).
-POST {{host}}/api/posts
-Authorization: Bearer {{token}}
-Content-Type: application/json
-
-{ "recipeId": {{recipeId}}, "isPrivate": false, "isFavourite": false, "status": "published" }
-
-### 6. UC1002 - log a meal (attach post id 1 from the seed data)
-POST {{host}}/api/planner
-Authorization: Bearer {{token}}
-Content-Type: application/json
-
-{ "notes": "Smoke test lunch", "postIds": [1], "consumed": true }
-
-### 7. Daily totals
-GET {{host}}/api/planner/daily?from=2026-01-20&to=2026-02-05
-Authorization: Bearer {{token}}
-
-### 8. UC1003 - grocery list from a recipe plus one item
-POST {{host}}/api/grocery-lists
-Authorization: Bearer {{token}}
-Content-Type: application/json
-
-{
-  "notes": "Smoke test list",
-  "bulkRecipeIds": [1],
-  "singleItems": [{ "ingredientName": "Apple", "buyQuantity": 1000 }]
-}
-
-### 9. Biometrics
-POST {{host}}/api/biometrics
-Authorization: Bearer {{token}}
-Content-Type: application/json
-
-{ "date": "2026-02-02", "weight": 72.0, "height": 168, "age": 29, "muscleFatRatio": 1.97 }
-
-### 10. Should be 403 - a regular user is not an administrator
-GET {{host}}/api/admin/accounts
-Authorization: Bearer {{token}}
-
-### 11. Log in as the trainer
-# @name trainerLogin
-POST {{host}}/api/auth/login
-Content-Type: application/json
-
-{ "email": "viktor.ilievski@nutritioneer.mk", "password": "testPass@1" }
-
-> {% client.global.set("trainerToken", response.body.token); %}
-
-### 12. UC3001 - client weekly intake
-GET {{host}}/api/trainer/clients/marija.trajkovska@gmail.com/intake?from=2026-01-22&to=2026-01-29
-Authorization: Bearer {{trainerToken}}
-
-### 13. UC3004 - protein density ranking
-GET {{host}}/api/trainer/analysis/nutrient-density?nutrient=Protein&limit=5
-Authorization: Bearer {{trainerToken}}
-
-### 14. UC3003 - feed filtered by restriction
-GET {{host}}/api/posts/feed/by-restriction?type=gluten_free&avoid=Contains tree nuts
-Authorization: Bearer {{trainerToken}}
Index: README.md
===================================================================
--- README.md	(revision 33b72d451e8daa85c40201d8170702f80bc3aea6)
+++ README.md	(revision 33b72d451e8daa85c40201d8170702f80bc3aea6)
@@ -0,0 +1,2 @@
+# Nutritioneer
+Проект по предметот Бази на податоци 2026з - ментор: проф. Вангел Ајановски
Index: ckend/.mvn/wrapper/maven-wrapper.properties
===================================================================
--- backend/.mvn/wrapper/maven-wrapper.properties	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,2 +1,0 @@
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
-wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar
Index: ckend/package-lock.json
===================================================================
--- backend/package-lock.json	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,6 +1,0 @@
-{
-  "name": "backend",
-  "lockfileVersion": 3,
-  "requires": true,
-  "packages": {}
-}
Index: ckend/pom.xml
===================================================================
--- backend/pom.xml	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,110 +1,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-
-    <parent>
-        <groupId>org.springframework.boot</groupId>
-        <artifactId>spring-boot-starter-parent</artifactId>
-        <version>3.4.2</version>
-        <relativePath/>
-    </parent>
-
-    <groupId>mk.finki</groupId>
-    <artifactId>nutritioneer</artifactId>
-    <version>0.1.0-SNAPSHOT</version>
-    <name>nutritioneer</name>
-    <description>Nutrient tracking, recipe sharing and meal planning</description>
-
-    <properties>
-        <java.version>21</java.version>
-        <jjwt.version>0.12.6</jjwt.version>
-        <springdoc.version>2.7.0</springdoc.version>
-    </properties>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-jpa</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-security</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-validation</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>org.postgresql</groupId>
-            <artifactId>postgresql</artifactId>
-            <scope>runtime</scope>
-        </dependency>
-
-        <!-- JWT -->
-        <dependency>
-            <groupId>io.jsonwebtoken</groupId>
-            <artifactId>jjwt-api</artifactId>
-            <version>${jjwt.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>io.jsonwebtoken</groupId>
-            <artifactId>jjwt-impl</artifactId>
-            <version>${jjwt.version}</version>
-            <scope>runtime</scope>
-        </dependency>
-        <dependency>
-            <groupId>io.jsonwebtoken</groupId>
-            <artifactId>jjwt-jackson</artifactId>
-            <version>${jjwt.version}</version>
-            <scope>runtime</scope>
-        </dependency>
-
-        <!-- OpenAPI / Swagger UI at /swagger-ui.html -->
-        <dependency>
-            <groupId>org.springdoc</groupId>
-            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
-            <version>${springdoc.version}</version>
-        </dependency>
-
-        <dependency>
-            <groupId>org.projectlombok</groupId>
-            <artifactId>lombok</artifactId>
-            <optional>true</optional>
-        </dependency>
-
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-test</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.security</groupId>
-            <artifactId>spring-security-test</artifactId>
-            <scope>test</scope>
-        </dependency>
-    </dependencies>
-
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.springframework.boot</groupId>
-                <artifactId>spring-boot-maven-plugin</artifactId>
-                <configuration>
-                    <excludes>
-                        <exclude>
-                            <groupId>org.projectlombok</groupId>
-                            <artifactId>lombok</artifactId>
-                        </exclude>
-                    </excludes>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-</project>
Index: ckend/src/main/java/mk/finki/nutritioneer/NutritioneerApplication.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/NutritioneerApplication.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,12 +1,0 @@
-package mk.finki.nutritioneer;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-@SpringBootApplication
-public class NutritioneerApplication {
-
-    public static void main(String[] args) {
-        SpringApplication.run(NutritioneerApplication.class, args);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/config/OpenApiConfig.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/config/OpenApiConfig.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,32 +1,0 @@
-package mk.finki.nutritioneer.config;
-
-import io.swagger.v3.oas.models.Components;
-import io.swagger.v3.oas.models.OpenAPI;
-import io.swagger.v3.oas.models.info.Info;
-import io.swagger.v3.oas.models.security.SecurityRequirement;
-import io.swagger.v3.oas.models.security.SecurityScheme;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-
-@Configuration
-public class OpenApiConfig {
-
-    private static final String SCHEME = "bearerAuth";
-
-    @Bean
-    public OpenAPI nutritioneerOpenApi() {
-        return new OpenAPI()
-                .info(new Info()
-                        .title("Nutritioneer API")
-                        .version("0.1.0")
-                        .description("Nutrient tracking, recipe sharing and meal planning."))
-                .addSecurityItem(new SecurityRequirement().addList(SCHEME))
-                .components(new Components().addSecuritySchemes(SCHEME,
-                        new SecurityScheme()
-                                .name(SCHEME)
-                                .type(SecurityScheme.Type.HTTP)
-                                .scheme("bearer")
-                                .bearerFormat("JWT")));
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/config/SecurityConfig.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/config/SecurityConfig.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,82 +1,0 @@
-package mk.finki.nutritioneer.config;
-
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.security.JwtAuthenticationFilter;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.http.HttpMethod;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
-import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.http.SessionCreationPolicy;
-import org.springframework.security.crypto.password.NoOpPasswordEncoder;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-import org.springframework.web.cors.CorsConfiguration;
-import org.springframework.web.cors.CorsConfigurationSource;
-import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
-
-import java.util.Arrays;
-import java.util.List;
-
-@Configuration
-@EnableMethodSecurity   // enables @PreAuthorize on the admin and trainer endpoints
-@RequiredArgsConstructor
-public class SecurityConfig {
-
-    private final JwtAuthenticationFilter jwtFilter;
-
-    @Value("${app.cors.allowed-origins}")
-    private String allowedOrigins;
-
-    @Bean
-    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
-        http
-            // Stateless JWT API: no session to protect, so CSRF is switched off.
-            .csrf(csrf -> csrf.disable())
-            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
-            .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
-            .authorizeHttpRequests(auth -> auth
-                    .requestMatchers("/api/auth/**").permitAll()
-                    .requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
-                    .requestMatchers(HttpMethod.GET, "/api/catalog/**").permitAll()
-                    .requestMatchers("/api/admin/**").hasRole("ADMINISTRATOR")
-                    .requestMatchers("/api/trainer/**").hasAnyRole("TRAINER", "ADMINISTRATOR")
-                    .anyRequest().authenticated())
-            .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
-
-        return http.build();
-    }
-
-    @Bean
-    public CorsConfigurationSource corsConfigurationSource() {
-        CorsConfiguration config = new CorsConfiguration();
-        config.setAllowedOrigins(Arrays.asList(allowedOrigins.split(",")));
-        config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
-        config.setAllowedHeaders(List.of("*"));
-        config.setAllowCredentials(true);
-
-        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
-        source.registerCorsConfiguration("/**", config);
-        return source;
-    }
-
-    /**
-     * Моментална состојва на passwords е plain text - понатаму ќе се замени со
-     * BCryptPasswordEncoder функција за хеширање поради безбедност.
-     */
-    @Bean
-    @SuppressWarnings("deprecation")
-    public PasswordEncoder passwordEncoder() {
-        return NoOpPasswordEncoder.getInstance();
-    }
-
-    @Bean
-    public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
-            throws Exception {
-        return config.getAuthenticationManager();
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/AppUser.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/AppUser.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,28 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-import jakarta.persistence.*;
-import lombok.*;
-import org.hibernate.annotations.JdbcTypeCode;
-import org.hibernate.type.SqlTypes;
-
-@Entity
-@Table(name = "\"user\"")
-@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
-public class AppUser {
-
-    @Id
-    @Column(name = "email", nullable = false)
-    private String email;
-
-    @Column(name = "username", nullable = false, unique = true)
-    private String username;
-
-
-    @Column(name = "password", nullable = false)
-    private String password;
-
-    @Enumerated(EnumType.STRING)
-    @JdbcTypeCode(SqlTypes.NAMED_ENUM)
-    @Column(name = "role", nullable = false, columnDefinition = "user_role")
-    private UserRole role;
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/Biometrics.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/Biometrics.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,49 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-import jakarta.persistence.*;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.time.LocalDate;
-
-@Entity
-@Table(name = "biometrics")
-@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
-public class Biometrics {
-
-    @Id
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
-    @Column(name = "id")
-    private Long id;
-
-    @Column(name = "date", nullable = false)
-    private LocalDate date;
-
-    /** kg */
-    @Column(name = "weight")
-    private BigDecimal weight;
-
-    /** cm */
-    @Column(name = "height")
-    private BigDecimal height;
-
-    @Column(name = "age")
-    private BigDecimal age;
-
-    @Column(name = "muscle_fat_ratio")
-    private BigDecimal muscleFatRatio;
-
-    @ManyToOne(fetch = FetchType.LAZY, optional = false)
-    @JoinColumn(name = "user_email", nullable = false)
-    private AppUser owner;
-
-    @Transient
-    public BigDecimal getBmi() {
-        if (weight == null || height == null || height.signum() == 0) {
-            return null;
-        }
-        BigDecimal metres = height.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP);
-        return weight.divide(metres.multiply(metres), 1, RoundingMode.HALF_UP);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/Comment.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/Comment.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,38 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-import jakarta.persistence.*;
-import lombok.*;
-
-import java.time.LocalDateTime;
-
-@Entity
-@Table(name = "comment")
-@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
-public class Comment {
-
-    @Id
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
-    @Column(name = "id")
-    private Long id;
-
-    @Column(name = "description", nullable = false)
-    private String description;
-
-    @Column(name = "created_at", nullable = false)
-    private LocalDateTime createdAt;
-
-    @ManyToOne(fetch = FetchType.LAZY, optional = false)
-    @JoinColumn(name = "post_id", nullable = false)
-    private Post post;
-
-    @ManyToOne(fetch = FetchType.EAGER, optional = false)
-    @JoinColumn(name = "created_by", nullable = false)
-    private AppUser author;
-
-    @PrePersist
-    void onCreate() {
-        if (createdAt == null) {
-            createdAt = LocalDateTime.now();
-        }
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/GroceryItem.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/GroceryItem.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,47 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-import jakarta.persistence.*;
-import lombok.*;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.util.Objects;
-
-@Entity
-@Table(name = "grocery_list_single_add_ingredient")
-@IdClass(GroceryItem.Key.class)
-@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
-public class GroceryItem {
-
-    @Id
-    @ManyToOne(fetch = FetchType.LAZY, optional = false)
-    @JoinColumn(name = "list_id", nullable = false)
-    private GroceryList list;
-
-    @Id
-    @ManyToOne(fetch = FetchType.EAGER, optional = false)
-    @JoinColumn(name = "ingredient_name", nullable = false)
-    private Ingredient ingredient;
-
-    @Column(name = "buy_quantity", nullable = false)
-    private BigDecimal buyQuantity;
-
-    @Getter @Setter @NoArgsConstructor @AllArgsConstructor
-    public static class Key implements Serializable {
-        private Long list;
-        private String ingredient;
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (!(o instanceof Key key)) return false;
-            return Objects.equals(list, key.list)
-                    && Objects.equals(ingredient, key.ingredient);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(list, ingredient);
-        }
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/GroceryList.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/GroceryList.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,58 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-import jakarta.persistence.*;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-@Entity
-@Table(name = "grocery_list")
-@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
-public class GroceryList {
-
-    @Id
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
-    @Column(name = "id")
-    private Long id;
-
-    @Column(name = "date_time", nullable = false)
-    private LocalDateTime dateTime;
-
-    @Column(name = "notes")
-    private String notes;
-
-    @Column(name = "is_bought", nullable = false)
-    private boolean isBought;
-
-    @Column(name = "kcal", nullable = false)
-    private BigDecimal kcal;
-
-    @ManyToOne(fetch = FetchType.LAZY, optional = false)
-    @JoinColumn(name = "user_email", nullable = false)
-    private AppUser owner;
-
-    @ManyToMany(fetch = FetchType.LAZY)
-    @JoinTable(
-            name = "grocery_list_bulk_add_ingredient",
-            joinColumns = @JoinColumn(name = "list_id"),
-            inverseJoinColumns = @JoinColumn(name = "recipe_id"))
-    @Builder.Default
-    private Set<Recipe> bulkRecipes = new LinkedHashSet<>();
-
-    @OneToMany(mappedBy = "list", cascade = CascadeType.ALL, orphanRemoval = true)
-    @Builder.Default
-    private Set<GroceryItem> singleItems = new LinkedHashSet<>();
-
-    @PrePersist
-    void onCreate() {
-        if (dateTime == null) {
-            dateTime = LocalDateTime.now();
-        }
-        if (kcal == null) {
-            kcal = BigDecimal.ZERO;
-        }
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/Ingredient.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/Ingredient.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,32 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-import jakarta.persistence.*;
-import lombok.*;
-import org.hibernate.annotations.JdbcTypeCode;
-import org.hibernate.type.SqlTypes;
-
-import java.math.BigDecimal;
-
-@Entity
-@Table(name = "ingredient")
-@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
-public class Ingredient {
-
-    @Id
-    @Column(name = "name", nullable = false)
-    private String name;
-
-    @Column(name = "description", nullable = false)
-    private String description;
-
-    @Column(name = "energy", nullable = false)
-    private BigDecimal energy;
-
-    @Column(name = "kcal", nullable = false)
-    private BigDecimal kcal;
-
-    @Enumerated(EnumType.STRING)
-    @JdbcTypeCode(SqlTypes.NAMED_ENUM)
-    @Column(name = "type", nullable = false, columnDefinition = "ingredient_type")
-    private IngredientType type;
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/IngredientNutrient.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/IngredientNutrient.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,47 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-import jakarta.persistence.*;
-import lombok.*;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.util.Objects;
-
-@Entity
-@Table(name = "ingredient_contains_nutrient")
-@IdClass(IngredientNutrient.Key.class)
-@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
-public class IngredientNutrient {
-
-    @Id
-    @ManyToOne(fetch = FetchType.LAZY, optional = false)
-    @JoinColumn(name = "ingredient_name", nullable = false)
-    private Ingredient ingredient;
-
-    @Id
-    @ManyToOne(fetch = FetchType.EAGER, optional = false)
-    @JoinColumn(name = "nutrient_id", nullable = false)
-    private Nutrient nutrient;
-
-    @Column(name = "quantity", nullable = false)
-    private BigDecimal quantity;
-
-    @Getter @Setter @NoArgsConstructor @AllArgsConstructor
-    public static class Key implements Serializable {
-        private String ingredient;
-        private Long nutrient;
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (!(o instanceof Key key)) return false;
-            return Objects.equals(ingredient, key.ingredient)
-                    && Objects.equals(nutrient, key.nutrient);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(ingredient, nutrient);
-        }
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/IngredientType.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/IngredientType.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,6 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-public enum IngredientType {
-    dairy, meat, fish, herb, carb, vegetable, fruit, legume,
-    nut, seed, oil, spice, beverage, other
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/IntakePlanner.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/IntakePlanner.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,55 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-import jakarta.persistence.*;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-
-@Entity
-@Table(name = "intake_planner")
-@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
-public class IntakePlanner {
-
-    @Id
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
-    @Column(name = "id")
-    private Long id;
-
-    @Column(name = "date_time", nullable = false)
-    private LocalDateTime dateTime;
-
-    @Column(name = "notes")
-    private String notes;
-
-    @Column(name = "kcal", nullable = false)
-    private BigDecimal kcal;
-
-    @Column(name = "is_consumed", nullable = false)
-    private boolean isConsumed;
-
-    @ManyToOne(fetch = FetchType.LAZY, optional = false)
-    @JoinColumn(name = "user_email", nullable = false)
-    private AppUser owner;
-
-    @ManyToMany(fetch = FetchType.LAZY)
-    @JoinTable(
-            name = "intake_planner_save_to_list_post",
-            joinColumns = @JoinColumn(name = "planner_id"),
-            inverseJoinColumns = @JoinColumn(name = "post_id"))
-    @Builder.Default
-    private Set<Post> savedPosts = new LinkedHashSet<>();
-
-    @PrePersist
-    void onCreate() {
-        if (dateTime == null) {
-            dateTime = LocalDateTime.now();
-        }
-        if (kcal == null) {
-            kcal = BigDecimal.ZERO;
-        }
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/Nutrient.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/Nutrient.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,35 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-import jakarta.persistence.*;
-import lombok.*;
-import org.hibernate.annotations.JdbcTypeCode;
-import org.hibernate.type.SqlTypes;
-
-import java.math.BigDecimal;
-
-@Entity
-@Table(name = "nutrient")
-@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
-public class Nutrient {
-
-    @Id
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
-    @Column(name = "id")
-    private Long id;
-
-    @Column(name = "quantity", nullable = false)
-    private BigDecimal quantity;
-
-    @Column(name = "description", nullable = false, unique = true)
-    private String description;
-
-    @Enumerated(EnumType.STRING)
-    @JdbcTypeCode(SqlTypes.NAMED_ENUM)
-    @Column(name = "unit", nullable = false, columnDefinition = "nutrient_unit")
-    private NutrientUnit unit;
-
-    @Enumerated(EnumType.STRING)
-    @JdbcTypeCode(SqlTypes.NAMED_ENUM)
-    @Column(name = "type", nullable = false, columnDefinition = "nutrient_type")
-    private NutrientType type;
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/NutrientType.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/NutrientType.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,5 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-public enum NutrientType {
-    macro, micro
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/NutrientUnit.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/NutrientUnit.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,5 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-public enum NutrientUnit {
-    milligram, gram, microgram
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/Post.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/Post.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,66 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-import jakarta.persistence.*;
-import lombok.*;
-import org.hibernate.annotations.JdbcTypeCode;
-import org.hibernate.type.SqlTypes;
-
-import java.time.LocalDateTime;
-
-@Entity
-@Table(name = "post")
-@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
-public class Post {
-
-    @Id
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
-    @Column(name = "id")
-    private Long id;
-
-    /**
-     Не е детално тестирано и имплементирано додавање на media
-     - ќе се додаде накнадно
-     */
-    @JdbcTypeCode(SqlTypes.VARBINARY)
-    @Basic(fetch = FetchType.LAZY)
-    @Column(name = "media", columnDefinition = "bytea")
-    private byte[] media;
-
-    @Column(name = "media_type")
-    private String mediaType;
-
-    @Column(name = "media_name")
-    private String mediaName;
-
-    @Column(name = "is_private", nullable = false)
-    private boolean isPrivate;
-
-    @Column(name = "is_favourite", nullable = false)
-    private boolean isFavourite;
-
-    @Enumerated(EnumType.STRING)
-    @JdbcTypeCode(SqlTypes.NAMED_ENUM)
-    @Column(name = "status", nullable = false, columnDefinition = "post_status")
-    private PostStatus status;
-
-    @Column(name = "created_at", nullable = false)
-    private LocalDateTime createdAt;
-
-    @ManyToOne(fetch = FetchType.EAGER, optional = false)
-    @JoinColumn(name = "created_by", nullable = false)
-    private AppUser author;
-
-    @OneToOne(fetch = FetchType.EAGER)
-    @JoinColumn(name = "recipe_id", unique = true)
-    private Recipe recipe;
-
-    @PrePersist
-    void onCreate() {
-        if (createdAt == null) {
-            createdAt = LocalDateTime.now();
-        }
-        if (status == null) {
-            status = PostStatus.draft;
-        }
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/PostStatus.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/PostStatus.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,5 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-public enum PostStatus {
-    published, draft, archived
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/Recipe.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/Recipe.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,56 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-import jakarta.persistence.*;
-import lombok.*;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-@Entity
-@Table(name = "recipe")
-@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
-public class Recipe {
-
-    @Id
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
-    @Column(name = "id")
-    private Long id;
-
-    @Column(name = "name", nullable = false)
-    private String name;
-
-    @Column(name = "guide", nullable = false)
-    private String guide;
-
-    @Column(name = "kcal_sum", nullable = false)
-    private BigDecimal kcalSum;
-
-    @Column(name = "servings", nullable = false)
-    private BigDecimal servings;
-
-    @ManyToOne(fetch = FetchType.LAZY, optional = false)
-    @JoinColumn(name = "user_email", nullable = false)
-    private AppUser owner;
-
-    @OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true)
-    @Builder.Default
-    private Set<RecipeIngredient> ingredients = new LinkedHashSet<>();
-
-    @ManyToMany(fetch = FetchType.LAZY)
-    @JoinTable(
-            name = "recipe_contains_restriction",
-            joinColumns = @JoinColumn(name = "recipe_id"),
-            inverseJoinColumns = @JoinColumn(name = "restriction_id"))
-    @Builder.Default
-    private Set<Restriction> restrictions = new LinkedHashSet<>();
-
-    @Transient
-    public BigDecimal getKcalPerServing() {
-        if (kcalSum == null || servings == null || servings.signum() == 0) {
-            return BigDecimal.ZERO;
-        }
-        return kcalSum.divide(servings, 2, RoundingMode.HALF_UP);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/RecipeIngredient.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/RecipeIngredient.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,48 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-import jakarta.persistence.*;
-import lombok.*;
-
-import java.io.Serializable;
-import java.math.BigDecimal;
-import java.util.Objects;
-
-
-@Entity
-@Table(name = "recipe_contains_ingredient")
-@IdClass(RecipeIngredient.Key.class)
-@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
-public class RecipeIngredient {
-
-    @Id
-    @ManyToOne(fetch = FetchType.LAZY, optional = false)
-    @JoinColumn(name = "recipe_id", nullable = false)
-    private Recipe recipe;
-
-    @Id
-    @ManyToOne(fetch = FetchType.EAGER, optional = false)
-    @JoinColumn(name = "ingredient_name", nullable = false)
-    private Ingredient ingredient;
-
-    @Column(name = "ingredient_quantity", nullable = false)
-    private BigDecimal quantity;
-
-    @Getter @Setter @NoArgsConstructor @AllArgsConstructor
-    public static class Key implements Serializable {
-        private Long recipe;
-        private String ingredient;
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (!(o instanceof Key key)) return false;
-            return Objects.equals(recipe, key.recipe)
-                    && Objects.equals(ingredient, key.ingredient);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(recipe, ingredient);
-        }
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/Restriction.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/Restriction.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,25 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-import jakarta.persistence.*;
-import lombok.*;
-import org.hibernate.annotations.JdbcTypeCode;
-import org.hibernate.type.SqlTypes;
-
-@Entity
-@Table(name = "restriction")
-@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder
-public class Restriction {
-
-    @Id
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
-    @Column(name = "id")
-    private Long id;
-
-    @Column(name = "description", nullable = false, unique = true)
-    private String description;
-
-    @Enumerated(EnumType.STRING)
-    @JdbcTypeCode(SqlTypes.NAMED_ENUM)
-    @Column(name = "type", nullable = false, columnDefinition = "restriction_type")
-    private RestrictionType type;
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/RestrictionType.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/RestrictionType.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,6 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-public enum RestrictionType {
-    vegan, vegetarian, halal, kosher, pescatarian, allergen, keto,
-    paleo, gluten_free, lactose_free, diabetic, low_sodium, other
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/domain/UserRole.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/domain/UserRole.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,12 +1,0 @@
-package mk.finki.nutritioneer.domain;
-
-
-public enum UserRole {
-    user,
-    administrator,
-    trainer;
-
-    public String authority() {
-        return "ROLE_" + name().toUpperCase();
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/exception/ApiException.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/exception/ApiException.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,34 +1,0 @@
-package mk.finki.nutritioneer.exception;
-
-import org.springframework.http.HttpStatus;
-
-/** Carries the HTTP status the client should see, so services stay web-agnostic. */
-public class ApiException extends RuntimeException {
-
-    private final HttpStatus status;
-
-    public ApiException(HttpStatus status, String message) {
-        super(message);
-        this.status = status;
-    }
-
-    public HttpStatus getStatus() {
-        return status;
-    }
-
-    public static ApiException notFound(String what) {
-        return new ApiException(HttpStatus.NOT_FOUND, what + " not found");
-    }
-
-    public static ApiException conflict(String message) {
-        return new ApiException(HttpStatus.CONFLICT, message);
-    }
-
-    public static ApiException forbidden(String message) {
-        return new ApiException(HttpStatus.FORBIDDEN, message);
-    }
-
-    public static ApiException badRequest(String message) {
-        return new ApiException(HttpStatus.BAD_REQUEST, message);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/exception/ApiExceptionHandler.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/exception/ApiExceptionHandler.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,89 +1,0 @@
-package mk.finki.nutritioneer.exception;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.dao.DataIntegrityViolationException;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.web.bind.MethodArgumentNotValidException;
-import org.springframework.web.bind.annotation.ExceptionHandler;
-import org.springframework.web.bind.annotation.RestControllerAdvice;
-
-import java.time.Instant;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Turns exceptions into a single JSON shape. Database constraint violations are
- * translated rather than leaked: every CHECK and UNIQUE in the DDL is a business
- * rule, so a violation is a 409 with a readable message, not a 500.
- */
-@Slf4j
-@RestControllerAdvice
-public class ApiExceptionHandler {
-
-    @ExceptionHandler(ApiException.class)
-    public ResponseEntity<Map<String, Object>> handleApi(ApiException ex) {
-        return build(ex.getStatus(), ex.getMessage());
-    }
-
-    @ExceptionHandler(BadCredentialsException.class)
-    public ResponseEntity<Map<String, Object>> handleBadCredentials(BadCredentialsException ex) {
-        return build(HttpStatus.UNAUTHORIZED, "Invalid email or password");
-    }
-
-    @ExceptionHandler(MethodArgumentNotValidException.class)
-    public ResponseEntity<Map<String, Object>> handleValidation(MethodArgumentNotValidException ex) {
-        Map<String, Object> body = base(HttpStatus.BAD_REQUEST, "Validation failed");
-        Map<String, String> fields = new HashMap<>();
-        ex.getBindingResult().getFieldErrors()
-                .forEach(e -> fields.put(e.getField(), e.getDefaultMessage()));
-        body.put("fields", fields);
-        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body);
-    }
-
-    @ExceptionHandler(DataIntegrityViolationException.class)
-    public ResponseEntity<Map<String, Object>> handleConstraint(DataIntegrityViolationException ex) {
-        String raw = ex.getMostSpecificCause().getMessage();
-        return build(HttpStatus.CONFLICT, translate(raw));
-    }
-
-    private String translate(String raw) {
-        if (raw == null) {
-            return "The request violates a database constraint";
-        }
-        if (raw.contains("uq_post_recipe")) {
-            return "This recipe has already been shared as a post";
-        }
-        if (raw.contains("uq_user_username")) {
-            return "That username is already taken";
-        }
-        if (raw.contains("uq_biometrics_user_date")) {
-            return "A measurement for that date already exists";
-        }
-        if (raw.contains("uq_nutrient_description")) {
-            return "That nutrient already exists in the catalogue";
-        }
-        if (raw.contains("ck_recipe_servings")) {
-            return "Servings must be greater than zero";
-        }
-        if (raw.contains("ck_user_email_format")) {
-            return "That email address is not valid";
-        }
-        log.warn("Unmapped constraint violation: {}", raw);
-        return "The request violates a database constraint";
-    }
-
-    private ResponseEntity<Map<String, Object>> build(HttpStatus status, String message) {
-        return ResponseEntity.status(status).body(base(status, message));
-    }
-
-    private Map<String, Object> base(HttpStatus status, String message) {
-        Map<String, Object> body = new HashMap<>();
-        body.put("timestamp", Instant.now().toString());
-        body.put("status", status.value());
-        body.put("error", status.getReasonPhrase());
-        body.put("message", message);
-        return body;
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/repo/AppUserRepository.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/repo/AppUserRepository.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,13 +1,0 @@
-package mk.finki.nutritioneer.repo;
-
-import mk.finki.nutritioneer.domain.AppUser;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-import java.util.Optional;
-
-public interface AppUserRepository extends JpaRepository<AppUser, String> {
-
-    Optional<AppUser> findByUsername(String username);
-
-    boolean existsByUsername(String username);
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/repo/BiometricsRepository.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/repo/BiometricsRepository.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,15 +1,0 @@
-package mk.finki.nutritioneer.repo;
-
-import mk.finki.nutritioneer.domain.Biometrics;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-import java.time.LocalDate;
-import java.util.List;
-import java.util.Optional;
-
-public interface BiometricsRepository extends JpaRepository<Biometrics, Long> {
-
-    List<Biometrics> findByOwnerEmailOrderByDate(String email);
-
-    Optional<Biometrics> findByOwnerEmailAndDate(String email, LocalDate date);
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/repo/CommentRepository.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/repo/CommentRepository.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,10 +1,0 @@
-package mk.finki.nutritioneer.repo;
-
-import mk.finki.nutritioneer.domain.Comment;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-import java.util.List;
-
-public interface CommentRepository extends JpaRepository<Comment, Long> {
-    List<Comment> findByPostIdOrderByCreatedAt(Long postId);
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/repo/GroceryListRepository.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/repo/GroceryListRepository.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,60 +1,0 @@
-package mk.finki.nutritioneer.repo;
-
-import mk.finki.nutritioneer.domain.GroceryList;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Modifying;
-import org.springframework.data.jpa.repository.Query;
-import org.springframework.data.repository.query.Param;
-
-import java.util.List;
-
-public interface GroceryListRepository extends JpaRepository<GroceryList, Long> {
-
-    List<GroceryList> findByOwnerEmailOrderByDateTimeDesc(String email);
-
-    /** bulk-added recipes plus individually added items. */
-    @Modifying(clearAutomatically = true, flushAutomatically = true)
-    @Query(value = """
-            UPDATE grocery_list g
-            SET kcal = ROUND(
-                  COALESCE((SELECT SUM(r.kcal_sum)
-                            FROM grocery_list_bulk_add_ingredient b
-                            JOIN recipe r ON r.id = b.recipe_id
-                            WHERE b.list_id = g.id), 0)
-                + COALESCE((SELECT SUM(i.kcal * s.buy_quantity / 100.0)
-                            FROM grocery_list_single_add_ingredient s
-                            JOIN ingredient i ON i.name = s.ingredient_name
-                            WHERE s.list_id = g.id), 0)
-                , 2)
-            WHERE g.id = :listId
-            """, nativeQuery = true)
-    void recalculateKcal(@Param("listId") Long listId);
-
-    /**
-     * The consolidated shopping list: everything pulled in through a recipe,
-     * unioned with everything added by hand, summed per ingredient.
-     */
-    @Query(value = """
-            SELECT i.type::text AS "type", i.name AS "name", SUM(x.quantity) AS "totalGrams"
-            FROM (
-                SELECT rci.ingredient_name AS name, rci.ingredient_quantity AS quantity
-                FROM grocery_list_bulk_add_ingredient b
-                JOIN recipe_contains_ingredient rci ON rci.recipe_id = b.recipe_id
-                WHERE b.list_id = :listId
-                UNION ALL
-                SELECT s.ingredient_name, s.buy_quantity
-                FROM grocery_list_single_add_ingredient s
-                WHERE s.list_id = :listId
-            ) AS x
-            JOIN ingredient i ON i.name = x.name
-            GROUP BY i.type, i.name
-            ORDER BY i.type, i.name
-            """, nativeQuery = true)
-    List<ShoppingRow> consolidate(@Param("listId") Long listId);
-
-    interface ShoppingRow {
-        String getType();
-        String getName();
-        java.math.BigDecimal getTotalGrams();
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/repo/IngredientRepository.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/repo/IngredientRepository.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,14 +1,0 @@
-package mk.finki.nutritioneer.repo;
-
-import mk.finki.nutritioneer.domain.Ingredient;
-import mk.finki.nutritioneer.domain.IngredientType;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-import java.util.List;
-
-public interface IngredientRepository extends JpaRepository<Ingredient, String> {
-
-    List<Ingredient> findByNameContainingIgnoreCaseOrderByName(String fragment);
-
-    List<Ingredient> findByTypeOrderByName(IngredientType type);
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/repo/IntakePlannerRepository.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/repo/IntakePlannerRepository.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,91 +1,0 @@
-package mk.finki.nutritioneer.repo;
-
-import mk.finki.nutritioneer.domain.IntakePlanner;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Modifying;
-import org.springframework.data.jpa.repository.Query;
-import org.springframework.data.repository.query.Param;
-
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.List;
-
-public interface IntakePlannerRepository extends JpaRepository<IntakePlanner, Long> {
-
-    List<IntakePlanner> findByOwnerEmailOrderByDateTimeDesc(String email);
-
-    List<IntakePlanner> findByOwnerEmailAndDateTimeBetweenOrderByDateTime(
-            String email, LocalDateTime from, LocalDateTime to);
-
-    /**
-     * one portion of every attached meal.
-     * COALESCE - an entry with no attached post would otherwise
-     * write NULL into a NOT NULL column.
-     */
-    @Modifying(clearAutomatically = true, flushAutomatically = true)
-    @Query(value = """
-            UPDATE intake_planner ip
-            SET kcal = ROUND(COALESCE((
-                    SELECT SUM(r.kcal_sum / r.servings)
-                    FROM intake_planner_save_to_list_post sp
-                    JOIN post   p ON p.id = sp.post_id
-                    JOIN recipe r ON r.id = p.recipe_id
-                    WHERE sp.planner_id = ip.id
-                ), 0), 2)
-            WHERE ip.id = :plannerId
-            """, nativeQuery = true)
-    void recalculateKcal(@Param("plannerId") Long plannerId);
-
-    /** daily totals for a client over a period. */
-    @Query(value = """
-            SELECT ip.date_time::date       AS "day",
-                   ROUND(SUM(ip.kcal))      AS "kcalConsumed",
-                   COUNT(*)                 AS "mealsLogged"
-            FROM intake_planner ip
-            WHERE ip.user_email = :email
-              AND ip.is_consumed = TRUE
-              AND ip.date_time >= :from
-              AND ip.date_time <  :to
-            GROUP BY 1
-            ORDER BY 1
-            """, nativeQuery = true)
-    List<DailyIntakeRow> dailyIntake(@Param("email") String email,
-                                     @Param("from") LocalDateTime from,
-                                     @Param("to") LocalDateTime to);
-
-    /** macronutrients supplied by the meals logged in a period. */
-    @Query(value = """
-            SELECT n.description AS "nutrient",
-                   n.unit::text  AS "unit",
-                   ROUND(SUM(icn.quantity * rci.ingredient_quantity / 100.0 / r.servings), 1) AS "total"
-            FROM intake_planner ip
-            JOIN intake_planner_save_to_list_post sp ON sp.planner_id = ip.id
-            JOIN post   p ON p.id = sp.post_id
-            JOIN recipe r ON r.id = p.recipe_id
-            JOIN recipe_contains_ingredient rci     ON rci.recipe_id = r.id
-            JOIN ingredient_contains_nutrient icn   ON icn.ingredient_name = rci.ingredient_name
-            JOIN nutrient n                         ON n.id = icn.nutrient_id
-            WHERE ip.user_email = :email
-              AND ip.is_consumed = TRUE
-              AND n.type = 'macro'
-              AND ip.date_time >= :from
-              AND ip.date_time <  :to
-            GROUP BY n.description, n.unit
-            ORDER BY n.description
-            """, nativeQuery = true)
-    List<NutrientTotalRow> macroTotals(@Param("email") String email,
-                                       @Param("from") LocalDateTime from,
-                                       @Param("to") LocalDateTime to);
-
-    interface DailyIntakeRow {
-        LocalDate getDay();
-        java.math.BigDecimal getKcalConsumed();
-        Long getMealsLogged();
-    }
-
-    interface NutrientTotalRow {
-        String getNutrient();
-        String getUnit();
-        java.math.BigDecimal getTotal();
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/repo/NutrientRepository.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/repo/NutrientRepository.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,10 +1,0 @@
-package mk.finki.nutritioneer.repo;
-
-import mk.finki.nutritioneer.domain.Nutrient;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-import java.util.Optional;
-
-public interface NutrientRepository extends JpaRepository<Nutrient, Long> {
-    Optional<Nutrient> findByDescription(String description);
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/repo/PostRepository.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/repo/PostRepository.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,50 +1,0 @@
-package mk.finki.nutritioneer.repo;
-
-import mk.finki.nutritioneer.domain.Post;
-import mk.finki.nutritioneer.domain.PostStatus;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.Pageable;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Query;
-import org.springframework.data.repository.query.Param;
-
-import java.util.List;
-import java.util.Optional;
-
-public interface PostRepository extends JpaRepository<Post, Long> {
-
-    Page<Post> findByStatusAndIsPrivateFalseOrderByCreatedAtDesc(PostStatus status,
-                                                                 Pageable pageable);
-
-    List<Post> findByAuthorEmailOrderByCreatedAtDesc(String email);
-
-    Optional<Post> findByRecipeId(Long recipeId);
-
-    boolean existsByRecipeId(Long recipeId);
-
-    /**
-     * the feed filtered by a dietary restriction type, with
-     * recipes carrying an unwanted allergen excluded. Native, because the pair
-     * of correlated EXISTS clauses over a link table is far clearer in SQL
-     * than in JPQL - and this is the exact statement verified against the
-     * seed data.
-     */
-    @Query(value = """
-            SELECT p.*
-            FROM post p
-            JOIN recipe r ON r.id = p.recipe_id
-            WHERE p.status = 'published'
-              AND p.is_private = FALSE
-              AND EXISTS (
-                  SELECT 1 FROM recipe_contains_restriction rcr
-                  JOIN restriction res ON res.id = rcr.restriction_id
-                  WHERE rcr.recipe_id = r.id AND res.type::text = :type)
-              AND NOT EXISTS (
-                  SELECT 1 FROM recipe_contains_restriction rcr
-                  JOIN restriction res ON res.id = rcr.restriction_id
-                  WHERE rcr.recipe_id = r.id AND res.description IN (:avoid))
-            ORDER BY r.kcal_sum / r.servings
-            """, nativeQuery = true)
-    List<Post> feedByRestriction(@Param("type") String type,
-                                 @Param("avoid") List<String> avoid);
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/repo/RecipeIngredientRepository.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/repo/RecipeIngredientRepository.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,14 +1,0 @@
-package mk.finki.nutritioneer.repo;
-
-import mk.finki.nutritioneer.domain.RecipeIngredient;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-import java.util.List;
-
-public interface RecipeIngredientRepository
-        extends JpaRepository<RecipeIngredient, RecipeIngredient.Key> {
-
-    List<RecipeIngredient> findByRecipeId(Long recipeId);
-
-    void deleteByRecipeId(Long recipeId);
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/repo/RecipeRepository.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/repo/RecipeRepository.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,61 +1,0 @@
-package mk.finki.nutritioneer.repo;
-
-import mk.finki.nutritioneer.domain.Recipe;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Modifying;
-import org.springframework.data.jpa.repository.Query;
-import org.springframework.data.repository.query.Param;
-
-import java.util.List;
-
-public interface RecipeRepository extends JpaRepository<Recipe, Long> {
-
-    List<Recipe> findByOwnerEmailOrderByNameAsc(String email);
-
-    List<Recipe> findByNameContainingIgnoreCaseOrderByNameAsc(String fragment);
-
-    /**
-     * Refreshes the derived column recipe.kcal_sum from the ingredient rows.
-     * keeping the calculation in one place means a recipe edited through the API
-     * and a recipe loaded from the seed file are computed identically.
-     */
-    @Modifying(clearAutomatically = true, flushAutomatically = true)
-    @Query(value = """
-            UPDATE recipe r
-            SET kcal_sum = ROUND(COALESCE((
-                    SELECT SUM(i.kcal * rci.ingredient_quantity / 100.0)
-                    FROM recipe_contains_ingredient rci
-                    JOIN ingredient i ON i.name = rci.ingredient_name
-                    WHERE rci.recipe_id = r.id
-                ), 0), 2)
-            WHERE r.id = :recipeId
-            """, nativeQuery = true)
-    void recalculateKcalSum(@Param("recipeId") Long recipeId);
-
-    /** rank recipes by how much of a nutrient they deliver per 100 kcal. */
-    @Query(value = """
-            SELECT r.name                                                       AS "name",
-                   ROUND(r.kcal_sum / r.servings)                               AS "kcalPerServing",
-                   ROUND(SUM(icn.quantity * rci.ingredient_quantity / 100.0)
-                         / r.servings, 1)                                       AS "amountPerServing",
-                   ROUND(SUM(icn.quantity * rci.ingredient_quantity / 100.0)
-                         / NULLIF(r.kcal_sum, 0) * 100, 2)                      AS "amountPer100Kcal"
-            FROM recipe r
-            JOIN recipe_contains_ingredient rci   ON rci.recipe_id = r.id
-            JOIN ingredient_contains_nutrient icn ON icn.ingredient_name = rci.ingredient_name
-            JOIN nutrient n                       ON n.id = icn.nutrient_id
-            WHERE n.description = :nutrient
-            GROUP BY r.id, r.name, r.kcal_sum, r.servings
-            ORDER BY "amountPer100Kcal" DESC
-            LIMIT :limit
-            """, nativeQuery = true)
-    List<NutrientDensityRow> rankByNutrientDensity(@Param("nutrient") String nutrient,
-                                                   @Param("limit") int limit);
-
-    interface NutrientDensityRow {
-        String getName();
-        java.math.BigDecimal getKcalPerServing();
-        java.math.BigDecimal getAmountPerServing();
-        java.math.BigDecimal getAmountPer100Kcal();
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/repo/RestrictionRepository.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/repo/RestrictionRepository.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,11 +1,0 @@
-package mk.finki.nutritioneer.repo;
-
-import mk.finki.nutritioneer.domain.Restriction;
-import mk.finki.nutritioneer.domain.RestrictionType;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-import java.util.List;
-
-public interface RestrictionRepository extends JpaRepository<Restriction, Long> {
-    List<Restriction> findByType(RestrictionType type);
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/security/AppUserDetailsService.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/security/AppUserDetailsService.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,31 +1,0 @@
-package mk.finki.nutritioneer.security;
-
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.domain.AppUser;
-import mk.finki.nutritioneer.repo.AppUserRepository;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.security.core.userdetails.User;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-import org.springframework.stereotype.Service;
-
-import java.util.List;
-
-@Service
-@RequiredArgsConstructor
-public class AppUserDetailsService implements UserDetailsService {
-
-    private final AppUserRepository users;
-
-    @Override
-    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
-        AppUser user = users.findById(email)
-                .orElseThrow(() -> new UsernameNotFoundException("No account for " + email));
-
-        return new User(
-                user.getEmail(),
-                user.getPassword(),
-                List.of(new SimpleGrantedAuthority(user.getRole().authority())));
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/security/JwtAuthenticationFilter.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/security/JwtAuthenticationFilter.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,48 +1,0 @@
-package mk.finki.nutritioneer.security;
-
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import lombok.RequiredArgsConstructor;
-import org.springframework.lang.NonNull;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
-import org.springframework.stereotype.Component;
-import org.springframework.web.filter.OncePerRequestFilter;
-
-import java.io.IOException;
-
-@Component
-@RequiredArgsConstructor
-public class JwtAuthenticationFilter extends OncePerRequestFilter {
-
-    private final JwtService jwt;
-    private final AppUserDetailsService userDetailsService;
-
-    @Override
-    protected void doFilterInternal(@NonNull HttpServletRequest request,
-                                    @NonNull HttpServletResponse response,
-                                    @NonNull FilterChain chain)
-            throws ServletException, IOException {
-
-        String header = request.getHeader("Authorization");
-        if (header == null || !header.startsWith("Bearer ")) {
-            chain.doFilter(request, response);
-            return;
-        }
-
-        String token = header.substring(7);
-        if (jwt.isValid(token) && SecurityContextHolder.getContext().getAuthentication() == null) {
-            UserDetails details = userDetailsService.loadUserByUsername(jwt.extractEmail(token));
-            var authentication = new UsernamePasswordAuthenticationToken(
-                    details, null, details.getAuthorities());
-            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
-            SecurityContextHolder.getContext().setAuthentication(authentication);
-        }
-
-        chain.doFilter(request, response);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/security/JwtService.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/security/JwtService.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,62 +1,0 @@
-package mk.finki.nutritioneer.security;
-
-import io.jsonwebtoken.Claims;
-import io.jsonwebtoken.Jwts;
-import io.jsonwebtoken.security.Keys;
-import mk.finki.nutritioneer.domain.AppUser;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-
-import javax.crypto.SecretKey;
-import java.nio.charset.StandardCharsets;
-import java.util.Date;
-
-@Service
-public class JwtService {
-
-    private final SecretKey key;
-    private final long expirationMillis;
-
-    public JwtService(@Value("${app.jwt.secret}") String secret,
-                      @Value("${app.jwt.expiration-minutes}") long expirationMinutes) {
-        this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
-        this.expirationMillis = expirationMinutes * 60_000;
-    }
-
-    public String issue(AppUser user) {
-        Date now = new Date();
-        return Jwts.builder()
-                .subject(user.getEmail())
-                .claim("username", user.getUsername())
-                .claim("role", user.getRole().name())
-                .issuedAt(now)
-                .expiration(new Date(now.getTime() + expirationMillis))
-                .signWith(key)
-                .compact();
-    }
-
-    public String extractEmail(String token) {
-        return parse(token).getSubject();
-    }
-
-    public boolean isValid(String token) {
-        try {
-            parse(token);
-            return true;
-        } catch (Exception e) {
-            return false;
-        }
-    }
-
-    private Claims parse(String token) {
-        return Jwts.parser()
-                .verifyWith(key)
-                .build()
-                .parseSignedClaims(token)
-                .getPayload();
-    }
-
-    public long getExpirationMillis() {
-        return expirationMillis;
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/service/AdminService.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/service/AdminService.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,49 +1,0 @@
-package mk.finki.nutritioneer.service;
-
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.domain.AppUser;
-import mk.finki.nutritioneer.domain.UserRole;
-import mk.finki.nutritioneer.exception.ApiException;
-import mk.finki.nutritioneer.repo.AppUserRepository;
-import mk.finki.nutritioneer.repo.PostRepository;
-import mk.finki.nutritioneer.repo.RecipeRepository;
-import mk.finki.nutritioneer.web.dto.AdminDtos.AccountView;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.List;
-
-@Service
-@RequiredArgsConstructor
-public class AdminService {
-
-    private final AppUserRepository users;
-    private final RecipeRepository recipes;
-    private final PostRepository posts;
-    private final CurrentUser currentUser;
-
-    @Transactional(readOnly = true)
-    public List<AccountView> accounts() {
-        return users.findAll().stream()
-                .map(u -> new AccountView(
-                        u.getEmail(),
-                        u.getUsername(),
-                        u.getRole(),
-                        recipes.findByOwnerEmailOrderByNameAsc(u.getEmail()).size(),
-                        posts.findByAuthorEmailOrderByCreatedAtDesc(u.getEmail()).size()))
-                .toList();
-    }
-
-    @Transactional
-    public AccountView changeRole(String email, UserRole role) {
-        if (email.equals(currentUser.email())) {
-            throw ApiException.badRequest("You cannot change your own role");
-        }
-        AppUser user = users.findById(email)
-                .orElseThrow(() -> ApiException.notFound("Account"));
-        user.setRole(role);
-        return new AccountView(user.getEmail(), user.getUsername(), role,
-                recipes.findByOwnerEmailOrderByNameAsc(email).size(),
-                posts.findByAuthorEmailOrderByCreatedAtDesc(email).size());
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/service/AuthService.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/service/AuthService.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,64 +1,0 @@
-package mk.finki.nutritioneer.service;
-
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.domain.AppUser;
-import mk.finki.nutritioneer.domain.UserRole;
-import mk.finki.nutritioneer.exception.ApiException;
-import mk.finki.nutritioneer.repo.AppUserRepository;
-import mk.finki.nutritioneer.security.JwtService;
-import mk.finki.nutritioneer.web.dto.AuthDtos.*;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-@Service
-@RequiredArgsConstructor
-public class AuthService {
-
-    private final AppUserRepository users;
-    private final PasswordEncoder encoder;
-    private final JwtService jwt;
-    private final AuthenticationManager authenticationManager;
-
-    @Transactional
-    public TokenResponse register(RegisterRequest request) {
-        if (users.existsById(request.email())) {
-            throw ApiException.conflict("An account with that email already exists");
-        }
-        if (users.existsByUsername(request.username())) {
-            throw ApiException.conflict("That username is already taken");
-        }
-
-        AppUser user = AppUser.builder()
-                .email(request.email())
-                .username(request.username())
-                .password(encoder.encode(request.password()))
-                .role(UserRole.user)          // self-registration never grants a privileged role
-                .build();
-
-        users.save(user);
-        return token(user);
-    }
-
-    public TokenResponse login(LoginRequest request) {
-        // BadCredentialsException - 401 handler.
-        authenticationManager.authenticate(
-                new UsernamePasswordAuthenticationToken(request.email(), request.password()));
-
-        AppUser user = users.findById(request.email())
-                .orElseThrow(() -> ApiException.notFound("Account"));
-        return token(user);
-    }
-
-    private TokenResponse token(AppUser user) {
-        return new TokenResponse(
-                jwt.issue(user),
-                "Bearer",
-                jwt.getExpirationMillis(),
-                user.getEmail(),
-                user.getUsername(),
-                user.getRole().name());
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/service/BiometricsService.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/service/BiometricsService.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,72 +1,0 @@
-package mk.finki.nutritioneer.service;
-
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.domain.Biometrics;
-import mk.finki.nutritioneer.exception.ApiException;
-import mk.finki.nutritioneer.repo.BiometricsRepository;
-import mk.finki.nutritioneer.web.dto.BiometricsDtos.*;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-
-@Service
-@RequiredArgsConstructor
-public class BiometricsService {
-
-    private final BiometricsRepository biometrics;
-    private final CurrentUser currentUser;
-
-    @Transactional
-    public MeasurementView record(RecordRequest request) {
-        String email = currentUser.email();
-
-        // uq_biometrics_user_date allows one measurement per day, so a second
-        // entry for the same date updates the first rather than failing.
-        Biometrics entry = biometrics.findByOwnerEmailAndDate(email, request.date())
-                .orElseGet(() -> Biometrics.builder()
-                        .owner(currentUser.get())
-                        .date(request.date())
-                        .build());
-
-        entry.setWeight(request.weight());
-        entry.setHeight(request.height());
-        entry.setAge(request.age());
-        entry.setMuscleFatRatio(request.muscleFatRatio());
-
-        Biometrics saved = biometrics.save(entry);
-        return new MeasurementView(saved.getId(), saved.getDate(), saved.getWeight(),
-                saved.getHeight(), saved.getMuscleFatRatio(), saved.getBmi(), null);
-    }
-
-    @Transactional(readOnly = true)
-    public List<MeasurementView> history(String email) {
-        List<Biometrics> rows = biometrics.findByOwnerEmailOrderByDate(email);
-        List<MeasurementView> out = new ArrayList<>(rows.size());
-
-        BigDecimal previousWeight = null;
-        for (Biometrics b : rows) {
-            BigDecimal change = (previousWeight == null || b.getWeight() == null)
-                    ? null
-                    : b.getWeight().subtract(previousWeight);
-            out.add(new MeasurementView(b.getId(), b.getDate(), b.getWeight(),
-                    b.getHeight(), b.getMuscleFatRatio(), b.getBmi(), change));
-            if (b.getWeight() != null) {
-                previousWeight = b.getWeight();
-            }
-        }
-        return out;
-    }
-
-    @Transactional
-    public void delete(Long id) {
-        Biometrics entry = biometrics.findById(id)
-                .orElseThrow(() -> ApiException.notFound("Measurement"));
-        if (!entry.getOwner().getEmail().equals(currentUser.email())) {
-            throw ApiException.forbidden("You can only delete your own measurements");
-        }
-        biometrics.delete(entry);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/service/CatalogService.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/service/CatalogService.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,59 +1,0 @@
-package mk.finki.nutritioneer.service;
-
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.domain.*;
-import mk.finki.nutritioneer.exception.ApiException;
-import mk.finki.nutritioneer.repo.*;
-import mk.finki.nutritioneer.web.dto.AdminDtos.*;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.List;
-
-@Service
-@RequiredArgsConstructor
-public class CatalogService {
-
-    private final IngredientRepository ingredients;
-    private final NutrientRepository nutrients;
-    private final RestrictionRepository restrictions;
-
-    public List<Ingredient> searchIngredients(String query) {
-        if (query == null || query.isBlank()) {
-            return ingredients.findAll(org.springframework.data.domain.Sort.by("name"));
-        }
-        return ingredients.findByNameContainingIgnoreCaseOrderByName(query);
-    }
-
-    public List<Nutrient> allNutrients() {
-        return nutrients.findAll();
-    }
-
-    public List<Restriction> allRestrictions() {
-        return restrictions.findAll();
-    }
-
-    @Transactional
-    public Ingredient createIngredient(CreateIngredientRequest request) {
-        if (ingredients.existsById(request.name())) {
-            throw ApiException.conflict("An ingredient with that name already exists");
-        }
-        return ingredients.save(Ingredient.builder()
-                .name(request.name())
-                .description(request.description())
-                .energy(request.energy())
-                .kcal(request.kcal())
-                .type(request.type())
-                .build());
-    }
-
-    @Transactional
-    public Nutrient createNutrient(CreateNutrientRequest request) {
-        return nutrients.save(Nutrient.builder()
-                .description(request.description())
-                .quantity(request.quantity())
-                .unit(request.unit())
-                .type(request.type())
-                .build());
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/service/CurrentUser.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/service/CurrentUser.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,29 +1,0 @@
-package mk.finki.nutritioneer.service;
-
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.domain.AppUser;
-import mk.finki.nutritioneer.exception.ApiException;
-import mk.finki.nutritioneer.repo.AppUserRepository;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.stereotype.Component;
-
-
-@Component
-@RequiredArgsConstructor
-public class CurrentUser {
-
-    private final AppUserRepository users;
-
-    public String email() {
-        var authentication = SecurityContextHolder.getContext().getAuthentication();
-        if (authentication == null || !authentication.isAuthenticated()) {
-            throw ApiException.forbidden("Not authenticated");
-        }
-        return authentication.getName();
-    }
-
-    public AppUser get() {
-        return users.findById(email())
-                .orElseThrow(() -> ApiException.notFound("Account"));
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/service/GroceryService.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/service/GroceryService.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,100 +1,0 @@
-package mk.finki.nutritioneer.service;
-
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.domain.GroceryItem;
-import mk.finki.nutritioneer.domain.GroceryList;
-import mk.finki.nutritioneer.domain.Ingredient;
-import mk.finki.nutritioneer.domain.Recipe;
-import mk.finki.nutritioneer.exception.ApiException;
-import mk.finki.nutritioneer.repo.GroceryListRepository;
-import mk.finki.nutritioneer.repo.IngredientRepository;
-import mk.finki.nutritioneer.repo.RecipeRepository;
-import mk.finki.nutritioneer.web.dto.GroceryDtos.*;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-@Service
-@RequiredArgsConstructor
-public class GroceryService {
-
-    private final GroceryListRepository lists;
-    private final RecipeRepository recipes;
-    private final IngredientRepository ingredients;
-    private final CurrentUser currentUser;
-
-    @Transactional
-    public ListView create(CreateListRequest request) {
-        GroceryList list = GroceryList.builder()
-                .owner(currentUser.get())
-                .dateTime(request.dateTime() == null ? LocalDateTime.now() : request.dateTime())
-                .notes(request.notes())
-                .isBought(false)
-                .kcal(BigDecimal.ZERO)
-                .build();
-
-        if (request.bulkRecipeIds() != null && !request.bulkRecipeIds().isEmpty()) {
-            Set<Recipe> bulk = new LinkedHashSet<>(recipes.findAllById(request.bulkRecipeIds()));
-            list.setBulkRecipes(bulk);
-        }
-
-        if (request.singleItems() != null) {
-            for (SingleItem item : request.singleItems()) {
-                Ingredient ingredient = ingredients.findById(item.ingredientName())
-                        .orElseThrow(() -> ApiException.badRequest(
-                                "Unknown ingredient: " + item.ingredientName()));
-                list.getSingleItems().add(GroceryItem.builder()
-                        .list(list)
-                        .ingredient(ingredient)
-                        .buyQuantity(item.buyQuantity())
-                        .build());
-            }
-        }
-
-        GroceryList saved = lists.save(list);
-        lists.recalculateKcal(saved.getId());
-
-        return view(lists.findById(saved.getId()).orElseThrow());
-    }
-
-    @Transactional(readOnly = true)
-    public List<ListView> mine() {
-        return lists.findByOwnerEmailOrderByDateTimeDesc(currentUser.email())
-                .stream().map(this::view).toList();
-    }
-
-    @Transactional(readOnly = true)
-    public ListView get(Long id) {
-        return view(ownedList(id));
-    }
-
-    @Transactional
-    public void markBought(Long id, boolean bought) {
-        ownedList(id).setBought(bought);
-    }
-
-    private GroceryList ownedList(Long id) {
-        GroceryList list = lists.findById(id)
-                .orElseThrow(() -> ApiException.notFound("Grocery list"));
-        if (!list.getOwner().getEmail().equals(currentUser.email())) {
-            throw ApiException.forbidden("You can only open your own lists");
-        }
-        return list;
-    }
-
-    private ListView view(GroceryList l) {
-        List<ShoppingLine> lines = lists.consolidate(l.getId()).stream()
-                .map(r -> new ShoppingLine(r.getType(), r.getName(), r.getTotalGrams()))
-                .toList();
-
-        List<String> bulk = l.getBulkRecipes().stream().map(Recipe::getName).toList();
-
-        return new ListView(l.getId(), l.getDateTime(), l.getNotes(), l.isBought(),
-                l.getKcal(), bulk, lines);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/service/PlannerService.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/service/PlannerService.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,98 +1,0 @@
-package mk.finki.nutritioneer.service;
-
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.domain.IntakePlanner;
-import mk.finki.nutritioneer.domain.Post;
-import mk.finki.nutritioneer.exception.ApiException;
-import mk.finki.nutritioneer.repo.IntakePlannerRepository;
-import mk.finki.nutritioneer.repo.PostRepository;
-import mk.finki.nutritioneer.web.dto.PlannerDtos.*;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-@Service
-@RequiredArgsConstructor
-public class PlannerService {
-
-    private final IntakePlannerRepository planner;
-    private final PostRepository posts;
-    private final CurrentUser currentUser;
-
-    @Transactional
-    public PlannerEntryView log(LogMealRequest request) {
-        boolean hasMeals = request.postIds() != null && !request.postIds().isEmpty();
-
-        IntakePlanner entry = IntakePlanner.builder()
-                .owner(currentUser.get())
-                .dateTime(request.dateTime() == null ? LocalDateTime.now() : request.dateTime())
-                .notes(request.notes())
-                .isConsumed(request.consumed())
-                // An entry with no attached meal keeps whatever the user typed;
-                // one with meals has its energy derived and the manual figure ignored.
-                .kcal(hasMeals || request.manualKcal() == null
-                        ? BigDecimal.ZERO
-                        : request.manualKcal())
-                .build();
-
-        if (hasMeals) {
-            Set<Post> meals = new LinkedHashSet<>(posts.findAllById(request.postIds()));
-            if (meals.size() != request.postIds().size()) {
-                throw ApiException.badRequest("One or more of the selected meals does not exist");
-            }
-            entry.setSavedPosts(meals);
-        }
-
-        IntakePlanner saved = planner.save(entry);
-
-        if (hasMeals) {
-            planner.recalculateKcal(saved.getId());
-        }
-
-        return view(planner.findById(saved.getId()).orElseThrow());
-    }
-
-    @Transactional(readOnly = true)
-    public List<PlannerEntryView> mine() {
-        return planner.findByOwnerEmailOrderByDateTimeDesc(currentUser.email())
-                .stream().map(this::view).toList();
-    }
-
-    @Transactional(readOnly = true)
-    public List<DailyTotalView> dailyTotals(String email, LocalDate from, LocalDate to) {
-        return planner.dailyIntake(email, from.atStartOfDay(), to.atStartOfDay()).stream()
-                .map(r -> new DailyTotalView(r.getDay(), r.getKcalConsumed(), r.getMealsLogged()))
-                .toList();
-    }
-
-    @Transactional(readOnly = true)
-    public List<NutrientTotalView> macroTotals(String email, LocalDate from, LocalDate to) {
-        return planner.macroTotals(email, from.atStartOfDay(), to.atStartOfDay()).stream()
-                .map(r -> new NutrientTotalView(r.getNutrient(), r.getUnit(), r.getTotal()))
-                .toList();
-    }
-
-    @Transactional
-    public void markConsumed(Long id, boolean consumed) {
-        IntakePlanner entry = planner.findById(id)
-                .orElseThrow(() -> ApiException.notFound("Planner entry"));
-        if (!entry.getOwner().getEmail().equals(currentUser.email())) {
-            throw ApiException.forbidden("You can only change your own planner");
-        }
-        entry.setConsumed(consumed);
-    }
-
-    private PlannerEntryView view(IntakePlanner e) {
-        List<String> meals = e.getSavedPosts().stream()
-                .map(p -> p.getRecipe() == null ? "(photo post)" : p.getRecipe().getName())
-                .toList();
-        return new PlannerEntryView(e.getId(), e.getDateTime(), e.getNotes(),
-                e.getKcal(), e.isConsumed(), meals);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/service/PostService.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/service/PostService.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,128 +1,0 @@
-package mk.finki.nutritioneer.service;
-
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.domain.*;
-import mk.finki.nutritioneer.exception.ApiException;
-import mk.finki.nutritioneer.repo.*;
-import mk.finki.nutritioneer.web.dto.PostDtos.*;
-import org.springframework.data.domain.PageRequest;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.math.BigDecimal;
-import java.util.List;
-
-@Service
-@RequiredArgsConstructor
-public class PostService {
-
-    private final PostRepository posts;
-    private final RecipeRepository recipes;
-    private final CommentRepository comments;
-    private final CurrentUser currentUser;
-
-    @Transactional
-    public PostView share(SharePostRequest request) {
-        AppUser author = currentUser.get();
-        Recipe recipe = recipes.findById(request.recipeId())
-                .orElseThrow(() -> ApiException.notFound("Recipe"));
-
-        if (!recipe.getOwner().getEmail().equals(author.getEmail())) {
-            throw ApiException.forbidden("You can only share your own recipes");
-        }
-        // uq_post_recipe would reject this anyway; checking first gives a clearer message.
-        if (posts.existsByRecipeId(recipe.getId())) {
-            throw ApiException.conflict("This recipe has already been shared as a post");
-        }
-
-        Post post = Post.builder()
-                .recipe(recipe)
-                .author(author)
-                .isPrivate(request.isPrivate())
-                .isFavourite(request.isFavourite())
-                .status(request.status() == null
-                        ? PostStatus.published
-                        : PostStatus.valueOf(request.status()))
-                .build();
-
-        return view(posts.save(post));
-    }
-
-    @Transactional(readOnly = true)
-    public List<PostView> feed(int page, int size) {
-        return posts.findByStatusAndIsPrivateFalseOrderByCreatedAtDesc(
-                        PostStatus.published, PageRequest.of(page, size))
-                .map(this::view)
-                .getContent();
-    }
-
-    @Transactional(readOnly = true)
-    public List<PostView> mine() {
-        return posts.findByAuthorEmailOrderByCreatedAtDesc(currentUser.email())
-                .stream().map(this::view).toList();
-    }
-
-    /** UseCase3003 - the feed filtered by dietary restriction. */
-    @Transactional(readOnly = true)
-    public List<PostView> feedByRestriction(RestrictionType type, List<String> avoid) {
-        List<String> exclusions = (avoid == null || avoid.isEmpty())
-                ? List.of("__none__")   // IN () is not valid SQL, so pass a value that matches nothing
-                : avoid;
-        return posts.feedByRestriction(type.name(), exclusions)
-                .stream().map(this::view).toList();
-    }
-
-    @Transactional
-    public CommentView comment(Long postId, CommentRequest request) {
-        Post post = posts.findById(postId)
-                .orElseThrow(() -> ApiException.notFound("Post"));
-
-        Comment comment = comments.save(Comment.builder()
-                .post(post)
-                .author(currentUser.get())
-                .description(request.description())
-                .build());
-
-        return new CommentView(comment.getId(), comment.getDescription(),
-                comment.getAuthor().getUsername(), comment.getCreatedAt());
-    }
-
-    @Transactional(readOnly = true)
-    public List<CommentView> comments(Long postId) {
-        return comments.findByPostIdOrderByCreatedAt(postId).stream()
-                .map(c -> new CommentView(c.getId(), c.getDescription(),
-                        c.getAuthor().getUsername(), c.getCreatedAt()))
-                .toList();
-    }
-
-    /** UseCase2003 - moderation. Archiving keeps the post for its author. */
-    @Transactional
-    public void setStatus(Long postId, PostStatus status) {
-        Post post = posts.findById(postId)
-                .orElseThrow(() -> ApiException.notFound("Post"));
-        post.setStatus(status);
-    }
-
-    @Transactional
-    public void deleteComment(Long commentId) {
-        if (!comments.existsById(commentId)) {
-            throw ApiException.notFound("Comment");
-        }
-        comments.deleteById(commentId);
-    }
-
-    private PostView view(Post p) {
-        Recipe r = p.getRecipe();
-        return new PostView(
-                p.getId(),
-                r == null ? null : r.getId(),
-                r == null ? null : r.getName(),
-                r == null ? BigDecimal.ZERO : r.getKcalPerServing(),
-                p.getAuthor().getUsername(),
-                p.getStatus().name(),
-                p.isPrivate(),
-                p.isFavourite(),
-                p.getCreatedAt(),
-                comments.findByPostIdOrderByCreatedAt(p.getId()).size());
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/service/RecipeService.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/service/RecipeService.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,129 +1,0 @@
-package mk.finki.nutritioneer.service;
-
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.domain.*;
-import mk.finki.nutritioneer.exception.ApiException;
-import mk.finki.nutritioneer.repo.*;
-import mk.finki.nutritioneer.web.dto.RecipeDtos.*;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-
-@Service
-@RequiredArgsConstructor
-public class RecipeService {
-
-    private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);
-
-    private final RecipeRepository recipes;
-    private final IngredientRepository ingredients;
-    private final RestrictionRepository restrictions;
-    private final CurrentUser currentUser;
-
-    @Transactional
-    public RecipeView create(CreateRecipeRequest request) {
-        AppUser owner = currentUser.get();
-
-        Recipe recipe = Recipe.builder()
-                .name(request.name())
-                .guide(request.guide())
-                .servings(request.servings())
-                .kcalSum(BigDecimal.ZERO)   // derived below, never taken from the client
-                .owner(owner)
-                .build();
-
-        for (IngredientLine line : request.ingredients()) {
-            Ingredient ingredient = ingredients.findById(line.ingredientName())
-                    .orElseThrow(() -> ApiException.badRequest(
-                            "Unknown ingredient: " + line.ingredientName()));
-
-            recipe.getIngredients().add(RecipeIngredient.builder()
-                    .recipe(recipe)
-                    .ingredient(ingredient)
-                    .quantity(line.quantity())
-                    .build());
-        }
-
-        if (request.restrictionIds() != null && !request.restrictionIds().isEmpty()) {
-            Set<Restriction> tags = new LinkedHashSet<>(
-                    restrictions.findAllById(request.restrictionIds()));
-            recipe.setRestrictions(tags);
-        }
-
-        Recipe saved = recipes.save(recipe);
-
-        // The energy of the recipe is computed by the database from the rows we
-        // just wrote, using the same statement as the DML script.
-        recipes.recalculateKcalSum(saved.getId());
-
-        return view(recipes.findById(saved.getId()).orElseThrow());
-    }
-
-    @Transactional(readOnly = true)
-    public RecipeView get(Long id) {
-        return view(recipes.findById(id).orElseThrow(() -> ApiException.notFound("Recipe")));
-    }
-
-    @Transactional(readOnly = true)
-    public List<RecipeSummary> search(String query) {
-        List<Recipe> found = (query == null || query.isBlank())
-                ? recipes.findAll()
-                : recipes.findByNameContainingIgnoreCaseOrderByNameAsc(query);
-        return found.stream().map(this::summary).toList();
-    }
-
-    @Transactional(readOnly = true)
-    public List<RecipeSummary> mine() {
-        return recipes.findByOwnerEmailOrderByNameAsc(currentUser.email())
-                .stream().map(this::summary).toList();
-    }
-
-    @Transactional
-    public void delete(Long id) {
-        Recipe recipe = recipes.findById(id)
-                .orElseThrow(() -> ApiException.notFound("Recipe"));
-        if (!recipe.getOwner().getEmail().equals(currentUser.email())) {
-            throw ApiException.forbidden("You can only delete your own recipes");
-        }
-        recipes.delete(recipe);
-    }
-
-    /** UseCase3004 - rank recipes by nutrient density. */
-    @Transactional(readOnly = true)
-    public List<NutrientDensityView> rankByNutrient(String nutrient, int limit) {
-        return recipes.rankByNutrientDensity(nutrient, limit).stream()
-                .map(r -> new NutrientDensityView(
-                        r.getName(), r.getKcalPerServing(),
-                        r.getAmountPerServing(), r.getAmountPer100Kcal()))
-                .toList();
-    }
-
-    private RecipeSummary summary(Recipe r) {
-        return new RecipeSummary(r.getId(), r.getName(), r.getKcalPerServing(),
-                r.getOwner().getUsername());
-    }
-
-    private RecipeView view(Recipe r) {
-        List<IngredientLineView> lines = r.getIngredients().stream()
-                .map(ri -> new IngredientLineView(
-                        ri.getIngredient().getName(),
-                        ri.getQuantity(),
-                        ri.getIngredient().getKcal(),
-                        ri.getIngredient().getKcal()
-                                .multiply(ri.getQuantity())
-                                .divide(HUNDRED, 2, RoundingMode.HALF_UP)))
-                .toList();
-
-        List<String> tags = r.getRestrictions().stream()
-                .map(Restriction::getDescription)
-                .toList();
-
-        return new RecipeView(r.getId(), r.getName(), r.getGuide(), r.getKcalSum(),
-                r.getServings(), r.getKcalPerServing(), r.getOwner().getUsername(), lines, tags);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/AdminController.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/AdminController.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,72 +1,0 @@
-package mk.finki.nutritioneer.web;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.validation.Valid;
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.domain.Ingredient;
-import mk.finki.nutritioneer.domain.Nutrient;
-import mk.finki.nutritioneer.domain.PostStatus;
-import mk.finki.nutritioneer.service.AdminService;
-import mk.finki.nutritioneer.service.CatalogService;
-import mk.finki.nutritioneer.service.PostService;
-import mk.finki.nutritioneer.web.dto.AdminDtos.*;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-
-@RestController
-@RequestMapping("/api/admin")
-@RequiredArgsConstructor
-@PreAuthorize("hasRole('ADMINISTRATOR')")
-@Tag(name = "Administration", description = "UseCase2001 - UseCase2004")
-public class AdminController {
-
-    private final CatalogService catalog;
-    private final AdminService admin;
-    private final PostService posts;
-
-    @PostMapping("/ingredients")
-    @ResponseStatus(HttpStatus.CREATED)
-    @Operation(summary = "UseCase2001 - add an ingredient to the catalogue")
-    public Ingredient addIngredient(@Valid @RequestBody CreateIngredientRequest request) {
-        return catalog.createIngredient(request);
-    }
-
-    @PostMapping("/nutrients")
-    @ResponseStatus(HttpStatus.CREATED)
-    @Operation(summary = "UseCase2002 - add a nutrient and its reference daily intake")
-    public Nutrient addNutrient(@Valid @RequestBody CreateNutrientRequest request) {
-        return catalog.createNutrient(request);
-    }
-
-    @GetMapping("/accounts")
-    @Operation(summary = "UseCase2004 - accounts with their role and activity")
-    public List<AccountView> accounts() {
-        return admin.accounts();
-    }
-
-    @PatchMapping("/accounts/{email}/role")
-    @Operation(summary = "UseCase2004 - promote or demote an account")
-    public AccountView changeRole(@PathVariable String email,
-                                  @Valid @RequestBody ChangeRoleRequest request) {
-        return admin.changeRole(email, request.role());
-    }
-
-    @PatchMapping("/posts/{id}/status")
-    @ResponseStatus(HttpStatus.NO_CONTENT)
-    @Operation(summary = "UseCase2003 - archive a post instead of deleting it")
-    public void moderatePost(@PathVariable Long id, @RequestParam PostStatus status) {
-        posts.setStatus(id, status);
-    }
-
-    @DeleteMapping("/comments/{id}")
-    @ResponseStatus(HttpStatus.NO_CONTENT)
-    @Operation(summary = "UseCase2003 - delete an individual comment")
-    public void deleteComment(@PathVariable Long id) {
-        posts.deleteComment(id);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/AuthController.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/AuthController.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,32 +1,0 @@
-package mk.finki.nutritioneer.web;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.security.SecurityRequirements;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.validation.Valid;
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.service.AuthService;
-import mk.finki.nutritioneer.web.dto.AuthDtos.*;
-import org.springframework.web.bind.annotation.*;
-
-@RestController
-@RequestMapping("/api/auth")
-@RequiredArgsConstructor
-@Tag(name = "Authentication")
-@SecurityRequirements
-public class AuthController {
-
-    private final AuthService auth;
-
-    @PostMapping("/register")
-    @Operation(summary = "Create an account and receive a JWT")
-    public TokenResponse register(@Valid @RequestBody RegisterRequest request) {
-        return auth.register(request);
-    }
-
-    @PostMapping("/login")
-    @Operation(summary = "Exchange email and password for a JWT")
-    public TokenResponse login(@Valid @RequestBody LoginRequest request) {
-        return auth.login(request);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/BiometricsController.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/BiometricsController.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,43 +1,0 @@
-package mk.finki.nutritioneer.web;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.validation.Valid;
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.service.BiometricsService;
-import mk.finki.nutritioneer.service.CurrentUser;
-import mk.finki.nutritioneer.web.dto.BiometricsDtos.*;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("/api/biometrics")
-@RequiredArgsConstructor
-@Tag(name = "Biometrics")
-public class BiometricsController {
-
-    private final BiometricsService biometrics;
-    private final CurrentUser currentUser;
-
-    @PostMapping
-    @ResponseStatus(HttpStatus.CREATED)
-    @Operation(summary = "Record a measurement; one per day, a repeat updates it")
-    public MeasurementView record(@Valid @RequestBody RecordRequest request) {
-        return biometrics.record(request);
-    }
-
-    @GetMapping
-    @Operation(summary = "History with BMI and the change since the previous measurement")
-    public List<MeasurementView> mine() {
-        return biometrics.history(currentUser.email());
-    }
-
-    @DeleteMapping("/{id}")
-    @ResponseStatus(HttpStatus.NO_CONTENT)
-    @Operation(summary = "Delete one of your own measurements")
-    public void delete(@PathVariable Long id) {
-        biometrics.delete(id);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/CatalogController.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/CatalogController.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,39 +1,0 @@
-package mk.finki.nutritioneer.web;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.domain.Ingredient;
-import mk.finki.nutritioneer.domain.Nutrient;
-import mk.finki.nutritioneer.domain.Restriction;
-import mk.finki.nutritioneer.service.CatalogService;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("/api/catalog")
-@RequiredArgsConstructor
-@Tag(name = "Catalogue")
-public class CatalogController {
-
-    private final CatalogService catalog;
-
-    @GetMapping("/ingredients")
-    @Operation(summary = "Search the ingredient catalogue")
-    public List<Ingredient> ingredients(@RequestParam(required = false) String q) {
-        return catalog.searchIngredients(q);
-    }
-
-    @GetMapping("/nutrients")
-    @Operation(summary = "List nutrients with their reference daily intake")
-    public List<Nutrient> nutrients() {
-        return catalog.allNutrients();
-    }
-
-    @GetMapping("/restrictions")
-    @Operation(summary = "List dietary restrictions and allergen warnings")
-    public List<Restriction> restrictions() {
-        return catalog.allRestrictions();
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/GroceryController.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/GroceryController.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,47 +1,0 @@
-package mk.finki.nutritioneer.web;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.validation.Valid;
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.service.GroceryService;
-import mk.finki.nutritioneer.web.dto.GroceryDtos.*;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("/api/grocery-lists")
-@RequiredArgsConstructor
-@Tag(name = "Grocery lists", description = "UseCase1003")
-public class GroceryController {
-
-    private final GroceryService grocery;
-
-    @PostMapping
-    @ResponseStatus(HttpStatus.CREATED)
-    @Operation(summary = "Create a list from whole recipes plus individual items")
-    public ListView create(@Valid @RequestBody CreateListRequest request) {
-        return grocery.create(request);
-    }
-
-    @GetMapping
-    @Operation(summary = "The signed-in user's lists")
-    public List<ListView> mine() {
-        return grocery.mine();
-    }
-
-    @GetMapping("/{id}")
-    @Operation(summary = "One list with its consolidated shopping lines")
-    public ListView get(@PathVariable Long id) {
-        return grocery.get(id);
-    }
-
-    @PatchMapping("/{id}/bought")
-    @ResponseStatus(HttpStatus.NO_CONTENT)
-    @Operation(summary = "Mark the list as bought")
-    public void markBought(@PathVariable Long id, @RequestParam boolean value) {
-        grocery.markBought(id, value);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/PlannerController.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/PlannerController.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,61 +1,0 @@
-package mk.finki.nutritioneer.web;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.validation.Valid;
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.service.CurrentUser;
-import mk.finki.nutritioneer.service.PlannerService;
-import mk.finki.nutritioneer.web.dto.PlannerDtos.*;
-import org.springframework.format.annotation.DateTimeFormat;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
-
-import java.time.LocalDate;
-import java.util.List;
-
-@RestController
-@RequestMapping("/api/planner")
-@RequiredArgsConstructor
-@Tag(name = "Intake planner", description = "UseCase1002")
-public class PlannerController {
-
-    private final PlannerService planner;
-    private final CurrentUser currentUser;
-
-    @PostMapping
-    @ResponseStatus(HttpStatus.CREATED)
-    @Operation(summary = "Log a meal; energy is derived as one portion per attached meal")
-    public PlannerEntryView log(@Valid @RequestBody LogMealRequest request) {
-        return planner.log(request);
-    }
-
-    @GetMapping
-    @Operation(summary = "The signed-in user's diary, newest first")
-    public List<PlannerEntryView> mine() {
-        return planner.mine();
-    }
-
-    @GetMapping("/daily")
-    @Operation(summary = "Daily totals for the signed-in user over a period")
-    public List<DailyTotalView> daily(
-            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
-            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to) {
-        return planner.dailyTotals(currentUser.email(), from, to);
-    }
-
-    @GetMapping("/macros")
-    @Operation(summary = "Macronutrients supplied by the meals logged in a period")
-    public List<NutrientTotalView> macros(
-            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
-            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to) {
-        return planner.macroTotals(currentUser.email(), from, to);
-    }
-
-    @PatchMapping("/{id}/consumed")
-    @ResponseStatus(HttpStatus.NO_CONTENT)
-    @Operation(summary = "Flip a planned meal to consumed, or back")
-    public void markConsumed(@PathVariable Long id, @RequestParam boolean value) {
-        planner.markConsumed(id, value);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/PostController.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/PostController.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,63 +1,0 @@
-package mk.finki.nutritioneer.web;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.validation.Valid;
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.domain.RestrictionType;
-import mk.finki.nutritioneer.service.PostService;
-import mk.finki.nutritioneer.web.dto.PostDtos.*;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("/api/posts")
-@RequiredArgsConstructor
-@Tag(name = "Feed and posts", description = "UseCase1004, UseCase3003")
-public class PostController {
-
-    private final PostService postService;
-
-    @PostMapping
-    @ResponseStatus(HttpStatus.CREATED)
-    @Operation(summary = "Share one of your recipes as a post (1:1 - a recipe can be shared once)")
-    public PostView share(@Valid @RequestBody SharePostRequest request) {
-        return postService.share(request);
-    }
-
-    @GetMapping("/feed")
-    @Operation(summary = "The public feed, newest first")
-    public List<PostView> feed(@RequestParam(defaultValue = "0") int page,
-                               @RequestParam(defaultValue = "20") int size) {
-        return postService.feed(page, size);
-    }
-
-    @GetMapping("/mine")
-    @Operation(summary = "Posts created by the signed-in user, any status")
-    public List<PostView> mine() {
-        return postService.mine();
-    }
-
-    @GetMapping("/feed/by-restriction")
-    @Operation(summary = "Feed filtered by a dietary restriction, excluding unwanted allergens")
-    public List<PostView> byRestriction(@RequestParam RestrictionType type,
-                                        @RequestParam(required = false) List<String> avoid) {
-        return postService.feedByRestriction(type, avoid);
-    }
-
-    @PostMapping("/{id}/comments")
-    @ResponseStatus(HttpStatus.CREATED)
-    @Operation(summary = "Comment on a post")
-    public CommentView comment(@PathVariable Long id,
-                               @Valid @RequestBody CommentRequest request) {
-        return postService.comment(id, request);
-    }
-
-    @GetMapping("/{id}/comments")
-    @Operation(summary = "Read the comment thread of a post")
-    public List<CommentView> comments(@PathVariable Long id) {
-        return postService.comments(id);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/RecipeController.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/RecipeController.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,53 +1,0 @@
-package mk.finki.nutritioneer.web;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import jakarta.validation.Valid;
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.service.RecipeService;
-import mk.finki.nutritioneer.web.dto.RecipeDtos.*;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.List;
-
-@RestController
-@RequestMapping("/api/recipes")
-@RequiredArgsConstructor
-@Tag(name = "Recipes", description = "UseCase1001")
-public class RecipeController {
-
-    private final RecipeService recipes;
-
-    @PostMapping
-    @ResponseStatus(HttpStatus.CREATED)
-    @Operation(summary = "Create a recipe; the server derives its energy from the ingredients")
-    public RecipeView create(@Valid @RequestBody CreateRecipeRequest request) {
-        return recipes.create(request);
-    }
-
-    @GetMapping
-    @Operation(summary = "Search all recipes")
-    public List<RecipeSummary> search(@RequestParam(required = false) String q) {
-        return recipes.search(q);
-    }
-
-    @GetMapping("/mine")
-    @Operation(summary = "Recipes owned by the signed-in user")
-    public List<RecipeSummary> mine() {
-        return recipes.mine();
-    }
-
-    @GetMapping("/{id}")
-    @Operation(summary = "Full recipe with ingredients, tags and per-portion energy")
-    public RecipeView get(@PathVariable Long id) {
-        return recipes.get(id);
-    }
-
-    @DeleteMapping("/{id}")
-    @ResponseStatus(HttpStatus.NO_CONTENT)
-    @Operation(summary = "Delete one of your own recipes")
-    public void delete(@PathVariable Long id) {
-        recipes.delete(id);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/TrainerController.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/TrainerController.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,63 +1,0 @@
-package mk.finki.nutritioneer.web;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import lombok.RequiredArgsConstructor;
-import mk.finki.nutritioneer.service.BiometricsService;
-import mk.finki.nutritioneer.service.PlannerService;
-import mk.finki.nutritioneer.service.RecipeService;
-import mk.finki.nutritioneer.web.dto.BiometricsDtos.MeasurementView;
-import mk.finki.nutritioneer.web.dto.PlannerDtos.DailyTotalView;
-import mk.finki.nutritioneer.web.dto.PlannerDtos.NutrientTotalView;
-import mk.finki.nutritioneer.web.dto.RecipeDtos.NutrientDensityView;
-import org.springframework.format.annotation.DateTimeFormat;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import java.time.LocalDate;
-import java.util.List;
-
-
-@RestController
-@RequestMapping("/api/trainer")
-@RequiredArgsConstructor
-@PreAuthorize("hasAnyRole('TRAINER','ADMINISTRATOR')")
-@Tag(name = "Trainer", description = "UseCase3001 - UseCase3004")
-public class TrainerController {
-
-    private final PlannerService planner;
-    private final BiometricsService biometrics;
-    private final RecipeService recipes;
-
-    @GetMapping("/clients/{email}/intake")
-    @Operation(summary = "UseCase3001 - a client's daily energy intake over a period")
-    public List<DailyTotalView> clientIntake(
-            @PathVariable String email,
-            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
-            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to) {
-        return planner.dailyTotals(email, from, to);
-    }
-
-    @GetMapping("/clients/{email}/macros")
-    @Operation(summary = "UseCase3001 - macronutrients a client's meals supplied")
-    public List<NutrientTotalView> clientMacros(
-            @PathVariable String email,
-            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
-            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to) {
-        return planner.macroTotals(email, from, to);
-    }
-
-    @GetMapping("/clients/{email}/progress")
-    @Operation(summary = "UseCase3002 - a client's biometric history with BMI and deltas")
-    public List<MeasurementView> clientProgress(@PathVariable String email) {
-        return biometrics.history(email);
-    }
-
-    @GetMapping("/analysis/nutrient-density")
-    @Operation(summary = "UseCase3004 - rank recipes by nutrient per 100 kcal")
-    public List<NutrientDensityView> nutrientDensity(
-            @RequestParam(defaultValue = "Protein") String nutrient,
-            @RequestParam(defaultValue = "10") int limit) {
-        return recipes.rankByNutrient(nutrient, limit);
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/dto/AdminDtos.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/dto/AdminDtos.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,41 +1,0 @@
-package mk.finki.nutritioneer.web.dto;
-
-import jakarta.validation.constraints.*;
-import mk.finki.nutritioneer.domain.IngredientType;
-import mk.finki.nutritioneer.domain.NutrientType;
-import mk.finki.nutritioneer.domain.NutrientUnit;
-import mk.finki.nutritioneer.domain.UserRole;
-
-import java.math.BigDecimal;
-
-public final class AdminDtos {
-
-    private AdminDtos() {
-    }
-
-    public record CreateIngredientRequest(
-            @NotBlank String name,
-            @NotBlank String description,
-            @NotNull @PositiveOrZero BigDecimal energy,
-            @NotNull @PositiveOrZero BigDecimal kcal,
-            @NotNull IngredientType type) {
-    }
-
-    public record CreateNutrientRequest(
-            @NotBlank String description,
-            @NotNull @Positive BigDecimal quantity,
-            @NotNull NutrientUnit unit,
-            @NotNull NutrientType type) {
-    }
-
-    public record ChangeRoleRequest(@NotNull UserRole role) {
-    }
-
-    public record AccountView(
-            String email,
-            String username,
-            UserRole role,
-            long recipes,
-            long posts) {
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/dto/AuthDtos.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/dto/AuthDtos.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,32 +1,0 @@
-package mk.finki.nutritioneer.web.dto;
-
-import jakarta.validation.constraints.Email;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.Size;
-
-public final class AuthDtos {
-
-    private AuthDtos() {
-    }
-
-    public record RegisterRequest(
-            @NotBlank @Email String email,
-            @NotBlank @Size(min = 3, max = 40) String username,
-            @NotBlank @Size(min = 8, message = "Password must be at least 8 characters")
-            String password) {
-    }
-
-    public record LoginRequest(
-            @NotBlank @Email String email,
-            @NotBlank String password) {
-    }
-
-    public record TokenResponse(
-            String token,
-            String tokenType,
-            long expiresInMs,
-            String email,
-            String username,
-            String role) {
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/dto/BiometricsDtos.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/dto/BiometricsDtos.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,30 +1,0 @@
-package mk.finki.nutritioneer.web.dto;
-
-import jakarta.validation.constraints.NotNull;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-
-public final class BiometricsDtos {
-
-    private BiometricsDtos() {
-    }
-
-    public record RecordRequest(
-            @NotNull LocalDate date,
-            BigDecimal weight,
-            BigDecimal height,
-            BigDecimal age,
-            BigDecimal muscleFatRatio) {
-    }
-
-    public record MeasurementView(
-            Long id,
-            LocalDate date,
-            BigDecimal weight,
-            BigDecimal height,
-            BigDecimal muscleFatRatio,
-            BigDecimal bmi,
-            BigDecimal changeSincePrevious) {
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/dto/GroceryDtos.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/dto/GroceryDtos.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,44 +1,0 @@
-package mk.finki.nutritioneer.web.dto;
-
-import jakarta.validation.Valid;
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import jakarta.validation.constraints.Positive;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.util.List;
-
-public final class GroceryDtos {
-
-    private GroceryDtos() {
-    }
-
-    public record SingleItem(
-            @NotBlank String ingredientName,
-            @NotNull @Positive BigDecimal buyQuantity) {
-    }
-
-    public record CreateListRequest(
-            LocalDateTime dateTime,
-            String notes,
-            List<Long> bulkRecipeIds,
-            @Valid List<SingleItem> singleItems) {
-    }
-
-    public record ListView(
-            Long id,
-            LocalDateTime dateTime,
-            String notes,
-            boolean bought,
-            BigDecimal kcal,
-            List<String> bulkRecipes,
-            List<ShoppingLine> shoppingLines) {
-    }
-
-    public record ShoppingLine(
-            String category,
-            String ingredient,
-            BigDecimal totalGrams) {
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/dto/PlannerDtos.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/dto/PlannerDtos.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,44 +1,0 @@
-package mk.finki.nutritioneer.web.dto;
-
-import jakarta.validation.constraints.PositiveOrZero;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.List;
-
-public final class PlannerDtos {
-
-    private PlannerDtos() {
-    }
-
-    public record LogMealRequest(
-            LocalDateTime dateTime,
-            String notes,
-            List<Long> postIds,
-            boolean consumed,
-            /* Only used when no meal is attached, e.g. "coffee and fruit". */
-            @PositiveOrZero BigDecimal manualKcal) {
-    }
-
-    public record PlannerEntryView(
-            Long id,
-            LocalDateTime dateTime,
-            String notes,
-            BigDecimal kcal,
-            boolean consumed,
-            List<String> meals) {
-    }
-
-    public record DailyTotalView(
-            LocalDate day,
-            BigDecimal kcalConsumed,
-            long mealsLogged) {
-    }
-
-    public record NutrientTotalView(
-            String nutrient,
-            String unit,
-            BigDecimal total) {
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/dto/PostDtos.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/dto/PostDtos.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,43 +1,0 @@
-package mk.finki.nutritioneer.web.dto;
-
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-
-public final class PostDtos {
-
-    private PostDtos() {
-    }
-
-    public record SharePostRequest(
-            @NotNull Long recipeId,
-            boolean isPrivate,
-            boolean isFavourite,
-            String status) {
-    }
-
-    public record PostView(
-            Long id,
-            Long recipeId,
-            String recipeName,
-            BigDecimal kcalPerServing,
-            String authorUsername,
-            String status,
-            boolean isPrivate,
-            boolean isFavourite,
-            LocalDateTime createdAt,
-            long commentCount) {
-    }
-
-    public record CommentRequest(@NotBlank String description) {
-    }
-
-    public record CommentView(
-            Long id,
-            String description,
-            String authorUsername,
-            LocalDateTime createdAt) {
-    }
-}
Index: ckend/src/main/java/mk/finki/nutritioneer/web/dto/RecipeDtos.java
===================================================================
--- backend/src/main/java/mk/finki/nutritioneer/web/dto/RecipeDtos.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,59 +1,0 @@
-package mk.finki.nutritioneer.web.dto;
-
-import jakarta.validation.Valid;
-import jakarta.validation.constraints.*;
-
-import java.math.BigDecimal;
-import java.util.List;
-
-public final class RecipeDtos {
-
-    private RecipeDtos() {
-    }
-
-    public record IngredientLine(
-            @NotBlank String ingredientName,
-            @NotNull @Positive BigDecimal quantity) {
-    }
-
-    public record CreateRecipeRequest(
-            @NotBlank String name,
-            @NotBlank String guide,
-            @NotNull @Positive BigDecimal servings,
-            @NotEmpty @Valid List<IngredientLine> ingredients,
-            List<Long> restrictionIds) {
-    }
-
-    public record IngredientLineView(
-            String ingredientName,
-            BigDecimal quantity,
-            BigDecimal kcalPer100g,
-            BigDecimal kcalContributed) {
-    }
-
-    public record RecipeView(
-            Long id,
-            String name,
-            String guide,
-            BigDecimal kcalSum,
-            BigDecimal servings,
-            BigDecimal kcalPerServing,
-            String ownerUsername,
-            List<IngredientLineView> ingredients,
-            List<String> restrictions) {
-    }
-
-    public record RecipeSummary(
-            Long id,
-            String name,
-            BigDecimal kcalPerServing,
-            String ownerUsername) {
-    }
-
-    public record NutrientDensityView(
-            String recipe,
-            BigDecimal kcalPerServing,
-            BigDecimal amountPerServing,
-            BigDecimal amountPer100Kcal) {
-    }
-}
Index: ckend/src/main/resources/application-local.yml
===================================================================
--- backend/src/main/resources/application-local.yml	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,8 +1,0 @@
-# Local development against the Docker database instead of the faculty
-# server. Activate with:  --spring.profiles.active=local
-# or in IntelliJ: Run > Edit Configurations > Active profiles: local
-spring:
-  datasource:
-    url: jdbc:postgresql://localhost:5433/nutritioneer
-    username: postgres
-    password: postgres
Index: ckend/src/main/resources/application.yml
===================================================================
--- backend/src/main/resources/application.yml	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,54 +1,0 @@
-server:
-  port: 8080
-
-spring:
-  application:
-    name: nutritioneer
-
-  datasource:
-    url: ${DB_URL:jdbc:postgresql://localhost:9999/db_202526z_prj_nutritioneer}
-    username: ${DB_USER:db_202526z_prj_nutritioneer_owner}
-    password: ${DB_PASSWORD:4da5d69c3fc5}
-    hikari:
-
-      maximum-pool-size: ${DB_POOL_SIZE:5}
-      minimum-idle: 1
-      max-lifetime: 300000          # 5 min, shorter than most SSH idle timeouts
-      idle-timeout: 120000
-      connection-timeout: 10000
-      validation-timeout: 5000
-      keepalive-time: 60000
-
-  jpa:
-    hibernate:
-      # The schema is owned by the SQL scripts in /sql, never by Hibernate.
-      ddl-auto: none
-    open-in-view: false
-    properties:
-      hibernate:
-        format_sql: true
-        default_schema: ${DB_SCHEMA:public}
-        jdbc:
-          time_zone: UTC
-    show-sql: ${SHOW_SQL:false}
-
-  jackson:
-    default-property-inclusion: non_null
-
-app:
-  jwt:
-    # Override in production. Must be at least 32 bytes for HS256.
-    secret: ${JWT_SECRET:change-me-in-production-this-is-a-dev-only-secret-key}
-    expiration-minutes: ${JWT_EXPIRATION_MINUTES:120}
-  cors:
-    allowed-origins: ${CORS_ORIGINS:http://localhost:5173}
-
-springdoc:
-  swagger-ui:
-    path: /swagger-ui.html
-    operations-sorter: alpha
-    tags-sorter: alpha
-
-logging:
-  level:
-    mk.finki.nutritioneer: DEBUG
Index: ckend/src/test/java/mk/finki/nutritioneer/ApplicationContextTest.java
===================================================================
--- backend/src/test/java/mk/finki/nutritioneer/ApplicationContextTest.java	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,17 +1,0 @@
-package mk.finki.nutritioneer;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-/**
- * Smoke test: the context loads, which means every bean, repository query and
- * security rule is at least wirable. Needs a running database, because the
- * repositories validate their native queries against it.
- */
-@SpringBootTest
-class ApplicationContextTest {
-
-    @Test
-    void contextLoads() {
-    }
-}
Index: cker-compose.yml
===================================================================
--- docker-compose.yml	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,23 +1,0 @@
-# OPTIONAL - local database for working offline, without the faculty tunnel.
-# Runs on 5433 so it cannot collide with a PostgreSQL service installed on
-# Windows (which holds 5432). Use it with the Spring profile "local".
-#   docker compose up -d
-#   run the app with --spring.profiles.active=local
-services:
-  db:
-    image: postgres:16
-    container_name: nutritioneer-db
-    environment:
-      POSTGRES_DB: nutritioneer
-      POSTGRES_USER: postgres
-      POSTGRES_PASSWORD: postgres
-    ports:
-      - "5433:5432"
-    volumes:
-      - nutritioneer-data:/var/lib/postgresql/data
-      # Both scripts run automatically the first time the volume is created,
-      # in filename order. Delete the volume to reload them.
-      - ./sql:/docker-entrypoint-initdb.d:ro
-
-volumes:
-  nutritioneer-data:
Index: ontend/index.html
===================================================================
--- frontend/index.html	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,12 +1,0 @@
-<!doctype html>
-<html lang="en">
-  <head>
-    <meta charset="UTF-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Nutritioneer</title>
-  </head>
-  <body>
-    <div id="root"></div>
-    <script type="module" src="/src/main.jsx"></script>
-  </body>
-</html>
Index: ontend/package-lock.json
===================================================================
--- frontend/package-lock.json	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,1815 +1,0 @@
-{
-  "name": "nutritioneer-frontend",
-  "version": "0.1.0",
-  "lockfileVersion": 3,
-  "requires": true,
-  "packages": {
-    "": {
-      "name": "nutritioneer-frontend",
-      "version": "0.1.0",
-      "dependencies": {
-        "react": "^18.3.1",
-        "react-dom": "^18.3.1"
-      },
-      "devDependencies": {
-        "@vitejs/plugin-react": "^4.3.4",
-        "vite": "^6.0.7"
-      }
-    },
-    "node_modules/@babel/code-frame": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
-      "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-validator-identifier": "^7.29.7",
-        "js-tokens": "^4.0.0",
-        "picocolors": "^1.1.1"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/compat-data": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
-      "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/core": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
-      "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/code-frame": "^7.29.7",
-        "@babel/generator": "^7.29.7",
-        "@babel/helper-compilation-targets": "^7.29.7",
-        "@babel/helper-module-transforms": "^7.29.7",
-        "@babel/helpers": "^7.29.7",
-        "@babel/parser": "^7.29.7",
-        "@babel/template": "^7.29.7",
-        "@babel/traverse": "^7.29.7",
-        "@babel/types": "^7.29.7",
-        "@jridgewell/remapping": "^2.3.5",
-        "convert-source-map": "^2.0.0",
-        "debug": "^4.1.0",
-        "gensync": "^1.0.0-beta.2",
-        "json5": "^2.2.3",
-        "semver": "^6.3.1"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/babel"
-      }
-    },
-    "node_modules/@babel/generator": {
-      "version": "7.29.8",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
-      "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/parser": "^7.29.8",
-        "@babel/types": "^7.29.8",
-        "@jridgewell/gen-mapping": "^0.3.12",
-        "@jridgewell/trace-mapping": "^0.3.28",
-        "jsesc": "^3.0.2"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-compilation-targets": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
-      "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/compat-data": "^7.29.7",
-        "@babel/helper-validator-option": "^7.29.7",
-        "browserslist": "^4.24.0",
-        "lru-cache": "^5.1.1",
-        "semver": "^6.3.1"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-globals": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
-      "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-module-imports": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
-      "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/traverse": "^7.29.7",
-        "@babel/types": "^7.29.7"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-module-transforms": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
-      "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-module-imports": "^7.29.7",
-        "@babel/helper-validator-identifier": "^7.29.7",
-        "@babel/traverse": "^7.29.7"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0"
-      }
-    },
-    "node_modules/@babel/helper-plugin-utils": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
-      "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-string-parser": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
-      "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-validator-identifier": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
-      "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-validator-option": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
-      "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helpers": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
-      "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/template": "^7.29.7",
-        "@babel/types": "^7.29.7"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/parser": {
-      "version": "7.29.8",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz",
-      "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/types": "^7.29.8"
-      },
-      "bin": {
-        "parser": "bin/babel-parser.js"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/@babel/plugin-transform-react-jsx-self": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
-      "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-plugin-utils": "^7.29.7"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
-    "node_modules/@babel/plugin-transform-react-jsx-source": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
-      "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-plugin-utils": "^7.29.7"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
-    "node_modules/@babel/template": {
-      "version": "7.29.7",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
-      "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/code-frame": "^7.29.7",
-        "@babel/parser": "^7.29.7",
-        "@babel/types": "^7.29.7"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/traverse": {
-      "version": "7.29.8",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz",
-      "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/code-frame": "^7.29.7",
-        "@babel/generator": "^7.29.8",
-        "@babel/helper-globals": "^7.29.7",
-        "@babel/parser": "^7.29.8",
-        "@babel/template": "^7.29.7",
-        "@babel/types": "^7.29.8",
-        "debug": "^4.3.1"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/types": {
-      "version": "7.29.8",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
-      "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-string-parser": "^7.29.7",
-        "@babel/helper-validator-identifier": "^7.29.7"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@esbuild/aix-ppc64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
-      "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
-      "cpu": [
-        "ppc64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "aix"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/android-arm": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
-      "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/android-arm64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
-      "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/android-x64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
-      "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/darwin-arm64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
-      "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/darwin-x64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
-      "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/freebsd-arm64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
-      "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/freebsd-x64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
-      "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-arm": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
-      "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-arm64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
-      "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-ia32": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
-      "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-loong64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
-      "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
-      "cpu": [
-        "loong64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-mips64el": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
-      "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
-      "cpu": [
-        "mips64el"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-ppc64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
-      "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
-      "cpu": [
-        "ppc64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-riscv64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
-      "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
-      "cpu": [
-        "riscv64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-s390x": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
-      "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
-      "cpu": [
-        "s390x"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-x64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
-      "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/netbsd-arm64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
-      "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "netbsd"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/netbsd-x64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
-      "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "netbsd"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/openbsd-arm64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
-      "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "openbsd"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/openbsd-x64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
-      "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "openbsd"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/openharmony-arm64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
-      "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "openharmony"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/sunos-x64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
-      "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "sunos"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/win32-arm64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
-      "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/win32-ia32": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
-      "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/win32-x64": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
-      "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@jridgewell/gen-mapping": {
-      "version": "0.3.13",
-      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
-      "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@jridgewell/sourcemap-codec": "^1.5.0",
-        "@jridgewell/trace-mapping": "^0.3.24"
-      }
-    },
-    "node_modules/@jridgewell/remapping": {
-      "version": "2.3.5",
-      "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
-      "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@jridgewell/gen-mapping": "^0.3.5",
-        "@jridgewell/trace-mapping": "^0.3.24"
-      }
-    },
-    "node_modules/@jridgewell/resolve-uri": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
-      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/@jridgewell/sourcemap-codec": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
-      "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/@jridgewell/trace-mapping": {
-      "version": "0.3.31",
-      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
-      "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@jridgewell/resolve-uri": "^3.1.0",
-        "@jridgewell/sourcemap-codec": "^1.4.14"
-      }
-    },
-    "node_modules/@napi-rs/lzma-linux-x64-gnu": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
-      "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": "^22.20 || ^24.12 || >=25"
-      }
-    },
-    "node_modules/@rolldown/pluginutils": {
-      "version": "1.0.0-beta.27",
-      "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
-      "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/@rollup/rollup-android-arm-eabi": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.3.tgz",
-      "integrity": "sha512-w3Jnvi1ocaVm/c7yVPpfB98XeSRBMyzp6njL5MVVbGyXjpmUkN+s6Hp4t0PqhGCCaI1ZHMKXt/w0lA1RCaLVcw==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ]
-    },
-    "node_modules/@rollup/rollup-android-arm64": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.3.tgz",
-      "integrity": "sha512-uI/ESiaIbbRYAEhzy8PCUWDp1hB0bjAqM06mW9flOoNO4Q8DQpeoREhBR5Hegfl+wpXiguyJv6XSPzEN7OxyHQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ]
-    },
-    "node_modules/@rollup/rollup-darwin-arm64": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.3.tgz",
-      "integrity": "sha512-oxhrd1jmXLwWZ83eQYDXxuqRdkqkzrjR3JobKeuUyfdNZo11FuQIvqEOZhyIT7OBHxXoGslDDjN0cQcM6T0TqQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/@rollup/rollup-darwin-x64": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.3.tgz",
-      "integrity": "sha512-7/YiIMghVE8DrxKvNdorAaJVdriOFgOIpdStnPx8ppx5zfTwC3jBCSEAIzB7JD5404m65THl6H93UTTVUvypmg==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/@rollup/rollup-freebsd-arm64": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.3.tgz",
-      "integrity": "sha512-GXFZRRoMAytaI5z6N3Zhfw0WL18Q0M8r95D5hlC4GqE/lGk8pbSJNUBoOWDfbm6dTciqHj2nU87tI5f6XhQiOg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "freebsd"
-      ]
-    },
-    "node_modules/@rollup/rollup-freebsd-x64": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.3.tgz",
-      "integrity": "sha512-77W+8X3ddYgPxUpB8nZFQs2Mq+wc4HVlcSRtApXLjYBcnPMkttrSnU8VwKQjeWYhMsITHFs5cWBQ8vz1Q+5RHQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "freebsd"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.3.tgz",
-      "integrity": "sha512-FVkwK+iUC+mq+GipVK46rRVticfAPtvPUNlqlGXUDxdVk/UGjQiiiUVPUrEXdSpU2ufU0XxLGyTqDtBidDOVmg==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.3.tgz",
-      "integrity": "sha512-+aGU1t3398yQOVj1Bz8o3e+KtswxAPvO+mtxtNdfXYMkXIHu7XhhkCD7/DEH9q8tF8uhDnMWvfpUKI8y1sZJsg==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm64-gnu": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.3.tgz",
-      "integrity": "sha512-cR0kjpRXR2KJ2oQK8E2KTPtphs+b9hZ8IhTZubNryt/RsqgdOZBQ2Zq0q5UedtiIi0rs3jVhJh55RE1ZHUVGUA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm64-musl": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.3.tgz",
-      "integrity": "sha512-y1RYi4Q3/9ByVWSSt9kX2ustE0B7kFYbJ6zZdVZVyqopZs3yhCTwRfrjIX4vezUJInma/Gs6BOFDJg7yZmJ0IQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-loong64-gnu": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.3.tgz",
-      "integrity": "sha512-DNhEA5viIj3Z5bZLE4z4oV8N5ozWqDwyt7T6KG7VdLDJ0nW+rNOYlphBl4/3HQkK75qipPLsVOfStHHOwN9WSg==",
-      "cpu": [
-        "loong64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-loong64-musl": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.3.tgz",
-      "integrity": "sha512-17gQCqrIpXBX2Cmi9/TygnVOqGbzsba/iaqcYSL8FY7lNugg+7AiYNs5c5nKWD+NRQha36Sa0CqkJqH4XVHwnQ==",
-      "cpu": [
-        "loong64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-ppc64-gnu": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.3.tgz",
-      "integrity": "sha512-6LwVnZRIyINpdku/yOcI8Tm9YqLmhHK5emmlOOnW9tO0SYEm1FmKPcsSAGp0NBlqR2P04xaND4jvN6sTHqhq8A==",
-      "cpu": [
-        "ppc64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-ppc64-musl": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.3.tgz",
-      "integrity": "sha512-xMUqkTXlEUtI/p5AAukMwBRr1enU3efsTeF+bskeFfk8t1C9rcC8sLREcZXmTfAXEbvRdJVSonVJez3TMlbR3w==",
-      "cpu": [
-        "ppc64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.3.tgz",
-      "integrity": "sha512-S3E94co9F9WRRqEaUoQZ38K1gCz6KiM+nL7/3ijq7fDGF3OznjS5TasgYITlvl27GQKtu4lOAOsr5MFwkijvOA==",
-      "cpu": [
-        "riscv64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-riscv64-musl": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.3.tgz",
-      "integrity": "sha512-1QtRDwG42x5BJI3s9mxu5rEjDnfbSnk20HQ9/ylTAYnSwYwxMVb+Vgu34wzzTQ7ogqBybebgQNUDAvZVQ38DbA==",
-      "cpu": [
-        "riscv64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-s390x-gnu": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.3.tgz",
-      "integrity": "sha512-BQhejF6ZXOpxbngiNTP12GCGQeaDVL2QXGeBVViKIYzFHM5RKxTxwUMB1fr1BeNFphFMpnRqC5QSXFSa4z6UQw==",
-      "cpu": [
-        "s390x"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-x64-gnu": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.3.tgz",
-      "integrity": "sha512-SXagRwnI2Wlwlitllu59UK/nGVbD1CKPcNqDplHwIC4BqJcpXFjD32d1R/RbuISa95HdQrZM3/7v4bKiowFaLA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-x64-musl": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.3.tgz",
-      "integrity": "sha512-2IPozoEALRCziGqE8O9KMK60PMu5TS1huv4fwoeCexj+WjmcwFtX9CTOVbfXCUqcELAubEwRFPYlzb/WvwY2HQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-openbsd-x64": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.3.tgz",
-      "integrity": "sha512-AoxqosUHT9IX54hFn2TiN6A7d6ZKTtE6pd2bqWtqkkNJ6HJGaU6FRouGX8L1O7R/ZwsnCnpQrHzb4pDEx+UHRQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "openbsd"
-      ]
-    },
-    "node_modules/@rollup/rollup-openharmony-arm64": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.3.tgz",
-      "integrity": "sha512-d+CaftKgmkFBzCwezMqqy1d0QNNYugqLCMcYVQWBy5SS2YfeMP8Q8ripkgx9O8IyBXXLHrJ+aaCV4U96usv6Yg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "openharmony"
-      ]
-    },
-    "node_modules/@rollup/rollup-win32-arm64-msvc": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.3.tgz",
-      "integrity": "sha512-xXlDF6nR1eOuXbdDy5Hl5fmtY7teUDevF/k0O7IPoZe4Tpmdv+lgdE5JRsnhQtt37ql9P0VF2kAN9a0OCZdo+Q==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/@rollup/rollup-win32-ia32-msvc": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.3.tgz",
-      "integrity": "sha512-YtXAgLN+JP7Ay6qG3eWhc7IHMQPzLc8r3uvhAvlJIoCz/4Q32+Bl9Fmnywidh8v1GOIMmymjovfqY9ETAtysvA==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/@rollup/rollup-win32-x64-gnu": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.3.tgz",
-      "integrity": "sha512-WuWtSJRNo549vzcfZyEgfqb6zeSgn1F+UE5kQ+BCjzz0W4MGCjntUHkZVc1VRuAM7+ULaSyhiPxD1spyewFvkQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/@rollup/rollup-win32-x64-msvc": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.3.tgz",
-      "integrity": "sha512-+lIKX7O0+IGe7WuhATaAMMeT7B76vfhXH/l9wLQL+nvyhbw2ohYCKIdWL56JfDu75CWt5oKRP4QFH/jkMtBquA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/@types/babel__core": {
-      "version": "7.20.5",
-      "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
-      "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/parser": "^7.20.7",
-        "@babel/types": "^7.20.7",
-        "@types/babel__generator": "*",
-        "@types/babel__template": "*",
-        "@types/babel__traverse": "*"
-      }
-    },
-    "node_modules/@types/babel__generator": {
-      "version": "7.27.0",
-      "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
-      "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/types": "^7.0.0"
-      }
-    },
-    "node_modules/@types/babel__template": {
-      "version": "7.4.4",
-      "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
-      "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/parser": "^7.1.0",
-        "@babel/types": "^7.0.0"
-      }
-    },
-    "node_modules/@types/babel__traverse": {
-      "version": "7.28.0",
-      "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
-      "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/types": "^7.28.2"
-      }
-    },
-    "node_modules/@types/estree": {
-      "version": "1.0.9",
-      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
-      "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/@vitejs/plugin-react": {
-      "version": "4.7.0",
-      "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
-      "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/core": "^7.28.0",
-        "@babel/plugin-transform-react-jsx-self": "^7.27.1",
-        "@babel/plugin-transform-react-jsx-source": "^7.27.1",
-        "@rolldown/pluginutils": "1.0.0-beta.27",
-        "@types/babel__core": "^7.20.5",
-        "react-refresh": "^0.17.0"
-      },
-      "engines": {
-        "node": "^14.18.0 || >=16.0.0"
-      },
-      "peerDependencies": {
-        "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
-      }
-    },
-    "node_modules/baseline-browser-mapping": {
-      "version": "2.11.24",
-      "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.24.tgz",
-      "integrity": "sha512-hYrgxie335U08WqICoGqKRzV1HFXv6zdxwJE4ekCb80CM9a0SVVsN4QPwT67RraRo+9h8IATk6uxHJw7QSkdOg==",
-      "dev": true,
-      "license": "Apache-2.0",
-      "bin": {
-        "baseline-browser-mapping": "dist/cli.cjs"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/browserslist": {
-      "version": "4.29.0",
-      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz",
-      "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/browserslist"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/browserslist"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "baseline-browser-mapping": "^2.11.23",
-        "caniuse-lite": "^1.0.30001810",
-        "electron-to-chromium": "^1.5.427",
-        "node-releases": "^2.0.55",
-        "update-browserslist-db": "^1.3.3"
-      },
-      "bin": {
-        "browserslist": "cli.js"
-      },
-      "engines": {
-        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
-      }
-    },
-    "node_modules/caniuse-lite": {
-      "version": "1.0.30001810",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
-      "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/browserslist"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "CC-BY-4.0"
-    },
-    "node_modules/convert-source-map": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
-      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/debug": {
-      "version": "4.4.3",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
-      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "ms": "^2.1.3"
-      },
-      "engines": {
-        "node": ">=6.0"
-      },
-      "peerDependenciesMeta": {
-        "supports-color": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/electron-to-chromium": {
-      "version": "1.5.430",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.430.tgz",
-      "integrity": "sha512-e1QEj72Y4zd8RlNZVmoTg+iCOSVwpk05IOiiQwdrkwCSVlZfPthevErhE+nckGd2YbsXfp1SkisznhGVIXP2NQ==",
-      "dev": true,
-      "license": "ISC"
-    },
-    "node_modules/esbuild": {
-      "version": "0.25.12",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
-      "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
-      "dev": true,
-      "hasInstallScript": true,
-      "license": "MIT",
-      "bin": {
-        "esbuild": "bin/esbuild"
-      },
-      "engines": {
-        "node": ">=18"
-      },
-      "optionalDependencies": {
-        "@esbuild/aix-ppc64": "0.25.12",
-        "@esbuild/android-arm": "0.25.12",
-        "@esbuild/android-arm64": "0.25.12",
-        "@esbuild/android-x64": "0.25.12",
-        "@esbuild/darwin-arm64": "0.25.12",
-        "@esbuild/darwin-x64": "0.25.12",
-        "@esbuild/freebsd-arm64": "0.25.12",
-        "@esbuild/freebsd-x64": "0.25.12",
-        "@esbuild/linux-arm": "0.25.12",
-        "@esbuild/linux-arm64": "0.25.12",
-        "@esbuild/linux-ia32": "0.25.12",
-        "@esbuild/linux-loong64": "0.25.12",
-        "@esbuild/linux-mips64el": "0.25.12",
-        "@esbuild/linux-ppc64": "0.25.12",
-        "@esbuild/linux-riscv64": "0.25.12",
-        "@esbuild/linux-s390x": "0.25.12",
-        "@esbuild/linux-x64": "0.25.12",
-        "@esbuild/netbsd-arm64": "0.25.12",
-        "@esbuild/netbsd-x64": "0.25.12",
-        "@esbuild/openbsd-arm64": "0.25.12",
-        "@esbuild/openbsd-x64": "0.25.12",
-        "@esbuild/openharmony-arm64": "0.25.12",
-        "@esbuild/sunos-x64": "0.25.12",
-        "@esbuild/win32-arm64": "0.25.12",
-        "@esbuild/win32-ia32": "0.25.12",
-        "@esbuild/win32-x64": "0.25.12"
-      }
-    },
-    "node_modules/escalade": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
-      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/fdir": {
-      "version": "6.5.0",
-      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
-      "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=12.0.0"
-      },
-      "peerDependencies": {
-        "picomatch": "^3 || ^4"
-      },
-      "peerDependenciesMeta": {
-        "picomatch": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/fsevents": {
-      "version": "2.3.3",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
-      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
-      "dev": true,
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
-      }
-    },
-    "node_modules/gensync": {
-      "version": "1.0.0-beta.2",
-      "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
-      "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/js-tokens": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
-      "license": "MIT"
-    },
-    "node_modules/jsesc": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
-      "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
-      "dev": true,
-      "license": "MIT",
-      "bin": {
-        "jsesc": "bin/jsesc"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/json5": {
-      "version": "2.2.3",
-      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
-      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
-      "dev": true,
-      "license": "MIT",
-      "bin": {
-        "json5": "lib/cli.js"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/loose-envify": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
-      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
-      "license": "MIT",
-      "dependencies": {
-        "js-tokens": "^3.0.0 || ^4.0.0"
-      },
-      "bin": {
-        "loose-envify": "cli.js"
-      }
-    },
-    "node_modules/lru-cache": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
-      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "yallist": "^3.0.2"
-      }
-    },
-    "node_modules/ms": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/nanoid": {
-      "version": "3.3.19",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
-      "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "MIT",
-      "bin": {
-        "nanoid": "bin/nanoid.cjs"
-      },
-      "engines": {
-        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
-      }
-    },
-    "node_modules/node-releases": {
-      "version": "2.0.55",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz",
-      "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/picocolors": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
-      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
-      "dev": true,
-      "license": "ISC"
-    },
-    "node_modules/picomatch": {
-      "version": "4.0.7",
-      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
-      "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/jonschlinkert"
-      }
-    },
-    "node_modules/postcss": {
-      "version": "8.5.28",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
-      "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/postcss/"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/postcss"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "nanoid": "^3.3.18",
-        "picocolors": "^1.1.1",
-        "source-map-js": "^1.2.1"
-      },
-      "engines": {
-        "node": "^10 || ^12 || >=14"
-      }
-    },
-    "node_modules/react": {
-      "version": "18.3.1",
-      "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
-      "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
-      "license": "MIT",
-      "dependencies": {
-        "loose-envify": "^1.1.0"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/react-dom": {
-      "version": "18.3.1",
-      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
-      "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
-      "license": "MIT",
-      "dependencies": {
-        "loose-envify": "^1.1.0",
-        "scheduler": "^0.23.2"
-      },
-      "peerDependencies": {
-        "react": "^18.3.1"
-      }
-    },
-    "node_modules/react-refresh": {
-      "version": "0.17.0",
-      "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
-      "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/rollup": {
-      "version": "4.63.3",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.3.tgz",
-      "integrity": "sha512-1i2XreiAoMMXuPGD6Msj2xWrMMkHojNRKivInxGQcg7/1KuPuYlfUutLyh4drnOxUTHX9cHI4wFoat8D/NKaBw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@types/estree": "1.0.9"
-      },
-      "bin": {
-        "rollup": "dist/bin/rollup"
-      },
-      "engines": {
-        "node": ">=18.0.0",
-        "npm": ">=8.0.0"
-      },
-      "optionalDependencies": {
-        "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
-        "@rollup/rollup-android-arm-eabi": "4.63.3",
-        "@rollup/rollup-android-arm64": "4.63.3",
-        "@rollup/rollup-darwin-arm64": "4.63.3",
-        "@rollup/rollup-darwin-x64": "4.63.3",
-        "@rollup/rollup-freebsd-arm64": "4.63.3",
-        "@rollup/rollup-freebsd-x64": "4.63.3",
-        "@rollup/rollup-linux-arm-gnueabihf": "4.63.3",
-        "@rollup/rollup-linux-arm-musleabihf": "4.63.3",
-        "@rollup/rollup-linux-arm64-gnu": "4.63.3",
-        "@rollup/rollup-linux-arm64-musl": "4.63.3",
-        "@rollup/rollup-linux-loong64-gnu": "4.63.3",
-        "@rollup/rollup-linux-loong64-musl": "4.63.3",
-        "@rollup/rollup-linux-ppc64-gnu": "4.63.3",
-        "@rollup/rollup-linux-ppc64-musl": "4.63.3",
-        "@rollup/rollup-linux-riscv64-gnu": "4.63.3",
-        "@rollup/rollup-linux-riscv64-musl": "4.63.3",
-        "@rollup/rollup-linux-s390x-gnu": "4.63.3",
-        "@rollup/rollup-linux-x64-gnu": "4.63.3",
-        "@rollup/rollup-linux-x64-musl": "4.63.3",
-        "@rollup/rollup-openbsd-x64": "4.63.3",
-        "@rollup/rollup-openharmony-arm64": "4.63.3",
-        "@rollup/rollup-win32-arm64-msvc": "4.63.3",
-        "@rollup/rollup-win32-ia32-msvc": "4.63.3",
-        "@rollup/rollup-win32-x64-gnu": "4.63.3",
-        "@rollup/rollup-win32-x64-msvc": "4.63.3",
-        "fsevents": "~2.3.2"
-      }
-    },
-    "node_modules/scheduler": {
-      "version": "0.23.2",
-      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
-      "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
-      "license": "MIT",
-      "dependencies": {
-        "loose-envify": "^1.1.0"
-      }
-    },
-    "node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
-    "node_modules/source-map-js": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
-      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
-      "dev": true,
-      "license": "BSD-3-Clause",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/tinyglobby": {
-      "version": "0.2.17",
-      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
-      "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "fdir": "^6.5.0",
-        "picomatch": "^4.0.4"
-      },
-      "engines": {
-        "node": ">=12.0.0"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/SuperchupuDev"
-      }
-    },
-    "node_modules/update-browserslist-db": {
-      "version": "1.3.3",
-      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz",
-      "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/browserslist"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/browserslist"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "escalade": "^3.2.0",
-        "picocolors": "^1.1.1"
-      },
-      "bin": {
-        "update-browserslist-db": "cli.js"
-      },
-      "peerDependencies": {
-        "browserslist": ">= 4.21.0"
-      }
-    },
-    "node_modules/vite": {
-      "version": "6.4.3",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
-      "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "esbuild": "^0.25.0",
-        "fdir": "^6.4.4",
-        "picomatch": "^4.0.2",
-        "postcss": "^8.5.3",
-        "rollup": "^4.34.9",
-        "tinyglobby": "^0.2.13"
-      },
-      "bin": {
-        "vite": "bin/vite.js"
-      },
-      "engines": {
-        "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
-      },
-      "funding": {
-        "url": "https://github.com/vitejs/vite?sponsor=1"
-      },
-      "optionalDependencies": {
-        "fsevents": "~2.3.3"
-      },
-      "peerDependencies": {
-        "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
-        "jiti": ">=1.21.0",
-        "less": "*",
-        "lightningcss": "^1.21.0",
-        "sass": "*",
-        "sass-embedded": "*",
-        "stylus": "*",
-        "sugarss": "*",
-        "terser": "^5.16.0",
-        "tsx": "^4.8.1",
-        "yaml": "^2.4.2"
-      },
-      "peerDependenciesMeta": {
-        "@types/node": {
-          "optional": true
-        },
-        "jiti": {
-          "optional": true
-        },
-        "less": {
-          "optional": true
-        },
-        "lightningcss": {
-          "optional": true
-        },
-        "sass": {
-          "optional": true
-        },
-        "sass-embedded": {
-          "optional": true
-        },
-        "stylus": {
-          "optional": true
-        },
-        "sugarss": {
-          "optional": true
-        },
-        "terser": {
-          "optional": true
-        },
-        "tsx": {
-          "optional": true
-        },
-        "yaml": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/yallist": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
-      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
-      "dev": true,
-      "license": "ISC"
-    }
-  }
-}
Index: ontend/package.json
===================================================================
--- frontend/package.json	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,19 +1,0 @@
-{
-  "name": "nutritioneer-frontend",
-  "private": true,
-  "version": "0.1.0",
-  "type": "module",
-  "scripts": {
-    "dev": "vite",
-    "build": "vite build",
-    "preview": "vite preview"
-  },
-  "dependencies": {
-    "react": "^18.3.1",
-    "react-dom": "^18.3.1"
-  },
-  "devDependencies": {
-    "@vitejs/plugin-react": "^4.3.4",
-    "vite": "^6.0.7"
-  }
-}
Index: ontend/src/App.jsx
===================================================================
--- frontend/src/App.jsx	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,57 +1,0 @@
-import { useState } from 'react'
-import { auth } from './lib/api.js'
-import Login from './pages/Login.jsx'
-import Feed from './pages/Feed.jsx'
-import Recipes from './pages/Recipes.jsx'
-import Planner from './pages/Planner.jsx'
-import Grocery from './pages/Grocery.jsx'
-import Biometrics from './pages/Biometrics.jsx'
-import Trainer from './pages/Trainer.jsx'
-
-const TABS = [
-  { key: 'feed', label: 'Feed', component: Feed },
-  { key: 'recipes', label: 'Recipes', component: Recipes },
-  { key: 'planner', label: 'Planner', component: Planner },
-  { key: 'grocery', label: 'Grocery', component: Grocery },
-  { key: 'biometrics', label: 'Biometrics', component: Biometrics },
-  { key: 'trainer', label: 'Trainer', component: Trainer, roles: ['trainer', 'administrator'] }
-]
-
-export default function App() {
-  const [user, setUser] = useState(auth.user())
-  const [tab, setTab] = useState('feed')
-
-  if (!user) return <Login onSignedIn={setUser} />
-
-  const visible = TABS.filter(t => !t.roles || t.roles.includes(user.role))
-  const Active = (visible.find(t => t.key === tab) || visible[0]).component
-
-  const signOut = () => {
-    auth.clear()
-    setUser(null)
-  }
-
-  return (
-    <div className="app">
-      <header className="top">
-        <h1>Nutritioneer</h1>
-        <span className="muted">prototype</span>
-        <span className="who">
-          {user.username} ({user.role}) <button className="small" onClick={signOut}>sign out</button>
-        </span>
-      </header>
-
-      <nav>
-        {visible.map(t => (
-          <button key={t.key}
-                  className={t.key === tab ? 'active' : ''}
-                  onClick={() => setTab(t.key)}>
-            {t.label}
-          </button>
-        ))}
-      </nav>
-
-      <Active user={user} />
-    </div>
-  )
-}
Index: ontend/src/lib/api.js
===================================================================
--- frontend/src/lib/api.js	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,60 +1,0 @@
-// Thin wrapper over fetch: attaches the JWT, unwraps JSON, surfaces the
-// backend's error message instead of a bare "500".
-
-const TOKEN_KEY = 'nutritioneer.token'
-const USER_KEY = 'nutritioneer.user'
-
-export const auth = {
-  token: () => localStorage.getItem(TOKEN_KEY),
-  user: () => {
-    const raw = localStorage.getItem(USER_KEY)
-    return raw ? JSON.parse(raw) : null
-  },
-  save: (res) => {
-    localStorage.setItem(TOKEN_KEY, res.token)
-    localStorage.setItem(USER_KEY, JSON.stringify({
-      email: res.email, username: res.username, role: res.role
-    }))
-  },
-  clear: () => {
-    localStorage.removeItem(TOKEN_KEY)
-    localStorage.removeItem(USER_KEY)
-  }
-}
-
-async function request(method, path, body) {
-  const headers = { 'Content-Type': 'application/json' }
-  const token = auth.token()
-  if (token) headers.Authorization = `Bearer ${token}`
-
-  const res = await fetch(`/api${path}`, {
-    method,
-    headers,
-    body: body === undefined ? undefined : JSON.stringify(body)
-  })
-
-  if (res.status === 204) return null
-
-  const text = await res.text()
-  const data = text ? JSON.parse(text) : null
-
-  if (res.status === 401) {
-    // A 401 while signing in means the credentials were rejected. Only treat
-    // it as an expired session if we actually sent a token.
-    if (token) {
-      auth.clear()
-      throw new Error(data?.message || 'Session expired, please sign in again')
-    }
-    throw new Error(data?.message || 'Invalid email or password')
-  }
-
-  if (!res.ok) throw new Error(data?.message || `Request failed (${res.status})`)
-  return data
-}
-
-export const api = {
-  get: (p) => request('GET', p),
-  post: (p, b) => request('POST', p, b),
-  patch: (p, b) => request('PATCH', p, b),
-  del: (p) => request('DELETE', p)
-}
Index: ontend/src/main.jsx
===================================================================
--- frontend/src/main.jsx	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,10 +1,0 @@
-import React from 'react'
-import ReactDOM from 'react-dom/client'
-import App from './App.jsx'
-import './styles.css'
-
-ReactDOM.createRoot(document.getElementById('root')).render(
-  <React.StrictMode>
-    <App />
-  </React.StrictMode>
-)
Index: ontend/src/pages/Biometrics.jsx
===================================================================
--- frontend/src/pages/Biometrics.jsx	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,80 +1,0 @@
-import { useEffect, useState } from 'react'
-import { api } from '../lib/api.js'
-
-export default function Biometrics() {
-  const [rows, setRows] = useState([])
-  const [error, setError] = useState(null)
-  const [form, setForm] = useState({
-    date: new Date().toISOString().slice(0, 10),
-    weight: '', height: '', age: '', muscleFatRatio: ''
-  })
-
-  const load = () => api.get('/biometrics').then(setRows).catch(e => setError(e.message))
-  useEffect(() => { load() }, [])
-
-  const set = (k) => (e) => setForm({ ...form, [k]: e.target.value })
-
-  const save = async () => {
-    setError(null)
-    try {
-      await api.post('/biometrics', {
-        date: form.date,
-        weight: form.weight ? Number(form.weight) : null,
-        height: form.height ? Number(form.height) : null,
-        age: form.age ? Number(form.age) : null,
-        muscleFatRatio: form.muscleFatRatio ? Number(form.muscleFatRatio) : null
-      })
-      load()
-    } catch (e) { setError(e.message) }
-  }
-
-  return (
-    <div>
-      {error && <div className="error">{error}</div>}
-
-      <div className="card">
-        <h3>Record a measurement</h3>
-        <p className="muted">One per day. Saving the same date again updates that entry.</p>
-        <div className="row">
-          <input type="date" value={form.date} onChange={set('date')} />
-          <input type="number" step="0.1" placeholder="weight kg" style={{ width: 120 }}
-                 value={form.weight} onChange={set('weight')} />
-          <input type="number" placeholder="height cm" style={{ width: 120 }}
-                 value={form.height} onChange={set('height')} />
-          <input type="number" placeholder="age" style={{ width: 90 }}
-                 value={form.age} onChange={set('age')} />
-          <input type="number" step="0.01" placeholder="muscle/fat" style={{ width: 120 }}
-                 value={form.muscleFatRatio} onChange={set('muscleFatRatio')} />
-          <button className="primary" onClick={save}>Save</button>
-        </div>
-      </div>
-
-      <div className="card">
-        <h3>Progress</h3>
-        <table>
-          <thead>
-            <tr><th>date</th><th>weight</th><th>change</th><th>BMI</th><th>muscle/fat</th><th></th></tr>
-          </thead>
-          <tbody>
-            {rows.map(r => (
-              <tr key={r.id}>
-                <td>{r.date}</td>
-                <td>{r.weight}</td>
-                <td className={r.changeSincePrevious < 0 ? 'kcal' : 'muted'}>
-                  {r.changeSincePrevious ?? '—'}
-                </td>
-                <td>{r.bmi ?? '—'}</td>
-                <td>{r.muscleFatRatio ?? '—'}</td>
-                <td style={{ width: 1 }}>
-                  <button className="small"
-                          onClick={() => api.del(`/biometrics/${r.id}`).then(load)}>delete</button>
-                </td>
-              </tr>
-            ))}
-          </tbody>
-        </table>
-        {rows.length === 0 && <p className="muted">No measurements yet.</p>}
-      </div>
-    </div>
-  )
-}
Index: ontend/src/pages/Feed.jsx
===================================================================
--- frontend/src/pages/Feed.jsx	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,70 +1,0 @@
-import { useEffect, useState } from 'react'
-import { api } from '../lib/api.js'
-
-export default function Feed() {
-  const [posts, setPosts] = useState([])
-  const [openId, setOpenId] = useState(null)
-  const [comments, setComments] = useState([])
-  const [draft, setDraft] = useState('')
-  const [error, setError] = useState(null)
-
-  const load = () => api.get('/posts/feed').then(setPosts).catch(e => setError(e.message))
-  useEffect(() => { load() }, [])
-
-  const open = async (id) => {
-    setOpenId(id)
-    setComments(await api.get(`/posts/${id}/comments`))
-  }
-
-  const send = async () => {
-    if (!draft.trim()) return
-    try {
-      await api.post(`/posts/${openId}/comments`, { description: draft })
-      setDraft('')
-      setComments(await api.get(`/posts/${openId}/comments`))
-      load()
-    } catch (e) { setError(e.message) }
-  }
-
-  return (
-    <div>
-      {error && <div className="error">{error}</div>}
-      {posts.length === 0 && <p className="muted">Nothing published yet.</p>}
-
-      {posts.map(p => (
-        <div className="card" key={p.id}>
-          <div className="row">
-            <h3 style={{ flex: 1 }}>{p.recipeName || '(photo post)'}</h3>
-            <span className="kcal">{p.kcalPerServing} kcal / portion</span>
-          </div>
-          <div className="muted">
-            by {p.authorUsername} · {new Date(p.createdAt).toLocaleString()} · {p.commentCount} comments
-            {p.isFavourite && ' · favourite'}
-          </div>
-          <div className="row" style={{ marginTop: 8 }}>
-            <button className="small" onClick={() => open(p.id)}>
-              {openId === p.id ? 'comments below' : 'open comments'}
-            </button>
-          </div>
-
-          {openId === p.id && (
-            <div style={{ marginTop: 10, borderTop: '1px solid var(--line)', paddingTop: 10 }}>
-              {comments.map(c => (
-                <div key={c.id} style={{ marginBottom: 6 }}>
-                  <b>{c.authorUsername}</b> <span className="muted">
-                    {new Date(c.createdAt).toLocaleString()}</span>
-                  <div>{c.description}</div>
-                </div>
-              ))}
-              <div className="row" style={{ marginTop: 8 }}>
-                <input style={{ flex: 1 }} placeholder="write a comment"
-                       value={draft} onChange={e => setDraft(e.target.value)} />
-                <button className="primary" onClick={send}>Send</button>
-              </div>
-            </div>
-          )}
-        </div>
-      ))}
-    </div>
-  )
-}
Index: ontend/src/pages/Grocery.jsx
===================================================================
--- frontend/src/pages/Grocery.jsx	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,110 +1,0 @@
-import { useEffect, useState } from 'react'
-import { api } from '../lib/api.js'
-
-export default function Grocery() {
-  const [lists, setLists] = useState([])
-  const [recipes, setRecipes] = useState([])
-  const [catalog, setCatalog] = useState([])
-  const [open, setOpen] = useState(null)
-  const [error, setError] = useState(null)
-  const [notes, setNotes] = useState('')
-  const [bulk, setBulk] = useState([])
-  const [item, setItem] = useState({ ingredientName: '', buyQuantity: '' })
-
-  const load = () => api.get('/grocery-lists').then(setLists).catch(e => setError(e.message))
-
-  useEffect(() => {
-    load()
-    api.get('/recipes').then(setRecipes).catch(() => {})
-    api.get('/catalog/ingredients').then(setCatalog).catch(() => {})
-  }, [])
-
-  const create = async () => {
-    setError(null)
-    try {
-      const created = await api.post('/grocery-lists', {
-        notes,
-        bulkRecipeIds: bulk.map(Number),
-        singleItems: item.ingredientName && item.buyQuantity
-          ? [{ ingredientName: item.ingredientName, buyQuantity: Number(item.buyQuantity) }]
-          : []
-      })
-      setOpen(created)
-      setNotes(''); setBulk([]); setItem({ ingredientName: '', buyQuantity: '' })
-      load()
-    } catch (e) { setError(e.message) }
-  }
-
-  return (
-    <div>
-      {error && <div className="error">{error}</div>}
-
-      <div className="card">
-        <h3>New grocery list</h3>
-        <div className="stack" style={{ maxWidth: 520 }}>
-          <input placeholder="notes" value={notes} onChange={e => setNotes(e.target.value)} />
-          <span className="muted">Add whole recipes:</span>
-          <select multiple size={5} value={bulk}
-                  onChange={e => setBulk(Array.from(e.target.selectedOptions, o => o.value))}>
-            {recipes.map(r => <option key={r.id} value={r.id}>{r.name}</option>)}
-          </select>
-          <span className="muted">Plus one item on its own:</span>
-          <div className="row">
-            <select style={{ flex: 1 }} value={item.ingredientName}
-                    onChange={e => setItem({ ...item, ingredientName: e.target.value })}>
-              <option value="">— ingredient —</option>
-              {catalog.map(c => <option key={c.name} value={c.name}>{c.name}</option>)}
-            </select>
-            <input type="number" placeholder="grams" style={{ width: 110 }}
-                   value={item.buyQuantity}
-                   onChange={e => setItem({ ...item, buyQuantity: e.target.value })} />
-          </div>
-          <button className="primary" onClick={create}>Create list</button>
-        </div>
-      </div>
-
-      {open && (
-        <div className="card">
-          <h3>Shopping list #{open.id}</h3>
-          <div className="muted">
-            {open.notes} · total <span className="kcal">{open.kcal} kcal</span>
-            {open.bulkRecipes.length > 0 && <> · from: {open.bulkRecipes.join(', ')}</>}
-          </div>
-          <table>
-            <thead><tr><th>category</th><th>ingredient</th><th>grams</th></tr></thead>
-            <tbody>
-              {open.shoppingLines.map(l => (
-                <tr key={l.ingredient}>
-                  <td className="muted">{l.category}</td>
-                  <td>{l.ingredient}</td>
-                  <td>{l.totalGrams}</td>
-                </tr>
-              ))}
-            </tbody>
-          </table>
-        </div>
-      )}
-
-      <div className="card">
-        <h3>My lists</h3>
-        <table>
-          <tbody>
-            {lists.map(l => (
-              <tr key={l.id}>
-                <td>{new Date(l.dateTime).toLocaleDateString()}</td>
-                <td>{l.notes}</td>
-                <td className="kcal">{l.kcal} kcal</td>
-                <td>{l.bought ? 'bought' : 'open'}</td>
-                <td style={{ width: 1 }}>
-                  <button className="small" onClick={() => api.get(`/grocery-lists/${l.id}`).then(setOpen)}>
-                    open
-                  </button>
-                </td>
-              </tr>
-            ))}
-          </tbody>
-        </table>
-      </div>
-    </div>
-  )
-}
Index: ontend/src/pages/Login.jsx
===================================================================
--- frontend/src/pages/Login.jsx	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,62 +1,0 @@
-import { useState } from 'react'
-import { api, auth } from '../lib/api.js'
-
-export default function Login({ onSignedIn }) {
-  const [mode, setMode] = useState('login')
-  const [form, setForm] = useState({ email: '', username: '', password: '' })
-  const [error, setError] = useState(null)
-  const [busy, setBusy] = useState(false)
-
-  const set = (k) => (e) => setForm({ ...form, [k]: e.target.value })
-
-  const submit = async () => {
-    setBusy(true)
-    setError(null)
-    try {
-      const body = mode === 'login'
-        ? { email: form.email, password: form.password }
-        : form
-      const res = await api.post(`/auth/${mode}`, body)
-      auth.save(res)
-      onSignedIn(auth.user())
-    } catch (e) {
-      setError(e.message)
-    } finally {
-      setBusy(false)
-    }
-  }
-
-  return (
-    <div className="app">
-      <header className="top"><h1>Nutritioneer</h1><span className="muted">prototype</span></header>
-
-      <div className="card" style={{ maxWidth: 440 }}>
-        <h3>{mode === 'login' ? 'Sign in' : 'Create an account'}</h3>
-        {error && <div className="error">{error}</div>}
-
-        <div className="stack">
-          <input placeholder="email" value={form.email} onChange={set('email')} />
-          {mode === 'register' &&
-            <input placeholder="username" value={form.username} onChange={set('username')} />}
-          <input placeholder="password" type="password"
-                 value={form.password} onChange={set('password')} />
-
-          <div className="row">
-            <button className="primary" onClick={submit} disabled={busy}>
-              {busy ? 'working...' : mode === 'login' ? 'Sign in' : 'Register'}
-            </button>
-            <button className="small"
-                    onClick={() => { setMode(mode === 'login' ? 'register' : 'login'); setError(null) }}>
-              {mode === 'login' ? 'need an account?' : 'have an account?'}
-            </button>
-          </div>
-        </div>
-
-        <p className="muted" style={{ marginTop: 14 }}>
-          Seed accounts use the password <code>testPass@1</code>, for example{' '}
-          <code>marija.trajkovska@gmail.com</code> or <code>admin@nutritioneer.mk</code>.
-        </p>
-      </div>
-    </div>
-  )
-}
Index: ontend/src/pages/Planner.jsx
===================================================================
--- frontend/src/pages/Planner.jsx	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,109 +1,0 @@
-import { useEffect, useState } from 'react'
-import { api } from '../lib/api.js'
-
-export default function Planner() {
-  const [entries, setEntries] = useState([])
-  const [feed, setFeed] = useState([])
-  const [daily, setDaily] = useState([])
-  const [macros, setMacros] = useState([])
-  const [error, setError] = useState(null)
-  const [pick, setPick] = useState([])
-  const [notes, setNotes] = useState('')
-
-  const today = new Date().toISOString().slice(0, 10)
-  const weekAgo = new Date(Date.now() - 7 * 864e5).toISOString().slice(0, 10)
-
-  const load = () => {
-    api.get('/planner').then(setEntries).catch(e => setError(e.message))
-    api.get(`/planner/daily?from=${weekAgo}&to=${today}`).then(setDaily).catch(() => {})
-    api.get(`/planner/macros?from=${weekAgo}&to=${today}`).then(setMacros).catch(() => {})
-  }
-
-  useEffect(() => {
-    load()
-    api.get('/posts/feed').then(setFeed).catch(() => {})
-  }, [])
-
-  const log = async () => {
-    setError(null)
-    try {
-      await api.post('/planner', {
-        notes,
-        postIds: pick.map(Number),
-        consumed: true
-      })
-      setNotes('')
-      setPick([])
-      load()
-    } catch (e) { setError(e.message) }
-  }
-
-  return (
-    <div>
-      {error && <div className="error">{error}</div>}
-
-      <div className="card">
-        <h3>Log a meal</h3>
-        <div className="stack" style={{ maxWidth: 520 }}>
-          <input placeholder="notes, e.g. Lunch at home"
-                 value={notes} onChange={e => setNotes(e.target.value)} />
-          <select multiple size={5} value={pick}
-                  onChange={e => setPick(Array.from(e.target.selectedOptions, o => o.value))}>
-            {feed.map(p => (
-              <option key={p.id} value={p.id}>
-                {p.recipeName} — {p.kcalPerServing} kcal / portion
-              </option>
-            ))}
-          </select>
-          <span className="muted">One portion is counted per selected meal.</span>
-          <button className="primary" onClick={log}>Log it</button>
-        </div>
-      </div>
-
-      <div className="card">
-        <h3>Last 7 days</h3>
-        <table>
-          <thead><tr><th>day</th><th>kcal</th><th>meals</th></tr></thead>
-          <tbody>
-            {daily.map(d => (
-              <tr key={d.day}>
-                <td>{d.day}</td><td className="kcal">{d.kcalConsumed}</td><td>{d.mealsLogged}</td>
-              </tr>
-            ))}
-          </tbody>
-        </table>
-        {daily.length === 0 && <p className="muted">Nothing logged in the last week.</p>}
-      </div>
-
-      {macros.length > 0 && (
-        <div className="card">
-          <h3>Macronutrients supplied this week</h3>
-          <table>
-            <tbody>
-              {macros.map(m => (
-                <tr key={m.nutrient}><td>{m.nutrient}</td><td>{m.total} {m.unit}</td></tr>
-              ))}
-            </tbody>
-          </table>
-        </div>
-      )}
-
-      <div className="card">
-        <h3>Diary</h3>
-        <table>
-          <tbody>
-            {entries.map(e => (
-              <tr key={e.id}>
-                <td>{new Date(e.dateTime).toLocaleString()}</td>
-                <td>{e.notes}</td>
-                <td className="muted">{e.meals.join(', ')}</td>
-                <td className="kcal">{e.kcal}</td>
-                <td>{e.consumed ? 'eaten' : 'planned'}</td>
-              </tr>
-            ))}
-          </tbody>
-        </table>
-      </div>
-    </div>
-  )
-}
Index: ontend/src/pages/Recipes.jsx
===================================================================
--- frontend/src/pages/Recipes.jsx	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,146 +1,0 @@
-import { useEffect, useState } from 'react'
-import { api } from '../lib/api.js'
-
-export default function Recipes() {
-  const [mine, setMine] = useState([])
-  const [catalog, setCatalog] = useState([])
-  const [restrictions, setRestrictions] = useState([])
-  const [detail, setDetail] = useState(null)
-  const [error, setError] = useState(null)
-  const [form, setForm] = useState({ name: '', guide: '', servings: 4, restrictionIds: [] })
-  const [lines, setLines] = useState([{ ingredientName: '', quantity: '' }])
-
-  const load = () => api.get('/recipes/mine').then(setMine).catch(e => setError(e.message))
-
-  useEffect(() => {
-    load()
-    api.get('/catalog/ingredients').then(setCatalog).catch(() => {})
-    api.get('/catalog/restrictions').then(setRestrictions).catch(() => {})
-  }, [])
-
-  const setLine = (i, key) => (e) => {
-    const copy = [...lines]
-    copy[i] = { ...copy[i], [key]: e.target.value }
-    setLines(copy)
-  }
-
-  const create = async () => {
-    setError(null)
-    try {
-      const body = {
-        name: form.name,
-        guide: form.guide,
-        servings: Number(form.servings),
-        ingredients: lines
-          .filter(l => l.ingredientName && l.quantity)
-          .map(l => ({ ingredientName: l.ingredientName, quantity: Number(l.quantity) })),
-        restrictionIds: form.restrictionIds.map(Number)
-      }
-      const created = await api.post('/recipes', body)
-      setDetail(created)
-      setForm({ name: '', guide: '', servings: 4, restrictionIds: [] })
-      setLines([{ ingredientName: '', quantity: '' }])
-      load()
-    } catch (e) { setError(e.message) }
-  }
-
-  const share = async (id) => {
-    setError(null)
-    try {
-      await api.post('/posts', { recipeId: id, isPrivate: false, isFavourite: false, status: 'published' })
-      alert('Shared to the feed')
-    } catch (e) { setError(e.message) }
-  }
-
-  return (
-    <div>
-      {error && <div className="error">{error}</div>}
-
-      <div className="card">
-        <h3>New recipe</h3>
-        <p className="muted">
-          Energy is not entered: the server sums it from the ingredients and divides by servings.
-        </p>
-        <div className="stack" style={{ maxWidth: 560 }}>
-          <input placeholder="name" value={form.name}
-                 onChange={e => setForm({ ...form, name: e.target.value })} />
-          <textarea placeholder="preparation steps" rows={3} value={form.guide}
-                    onChange={e => setForm({ ...form, guide: e.target.value })} />
-          <input type="number" min="1" placeholder="servings" value={form.servings}
-                 onChange={e => setForm({ ...form, servings: e.target.value })} />
-
-          {lines.map((l, i) => (
-            <div className="row" key={i}>
-              <select value={l.ingredientName} onChange={setLine(i, 'ingredientName')} style={{ flex: 1 }}>
-                <option value="">— ingredient —</option>
-                {catalog.map(c => <option key={c.name} value={c.name}>{c.name} ({c.kcal} kcal)</option>)}
-              </select>
-              <input type="number" placeholder="grams" style={{ width: 110 }}
-                     value={l.quantity} onChange={setLine(i, 'quantity')} />
-            </div>
-          ))}
-          <button className="small" onClick={() => setLines([...lines, { ingredientName: '', quantity: '' }])}>
-            + another ingredient
-          </button>
-
-          <select multiple size={4} value={form.restrictionIds}
-                  onChange={e => setForm({
-                    ...form,
-                    restrictionIds: Array.from(e.target.selectedOptions, o => o.value)
-                  })}>
-            {restrictions.map(r => <option key={r.id} value={r.id}>{r.type} — {r.description}</option>)}
-          </select>
-
-          <button className="primary" onClick={create}>Create recipe</button>
-        </div>
-      </div>
-
-      {detail && (
-        <div className="card">
-          <h3>{detail.name}</h3>
-          <div className="muted">
-            total <span className="kcal">{detail.kcalSum} kcal</span> ·
-            {' '}{String(detail.servings)} servings ·
-            {' '}<span className="kcal">{detail.kcalPerServing} kcal / portion</span>
-          </div>
-          <table>
-            <thead><tr><th>ingredient</th><th>grams</th><th>kcal/100g</th><th>contributes</th></tr></thead>
-            <tbody>
-              {detail.ingredients.map(i => (
-                <tr key={i.ingredientName}>
-                  <td>{i.ingredientName}</td><td>{i.quantity}</td>
-                  <td>{i.kcalPer100g}</td><td>{i.kcalContributed}</td>
-                </tr>
-              ))}
-            </tbody>
-          </table>
-          {detail.restrictions.length > 0 &&
-            <p className="muted">tags: {detail.restrictions.join(', ')}</p>}
-        </div>
-      )}
-
-      <div className="card">
-        <h3>My recipes</h3>
-        <table>
-          <tbody>
-            {mine.map(r => (
-              <tr key={r.id}>
-                <td>{r.name}</td>
-                <td className="kcal">{r.kcalPerServing} kcal / portion</td>
-                <td style={{ width: 1 }}>
-                  <button className="small" onClick={() => api.get(`/recipes/${r.id}`).then(setDetail)}>
-                    view
-                  </button>
-                </td>
-                <td style={{ width: 1 }}>
-                  <button className="small" onClick={() => share(r.id)}>share</button>
-                </td>
-              </tr>
-            ))}
-          </tbody>
-        </table>
-        {mine.length === 0 && <p className="muted">No recipes yet.</p>}
-      </div>
-    </div>
-  )
-}
Index: ontend/src/pages/Trainer.jsx
===================================================================
--- frontend/src/pages/Trainer.jsx	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,111 +1,0 @@
-import { useEffect, useState } from 'react'
-import { api } from '../lib/api.js'
-
-export default function Trainer() {
-  const [email, setEmail] = useState('marija.trajkovska@gmail.com')
-  const [from, setFrom] = useState('2026-01-20')
-  const [to, setTo] = useState('2026-02-01')
-  const [intake, setIntake] = useState([])
-  const [macros, setMacros] = useState([])
-  const [progress, setProgress] = useState([])
-  const [density, setDensity] = useState([])
-  const [nutrient, setNutrient] = useState('Protein')
-  const [error, setError] = useState(null)
-
-  const loadClient = async () => {
-    setError(null)
-    try {
-      setIntake(await api.get(`/trainer/clients/${email}/intake?from=${from}&to=${to}`))
-      setMacros(await api.get(`/trainer/clients/${email}/macros?from=${from}&to=${to}`))
-      setProgress(await api.get(`/trainer/clients/${email}/progress`))
-    } catch (e) { setError(e.message) }
-  }
-
-  const loadDensity = () =>
-    api.get(`/trainer/analysis/nutrient-density?nutrient=${nutrient}&limit=8`)
-      .then(setDensity).catch(e => setError(e.message))
-
-  useEffect(() => { loadDensity() }, [])
-
-  return (
-    <div>
-      {error && <div className="error">{error}</div>}
-
-      <div className="card">
-        <h3>Client report</h3>
-        <div className="row">
-          <input style={{ flex: 1, minWidth: 240 }} value={email}
-                 onChange={e => setEmail(e.target.value)} placeholder="client email" />
-          <input type="date" value={from} onChange={e => setFrom(e.target.value)} />
-          <input type="date" value={to} onChange={e => setTo(e.target.value)} />
-          <button className="primary" onClick={loadClient}>Load</button>
-        </div>
-      </div>
-
-      {intake.length > 0 && (
-        <div className="card">
-          <h3>Daily intake</h3>
-          <table>
-            <thead><tr><th>day</th><th>kcal</th><th>meals</th></tr></thead>
-            <tbody>
-              {intake.map(d => (
-                <tr key={d.day}><td>{d.day}</td>
-                  <td className="kcal">{d.kcalConsumed}</td><td>{d.mealsLogged}</td></tr>
-              ))}
-            </tbody>
-          </table>
-        </div>
-      )}
-
-      {macros.length > 0 && (
-        <div className="card">
-          <h3>Macronutrients over the period</h3>
-          <table>
-            <tbody>
-              {macros.map(m => (
-                <tr key={m.nutrient}><td>{m.nutrient}</td><td>{m.total} {m.unit}</td></tr>
-              ))}
-            </tbody>
-          </table>
-        </div>
-      )}
-
-      {progress.length > 0 && (
-        <div className="card">
-          <h3>Biometric progress</h3>
-          <table>
-            <thead><tr><th>date</th><th>weight</th><th>change</th><th>BMI</th></tr></thead>
-            <tbody>
-              {progress.map(p => (
-                <tr key={p.id}><td>{p.date}</td><td>{p.weight}</td>
-                  <td>{p.changeSincePrevious ?? '—'}</td><td>{p.bmi ?? '—'}</td></tr>
-              ))}
-            </tbody>
-          </table>
-        </div>
-      )}
-
-      <div className="card">
-        <h3>Recipe analysis</h3>
-        <div className="row" style={{ marginBottom: 8 }}>
-          <input value={nutrient} onChange={e => setNutrient(e.target.value)}
-                 placeholder="nutrient, e.g. Protein" />
-          <button className="primary" onClick={loadDensity}>Rank</button>
-        </div>
-        <table>
-          <thead>
-            <tr><th>recipe</th><th>kcal/portion</th><th>per portion</th><th>per 100 kcal</th></tr>
-          </thead>
-          <tbody>
-            {density.map(d => (
-              <tr key={d.recipe}>
-                <td>{d.recipe}</td><td>{d.kcalPerServing}</td>
-                <td>{d.amountPerServing}</td><td className="kcal">{d.amountPer100Kcal}</td>
-              </tr>
-            ))}
-          </tbody>
-        </table>
-      </div>
-    </div>
-  )
-}
Index: ontend/src/styles.css
===================================================================
--- frontend/src/styles.css	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,67 +1,0 @@
-:root {
-  --bg: #16181a;
-  --card: #1f2224;
-  --line: #33383b;
-  --text: #e6e6e6;
-  --muted: #9aa0a6;
-  --accent: #8cc152;
-  --warn: #e0a33e;
-  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
-}
-
-* { box-sizing: border-box; }
-
-body {
-  margin: 0;
-  background: var(--bg);
-  color: var(--text);
-}
-
-.app { max-width: 1000px; margin: 0 auto; padding: 24px 16px 64px; }
-
-header.top {
-  display: flex; align-items: baseline; gap: 12px;
-  border-bottom: 1px solid var(--line); padding-bottom: 12px; margin-bottom: 16px;
-}
-header.top h1 { margin: 0; font-size: 20px; color: var(--accent); }
-header.top .who { margin-left: auto; color: var(--muted); font-size: 14px; }
-
-nav { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 20px; }
-nav button {
-  background: var(--card); color: var(--text); border: 1px solid var(--line);
-  padding: 7px 13px; border-radius: 6px; cursor: pointer; font-size: 14px;
-}
-nav button.active { border-color: var(--accent); color: var(--accent); }
-
-.card {
-  background: var(--card); border: 1px solid var(--line);
-  border-radius: 8px; padding: 14px; margin-bottom: 12px;
-}
-.card h3 { margin: 0 0 8px; font-size: 15px; }
-
-.row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
-.muted { color: var(--muted); font-size: 13px; }
-.kcal { color: var(--warn); font-weight: 600; }
-
-input, select, textarea {
-  background: #14171a; color: var(--text); border: 1px solid var(--line);
-  border-radius: 6px; padding: 7px 9px; font-size: 14px; font-family: inherit;
-}
-button.primary {
-  background: var(--accent); color: #11210a; border: none;
-  padding: 8px 14px; border-radius: 6px; cursor: pointer; font-weight: 600;
-}
-button.small {
-  background: transparent; color: var(--muted); border: 1px solid var(--line);
-  padding: 4px 8px; border-radius: 5px; cursor: pointer; font-size: 12px;
-}
-
-table { width: 100%; border-collapse: collapse; font-size: 14px; }
-th, td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--line); }
-th { color: var(--muted); font-weight: 500; }
-
-.error {
-  background: #3b1f1f; border: 1px solid #7a3b3b; color: #f0b5b5;
-  padding: 9px 12px; border-radius: 6px; margin-bottom: 12px; font-size: 14px;
-}
-.stack { display: flex; flex-direction: column; gap: 8px; max-width: 420px; }
Index: ontend/vite.config.js
===================================================================
--- frontend/vite.config.js	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,14 +1,0 @@
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
-
-export default defineConfig({
-  plugins: [react()],
-  server: {
-    port: 5173,
-    // Calls to /api are forwarded to Spring Boot, so the browser sees a single
-    // origin during development and CORS never gets in the way.
-    proxy: {
-      '/api': { target: 'http://localhost:8080', changeOrigin: true }
-    }
-  }
-})
Index: l/00_drop_all.sql
===================================================================
--- sql/00_drop_all.sql	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,27 +1,0 @@
--- =====================================================================
---  NUTRITIONEER | 00 - RESET QUERY
--- =====================================================================
-
-DROP TABLE IF EXISTS intake_planner_save_to_list_post   CASCADE;
-DROP TABLE IF EXISTS grocery_list_single_add_ingredient CASCADE;
-DROP TABLE IF EXISTS grocery_list_bulk_add_ingredient   CASCADE;
-DROP TABLE IF EXISTS recipe_contains_restriction        CASCADE;
-DROP TABLE IF EXISTS ingredient_contains_nutrient       CASCADE;
-DROP TABLE IF EXISTS recipe_contains_ingredient         CASCADE;
-DROP TABLE IF EXISTS biometrics                         CASCADE;
-DROP TABLE IF EXISTS intake_planner                     CASCADE;
-DROP TABLE IF EXISTS grocery_list                       CASCADE;
-DROP TABLE IF EXISTS comment                            CASCADE;
-DROP TABLE IF EXISTS post                               CASCADE;
-DROP TABLE IF EXISTS recipe                             CASCADE;
-DROP TABLE IF EXISTS restriction                        CASCADE;
-DROP TABLE IF EXISTS nutrient                           CASCADE;
-DROP TABLE IF EXISTS ingredient                         CASCADE;
-DROP TABLE IF EXISTS "user"                             CASCADE;
-
-DROP TYPE  IF EXISTS post_status      CASCADE;
-DROP TYPE  IF EXISTS restriction_type CASCADE;
-DROP TYPE  IF EXISTS nutrient_type    CASCADE;
-DROP TYPE  IF EXISTS nutrient_unit    CASCADE;
-DROP TYPE  IF EXISTS ingredient_type  CASCADE;
-DROP TYPE  IF EXISTS user_role        CASCADE;
Index: l/ddl_nutritioneer_1_0.sql
===================================================================
--- sql/ddl_nutritioneer_1_0.sql	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,217 +1,0 @@
--- ---------------------------------------------------------------------
--- Користени queries
--- ---------------------------------------------------------------------
-CREATE TYPE user_role AS ENUM (
-    'user',
-    'administrator',
-    'trainer'
-);
- 
-CREATE TYPE ingredient_type AS ENUM (
-    'dairy',
-    'meat',
-    'fish',
-    'herb',
-    'carb',
-    'vegetable',
-    'fruit',
-    'legume',
-    'nut',
-    'seed',
-    'oil',
-    'spice',
-    'beverage',
-    'other'
-);
- 
-CREATE TYPE nutrient_unit AS ENUM (
-    'milligram',
-    'gram',
-    'microgram'
-);
- 
-CREATE TYPE nutrient_type AS ENUM (
-    'macro',
-    'micro'
-);
- 
-CREATE TYPE restriction_type AS ENUM (
-    'vegan',
-    'vegetarian',
-    'halal',
-    'kosher',
-    'pescatarian',
-    'allergen',
-    'keto',
-    'paleo',
-    'gluten_free',
-    'lactose_free',
-    'diabetic',
-    'low_sodium',
-    'other'
-);
- 
-CREATE TYPE post_status AS ENUM (
-    'published',
-    'draft',
-    'archived'
-);
- 
-
-CREATE TABLE "user" (
-    email       TEXT        PRIMARY KEY,
-    username    TEXT        NOT NULL,
-    password    TEXT        NOT NULL,        
-    role        user_role   NOT NULL DEFAULT 'user',
- 
-    CONSTRAINT uq_user_username     UNIQUE (username),
-    CONSTRAINT ck_user_email_format CHECK  (email LIKE '%_@_%.__%'),
-    CONSTRAINT ck_user_username_len CHECK  (length(username) BETWEEN 3 AND 40),
-    CONSTRAINT ck_user_password_len CHECK  (length(password) >= 8)
-);
- 
-CREATE TABLE ingredient (
-    name        TEXT                NOT NULL PRIMARY KEY,
-    description TEXT                NOT NULL,
-    energy      NUMERIC             NOT NULL,   
-    kcal        NUMERIC             NOT NULL,   
-    type        ingredient_type     NOT NULL DEFAULT 'other',
- 
-    CONSTRAINT ck_ingredient_energy CHECK (energy >= 0),
-    CONSTRAINT ck_ingredient_kcal   CHECK (kcal   >= 0)
-);
- 
-CREATE TABLE nutrient (
-    id          BIGINT          PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    quantity    NUMERIC         NOT NULL,   
-    description TEXT            NOT NULL,   
-    unit        nutrient_unit   NOT NULL,
-    type        nutrient_type   NOT NULL,
- 
-    CONSTRAINT uq_nutrient_description UNIQUE (description),
-    CONSTRAINT ck_nutrient_quantity    CHECK  (quantity > 0)
-);
- 
-CREATE TABLE restriction (
-    id          BIGINT              PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    description TEXT                NOT NULL,
-    type        restriction_type    NOT NULL,
- 
-    CONSTRAINT uq_restriction_description UNIQUE (description)
-);
- 
-
-CREATE TABLE recipe (
-    id          BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    name        TEXT        NOT NULL,
-    guide       TEXT        NOT NULL,
-    kcal_sum    NUMERIC     NOT NULL,
-    servings    NUMERIC     NOT NULL DEFAULT 1,
-    user_email  TEXT        NOT NULL REFERENCES "user"(email) ON DELETE CASCADE,
- 
-    CONSTRAINT ck_recipe_kcal_sum   CHECK  (kcal_sum >= 0),
-    CONSTRAINT ck_recipe_servings   CHECK  (servings > 0)
-);
- 
-
-CREATE TABLE post (
-    id           BIGINT          PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    media        BYTEA,
-    media_type   TEXT,                 
-    media_name   TEXT,
-    is_private   BOOLEAN         NOT NULL DEFAULT FALSE,
-    is_favourite BOOLEAN         NOT NULL DEFAULT FALSE,
-    status       post_status     NOT NULL DEFAULT 'draft',
-    created_at   TIMESTAMP       NOT NULL DEFAULT NOW(),
-    created_by   TEXT            NOT NULL REFERENCES "user"(email) ON DELETE CASCADE,
-    recipe_id    BIGINT          REFERENCES recipe(id) ON DELETE SET NULL,
- 
-    CONSTRAINT ck_post_media_type CHECK (media IS NULL OR media_type IS NOT NULL),
-    CONSTRAINT uq_post_recipe     UNIQUE (recipe_id)
-);
- 
-CREATE TABLE comment (
-    id          BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    description TEXT        NOT NULL,
-    created_at  TIMESTAMP   NOT NULL DEFAULT NOW(),
-    post_id     BIGINT      NOT NULL REFERENCES post(id)     ON DELETE CASCADE,
-    created_by  TEXT        NOT NULL REFERENCES "user"(email) ON DELETE CASCADE,
- 
-    CONSTRAINT ck_comment_not_empty CHECK (length(trim(description)) > 0)
-);
- 
-CREATE TABLE grocery_list (
-    id          BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    date_time   TIMESTAMP   NOT NULL DEFAULT NOW(),
-    notes       TEXT,
-    is_bought   BOOLEAN     NOT NULL DEFAULT FALSE,
-    kcal        NUMERIC     NOT NULL,  
-    user_email  TEXT        NOT NULL REFERENCES "user"(email) ON DELETE CASCADE,
- 
-    CONSTRAINT ck_grocery_list_kcal CHECK (kcal >= 0)
-);
- 
-CREATE TABLE intake_planner (
-    id          BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    date_time   TIMESTAMP   NOT NULL DEFAULT NOW(),
-    notes       TEXT,
-    kcal        NUMERIC     NOT NULL,   
-    is_consumed BOOLEAN     NOT NULL DEFAULT FALSE,
-    user_email  TEXT        NOT NULL REFERENCES "user"(email) ON DELETE CASCADE,
- 
-    CONSTRAINT ck_intake_planner_kcal CHECK (kcal >= 0)
-);
- 
-CREATE TABLE biometrics (
-    id               BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    date             DATE        NOT NULL,
-    weight           NUMERIC,            
-    height           NUMERIC,            
-    age              NUMERIC,            
-    muscle_fat_ratio NUMERIC,            
-    user_email       TEXT        NOT NULL REFERENCES "user"(email) ON DELETE CASCADE,
- 
-    CONSTRAINT uq_biometrics_user_date UNIQUE (user_email, date)
-);
- 
- 
-CREATE TABLE recipe_contains_ingredient (
-    recipe_id           BIGINT      NOT NULL REFERENCES recipe(id)          ON DELETE CASCADE,
-    ingredient_name     TEXT        NOT NULL REFERENCES ingredient(name)    ON DELETE CASCADE,
-    ingredient_quantity NUMERIC     NOT NULL,   
-    PRIMARY KEY (recipe_id, ingredient_name)
-);
- 
-CREATE TABLE ingredient_contains_nutrient (
-    ingredient_name TEXT        NOT NULL REFERENCES ingredient(name) ON DELETE CASCADE,
-    nutrient_id     BIGINT      NOT NULL REFERENCES nutrient(id)     ON DELETE CASCADE,
-    quantity        NUMERIC     NOT NULL,   
-    PRIMARY KEY (ingredient_name, nutrient_id)
-);
- 
-CREATE TABLE recipe_contains_restriction (
-    recipe_id       BIGINT      NOT NULL REFERENCES recipe(id)      ON DELETE CASCADE,
-    restriction_id  BIGINT      NOT NULL REFERENCES restriction(id) ON DELETE CASCADE,
-    PRIMARY KEY (recipe_id, restriction_id)
-);
- 
-CREATE TABLE grocery_list_bulk_add_ingredient (
-    list_id     BIGINT      NOT NULL REFERENCES grocery_list(id) ON DELETE CASCADE,
-    recipe_id   BIGINT      NOT NULL REFERENCES recipe(id)       ON DELETE CASCADE,
-    PRIMARY KEY (list_id, recipe_id)
-);
- 
-CREATE TABLE grocery_list_single_add_ingredient (
-    list_id             BIGINT      NOT NULL REFERENCES grocery_list(id)    ON DELETE CASCADE,
-    ingredient_name     TEXT        NOT NULL REFERENCES ingredient(name)    ON DELETE CASCADE,
-    buy_quantity        NUMERIC     NOT NULL,   
-    PRIMARY KEY (list_id, ingredient_name)
-);
- 
-CREATE TABLE intake_planner_save_to_list_post (
-    planner_id  BIGINT      NOT NULL REFERENCES intake_planner(id) ON DELETE CASCADE,
-    post_id     BIGINT      NOT NULL REFERENCES post(id)           ON DELETE CASCADE,
-    PRIMARY KEY (planner_id, post_id)
-);
- 
- 
Index: l/dml_nutritioneer_calculations_1_0.sql
===================================================================
--- sql/dml_nutritioneer_calculations_1_0.sql	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,39 +1,0 @@
--- ---------------------------------------------------------------------
--- Користени queries
--- ---------------------------------------------------------------------
-
--- recipe.kcal_sum = SUM(kcal per 100g * gramsInRecipe / 100)
-UPDATE recipe r
-SET kcal_sum = ROUND(COALESCE((
-        SELECT SUM(i.kcal * rci.ingredient_quantity / 100.0)
-        FROM recipe_contains_ingredient rci
-        JOIN ingredient i ON i.name = rci.ingredient_name
-        WHERE rci.recipe_id = r.id
-    ), 0), 2);
-
--- grocery_list.kcal = kcal bulk-added + kcal single-added
-UPDATE grocery_list g
-SET kcal = ROUND(
-      COALESCE((SELECT SUM(r.kcal_sum)
-                FROM grocery_list_bulk_add_ingredient b
-                JOIN recipe r ON r.id = b.recipe_id
-                WHERE b.list_id = g.id), 0)
-    + COALESCE((SELECT SUM(i.kcal * s.buy_quantity / 100.0)
-                FROM grocery_list_single_add_ingredient s
-                JOIN ingredient i ON i.name = s.ingredient_name
-                WHERE s.list_id = g.id), 0)
-    , 2);
-
--- intake_planner.kcal = Portion based
-UPDATE intake_planner ip
-SET kcal = ROUND((
-        SELECT SUM(r.kcal_sum / r.servings)   -- one portion per saved post
-        FROM intake_planner_save_to_list_post sp
-        JOIN post   p ON p.id = sp.post_id
-        JOIN recipe r ON r.id = p.recipe_id
-        WHERE sp.planner_id = ip.id
-    ), 2)
-WHERE EXISTS (SELECT 1
-              FROM intake_planner_save_to_list_post sp
-              JOIN post p ON p.id = sp.post_id
-              WHERE sp.planner_id = ip.id AND p.recipe_id IS NOT NULL);
Index: l/dml_nutritioneer_populating_1_0.sql
===================================================================
--- sql/dml_nutritioneer_populating_1_0.sql	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,563 +1,0 @@
--- ---------------------------------------------------------------------
--- Користени queries
--- ---------------------------------------------------------------------
-
-INSERT INTO "user" (email, username, password, role) VALUES
-('admin@nutritioneer.mk',           'admin_nutri',    'testPass@1', 'administrator'),
-('viktor.ilievski@nutritioneer.mk', 'coach_viktor',   'testPass@1', 'trainer'),
-('teodora.mihajlova@nutritioneer.mk','coach_teodora', 'testPass@1', 'trainer'),
-('marija.trajkovska@gmail.com',     'marija_t',       'testPass@1', 'user'),
-('stefan.petrov@gmail.com',         'stefan_p',       'testPass@1', 'user'),
-('ana.jovanovska@yahoo.com',        'ana_j',          'testPass@1', 'user'),
-('nikola.stojanov@gmail.com',       'nikola_s',       'testPass@1', 'user'),
-('elena.dimitrova@hotmail.com',     'elena_d',        'testPass@1', 'user'),
-('bojan.kostov@gmail.com',          'bojan_k',        'testPass@1', 'user'),
-('filip.angelov@gmail.com',         'filip_a',        'testPass@1', 'user');
-
-INSERT INTO ingredient (name, description, energy, kcal, type) VALUES
-('Chicken breast',      'Skinless boneless chicken breast, raw',            690,  165, 'meat'),
-('Beef mince',          'Ground beef, 20 percent fat, raw',                1046,  250, 'meat'),
-('Pork shoulder',       'Pork shoulder cut, raw',                          1013,  242, 'meat'),
-('Trout fillet',        'Freshwater trout fillet, raw',                     619,  148, 'fish'),
-('Mackerel',            'Atlantic mackerel, raw',                           858,  205, 'fish'),
-('Egg',                 'Whole chicken egg, raw',                           598,  143, 'other'),
-('Cow milk',            'Pasteurised cow milk, 3.2 percent fat',            255,   61, 'dairy'),
-('Yogurt',              'Plain yogurt, 3.5 percent fat',                    264,   63, 'dairy'),
-('White brine cheese',  'Traditional white brine cheese (sirenje)',        1105,  264, 'dairy'),
-('Kashkaval',           'Semi-hard yellow cheese (kashkaval)',             1490,  356, 'dairy'),
-('Butter',              'Unsalted cow butter, 82 percent fat',             3000,  717, 'dairy'),
-('Sunflower oil',       'Refined sunflower oil',                           3699,  884, 'oil'),
-('Olive oil',           'Extra virgin olive oil',                          3699,  884, 'oil'),
-('White rice',          'Long grain white rice, uncooked',                 1527,  365, 'carb'),
-('Bulgur',              'Coarse bulgur wheat, uncooked',                   1431,  342, 'carb'),
-('White flour',         'Wheat flour type 500, all purpose',               1523,  364, 'carb'),
-('Spaghetti',           'Durum wheat spaghetti, dry',                      1552,  371, 'carb'),
-('White bread',         'White wheat bread, baked',                        1109,  265, 'carb'),
-('Oats',                'Rolled oat flakes',                               1628,  389, 'carb'),
-('Potato',              'White potato, raw, peeled',                        322,   77, 'vegetable'),
-('Dry white beans',     'Dry white beans (tetovec variety)',               1393,  333, 'legume'),
-('Lentils',             'Dry brown lentils',                               1473,  352, 'legume'),
-('Chickpeas',           'Dry chickpeas',                                   1523,  364, 'legume'),
-('Tomato',              'Fresh ripe tomato',                                 75,   18, 'vegetable'),
-('Red bell pepper',     'Fresh red bell pepper (kapija)',                   130,   31, 'vegetable'),
-('Onion',               'Yellow onion, raw',                                167,   40, 'vegetable'),
-('Garlic',              'Fresh garlic cloves',                              623,  149, 'vegetable'),
-('Cucumber',            'Fresh cucumber with skin',                          63,   15, 'vegetable'),
-('Aubergine',           'Fresh aubergine (eggplant)',                       105,   25, 'vegetable'),
-('Zucchini',            'Fresh green zucchini',                              71,   17, 'vegetable'),
-('Carrot',              'Fresh carrot, raw',                                172,   41, 'vegetable'),
-('Spinach',             'Fresh spinach leaves',                              96,   23, 'vegetable'),
-('Cabbage',             'White cabbage, raw',                               105,   25, 'vegetable'),
-('Leek',                'Fresh leek, raw',                                  255,   61, 'vegetable'),
-('Apple',               'Fresh apple with skin',                            218,   52, 'fruit'),
-('Banana',              'Fresh banana, peeled',                             372,   89, 'fruit'),
-('Strawberry',          'Fresh strawberries',                               134,   32, 'fruit'),
-('Grapes',              'Fresh table grapes',                               288,   69, 'fruit'),
-('Lemon',               'Fresh lemon, peeled',                              121,   29, 'fruit'),
-('Walnut',              'Shelled walnut kernels',                          2736,  654, 'nut'),
-('Almond',              'Raw almonds',                                     2423,  579, 'nut'),
-('Sunflower seed',      'Hulled sunflower seeds',                          2444,  584, 'seed'),
-('Chia seed',           'Whole chia seeds',                                2033,  486, 'seed'),
-('Parsley',             'Fresh flat leaf parsley',                          151,   36, 'herb'),
-('Dill',                'Fresh dill weed',                                  180,   43, 'herb'),
-('Salt',                'Refined table salt, iodised',                        0,    0, 'spice'),
-('Black pepper',        'Ground black pepper',                             1050,  251, 'spice'),
-('Paprika powder',      'Sweet red paprika powder',                        1180,  282, 'spice'),
-('Honey',               'Raw flower honey',                                1272,  304, 'other'),
-('Water',               'Drinking water',                                     0,    0, 'beverage');
-
-INSERT INTO nutrient (description, quantity, unit, type) VALUES
-('Protein',             50,   'gram',      'macro'),
-('Total carbohydrate', 260,   'gram',      'macro'),
-('Total fat',           70,   'gram',      'macro'),
-('Saturated fat',       20,   'gram',      'macro'),
-('Dietary fibre',       30,   'gram',      'macro'),
-('Sugars',              90,   'gram',      'macro'),
-('Vitamin C',           80,   'milligram', 'micro'),
-('Vitamin A',          800,   'microgram', 'micro'),
-('Vitamin D',            5,   'microgram', 'micro'),
-('Vitamin E',           12,   'milligram', 'micro'),
-('Vitamin K',           75,   'microgram', 'micro'),
-('Vitamin B12',        2.5,   'microgram', 'micro'),
-('Folate',             200,   'microgram', 'micro'),
-('Calcium',            800,   'milligram', 'micro'),
-('Iron',                14,   'milligram', 'micro'),
-('Magnesium',          375,   'milligram', 'micro'),
-('Potassium',         2000,   'milligram', 'micro'),
-('Zinc',                10,   'milligram', 'micro'),
-('Sodium',            2000,   'milligram', 'micro'),
-('Phosphorus',         700,   'milligram', 'micro'),
-('Selenium',            55,   'microgram', 'micro'),
-('Iodine',             150,   'microgram', 'micro');
-
-INSERT INTO restriction (description, type) VALUES
-('Contains no ingredient of animal origin',                 'vegan'),
-('Contains no meat and no fish',                            'vegetarian'),
-('Prepared according to Islamic dietary law',               'halal'),
-('Prepared according to Jewish dietary law',                'kosher'),
-('Fish and seafood allowed, no other meat',                 'pescatarian'),
-('Contains gluten from wheat, barley or rye',               'allergen'),
-('Contains milk protein and lactose',                       'allergen'),
-('Contains tree nuts',                                      'allergen'),
-('Contains egg',                                            'allergen'),
-('Contains fish',                                           'allergen'),
-('Low carbohydrate and high fat, ketogenic',                'keto'),
-('No grains, legumes or processed food',                    'paleo'),
-('Free of gluten containing cereals',                       'gluten_free'),
-('Free of lactose',                                         'lactose_free'),
-('Suitable for a diabetic meal plan, low glycaemic load',   'diabetic'),
-('Reduced sodium content, under 120 mg per 100 g',          'low_sodium');
-
-INSERT INTO ingredient_contains_nutrient (ingredient_name, nutrient_id, quantity)
-SELECT m.ing, n.id, v.qty
-FROM (VALUES
-    ('Chicken breast',     31.0,   0.0,   3.6,   0.0),
-    ('Beef mince',         26.0,   0.0,  15.0,   0.0),
-    ('Pork shoulder',      20.0,   0.0,  18.0,   0.0),
-    ('Trout fillet',       20.5,   0.0,   6.6,   0.0),
-    ('Mackerel',           19.0,   0.0,  13.9,   0.0),
-    ('Egg',                12.6,   0.7,   9.5,   0.0),
-    ('Cow milk',            3.3,   4.8,   3.2,   0.0),
-    ('Yogurt',              3.5,   4.7,   3.5,   0.0),
-    ('White brine cheese', 14.0,   2.0,  21.0,   0.0),
-    ('Kashkaval',          25.0,   1.5,  28.0,   0.0),
-    ('Butter',              0.9,   0.1,  81.0,   0.0),
-    ('Sunflower oil',       0.0,   0.0, 100.0,   0.0),
-    ('Olive oil',           0.0,   0.0, 100.0,   0.0),
-    ('White rice',          7.0,  80.0,   0.7,   1.3),
-    ('Bulgur',             12.0,  76.0,   1.3,  18.0),
-    ('White flour',        10.0,  76.0,   1.0,   2.7),
-    ('Spaghetti',          13.0,  75.0,   1.5,   3.2),
-    ('White bread',         9.0,  49.0,   3.2,   2.7),
-    ('Oats',               17.0,  66.0,   7.0,  11.0),
-    ('Potato',              2.0,  17.0,   0.1,   2.2),
-    ('Dry white beans',    21.0,  60.0,   1.2,  15.0),
-    ('Lentils',            25.0,  60.0,   1.1,  30.0),
-    ('Chickpeas',          19.0,  61.0,   6.0,  17.0),
-    ('Tomato',              0.9,   3.9,   0.2,   1.2),
-    ('Red bell pepper',     1.0,   6.0,   0.3,   2.1),
-    ('Onion',               1.1,   9.3,   0.1,   1.7),
-    ('Garlic',              6.4,  33.0,   0.5,   2.1),
-    ('Cucumber',            0.7,   3.6,   0.1,   0.5),
-    ('Aubergine',           1.0,   5.9,   0.2,   3.0),
-    ('Zucchini',            1.2,   3.1,   0.3,   1.0),
-    ('Carrot',              0.9,   9.6,   0.2,   2.8),
-    ('Spinach',             2.9,   3.6,   0.4,   2.2),
-    ('Cabbage',             1.3,   5.8,   0.1,   2.5),
-    ('Leek',                1.5,  14.0,   0.3,   1.8),
-    ('Apple',               0.3,  14.0,   0.2,   2.4),
-    ('Banana',              1.1,  23.0,   0.3,   2.6),
-    ('Strawberry',          0.7,   7.7,   0.3,   2.0),
-    ('Grapes',              0.7,  18.0,   0.2,   0.9),
-    ('Lemon',               1.1,   9.3,   0.3,   2.8),
-    ('Walnut',             15.0,  14.0,  65.0,   6.7),
-    ('Almond',             21.0,  22.0,  50.0,  12.5),
-    ('Sunflower seed',     21.0,  20.0,  51.0,   8.6),
-    ('Chia seed',          17.0,  42.0,  31.0,  34.0),
-    ('Parsley',             3.0,   6.3,   0.8,   3.3),
-    ('Dill',                3.5,   7.0,   1.1,   2.1),
-    ('Paprika powder',     14.0,  54.0,  13.0,  35.0),
-    ('Black pepper',       10.0,  64.0,   3.3,  25.0),
-    ('Honey',               0.3,  82.0,   0.0,   0.2)
-) AS m(ing, protein, carbohydrate, fat, fibre)
-CROSS JOIN LATERAL (VALUES
-    ('Protein',            m.protein),
-    ('Total carbohydrate', m.carbohydrate),
-    ('Total fat',          m.fat),
-    ('Dietary fibre',      m.fibre)
-) AS v(nut, qty)
-JOIN nutrient n ON n.description = v.nut;
-
---    5.b Selected micronutrients (per 100 g, in the unit of the nutrient)
-INSERT INTO ingredient_contains_nutrient (ingredient_name, nutrient_id, quantity)
-SELECT v.ing, n.id, v.qty
-FROM (VALUES
-    ('Red bell pepper', 'Vitamin C',    128.0),
-    ('Parsley',         'Vitamin C',    133.0),
-    ('Spinach',         'Vitamin C',     28.0),
-    ('Tomato',          'Vitamin C',     14.0),
-    ('Cabbage',         'Vitamin C',     36.0),
-    ('Strawberry',      'Vitamin C',     59.0),
-    ('Potato',          'Vitamin C',     20.0),
-    ('Lemon',           'Vitamin C',     53.0),
-    ('Carrot',          'Vitamin A',    835.0),
-    ('Spinach',         'Vitamin A',    469.0),
-    ('Red bell pepper', 'Vitamin A',    157.0),
-    ('Butter',          'Vitamin A',    684.0),
-    ('Trout fillet',    'Vitamin D',      6.5),
-    ('Mackerel',        'Vitamin D',     16.0),
-    ('Egg',             'Vitamin D',      2.0),
-    ('Beef mince',      'Vitamin B12',    2.6),
-    ('Trout fillet',    'Vitamin B12',    4.5),
-    ('Mackerel',        'Vitamin B12',    8.7),
-    ('Egg',             'Vitamin B12',    1.1),
-    ('Cow milk',        'Vitamin B12',    0.5),
-    ('Spinach',         'Folate',       194.0),
-    ('Lentils',         'Folate',       479.0),
-    ('Dry white beans', 'Folate',       388.0),
-    ('Parsley',         'Folate',       152.0),
-    ('Cow milk',        'Calcium',      120.0),
-    ('Yogurt',          'Calcium',      121.0),
-    ('White brine cheese','Calcium',    493.0),
-    ('Kashkaval',       'Calcium',      700.0),
-    ('Spinach',         'Calcium',       99.0),
-    ('Almond',          'Calcium',      269.0),
-    ('Chia seed',       'Calcium',      631.0),
-    ('Spinach',         'Iron',           2.7),
-    ('Lentils',         'Iron',           7.5),
-    ('Dry white beans', 'Iron',           5.5),
-    ('Beef mince',      'Iron',           2.6),
-    ('Oats',            'Iron',           4.7),
-    ('Chia seed',       'Iron',           7.7),
-    ('Almond',          'Magnesium',    270.0),
-    ('Oats',            'Magnesium',    177.0),
-    ('Spinach',         'Magnesium',     79.0),
-    ('Walnut',          'Magnesium',    158.0),
-    ('Potato',          'Potassium',    425.0),
-    ('Banana',          'Potassium',    358.0),
-    ('Spinach',         'Potassium',    558.0),
-    ('Tomato',          'Potassium',    237.0),
-    ('Dry white beans', 'Potassium',   1795.0),
-    ('Beef mince',      'Zinc',           4.8),
-    ('Lentils',         'Zinc',           3.3),
-    ('Oats',            'Zinc',           4.0),
-    ('Salt',            'Sodium',     38758.0),
-    ('White brine cheese','Sodium',    1200.0),
-    ('White bread',     'Sodium',       490.0),
-    ('Kashkaval',       'Sodium',       800.0),
-    ('Salt',            'Iodine',      2500.0),
-    ('Trout fillet',    'Selenium',      12.6),
-    ('Mackerel',        'Selenium',      44.1),
-    ('Chicken breast',  'Selenium',      22.8),
-    ('Chicken breast',  'Phosphorus',   210.0),
-    ('Cow milk',        'Phosphorus',    92.0),
-    ('Almond',          'Vitamin E',     25.6),
-    ('Sunflower oil',   'Vitamin E',     41.1),
-    ('Olive oil',       'Vitamin E',     14.4),
-    ('Spinach',         'Vitamin K',    483.0),
-    ('Parsley',         'Vitamin K',   1640.0)
-) AS v(ing, nut, qty)
-JOIN nutrient n ON n.description = v.nut;
-
-INSERT INTO recipe (name, guide, kcal_sum, servings, user_email) VALUES
-('Tavce Gravce',
- E'1. Soak the beans overnight, then boil until soft.\n2. Fry the chopped onion in oil, add paprika off the heat.\n3. Combine everything in a clay pan, season and bake at 200 C for 40 minutes.\n4. Serve hot with fresh bread.',
- 0, 6, 'marija.trajkovska@gmail.com'),
-
-('Shopska Salad',
- E'1. Dice the tomatoes and cucumbers, slice the onion thinly.\n2. Season with salt and olive oil and toss.\n3. Grate the white brine cheese generously over the top and serve immediately.',
- 0, 4, 'stefan.petrov@gmail.com'),
-
-('Turli Tava',
- E'1. Brown the beef mince with the onion.\n2. Cut all vegetables into large chunks and layer them in a deep tray.\n3. Add the meat, oil, spices and a cup of water.\n4. Bake covered at 190 C for 60 minutes, then 15 minutes uncovered.',
- 0, 6, 'ana.jovanovska@yahoo.com'),
-
-('Homemade Ajvar',
- E'1. Roast the peppers and aubergine until the skins blister, then peel them.\n2. Mince the flesh and cook it slowly in oil, stirring constantly for about two hours.\n3. Add garlic and salt near the end and jar while hot.',
- 0, 20, 'elena.dimitrova@hotmail.com'),
-
-('Grilled Trout with Parsley',
- E'1. Clean the fillets and pat them dry.\n2. Rub with olive oil, crushed garlic, salt and pepper.\n3. Grill 4 minutes per side and finish with chopped parsley and lemon.',
- 0, 2, 'nikola.stojanov@gmail.com'),
-
-('Chicken Bulgur Bowl',
- E'1. Boil the bulgur in twice its volume of water for 12 minutes.\n2. Grill the seasoned chicken breast and slice it.\n3. Assemble with diced tomato and cucumber, finish with olive oil.',
- 0, 3, 'viktor.ilievski@nutritioneer.mk'),
-
-('Oat Porridge with Banana and Walnuts',
- E'1. Simmer the oats in milk for 5 minutes, stirring.\n2. Pour into a bowl and top with sliced banana and crushed walnuts.\n3. Drizzle honey over the top.',
- 0, 2, 'marija.trajkovska@gmail.com'),
-
-('Lentil Soup',
- E'1. Saute the diced onion, carrot and garlic in olive oil.\n2. Add the rinsed lentils and 1.5 litres of water.\n3. Simmer for 35 minutes, season and blend half of the soup for a thicker texture.',
- 0, 5, 'bojan.kostov@gmail.com'),
-
-('Zelnik Spinach and Cheese Filling',
- E'1. Wilt the washed spinach and squeeze out all excess water.\n2. Crumble in the white brine cheese and mix with beaten egg and oil.\n3. Season lightly - the cheese is already salty. Use as filling for zelnik.',
- 0, 8, 'elena.dimitrova@hotmail.com'),
-
-('Tarator',
- E'1. Grate the cucumber and drain it briefly.\n2. Whisk the yogurt with crushed garlic, chopped dill, salt and olive oil.\n3. Fold in the cucumber and crushed walnuts, chill for one hour before serving.',
- 0, 4, 'ana.jovanovska@yahoo.com'),
-
-('Baked Mackerel with Vegetables',
- E'1. Slice the potatoes, carrots and onion and spread them on a tray with oil and salt.\n2. Bake at 200 C for 25 minutes.\n3. Place the cleaned mackerel on top and bake for another 20 minutes.',
- 0, 3, 'nikola.stojanov@gmail.com'),
-
-('Protein Pancakes',
- E'1. Blend the oats, eggs, milk and banana into a smooth batter.\n2. Rest the batter for 10 minutes.\n3. Fry small pancakes in a little butter over medium heat, about 2 minutes per side.',
- 0, 3, 'teodora.mihajlova@nutritioneer.mk'),
-
-('Grilled Chicken Salad',
- E'1. Grill the seasoned chicken breast and let it rest, then slice.\n2. Toss the spinach, tomato and cucumber with olive oil and salt.\n3. Arrange the chicken on top and serve at room temperature.',
- 0, 2, 'stefan.petrov@gmail.com'),
-
-('Stuffed Peppers with Rice',
- E'1. Core the peppers carefully without splitting them.\n2. Mix the rice, browned mince, onion, oil and spices.\n3. Fill the peppers, stand them upright in a pot with water and simmer for 50 minutes.',
- 0, 4, 'filip.angelov@gmail.com');
-
-INSERT INTO recipe_contains_ingredient (recipe_id, ingredient_name, ingredient_quantity)
-SELECT r.id, v.ing, v.qty
-FROM (VALUES
-    -- Tavce Gravce
-    ('Tavce Gravce','Dry white beans',500), ('Tavce Gravce','Onion',200),
-    ('Tavce Gravce','Red bell pepper',100), ('Tavce Gravce','Sunflower oil',60),
-    ('Tavce Gravce','Paprika powder',10),   ('Tavce Gravce','Salt',8),
-    ('Tavce Gravce','Black pepper',2),      ('Tavce Gravce','Parsley',15),
-    -- Shopska Salad
-    ('Shopska Salad','Tomato',400),         ('Shopska Salad','Cucumber',300),
-    ('Shopska Salad','Onion',80),           ('Shopska Salad','White brine cheese',150),
-    ('Shopska Salad','Olive oil',30),       ('Shopska Salad','Salt',5),
-    -- Turli Tava
-    ('Turli Tava','Beef mince',300),        ('Turli Tava','Potato',400),
-    ('Turli Tava','Aubergine',300),         ('Turli Tava','Zucchini',250),
-    ('Turli Tava','Red bell pepper',200),   ('Turli Tava','Tomato',250),
-    ('Turli Tava','Onion',150),             ('Turli Tava','Sunflower oil',50),
-    ('Turli Tava','Salt',10),               ('Turli Tava','Black pepper',3),
-    ('Turli Tava','Paprika powder',8),
-    -- Homemade Ajvar
-    ('Homemade Ajvar','Red bell pepper',2000), ('Homemade Ajvar','Aubergine',500),
-    ('Homemade Ajvar','Garlic',30),            ('Homemade Ajvar','Sunflower oil',200),
-    ('Homemade Ajvar','Salt',20),
-    -- Grilled Trout with Parsley
-    ('Grilled Trout with Parsley','Trout fillet',400), ('Grilled Trout with Parsley','Olive oil',20),
-    ('Grilled Trout with Parsley','Garlic',10),        ('Grilled Trout with Parsley','Parsley',20),
-    ('Grilled Trout with Parsley','Lemon',60),         ('Grilled Trout with Parsley','Salt',5),
-    ('Grilled Trout with Parsley','Black pepper',2),
-    -- Chicken Bulgur Bowl
-    ('Chicken Bulgur Bowl','Chicken breast',400), ('Chicken Bulgur Bowl','Bulgur',200),
-    ('Chicken Bulgur Bowl','Tomato',150),         ('Chicken Bulgur Bowl','Cucumber',100),
-    ('Chicken Bulgur Bowl','Olive oil',20),       ('Chicken Bulgur Bowl','Salt',5),
-    -- Oat Porridge with Banana and Walnuts
-    ('Oat Porridge with Banana and Walnuts','Oats',80),   ('Oat Porridge with Banana and Walnuts','Cow milk',250),
-    ('Oat Porridge with Banana and Walnuts','Banana',120),('Oat Porridge with Banana and Walnuts','Walnut',30),
-    ('Oat Porridge with Banana and Walnuts','Honey',20),
-    -- Lentil Soup
-    ('Lentil Soup','Lentils',300),   ('Lentil Soup','Carrot',150),
-    ('Lentil Soup','Onion',120),     ('Lentil Soup','Garlic',15),
-    ('Lentil Soup','Olive oil',30),  ('Lentil Soup','Salt',8),
-    ('Lentil Soup','Black pepper',2),
-    -- Zelnik Spinach and Cheese Filling
-    ('Zelnik Spinach and Cheese Filling','Spinach',500),  ('Zelnik Spinach and Cheese Filling','White brine cheese',300),
-    ('Zelnik Spinach and Cheese Filling','Egg',120),      ('Zelnik Spinach and Cheese Filling','Sunflower oil',40),
-    ('Zelnik Spinach and Cheese Filling','Salt',6),
-    -- Tarator
-    ('Tarator','Yogurt',500),   ('Tarator','Cucumber',300),
-    ('Tarator','Garlic',10),    ('Tarator','Dill',15),
-    ('Tarator','Olive oil',15), ('Tarator','Walnut',25),
-    ('Tarator','Salt',4),
-    -- Baked Mackerel with Vegetables
-    ('Baked Mackerel with Vegetables','Mackerel',500), ('Baked Mackerel with Vegetables','Potato',300),
-    ('Baked Mackerel with Vegetables','Carrot',150),   ('Baked Mackerel with Vegetables','Onion',100),
-    ('Baked Mackerel with Vegetables','Olive oil',25), ('Baked Mackerel with Vegetables','Salt',6),
-    ('Baked Mackerel with Vegetables','Black pepper',2),
-    -- Protein Pancakes
-    ('Protein Pancakes','Oats',100),    ('Protein Pancakes','Egg',150),
-    ('Protein Pancakes','Cow milk',150),('Protein Pancakes','Banana',100),
-    ('Protein Pancakes','Butter',15),
-    -- Grilled Chicken Salad
-    ('Grilled Chicken Salad','Chicken breast',300), ('Grilled Chicken Salad','Spinach',150),
-    ('Grilled Chicken Salad','Tomato',150),         ('Grilled Chicken Salad','Cucumber',100),
-    ('Grilled Chicken Salad','Olive oil',20),       ('Grilled Chicken Salad','Salt',4),
-    -- Stuffed Peppers with Rice
-    ('Stuffed Peppers with Rice','Red bell pepper',600), ('Stuffed Peppers with Rice','White rice',200),
-    ('Stuffed Peppers with Rice','Beef mince',250),      ('Stuffed Peppers with Rice','Onion',100),
-    ('Stuffed Peppers with Rice','Sunflower oil',40),    ('Stuffed Peppers with Rice','Salt',8),
-    ('Stuffed Peppers with Rice','Paprika powder',5)
-) AS v(recipe_name, ing, qty)
-JOIN recipe r ON r.name = v.recipe_name;
-
-INSERT INTO recipe_contains_restriction (recipe_id, restriction_id)
-SELECT r.id, res.id
-FROM (VALUES
-    ('Tavce Gravce','Contains no ingredient of animal origin'),
-    ('Tavce Gravce','Contains no meat and no fish'),
-    ('Tavce Gravce','Free of gluten containing cereals'),
-    ('Tavce Gravce','Free of lactose'),
-    ('Shopska Salad','Contains no meat and no fish'),
-    ('Shopska Salad','Contains milk protein and lactose'),
-    ('Shopska Salad','Free of gluten containing cereals'),
-    ('Turli Tava','Free of gluten containing cereals'),
-    ('Turli Tava','Free of lactose'),
-    ('Homemade Ajvar','Contains no ingredient of animal origin'),
-    ('Homemade Ajvar','Contains no meat and no fish'),
-    ('Homemade Ajvar','Free of gluten containing cereals'),
-    ('Homemade Ajvar','Free of lactose'),
-    ('Grilled Trout with Parsley','Fish and seafood allowed, no other meat'),
-    ('Grilled Trout with Parsley','Contains fish'),
-    ('Grilled Trout with Parsley','Free of gluten containing cereals'),
-    ('Grilled Trout with Parsley','Low carbohydrate and high fat, ketogenic'),
-    ('Chicken Bulgur Bowl','Contains gluten from wheat, barley or rye'),
-    ('Chicken Bulgur Bowl','Free of lactose'),
-    ('Oat Porridge with Banana and Walnuts','Contains no meat and no fish'),
-    ('Oat Porridge with Banana and Walnuts','Contains milk protein and lactose'),
-    ('Oat Porridge with Banana and Walnuts','Contains tree nuts'),
-    ('Lentil Soup','Contains no ingredient of animal origin'),
-    ('Lentil Soup','Contains no meat and no fish'),
-    ('Lentil Soup','Free of gluten containing cereals'),
-    ('Lentil Soup','Free of lactose'),
-    ('Zelnik Spinach and Cheese Filling','Contains no meat and no fish'),
-    ('Zelnik Spinach and Cheese Filling','Contains milk protein and lactose'),
-    ('Zelnik Spinach and Cheese Filling','Contains egg'),
-    ('Tarator','Contains no meat and no fish'),
-    ('Tarator','Contains milk protein and lactose'),
-    ('Tarator','Contains tree nuts'),
-    ('Tarator','Free of gluten containing cereals'),
-    ('Baked Mackerel with Vegetables','Fish and seafood allowed, no other meat'),
-    ('Baked Mackerel with Vegetables','Contains fish'),
-    ('Baked Mackerel with Vegetables','Free of lactose'),
-    ('Protein Pancakes','Contains no meat and no fish'),
-    ('Protein Pancakes','Contains milk protein and lactose'),
-    ('Protein Pancakes','Contains egg'),
-    ('Grilled Chicken Salad','Free of gluten containing cereals'),
-    ('Grilled Chicken Salad','Free of lactose'),
-    ('Grilled Chicken Salad','Suitable for a diabetic meal plan, low glycaemic load'),
-    ('Stuffed Peppers with Rice','Free of gluten containing cereals'),
-    ('Stuffed Peppers with Rice','Free of lactose')
-) AS v(recipe_name, restriction_description)
-JOIN recipe      r   ON r.name        = v.recipe_name
-JOIN restriction res ON res.description = v.restriction_description;
-
-INSERT INTO post (created_by, recipe_id, is_private, is_favourite, status, created_at, media, media_type, media_name)
-VALUES ('marija.trajkovska@gmail.com', NULL, TRUE, FALSE, 'published', '2026-01-30 07:30:00',
-        decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64'),
-        'image/png', 'progress_week4.png');
-
-INSERT INTO post (created_by, recipe_id, is_private, is_favourite, status, created_at, media, media_type, media_name)
-SELECT v.author, r.id, v.is_private, v.is_favourite, v.status::post_status, v.created_at::timestamp, NULL, NULL, v.media_name
-FROM (VALUES
-    ('marija.trajkovska@gmail.com',      'Tavce Gravce',                          FALSE, TRUE,  'published', '2026-01-12 18:20:00', 'tavce_gravce.jpg'),
-    ('stefan.petrov@gmail.com',          'Shopska Salad',                         FALSE, FALSE, 'published', '2026-01-14 12:05:00', 'shopska.jpg'),
-    ('ana.jovanovska@yahoo.com',         'Turli Tava',                            FALSE, TRUE,  'published', '2026-01-15 19:40:00', 'turli_tava.jpg'),
-    ('elena.dimitrova@hotmail.com',      'Homemade Ajvar',                        FALSE, TRUE,  'published', '2026-01-18 16:10:00', 'ajvar_jars.jpg'),
-    ('nikola.stojanov@gmail.com',        'Grilled Trout with Parsley',            FALSE, FALSE, 'published', '2026-01-20 20:00:00', 'trout.jpg'),
-    ('viktor.ilievski@nutritioneer.mk',  'Chicken Bulgur Bowl',                   FALSE, TRUE,  'published', '2026-01-21 13:30:00', 'bulgur_bowl.jpg'),
-    ('marija.trajkovska@gmail.com',      'Oat Porridge with Banana and Walnuts',  TRUE,  FALSE, 'published', '2026-01-22 08:15:00', 'porridge.jpg'),
-    ('bojan.kostov@gmail.com',           'Lentil Soup',                           FALSE, FALSE, 'draft',     '2026-01-23 17:45:00', NULL),
-    ('elena.dimitrova@hotmail.com',      'Zelnik Spinach and Cheese Filling',     FALSE, FALSE, 'archived',  '2026-01-05 11:00:00', 'zelnik.jpg'),
-    ('ana.jovanovska@yahoo.com',         'Tarator',                               FALSE, TRUE,  'published', '2026-01-25 14:20:00', 'tarator.jpg'),
-    ('nikola.stojanov@gmail.com',        'Baked Mackerel with Vegetables',        FALSE, FALSE, 'published', '2026-01-26 19:10:00', 'mackerel.jpg'),
-    ('teodora.mihajlova@nutritioneer.mk','Protein Pancakes',                      FALSE, TRUE,  'published', '2026-01-27 09:00:00', 'pancakes.jpg'),
-    ('stefan.petrov@gmail.com',          'Grilled Chicken Salad',                 TRUE,  FALSE, 'draft',     '2026-01-28 12:40:00', NULL),
-    ('filip.angelov@gmail.com',          'Stuffed Peppers with Rice',             FALSE, FALSE, 'published', '2026-01-29 18:55:00', 'peppers.jpg')
-) AS v(author, recipe_name, is_private, is_favourite, status, created_at, media_name)
-JOIN recipe r ON r.name = v.recipe_name;
- 
-INSERT INTO comment (description, created_at, post_id, created_by)
-SELECT v.body, v.created_at::timestamp, p.id, v.author
-FROM (VALUES
-    ('Tavce Gravce',               'stefan.petrov@gmail.com',          'Exactly how my grandmother made it. Adding it to my planner.', '2026-01-12 19:02:00'),
-    ('Tavce Gravce',               'ana.jovanovska@yahoo.com',         'How long do the beans need to soak?',                          '2026-01-13 09:11:00'),
-    ('Tavce Gravce',               'marija.trajkovska@gmail.com',      'At least eight hours, overnight is best.',                     '2026-01-13 09:40:00'),
-    ('Shopska Salad',              'elena.dimitrova@hotmail.com',      'Simple and perfect. I use half the cheese to cut the sodium.',  '2026-01-14 13:20:00'),
-    ('Turli Tava',                 'bojan.kostov@gmail.com',           'Made it without the mince, still excellent.',                  '2026-01-16 20:15:00'),
-    ('Turli Tava',                 'viktor.ilievski@nutritioneer.mk',  'Good macro balance for a rest day.',                           '2026-01-16 21:00:00'),
-    ('Homemade Ajvar',             'marija.trajkovska@gmail.com',      'Two hours of stirring is no joke but worth every minute.',      '2026-01-19 10:05:00'),
-    ('Homemade Ajvar',             'filip.angelov@gmail.com',          'How many jars does this quantity fill?',                       '2026-01-19 11:30:00'),
-    ('Grilled Trout with Parsley', 'teodora.mihajlova@nutritioneer.mk','Very clean protein source, great for a cutting phase.',         '2026-01-21 08:45:00'),
-    ('Chicken Bulgur Bowl',        'stefan.petrov@gmail.com',          'This is now my default meal prep for the week.',                '2026-01-21 18:10:00'),
-    ('Chicken Bulgur Bowl',        'nikola.stojanov@gmail.com',        'Swapped bulgur for rice to keep it gluten free.',               '2026-01-22 12:25:00'),
-    ('Tarator',                    'stefan.petrov@gmail.com',          'Draining the cucumber really does make a difference.',          '2026-01-25 16:00:00'),
-    ('Baked Mackerel with Vegetables','ana.jovanovska@yahoo.com',      'The vitamin D content on this one is impressive.',              '2026-01-27 08:20:00'),
-    ('Protein Pancakes',           'marija.trajkovska@gmail.com',      'Thirty grams of protein in a breakfast, finally.',              '2026-01-27 10:15:00'),
-    ('Stuffed Peppers with Rice',  'elena.dimitrova@hotmail.com',      'Try it with a spoon of ajvar mixed into the filling.',          '2026-01-30 09:05:00')
-) AS v(recipe_name, author, body, created_at)
-JOIN recipe r ON r.name      = v.recipe_name
-JOIN post   p ON p.recipe_id = r.id;
-
-
-INSERT INTO grocery_list (date_time, notes, is_bought, kcal, user_email) VALUES
-('2026-01-11 10:00:00', 'Weekly shopping - Sunday cooking',        TRUE,  0, 'marija.trajkovska@gmail.com'),
-('2026-01-14 17:30:00', 'Vegetables for the tray bake',            TRUE,  0, 'ana.jovanovska@yahoo.com'),
-('2026-01-17 09:15:00', 'Ajvar season - green market',             TRUE,  0, 'elena.dimitrova@hotmail.com'),
-('2026-01-19 18:00:00', 'Fish market on Saturday morning',         FALSE, 0, 'nikola.stojanov@gmail.com'),
-('2026-01-22 11:45:00', 'Cheap protein week',                      FALSE, 0, 'bojan.kostov@gmail.com');
-
--- Bulk add
-INSERT INTO grocery_list_bulk_add_ingredient (list_id, recipe_id)
-SELECT g.id, r.id
-FROM (VALUES
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Tavce Gravce'),
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Oat Porridge with Banana and Walnuts'),
-    ('ana.jovanovska@yahoo.com',    '2026-01-14 17:30:00', 'Turli Tava'),
-    ('ana.jovanovska@yahoo.com',    '2026-01-14 17:30:00', 'Tarator'),
-    ('elena.dimitrova@hotmail.com', '2026-01-17 09:15:00', 'Homemade Ajvar'),
-    ('nikola.stojanov@gmail.com',   '2026-01-19 18:00:00', 'Grilled Trout with Parsley'),
-    ('nikola.stojanov@gmail.com',   '2026-01-19 18:00:00', 'Baked Mackerel with Vegetables'),
-    ('bojan.kostov@gmail.com',      '2026-01-22 11:45:00', 'Lentil Soup')
-) AS v(user_email, dt, recipe_name)
-JOIN grocery_list g ON g.user_email = v.user_email AND g.date_time = v.dt::timestamp
-JOIN recipe       r ON r.name       = v.recipe_name;
-
--- Single add
-INSERT INTO grocery_list_single_add_ingredient (list_id, ingredient_name, buy_quantity)
-SELECT g.id, v.ing, v.qty
-FROM (VALUES
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Sunflower oil',   1000),
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Salt',             500),
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Apple',           1500),
-    ('ana.jovanovska@yahoo.com',    '2026-01-14 17:30:00', 'Potato',          2000),
-    ('ana.jovanovska@yahoo.com',    '2026-01-14 17:30:00', 'White bread',      500),
-    ('elena.dimitrova@hotmail.com', '2026-01-17 09:15:00', 'Red bell pepper', 5000),
-    ('elena.dimitrova@hotmail.com', '2026-01-17 09:15:00', 'Garlic',           150),
-    ('nikola.stojanov@gmail.com',   '2026-01-19 18:00:00', 'Olive oil',        750),
-    ('nikola.stojanov@gmail.com',   '2026-01-19 18:00:00', 'Lemon',            300),
-    ('bojan.kostov@gmail.com',      '2026-01-22 11:45:00', 'Chickpeas',       1000),
-    ('bojan.kostov@gmail.com',      '2026-01-22 11:45:00', 'Carrot',           800),
-    ('bojan.kostov@gmail.com',      '2026-01-22 11:45:00', 'Onion',            700)
-) AS v(user_email, dt, ing, qty)
-JOIN grocery_list g ON g.user_email = v.user_email AND g.date_time = v.dt::timestamp;
-
-INSERT INTO intake_planner (date_time, notes, kcal, is_consumed, user_email) VALUES
-('2026-01-22 08:30:00', 'Breakfast',                         0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-22 13:00:00', 'Lunch at home',                     0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-22 19:30:00', 'Light dinner',                      0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-23 08:30:00', 'Breakfast',                         0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-23 13:15:00', 'Lunch',                             0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-24 09:00:00', 'Late breakfast after training',     0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-24 20:00:00', 'Dinner with family',                0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-25 12:30:00', 'Planned lunch - not eaten yet',     0, FALSE, 'marija.trajkovska@gmail.com'),
-('2026-01-22 12:45:00', 'Meal prep box 1',                   0, TRUE,  'stefan.petrov@gmail.com'),
-('2026-01-23 12:45:00', 'Meal prep box 2',                   0, TRUE,  'stefan.petrov@gmail.com'),
-('2026-01-26 19:45:00', 'Dinner',                            0, TRUE,  'nikola.stojanov@gmail.com'),
-('2026-01-27 09:30:00', 'Post workout breakfast',            0, TRUE,  'teodora.mihajlova@nutritioneer.mk'),
-('2026-01-28 13:00:00', 'Lunch',                             0, TRUE,  'ana.jovanovska@yahoo.com'),
-('2026-01-29 10:00:00', 'Coffee and fruit only',           180, TRUE,  'bojan.kostov@gmail.com');
-
-INSERT INTO intake_planner_save_to_list_post (planner_id, post_id)
-SELECT ip.id, p.id
-FROM (VALUES
-    ('marija.trajkovska@gmail.com',     '2026-01-22 08:30:00', 'Oat Porridge with Banana and Walnuts'),
-    ('marija.trajkovska@gmail.com',     '2026-01-22 13:00:00', 'Tavce Gravce'),
-    ('marija.trajkovska@gmail.com',     '2026-01-22 19:30:00', 'Shopska Salad'),
-    ('marija.trajkovska@gmail.com',     '2026-01-23 08:30:00', 'Protein Pancakes'),
-    ('marija.trajkovska@gmail.com',     '2026-01-23 13:15:00', 'Chicken Bulgur Bowl'),
-    ('marija.trajkovska@gmail.com',     '2026-01-24 09:00:00', 'Oat Porridge with Banana and Walnuts'),
-    ('marija.trajkovska@gmail.com',     '2026-01-24 20:00:00', 'Turli Tava'),
-    ('marija.trajkovska@gmail.com',     '2026-01-25 12:30:00', 'Grilled Trout with Parsley'),
-    ('stefan.petrov@gmail.com',         '2026-01-22 12:45:00', 'Chicken Bulgur Bowl'),
-    ('stefan.petrov@gmail.com',         '2026-01-23 12:45:00', 'Chicken Bulgur Bowl'),
-    ('nikola.stojanov@gmail.com',       '2026-01-26 19:45:00', 'Baked Mackerel with Vegetables'),
-    ('teodora.mihajlova@nutritioneer.mk','2026-01-27 09:30:00','Protein Pancakes'),
-    ('ana.jovanovska@yahoo.com',        '2026-01-28 13:00:00', 'Tarator'),
-    ('ana.jovanovska@yahoo.com',        '2026-01-28 13:00:00', 'Stuffed Peppers with Rice')
-) AS v(user_email, dt, recipe_name)
-JOIN intake_planner ip ON ip.user_email = v.user_email AND ip.date_time = v.dt::timestamp
-JOIN recipe          r ON r.name        = v.recipe_name
-JOIN post            p ON p.recipe_id   = r.id;
-
-INSERT INTO biometrics (date, weight, height, age, muscle_fat_ratio, user_email) VALUES
-('2026-01-05', 74.5, 168, 29, 1.80, 'marija.trajkovska@gmail.com'),
-('2026-01-12', 73.8, 168, 29, 1.84, 'marija.trajkovska@gmail.com'),
-('2026-01-19', 73.1, 168, 29, 1.90, 'marija.trajkovska@gmail.com'),
-('2026-01-26', 72.4, 168, 29, 1.95, 'marija.trajkovska@gmail.com'),
-('2026-01-06',  88.2, 182, 34, 2.10, 'stefan.petrov@gmail.com'),
-('2026-01-20',  87.0, 182, 34, 2.18, 'stefan.petrov@gmail.com'),
-('2026-01-27',  86.3, 182, 34, 2.24, 'stefan.petrov@gmail.com'),
-('2026-01-10',  61.0, 163, 41, 1.65, 'ana.jovanovska@yahoo.com'),
-('2026-01-24',  60.4, 163, 41, 1.70, 'ana.jovanovska@yahoo.com'),
-('2026-01-08',  95.7, 190, 27, 2.35, 'nikola.stojanov@gmail.com'),
-('2026-01-22',  94.9, 190, 27, 2.40, 'nikola.stojanov@gmail.com'),
-('2026-01-15',  68.3, 171, 36, 1.72, 'elena.dimitrova@hotmail.com'),
-('2026-01-29',  67.9, 171, 36, 1.75, 'elena.dimitrova@hotmail.com'),
-('2026-01-18',  79.0, 176, 31, 1.60, 'bojan.kostov@gmail.com');
Index: l/latest-unmigrated/ddl_nutritioneer_2_0.sql
===================================================================
--- sql/latest-unmigrated/ddl_nutritioneer_2_0.sql	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,218 +1,0 @@
-
-CREATE TYPE user_role AS ENUM (
-    'user',
-    'administrator',
-    'trainer'
-);
-
-CREATE TYPE ingredient_type AS ENUM (
-    'dairy',
-    'meat',
-    'fish',
-    'herb',
-    'carb',
-    'vegetable',
-    'fruit',
-    'legume',
-    'nut',
-    'seed',
-    'oil',
-    'spice',
-    'beverage',
-    'other'
-);
-
-CREATE TYPE nutrient_unit AS ENUM (
-    'milligram',
-    'gram',
-    'microgram'
-);
-
-CREATE TYPE nutrient_type AS ENUM (
-    'macro',
-    'micro'
-);
-
-CREATE TYPE restriction_type AS ENUM (
-    'vegan',
-    'vegetarian',
-    'halal',
-    'kosher',
-    'pescatarian',
-    'allergen',
-    'keto',
-    'paleo',
-    'gluten_free',
-    'lactose_free',
-    'diabetic',
-    'low_sodium',
-    'other'
-);
-
-CREATE TYPE post_status AS ENUM (
-    'published',
-    'draft',
-    'archived'
-);
-
-
-CREATE TABLE "user" (
-    id          BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    email       TEXT        NOT NULL,
-    username    TEXT        NOT NULL,
-    password    TEXT        NOT NULL,
-    role        user_role   NOT NULL DEFAULT 'user',
-
-    CONSTRAINT uq_user_email        UNIQUE (email),
-    CONSTRAINT uq_user_username     UNIQUE (username),
-    CONSTRAINT ck_user_email_format CHECK  (email LIKE '%_@_%.__%'),
-    CONSTRAINT ck_user_username_len CHECK  (length(username) BETWEEN 3 AND 40),
-    CONSTRAINT ck_user_password_len CHECK  (length(password) >= 8)
-);
-
-CREATE TABLE ingredient (
-    id          BIGINT              PRIMARY KEY GENERATED ALWAYS AS IDENTITY, --FIX
-    name        TEXT                NOT NULL,
-    description TEXT                NOT NULL,
-    energy      NUMERIC             NOT NULL,
-    kcal        NUMERIC             NOT NULL,
-    type        ingredient_type     NOT NULL DEFAULT 'other',
-
-    CONSTRAINT uq_ingredient_name   UNIQUE (name),
-    CONSTRAINT ck_ingredient_energy CHECK  (energy >= 0),
-    CONSTRAINT ck_ingredient_kcal   CHECK  (kcal   >= 0)
-);
-
-CREATE TABLE nutrient (
-    id          BIGINT          PRIMARY KEY GENERATED ALWAYS AS IDENTITY, -- FIX
-    quantity    NUMERIC         NOT NULL,
-    description TEXT            NOT NULL,
-    unit        nutrient_unit   NOT NULL,
-    type        nutrient_type   NOT NULL,
-
-    CONSTRAINT uq_nutrient_description UNIQUE (description),
-    CONSTRAINT ck_nutrient_quantity    CHECK  (quantity > 0)
-);
-
-CREATE TABLE restriction (
-    id          BIGINT              PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    description TEXT                NOT NULL,
-    type        restriction_type    NOT NULL,
-
-    CONSTRAINT uq_restriction_description UNIQUE (description)
-);
-
-
-CREATE TABLE recipe (
-    id          BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    name        TEXT        NOT NULL,
-    guide       TEXT        NOT NULL,
-    kcal_sum    NUMERIC     NOT NULL,
-    servings    NUMERIC     NOT NULL DEFAULT 1,
-    user_id     BIGINT      NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, -- FIX (user_email)
-
-    CONSTRAINT ck_recipe_kcal_sum   CHECK  (kcal_sum >= 0),
-    CONSTRAINT ck_recipe_servings   CHECK  (servings > 0)
-);
-
-
-CREATE TABLE post (
-    id           BIGINT          PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    media        BYTEA,
-    media_type   TEXT,
-    media_name   TEXT,
-    is_private   BOOLEAN         NOT NULL DEFAULT FALSE,
-    is_favourite BOOLEAN         NOT NULL DEFAULT FALSE,
-    status       post_status     NOT NULL DEFAULT 'draft',
-    created_at   TIMESTAMP       NOT NULL DEFAULT NOW(),
-    user_id      BIGINT          NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, --FIX (created_by)
-    recipe_id    BIGINT          REFERENCES recipe(id) ON DELETE SET NULL,
-
-    CONSTRAINT ck_post_media_type CHECK  (media IS NULL OR media_type IS NOT NULL),
-    CONSTRAINT uq_post_recipe     UNIQUE (recipe_id)
-);
-
-CREATE TABLE comment (
-    id          BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    description TEXT        NOT NULL,
-    created_at  TIMESTAMP   NOT NULL DEFAULT NOW(),
-    post_id     BIGINT      NOT NULL REFERENCES post(id)   ON DELETE CASCADE,
-    user_id     BIGINT      NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, --FIX (created_by)
-
-    CONSTRAINT ck_comment_not_empty CHECK (length(trim(description)) > 0)
-);
-
-CREATE TABLE grocery_list (
-    id          BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    date_time   TIMESTAMP   NOT NULL DEFAULT NOW(),
-    notes       TEXT,
-    is_bought   BOOLEAN     NOT NULL DEFAULT FALSE,
-    kcal        NUMERIC     NOT NULL,
-    user_id     BIGINT      NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, -- FIX (user_email)
-
-    CONSTRAINT ck_grocery_list_kcal CHECK (kcal >= 0)
-);
-
-CREATE TABLE intake_planner (
-    id          BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    date_time   TIMESTAMP   NOT NULL DEFAULT NOW(),
-    notes       TEXT,
-    kcal        NUMERIC     NOT NULL,
-    is_consumed BOOLEAN     NOT NULL DEFAULT FALSE,
-    user_id     BIGINT      NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, -- FIX (user_email)
-
-    CONSTRAINT ck_intake_planner_kcal CHECK (kcal >= 0)
-);
-
-CREATE TABLE biometrics (
-    id               BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    date             DATE        NOT NULL,
-    weight           NUMERIC,
-    height           NUMERIC,
-    age              NUMERIC,
-    muscle_fat_ratio NUMERIC,
-    user_id          BIGINT      NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, -- FIX (user_email)
-
-    CONSTRAINT uq_biometrics_user_date UNIQUE (user_id, date)
-);
-
-
-CREATE TABLE recipe_contains_ingredient (
-    recipe_id           BIGINT      NOT NULL REFERENCES recipe(id)     ON DELETE CASCADE,
-    ingredient_id       BIGINT      NOT NULL REFERENCES ingredient(id) ON DELETE CASCADE, -- FIX (ingredient_name)
-    ingredient_quantity NUMERIC     NOT NULL,
-    PRIMARY KEY (recipe_id, ingredient_id)
-);
-
-CREATE TABLE ingredient_contains_nutrient ( 
-    ingredient_id   BIGINT      NOT NULL REFERENCES ingredient(id) ON DELETE CASCADE, -- FIX (ingredient_name)
-    nutrient_id     BIGINT      NOT NULL REFERENCES nutrient(id)   ON DELETE CASCADE,
-    quantity        NUMERIC     NOT NULL,
-    PRIMARY KEY (ingredient_id, nutrient_id)
-);
-
-CREATE TABLE recipe_contains_restriction (
-    recipe_id       BIGINT      NOT NULL REFERENCES recipe(id)      ON DELETE CASCADE,
-    restriction_id  BIGINT      NOT NULL REFERENCES restriction(id) ON DELETE CASCADE,
-    PRIMARY KEY (recipe_id, restriction_id)
-);
-
-CREATE TABLE grocery_list_bulk_add_ingredient (
-    list_id     BIGINT      NOT NULL REFERENCES grocery_list(id) ON DELETE CASCADE,
-    recipe_id   BIGINT      NOT NULL REFERENCES recipe(id)       ON DELETE CASCADE,
-    PRIMARY KEY (list_id, recipe_id)
-);
-
-CREATE TABLE grocery_list_single_add_ingredient (
-    list_id             BIGINT      NOT NULL REFERENCES grocery_list(id) ON DELETE CASCADE,
-    ingredient_id       BIGINT      NOT NULL REFERENCES ingredient(id)   ON DELETE CASCADE, -- FIX (ingredient_name)
-    buy_quantity        NUMERIC     NOT NULL,
-    PRIMARY KEY (list_id, ingredient_id)
-);
-
-CREATE TABLE intake_planner_save_to_list_post (
-    planner_id  BIGINT      NOT NULL REFERENCES intake_planner(id) ON DELETE CASCADE,
-    post_id     BIGINT      NOT NULL REFERENCES post(id)           ON DELETE CASCADE,
-    PRIMARY KEY (planner_id, post_id)
-);
-
Index: l/latest-unmigrated/dml_nutritioneer_calculations_2_0.sql
===================================================================
--- sql/latest-unmigrated/dml_nutritioneer_calculations_2_0.sql	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,34 +1,0 @@
-
-
-UPDATE recipe r --FIX: Join on ID not Name
-SET kcal_sum = ROUND(COALESCE((
-        SELECT SUM(i.kcal * rci.ingredient_quantity / 100.0)
-        FROM recipe_contains_ingredient rci
-        JOIN ingredient i ON i.id = rci.ingredient_id
-        WHERE rci.recipe_id = r.id
-    ), 0), 2);
-
-UPDATE grocery_list g --FIX: Join on ID not Name
-SET kcal = ROUND(
-      COALESCE((SELECT SUM(r.kcal_sum)
-                FROM grocery_list_bulk_add_ingredient b
-                JOIN recipe r ON r.id = b.recipe_id
-                WHERE b.list_id = g.id), 0)
-    + COALESCE((SELECT SUM(i.kcal * s.buy_quantity / 100.0)
-                FROM grocery_list_single_add_ingredient s
-                JOIN ingredient i ON i.id = s.ingredient_id
-                WHERE s.list_id = g.id), 0)
-    , 2);
-
-UPDATE intake_planner ip
-SET kcal = ROUND((
-        SELECT SUM(r.kcal_sum / r.servings)   
-        FROM intake_planner_save_to_list_post sp
-        JOIN post   p ON p.id = sp.post_id
-        JOIN recipe r ON r.id = p.recipe_id
-        WHERE sp.planner_id = ip.id
-    ), 2)
-WHERE EXISTS (SELECT 1
-              FROM intake_planner_save_to_list_post sp
-              JOIN post p ON p.id = sp.post_id
-              WHERE sp.planner_id = ip.id AND p.recipe_id IS NOT NULL);
Index: l/latest-unmigrated/dml_nutritioneer_populating_2_0.sql
===================================================================
--- sql/latest-unmigrated/dml_nutritioneer_populating_2_0.sql	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,571 +1,0 @@
---FIX: Change JOINS {user_email, Created_by, ingredient_name -> user_id, user_id, ingredient_id}
-
-INSERT INTO "user" (email, username, password, role) VALUES
-('admin@nutritioneer.mk',           'admin_nutri',    'testPass@1', 'administrator'),
-('viktor.ilievski@nutritioneer.mk', 'coach_viktor',   'testPass@1', 'trainer'),
-('teodora.mihajlova@nutritioneer.mk','coach_teodora', 'testPass@1', 'trainer'),
-('marija.trajkovska@gmail.com',     'marija_t',       'testPass@1', 'user'),
-('stefan.petrov@gmail.com',         'stefan_p',       'testPass@1', 'user'),
-('ana.jovanovska@yahoo.com',        'ana_j',          'testPass@1', 'user'),
-('nikola.stojanov@gmail.com',       'nikola_s',       'testPass@1', 'user'),
-('elena.dimitrova@hotmail.com',     'elena_d',        'testPass@1', 'user'),
-('bojan.kostov@gmail.com',          'bojan_k',        'testPass@1', 'user'),
-('filip.angelov@gmail.com',         'filip_a',        'testPass@1', 'user');
-
-INSERT INTO ingredient (name, description, energy, kcal, type) VALUES
-('Chicken breast',      'Skinless boneless chicken breast, raw',            690,  165, 'meat'),
-('Beef mince',          'Ground beef, 20 percent fat, raw',                1046,  250, 'meat'),
-('Pork shoulder',       'Pork shoulder cut, raw',                          1013,  242, 'meat'),
-('Trout fillet',        'Freshwater trout fillet, raw',                     619,  148, 'fish'),
-('Mackerel',            'Atlantic mackerel, raw',                           858,  205, 'fish'),
-('Egg',                 'Whole chicken egg, raw',                           598,  143, 'other'),
-('Cow milk',            'Pasteurised cow milk, 3.2 percent fat',            255,   61, 'dairy'),
-('Yogurt',              'Plain yogurt, 3.5 percent fat',                    264,   63, 'dairy'),
-('White brine cheese',  'Traditional white brine cheese (sirenje)',        1105,  264, 'dairy'),
-('Kashkaval',           'Semi-hard yellow cheese (kashkaval)',             1490,  356, 'dairy'),
-('Butter',              'Unsalted cow butter, 82 percent fat',             3000,  717, 'dairy'),
-('Sunflower oil',       'Refined sunflower oil',                           3699,  884, 'oil'),
-('Olive oil',           'Extra virgin olive oil',                          3699,  884, 'oil'),
-('White rice',          'Long grain white rice, uncooked',                 1527,  365, 'carb'),
-('Bulgur',              'Coarse bulgur wheat, uncooked',                   1431,  342, 'carb'),
-('White flour',         'Wheat flour type 500, all purpose',               1523,  364, 'carb'),
-('Spaghetti',           'Durum wheat spaghetti, dry',                      1552,  371, 'carb'),
-('White bread',         'White wheat bread, baked',                        1109,  265, 'carb'),
-('Oats',                'Rolled oat flakes',                               1628,  389, 'carb'),
-('Potato',              'White potato, raw, peeled',                        322,   77, 'vegetable'),
-('Dry white beans',     'Dry white beans (tetovec variety)',               1393,  333, 'legume'),
-('Lentils',             'Dry brown lentils',                               1473,  352, 'legume'),
-('Chickpeas',           'Dry chickpeas',                                   1523,  364, 'legume'),
-('Tomato',              'Fresh ripe tomato',                                 75,   18, 'vegetable'),
-('Red bell pepper',     'Fresh red bell pepper (kapija)',                   130,   31, 'vegetable'),
-('Onion',               'Yellow onion, raw',                                167,   40, 'vegetable'),
-('Garlic',              'Fresh garlic cloves',                              623,  149, 'vegetable'),
-('Cucumber',            'Fresh cucumber with skin',                          63,   15, 'vegetable'),
-('Aubergine',           'Fresh aubergine (eggplant)',                       105,   25, 'vegetable'),
-('Zucchini',            'Fresh green zucchini',                              71,   17, 'vegetable'),
-('Carrot',              'Fresh carrot, raw',                                172,   41, 'vegetable'),
-('Spinach',             'Fresh spinach leaves',                              96,   23, 'vegetable'),
-('Cabbage',             'White cabbage, raw',                               105,   25, 'vegetable'),
-('Leek',                'Fresh leek, raw',                                  255,   61, 'vegetable'),
-('Apple',               'Fresh apple with skin',                            218,   52, 'fruit'),
-('Banana',              'Fresh banana, peeled',                             372,   89, 'fruit'),
-('Strawberry',          'Fresh strawberries',                               134,   32, 'fruit'),
-('Grapes',              'Fresh table grapes',                               288,   69, 'fruit'),
-('Lemon',               'Fresh lemon, peeled',                              121,   29, 'fruit'),
-('Walnut',              'Shelled walnut kernels',                          2736,  654, 'nut'),
-('Almond',              'Raw almonds',                                     2423,  579, 'nut'),
-('Sunflower seed',      'Hulled sunflower seeds',                          2444,  584, 'seed'),
-('Chia seed',           'Whole chia seeds',                                2033,  486, 'seed'),
-('Parsley',             'Fresh flat leaf parsley',                          151,   36, 'herb'),
-('Dill',                'Fresh dill weed',                                  180,   43, 'herb'),
-('Salt',                'Refined table salt, iodised',                        0,    0, 'spice'),
-('Black pepper',        'Ground black pepper',                             1050,  251, 'spice'),
-('Paprika powder',      'Sweet red paprika powder',                        1180,  282, 'spice'),
-('Honey',               'Raw flower honey',                                1272,  304, 'other'),
-('Water',               'Drinking water',                                     0,    0, 'beverage');
-
-INSERT INTO nutrient (description, quantity, unit, type) VALUES
-('Protein',             50,   'gram',      'macro'),
-('Total carbohydrate', 260,   'gram',      'macro'),
-('Total fat',           70,   'gram',      'macro'),
-('Saturated fat',       20,   'gram',      'macro'),
-('Dietary fibre',       30,   'gram',      'macro'),
-('Sugars',              90,   'gram',      'macro'),
-('Vitamin C',           80,   'milligram', 'micro'),
-('Vitamin A',          800,   'microgram', 'micro'),
-('Vitamin D',            5,   'microgram', 'micro'),
-('Vitamin E',           12,   'milligram', 'micro'),
-('Vitamin K',           75,   'microgram', 'micro'),
-('Vitamin B12',        2.5,   'microgram', 'micro'),
-('Folate',             200,   'microgram', 'micro'),
-('Calcium',            800,   'milligram', 'micro'),
-('Iron',                14,   'milligram', 'micro'),
-('Magnesium',          375,   'milligram', 'micro'),
-('Potassium',         2000,   'milligram', 'micro'),
-('Zinc',                10,   'milligram', 'micro'),
-('Sodium',            2000,   'milligram', 'micro'),
-('Phosphorus',         700,   'milligram', 'micro'),
-('Selenium',            55,   'microgram', 'micro'),
-('Iodine',             150,   'microgram', 'micro');
-
-INSERT INTO restriction (description, type) VALUES
-('Contains no ingredient of animal origin',                 'vegan'),
-('Contains no meat and no fish',                            'vegetarian'),
-('Prepared according to Islamic dietary law',               'halal'),
-('Prepared according to Jewish dietary law',                'kosher'),
-('Fish and seafood allowed, no other meat',                 'pescatarian'),
-('Contains gluten from wheat, barley or rye',               'allergen'),
-('Contains milk protein and lactose',                       'allergen'),
-('Contains tree nuts',                                      'allergen'),
-('Contains egg',                                            'allergen'),
-('Contains fish',                                           'allergen'),
-('Low carbohydrate and high fat, ketogenic',                'keto'),
-('No grains, legumes or processed food',                    'paleo'),
-('Free of gluten containing cereals',                       'gluten_free'),
-('Free of lactose',                                         'lactose_free'),
-('Suitable for a diabetic meal plan, low glycaemic load',   'diabetic'),
-('Reduced sodium content, under 120 mg per 100 g',          'low_sodium');
-
-INSERT INTO ingredient_contains_nutrient (ingredient_id, nutrient_id, quantity)
-SELECT i.id, n.id, v.qty
-FROM (VALUES
-    ('Chicken breast',     31.0,   0.0,   3.6,   0.0),
-    ('Beef mince',         26.0,   0.0,  15.0,   0.0),
-    ('Pork shoulder',      20.0,   0.0,  18.0,   0.0),
-    ('Trout fillet',       20.5,   0.0,   6.6,   0.0),
-    ('Mackerel',           19.0,   0.0,  13.9,   0.0),
-    ('Egg',                12.6,   0.7,   9.5,   0.0),
-    ('Cow milk',            3.3,   4.8,   3.2,   0.0),
-    ('Yogurt',              3.5,   4.7,   3.5,   0.0),
-    ('White brine cheese', 14.0,   2.0,  21.0,   0.0),
-    ('Kashkaval',          25.0,   1.5,  28.0,   0.0),
-    ('Butter',              0.9,   0.1,  81.0,   0.0),
-    ('Sunflower oil',       0.0,   0.0, 100.0,   0.0),
-    ('Olive oil',           0.0,   0.0, 100.0,   0.0),
-    ('White rice',          7.0,  80.0,   0.7,   1.3),
-    ('Bulgur',             12.0,  76.0,   1.3,  18.0),
-    ('White flour',        10.0,  76.0,   1.0,   2.7),
-    ('Spaghetti',          13.0,  75.0,   1.5,   3.2),
-    ('White bread',         9.0,  49.0,   3.2,   2.7),
-    ('Oats',               17.0,  66.0,   7.0,  11.0),
-    ('Potato',              2.0,  17.0,   0.1,   2.2),
-    ('Dry white beans',    21.0,  60.0,   1.2,  15.0),
-    ('Lentils',            25.0,  60.0,   1.1,  30.0),
-    ('Chickpeas',          19.0,  61.0,   6.0,  17.0),
-    ('Tomato',              0.9,   3.9,   0.2,   1.2),
-    ('Red bell pepper',     1.0,   6.0,   0.3,   2.1),
-    ('Onion',               1.1,   9.3,   0.1,   1.7),
-    ('Garlic',              6.4,  33.0,   0.5,   2.1),
-    ('Cucumber',            0.7,   3.6,   0.1,   0.5),
-    ('Aubergine',           1.0,   5.9,   0.2,   3.0),
-    ('Zucchini',            1.2,   3.1,   0.3,   1.0),
-    ('Carrot',              0.9,   9.6,   0.2,   2.8),
-    ('Spinach',             2.9,   3.6,   0.4,   2.2),
-    ('Cabbage',             1.3,   5.8,   0.1,   2.5),
-    ('Leek',                1.5,  14.0,   0.3,   1.8),
-    ('Apple',               0.3,  14.0,   0.2,   2.4),
-    ('Banana',              1.1,  23.0,   0.3,   2.6),
-    ('Strawberry',          0.7,   7.7,   0.3,   2.0),
-    ('Grapes',              0.7,  18.0,   0.2,   0.9),
-    ('Lemon',               1.1,   9.3,   0.3,   2.8),
-    ('Walnut',             15.0,  14.0,  65.0,   6.7),
-    ('Almond',             21.0,  22.0,  50.0,  12.5),
-    ('Sunflower seed',     21.0,  20.0,  51.0,   8.6),
-    ('Chia seed',          17.0,  42.0,  31.0,  34.0),
-    ('Parsley',             3.0,   6.3,   0.8,   3.3),
-    ('Dill',                3.5,   7.0,   1.1,   2.1),
-    ('Paprika powder',     14.0,  54.0,  13.0,  35.0),
-    ('Black pepper',       10.0,  64.0,   3.3,  25.0),
-    ('Honey',               0.3,  82.0,   0.0,   0.2)
-) AS m(ing, protein, carbohydrate, fat, fibre)
-CROSS JOIN LATERAL (VALUES
-    ('Protein',            m.protein),
-    ('Total carbohydrate', m.carbohydrate),
-    ('Total fat',          m.fat),
-    ('Dietary fibre',      m.fibre)
-) AS v(nut, qty)
-JOIN nutrient   n ON n.description = v.nut
-JOIN ingredient i ON i.name        = m.ing;
-
-INSERT INTO ingredient_contains_nutrient (ingredient_id, nutrient_id, quantity)
-SELECT i.id, n.id, v.qty
-FROM (VALUES
-    ('Red bell pepper', 'Vitamin C',    128.0),
-    ('Parsley',         'Vitamin C',    133.0),
-    ('Spinach',         'Vitamin C',     28.0),
-    ('Tomato',          'Vitamin C',     14.0),
-    ('Cabbage',         'Vitamin C',     36.0),
-    ('Strawberry',      'Vitamin C',     59.0),
-    ('Potato',          'Vitamin C',     20.0),
-    ('Lemon',           'Vitamin C',     53.0),
-    ('Carrot',          'Vitamin A',    835.0),
-    ('Spinach',         'Vitamin A',    469.0),
-    ('Red bell pepper', 'Vitamin A',    157.0),
-    ('Butter',          'Vitamin A',    684.0),
-    ('Trout fillet',    'Vitamin D',      6.5),
-    ('Mackerel',        'Vitamin D',     16.0),
-    ('Egg',             'Vitamin D',      2.0),
-    ('Beef mince',      'Vitamin B12',    2.6),
-    ('Trout fillet',    'Vitamin B12',    4.5),
-    ('Mackerel',        'Vitamin B12',    8.7),
-    ('Egg',             'Vitamin B12',    1.1),
-    ('Cow milk',        'Vitamin B12',    0.5),
-    ('Spinach',         'Folate',       194.0),
-    ('Lentils',         'Folate',       479.0),
-    ('Dry white beans', 'Folate',       388.0),
-    ('Parsley',         'Folate',       152.0),
-    ('Cow milk',        'Calcium',      120.0),
-    ('Yogurt',          'Calcium',      121.0),
-    ('White brine cheese','Calcium',    493.0),
-    ('Kashkaval',       'Calcium',      700.0),
-    ('Spinach',         'Calcium',       99.0),
-    ('Almond',          'Calcium',      269.0),
-    ('Chia seed',       'Calcium',      631.0),
-    ('Spinach',         'Iron',           2.7),
-    ('Lentils',         'Iron',           7.5),
-    ('Dry white beans', 'Iron',           5.5),
-    ('Beef mince',      'Iron',           2.6),
-    ('Oats',            'Iron',           4.7),
-    ('Chia seed',       'Iron',           7.7),
-    ('Almond',          'Magnesium',    270.0),
-    ('Oats',            'Magnesium',    177.0),
-    ('Spinach',         'Magnesium',     79.0),
-    ('Walnut',          'Magnesium',    158.0),
-    ('Potato',          'Potassium',    425.0),
-    ('Banana',          'Potassium',    358.0),
-    ('Spinach',         'Potassium',    558.0),
-    ('Tomato',          'Potassium',    237.0),
-    ('Dry white beans', 'Potassium',   1795.0),
-    ('Beef mince',      'Zinc',           4.8),
-    ('Lentils',         'Zinc',           3.3),
-    ('Oats',            'Zinc',           4.0),
-    ('Salt',            'Sodium',     38758.0),
-    ('White brine cheese','Sodium',    1200.0),
-    ('White bread',     'Sodium',       490.0),
-    ('Kashkaval',       'Sodium',       800.0),
-    ('Salt',            'Iodine',      2500.0),
-    ('Trout fillet',    'Selenium',      12.6),
-    ('Mackerel',        'Selenium',      44.1),
-    ('Chicken breast',  'Selenium',      22.8),
-    ('Chicken breast',  'Phosphorus',   210.0),
-    ('Cow milk',        'Phosphorus',    92.0),
-    ('Almond',          'Vitamin E',     25.6),
-    ('Sunflower oil',   'Vitamin E',     41.1),
-    ('Olive oil',       'Vitamin E',     14.4),
-    ('Spinach',         'Vitamin K',    483.0),
-    ('Parsley',         'Vitamin K',   1640.0)
-) AS v(ing, nut, qty)
-JOIN nutrient   n ON n.description = v.nut
-JOIN ingredient i ON i.name        = v.ing;
-
-INSERT INTO recipe (name, guide, kcal_sum, servings, user_id)
-SELECT v.name, v.guide, v.kcal_sum, v.servings, u.id
-FROM (VALUES
-('Tavce Gravce',
- E'1. Soak the beans overnight, then boil until soft.\n2. Fry the chopped onion in oil, add paprika off the heat.\n3. Combine everything in a clay pan, season and bake at 200 C for 40 minutes.\n4. Serve hot with fresh bread.',
- 0, 6, 'marija.trajkovska@gmail.com'),
-
-('Shopska Salad',
- E'1. Dice the tomatoes and cucumbers, slice the onion thinly.\n2. Season with salt and olive oil and toss.\n3. Grate the white brine cheese generously over the top and serve immediately.',
- 0, 4, 'stefan.petrov@gmail.com'),
-
-('Turli Tava',
- E'1. Brown the beef mince with the onion.\n2. Cut all vegetables into large chunks and layer them in a deep tray.\n3. Add the meat, oil, spices and a cup of water.\n4. Bake covered at 190 C for 60 minutes, then 15 minutes uncovered.',
- 0, 6, 'ana.jovanovska@yahoo.com'),
-
-('Homemade Ajvar',
- E'1. Roast the peppers and aubergine until the skins blister, then peel them.\n2. Mince the flesh and cook it slowly in oil, stirring constantly for about two hours.\n3. Add garlic and salt near the end and jar while hot.',
- 0, 20, 'elena.dimitrova@hotmail.com'),
-
-('Grilled Trout with Parsley',
- E'1. Clean the fillets and pat them dry.\n2. Rub with olive oil, crushed garlic, salt and pepper.\n3. Grill 4 minutes per side and finish with chopped parsley and lemon.',
- 0, 2, 'nikola.stojanov@gmail.com'),
-
-('Chicken Bulgur Bowl',
- E'1. Boil the bulgur in twice its volume of water for 12 minutes.\n2. Grill the seasoned chicken breast and slice it.\n3. Assemble with diced tomato and cucumber, finish with olive oil.',
- 0, 3, 'viktor.ilievski@nutritioneer.mk'),
-
-('Oat Porridge with Banana and Walnuts',
- E'1. Simmer the oats in milk for 5 minutes, stirring.\n2. Pour into a bowl and top with sliced banana and crushed walnuts.\n3. Drizzle honey over the top.',
- 0, 2, 'marija.trajkovska@gmail.com'),
-
-('Lentil Soup',
- E'1. Saute the diced onion, carrot and garlic in olive oil.\n2. Add the rinsed lentils and 1.5 litres of water.\n3. Simmer for 35 minutes, season and blend half of the soup for a thicker texture.',
- 0, 5, 'bojan.kostov@gmail.com'),
-
-('Zelnik Spinach and Cheese Filling',
- E'1. Wilt the washed spinach and squeeze out all excess water.\n2. Crumble in the white brine cheese and mix with beaten egg and oil.\n3. Season lightly - the cheese is already salty. Use as filling for zelnik.',
- 0, 8, 'elena.dimitrova@hotmail.com'),
-
-('Tarator',
- E'1. Grate the cucumber and drain it briefly.\n2. Whisk the yogurt with crushed garlic, chopped dill, salt and olive oil.\n3. Fold in the cucumber and crushed walnuts, chill for one hour before serving.',
- 0, 4, 'ana.jovanovska@yahoo.com'),
-
-('Baked Mackerel with Vegetables',
- E'1. Slice the potatoes, carrots and onion and spread them on a tray with oil and salt.\n2. Bake at 200 C for 25 minutes.\n3. Place the cleaned mackerel on top and bake for another 20 minutes.',
- 0, 3, 'nikola.stojanov@gmail.com'),
-
-('Protein Pancakes',
- E'1. Blend the oats, eggs, milk and banana into a smooth batter.\n2. Rest the batter for 10 minutes.\n3. Fry small pancakes in a little butter over medium heat, about 2 minutes per side.',
- 0, 3, 'teodora.mihajlova@nutritioneer.mk'),
-
-('Grilled Chicken Salad',
- E'1. Grill the seasoned chicken breast and let it rest, then slice.\n2. Toss the spinach, tomato and cucumber with olive oil and salt.\n3. Arrange the chicken on top and serve at room temperature.',
- 0, 2, 'stefan.petrov@gmail.com'),
-
-('Stuffed Peppers with Rice',
- E'1. Core the peppers carefully without splitting them.\n2. Mix the rice, browned mince, onion, oil and spices.\n3. Fill the peppers, stand them upright in a pot with water and simmer for 50 minutes.',
- 0, 4, 'filip.angelov@gmail.com')
-) AS v(name, guide, kcal_sum, servings, email)
-JOIN "user" u ON u.email = v.email;
-
-INSERT INTO recipe_contains_ingredient (recipe_id, ingredient_id, ingredient_quantity)
-SELECT r.id, i.id, v.qty
-FROM (VALUES
-    ('Tavce Gravce','Dry white beans',500), ('Tavce Gravce','Onion',200),
-    ('Tavce Gravce','Red bell pepper',100), ('Tavce Gravce','Sunflower oil',60),
-    ('Tavce Gravce','Paprika powder',10),   ('Tavce Gravce','Salt',8),
-    ('Tavce Gravce','Black pepper',2),      ('Tavce Gravce','Parsley',15),
-    ('Shopska Salad','Tomato',400),         ('Shopska Salad','Cucumber',300),
-    ('Shopska Salad','Onion',80),           ('Shopska Salad','White brine cheese',150),
-    ('Shopska Salad','Olive oil',30),       ('Shopska Salad','Salt',5),
-    ('Turli Tava','Beef mince',300),        ('Turli Tava','Potato',400),
-    ('Turli Tava','Aubergine',300),         ('Turli Tava','Zucchini',250),
-    ('Turli Tava','Red bell pepper',200),   ('Turli Tava','Tomato',250),
-    ('Turli Tava','Onion',150),             ('Turli Tava','Sunflower oil',50),
-    ('Turli Tava','Salt',10),               ('Turli Tava','Black pepper',3),
-    ('Turli Tava','Paprika powder',8),
-    ('Homemade Ajvar','Red bell pepper',2000), ('Homemade Ajvar','Aubergine',500),
-    ('Homemade Ajvar','Garlic',30),            ('Homemade Ajvar','Sunflower oil',200),
-    ('Homemade Ajvar','Salt',20),
-    ('Grilled Trout with Parsley','Trout fillet',400), ('Grilled Trout with Parsley','Olive oil',20),
-    ('Grilled Trout with Parsley','Garlic',10),        ('Grilled Trout with Parsley','Parsley',20),
-    ('Grilled Trout with Parsley','Lemon',60),         ('Grilled Trout with Parsley','Salt',5),
-    ('Grilled Trout with Parsley','Black pepper',2),
-    ('Chicken Bulgur Bowl','Chicken breast',400), ('Chicken Bulgur Bowl','Bulgur',200),
-    ('Chicken Bulgur Bowl','Tomato',150),         ('Chicken Bulgur Bowl','Cucumber',100),
-    ('Chicken Bulgur Bowl','Olive oil',20),       ('Chicken Bulgur Bowl','Salt',5),
-    ('Oat Porridge with Banana and Walnuts','Oats',80),   ('Oat Porridge with Banana and Walnuts','Cow milk',250),
-    ('Oat Porridge with Banana and Walnuts','Banana',120),('Oat Porridge with Banana and Walnuts','Walnut',30),
-    ('Oat Porridge with Banana and Walnuts','Honey',20),
-    ('Lentil Soup','Lentils',300),   ('Lentil Soup','Carrot',150),
-    ('Lentil Soup','Onion',120),     ('Lentil Soup','Garlic',15),
-    ('Lentil Soup','Olive oil',30),  ('Lentil Soup','Salt',8),
-    ('Lentil Soup','Black pepper',2),
-    ('Zelnik Spinach and Cheese Filling','Spinach',500),  ('Zelnik Spinach and Cheese Filling','White brine cheese',300),
-    ('Zelnik Spinach and Cheese Filling','Egg',120),      ('Zelnik Spinach and Cheese Filling','Sunflower oil',40),
-    ('Zelnik Spinach and Cheese Filling','Salt',6),
-    ('Tarator','Yogurt',500),   ('Tarator','Cucumber',300),
-    ('Tarator','Garlic',10),    ('Tarator','Dill',15),
-    ('Tarator','Olive oil',15), ('Tarator','Walnut',25),
-    ('Tarator','Salt',4),
-    ('Baked Mackerel with Vegetables','Mackerel',500), ('Baked Mackerel with Vegetables','Potato',300),
-    ('Baked Mackerel with Vegetables','Carrot',150),   ('Baked Mackerel with Vegetables','Onion',100),
-    ('Baked Mackerel with Vegetables','Olive oil',25), ('Baked Mackerel with Vegetables','Salt',6),
-    ('Baked Mackerel with Vegetables','Black pepper',2),
-    ('Protein Pancakes','Oats',100),    ('Protein Pancakes','Egg',150),
-    ('Protein Pancakes','Cow milk',150),('Protein Pancakes','Banana',100),
-    ('Protein Pancakes','Butter',15),
-    ('Grilled Chicken Salad','Chicken breast',300), ('Grilled Chicken Salad','Spinach',150),
-    ('Grilled Chicken Salad','Tomato',150),         ('Grilled Chicken Salad','Cucumber',100),
-    ('Grilled Chicken Salad','Olive oil',20),       ('Grilled Chicken Salad','Salt',4),
-    ('Stuffed Peppers with Rice','Red bell pepper',600), ('Stuffed Peppers with Rice','White rice',200),
-    ('Stuffed Peppers with Rice','Beef mince',250),      ('Stuffed Peppers with Rice','Onion',100),
-    ('Stuffed Peppers with Rice','Sunflower oil',40),    ('Stuffed Peppers with Rice','Salt',8),
-    ('Stuffed Peppers with Rice','Paprika powder',5)
-) AS v(recipe_name, ing, qty)
-JOIN recipe     r ON r.name = v.recipe_name
-JOIN ingredient i ON i.name = v.ing;
-
-INSERT INTO recipe_contains_restriction (recipe_id, restriction_id)
-SELECT r.id, res.id
-FROM (VALUES
-    ('Tavce Gravce','Contains no ingredient of animal origin'),
-    ('Tavce Gravce','Contains no meat and no fish'),
-    ('Tavce Gravce','Free of gluten containing cereals'),
-    ('Tavce Gravce','Free of lactose'),
-    ('Shopska Salad','Contains no meat and no fish'),
-    ('Shopska Salad','Contains milk protein and lactose'),
-    ('Shopska Salad','Free of gluten containing cereals'),
-    ('Turli Tava','Free of gluten containing cereals'),
-    ('Turli Tava','Free of lactose'),
-    ('Homemade Ajvar','Contains no ingredient of animal origin'),
-    ('Homemade Ajvar','Contains no meat and no fish'),
-    ('Homemade Ajvar','Free of gluten containing cereals'),
-    ('Homemade Ajvar','Free of lactose'),
-    ('Grilled Trout with Parsley','Fish and seafood allowed, no other meat'),
-    ('Grilled Trout with Parsley','Contains fish'),
-    ('Grilled Trout with Parsley','Free of gluten containing cereals'),
-    ('Grilled Trout with Parsley','Low carbohydrate and high fat, ketogenic'),
-    ('Chicken Bulgur Bowl','Contains gluten from wheat, barley or rye'),
-    ('Chicken Bulgur Bowl','Free of lactose'),
-    ('Oat Porridge with Banana and Walnuts','Contains no meat and no fish'),
-    ('Oat Porridge with Banana and Walnuts','Contains milk protein and lactose'),
-    ('Oat Porridge with Banana and Walnuts','Contains tree nuts'),
-    ('Lentil Soup','Contains no ingredient of animal origin'),
-    ('Lentil Soup','Contains no meat and no fish'),
-    ('Lentil Soup','Free of gluten containing cereals'),
-    ('Lentil Soup','Free of lactose'),
-    ('Zelnik Spinach and Cheese Filling','Contains no meat and no fish'),
-    ('Zelnik Spinach and Cheese Filling','Contains milk protein and lactose'),
-    ('Zelnik Spinach and Cheese Filling','Contains egg'),
-    ('Tarator','Contains no meat and no fish'),
-    ('Tarator','Contains milk protein and lactose'),
-    ('Tarator','Contains tree nuts'),
-    ('Tarator','Free of gluten containing cereals'),
-    ('Baked Mackerel with Vegetables','Fish and seafood allowed, no other meat'),
-    ('Baked Mackerel with Vegetables','Contains fish'),
-    ('Baked Mackerel with Vegetables','Free of lactose'),
-    ('Protein Pancakes','Contains no meat and no fish'),
-    ('Protein Pancakes','Contains milk protein and lactose'),
-    ('Protein Pancakes','Contains egg'),
-    ('Grilled Chicken Salad','Free of gluten containing cereals'),
-    ('Grilled Chicken Salad','Free of lactose'),
-    ('Grilled Chicken Salad','Suitable for a diabetic meal plan, low glycaemic load'),
-    ('Stuffed Peppers with Rice','Free of gluten containing cereals'),
-    ('Stuffed Peppers with Rice','Free of lactose')
-) AS v(recipe_name, restriction_description)
-JOIN recipe      r   ON r.name        = v.recipe_name
-JOIN restriction res ON res.description = v.restriction_description;
-
-INSERT INTO post (user_id, recipe_id, is_private, is_favourite, status, created_at, media, media_type, media_name)
-VALUES ((SELECT id FROM "user" WHERE email = 'marija.trajkovska@gmail.com'), NULL, TRUE, FALSE, 'published', '2026-01-30 07:30:00',
-        decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64'),
-        'image/png', 'progress_week4.png');
-
-INSERT INTO post (user_id, recipe_id, is_private, is_favourite, status, created_at, media, media_type, media_name)
-SELECT u.id, r.id, v.is_private, v.is_favourite, v.status::post_status, v.created_at::timestamp, NULL, NULL, v.media_name
-FROM (VALUES
-    ('marija.trajkovska@gmail.com',      'Tavce Gravce',                          FALSE, TRUE,  'published', '2026-01-12 18:20:00', 'tavce_gravce.jpg'),
-    ('stefan.petrov@gmail.com',          'Shopska Salad',                         FALSE, FALSE, 'published', '2026-01-14 12:05:00', 'shopska.jpg'),
-    ('ana.jovanovska@yahoo.com',         'Turli Tava',                            FALSE, TRUE,  'published', '2026-01-15 19:40:00', 'turli_tava.jpg'),
-    ('elena.dimitrova@hotmail.com',      'Homemade Ajvar',                        FALSE, TRUE,  'published', '2026-01-18 16:10:00', 'ajvar_jars.jpg'),
-    ('nikola.stojanov@gmail.com',        'Grilled Trout with Parsley',            FALSE, FALSE, 'published', '2026-01-20 20:00:00', 'trout.jpg'),
-    ('viktor.ilievski@nutritioneer.mk',  'Chicken Bulgur Bowl',                   FALSE, TRUE,  'published', '2026-01-21 13:30:00', 'bulgur_bowl.jpg'),
-    ('marija.trajkovska@gmail.com',      'Oat Porridge with Banana and Walnuts',  TRUE,  FALSE, 'published', '2026-01-22 08:15:00', 'porridge.jpg'),
-    ('bojan.kostov@gmail.com',           'Lentil Soup',                           FALSE, FALSE, 'draft',     '2026-01-23 17:45:00', NULL),
-    ('elena.dimitrova@hotmail.com',      'Zelnik Spinach and Cheese Filling',     FALSE, FALSE, 'archived',  '2026-01-05 11:00:00', 'zelnik.jpg'),
-    ('ana.jovanovska@yahoo.com',         'Tarator',                               FALSE, TRUE,  'published', '2026-01-25 14:20:00', 'tarator.jpg'),
-    ('nikola.stojanov@gmail.com',        'Baked Mackerel with Vegetables',        FALSE, FALSE, 'published', '2026-01-26 19:10:00', 'mackerel.jpg'),
-    ('teodora.mihajlova@nutritioneer.mk','Protein Pancakes',                      FALSE, TRUE,  'published', '2026-01-27 09:00:00', 'pancakes.jpg'),
-    ('stefan.petrov@gmail.com',          'Grilled Chicken Salad',                 TRUE,  FALSE, 'draft',     '2026-01-28 12:40:00', NULL),
-    ('filip.angelov@gmail.com',          'Stuffed Peppers with Rice',             FALSE, FALSE, 'published', '2026-01-29 18:55:00', 'peppers.jpg')
-) AS v(author, recipe_name, is_private, is_favourite, status, created_at, media_name)
-JOIN recipe r ON r.name  = v.recipe_name
-JOIN "user" u ON u.email = v.author;
- 
-INSERT INTO comment (description, created_at, post_id, user_id)
-SELECT v.body, v.created_at::timestamp, p.id, u.id
-FROM (VALUES
-    ('Tavce Gravce',               'stefan.petrov@gmail.com',          'Exactly how my grandmother made it. Adding it to my planner.', '2026-01-12 19:02:00'),
-    ('Tavce Gravce',               'ana.jovanovska@yahoo.com',         'How long do the beans need to soak?',                          '2026-01-13 09:11:00'),
-    ('Tavce Gravce',               'marija.trajkovska@gmail.com',      'At least eight hours, overnight is best.',                     '2026-01-13 09:40:00'),
-    ('Shopska Salad',              'elena.dimitrova@hotmail.com',      'Simple and perfect. I use half the cheese to cut the sodium.',  '2026-01-14 13:20:00'),
-    ('Turli Tava',                 'bojan.kostov@gmail.com',           'Made it without the mince, still excellent.',                  '2026-01-16 20:15:00'),
-    ('Turli Tava',                 'viktor.ilievski@nutritioneer.mk',  'Good macro balance for a rest day.',                           '2026-01-16 21:00:00'),
-    ('Homemade Ajvar',             'marija.trajkovska@gmail.com',      'Two hours of stirring is no joke but worth every minute.',      '2026-01-19 10:05:00'),
-    ('Homemade Ajvar',             'filip.angelov@gmail.com',          'How many jars does this quantity fill?',                       '2026-01-19 11:30:00'),
-    ('Grilled Trout with Parsley', 'teodora.mihajlova@nutritioneer.mk','Very clean protein source, great for a cutting phase.',         '2026-01-21 08:45:00'),
-    ('Chicken Bulgur Bowl',        'stefan.petrov@gmail.com',          'This is now my default meal prep for the week.',                '2026-01-21 18:10:00'),
-    ('Chicken Bulgur Bowl',        'nikola.stojanov@gmail.com',        'Swapped bulgur for rice to keep it gluten free.',               '2026-01-22 12:25:00'),
-    ('Tarator',                    'stefan.petrov@gmail.com',          'Draining the cucumber really does make a difference.',          '2026-01-25 16:00:00'),
-    ('Baked Mackerel with Vegetables','ana.jovanovska@yahoo.com',      'The vitamin D content on this one is impressive.',              '2026-01-27 08:20:00'),
-    ('Protein Pancakes',           'marija.trajkovska@gmail.com',      'Thirty grams of protein in a breakfast, finally.',              '2026-01-27 10:15:00'),
-    ('Stuffed Peppers with Rice',  'elena.dimitrova@hotmail.com',      'Try it with a spoon of ajvar mixed into the filling.',          '2026-01-30 09:05:00')
-) AS v(recipe_name, author, body, created_at)
-JOIN recipe r ON r.name      = v.recipe_name
-JOIN post   p ON p.recipe_id = r.id
-JOIN "user" u ON u.email     = v.author;
-
-
-INSERT INTO grocery_list (date_time, notes, is_bought, kcal, user_id)
-SELECT v.dt::timestamp, v.notes, v.is_bought, v.kcal, u.id
-FROM (VALUES
-('2026-01-11 10:00:00', 'Weekly shopping - Sunday cooking',        TRUE,  0, 'marija.trajkovska@gmail.com'),
-('2026-01-14 17:30:00', 'Vegetables for the tray bake',            TRUE,  0, 'ana.jovanovska@yahoo.com'),
-('2026-01-17 09:15:00', 'Ajvar season - green market',             TRUE,  0, 'elena.dimitrova@hotmail.com'),
-('2026-01-19 18:00:00', 'Fish market on Saturday morning',         FALSE, 0, 'nikola.stojanov@gmail.com'),
-('2026-01-22 11:45:00', 'Cheap protein week',                      FALSE, 0, 'bojan.kostov@gmail.com')
-) AS v(dt, notes, is_bought, kcal, email)
-JOIN "user" u ON u.email = v.email;
-
--- Bulk add
-INSERT INTO grocery_list_bulk_add_ingredient (list_id, recipe_id)
-SELECT g.id, r.id
-FROM (VALUES
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Tavce Gravce'),
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Oat Porridge with Banana and Walnuts'),
-    ('ana.jovanovska@yahoo.com',    '2026-01-14 17:30:00', 'Turli Tava'),
-    ('ana.jovanovska@yahoo.com',    '2026-01-14 17:30:00', 'Tarator'),
-    ('elena.dimitrova@hotmail.com', '2026-01-17 09:15:00', 'Homemade Ajvar'),
-    ('nikola.stojanov@gmail.com',   '2026-01-19 18:00:00', 'Grilled Trout with Parsley'),
-    ('nikola.stojanov@gmail.com',   '2026-01-19 18:00:00', 'Baked Mackerel with Vegetables'),
-    ('bojan.kostov@gmail.com',      '2026-01-22 11:45:00', 'Lentil Soup')
-) AS v(user_email, dt, recipe_name)
-JOIN "user"       u ON u.email   = v.user_email
-JOIN grocery_list g ON g.user_id = u.id AND g.date_time = v.dt::timestamp
-JOIN recipe       r ON r.name    = v.recipe_name;
-
--- Single add
-INSERT INTO grocery_list_single_add_ingredient (list_id, ingredient_id, buy_quantity)
-SELECT g.id, i.id, v.qty
-FROM (VALUES
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Sunflower oil',   1000),
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Salt',             500),
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Apple',           1500),
-    ('ana.jovanovska@yahoo.com',    '2026-01-14 17:30:00', 'Potato',          2000),
-    ('ana.jovanovska@yahoo.com',    '2026-01-14 17:30:00', 'White bread',      500),
-    ('elena.dimitrova@hotmail.com', '2026-01-17 09:15:00', 'Red bell pepper', 5000),
-    ('elena.dimitrova@hotmail.com', '2026-01-17 09:15:00', 'Garlic',           150),
-    ('nikola.stojanov@gmail.com',   '2026-01-19 18:00:00', 'Olive oil',        750),
-    ('nikola.stojanov@gmail.com',   '2026-01-19 18:00:00', 'Lemon',            300),
-    ('bojan.kostov@gmail.com',      '2026-01-22 11:45:00', 'Chickpeas',       1000),
-    ('bojan.kostov@gmail.com',      '2026-01-22 11:45:00', 'Carrot',           800),
-    ('bojan.kostov@gmail.com',      '2026-01-22 11:45:00', 'Onion',            700)
-) AS v(user_email, dt, ing, qty)
-JOIN "user"       u ON u.email   = v.user_email
-JOIN grocery_list g ON g.user_id = u.id AND g.date_time = v.dt::timestamp
-JOIN ingredient   i ON i.name    = v.ing;
-
-INSERT INTO intake_planner (date_time, notes, kcal, is_consumed, user_id)
-SELECT v.dt::timestamp, v.notes, v.kcal, v.is_consumed, u.id
-FROM (VALUES
-('2026-01-22 08:30:00', 'Breakfast',                         0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-22 13:00:00', 'Lunch at home',                     0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-22 19:30:00', 'Light dinner',                      0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-23 08:30:00', 'Breakfast',                         0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-23 13:15:00', 'Lunch',                             0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-24 09:00:00', 'Late breakfast after training',     0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-24 20:00:00', 'Dinner with family',                0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-25 12:30:00', 'Planned lunch - not eaten yet',     0, FALSE, 'marija.trajkovska@gmail.com'),
-('2026-01-22 12:45:00', 'Meal prep box 1',                   0, TRUE,  'stefan.petrov@gmail.com'),
-('2026-01-23 12:45:00', 'Meal prep box 2',                   0, TRUE,  'stefan.petrov@gmail.com'),
-('2026-01-26 19:45:00', 'Dinner',                            0, TRUE,  'nikola.stojanov@gmail.com'),
-('2026-01-27 09:30:00', 'Post workout breakfast',            0, TRUE,  'teodora.mihajlova@nutritioneer.mk'),
-('2026-01-28 13:00:00', 'Lunch',                             0, TRUE,  'ana.jovanovska@yahoo.com'),
-('2026-01-29 10:00:00', 'Coffee and fruit only',           180, TRUE,  'bojan.kostov@gmail.com')
-) AS v(dt, notes, kcal, is_consumed, email)
-JOIN "user" u ON u.email = v.email;
-
-INSERT INTO intake_planner_save_to_list_post (planner_id, post_id)
-SELECT ip.id, p.id
-FROM (VALUES
-    ('marija.trajkovska@gmail.com',     '2026-01-22 08:30:00', 'Oat Porridge with Banana and Walnuts'),
-    ('marija.trajkovska@gmail.com',     '2026-01-22 13:00:00', 'Tavce Gravce'),
-    ('marija.trajkovska@gmail.com',     '2026-01-22 19:30:00', 'Shopska Salad'),
-    ('marija.trajkovska@gmail.com',     '2026-01-23 08:30:00', 'Protein Pancakes'),
-    ('marija.trajkovska@gmail.com',     '2026-01-23 13:15:00', 'Chicken Bulgur Bowl'),
-    ('marija.trajkovska@gmail.com',     '2026-01-24 09:00:00', 'Oat Porridge with Banana and Walnuts'),
-    ('marija.trajkovska@gmail.com',     '2026-01-24 20:00:00', 'Turli Tava'),
-    ('marija.trajkovska@gmail.com',     '2026-01-25 12:30:00', 'Grilled Trout with Parsley'),
-    ('stefan.petrov@gmail.com',         '2026-01-22 12:45:00', 'Chicken Bulgur Bowl'),
-    ('stefan.petrov@gmail.com',         '2026-01-23 12:45:00', 'Chicken Bulgur Bowl'),
-    ('nikola.stojanov@gmail.com',       '2026-01-26 19:45:00', 'Baked Mackerel with Vegetables'),
-    ('teodora.mihajlova@nutritioneer.mk','2026-01-27 09:30:00','Protein Pancakes'),
-    ('ana.jovanovska@yahoo.com',        '2026-01-28 13:00:00', 'Tarator'),
-    ('ana.jovanovska@yahoo.com',        '2026-01-28 13:00:00', 'Stuffed Peppers with Rice')
-) AS v(user_email, dt, recipe_name)
-JOIN "user"          u ON u.email      = v.user_email
-JOIN intake_planner ip ON ip.user_id   = u.id AND ip.date_time = v.dt::timestamp
-JOIN recipe          r ON r.name        = v.recipe_name
-JOIN post            p ON p.recipe_id   = r.id;
-
-INSERT INTO biometrics (date, weight, height, age, muscle_fat_ratio, user_id)
-SELECT v.d::date, v.weight, v.height, v.age, v.ratio, u.id
-FROM (VALUES
-('2026-01-05', 74.5, 168, 29, 1.80, 'marija.trajkovska@gmail.com'),
-('2026-01-12', 73.8, 168, 29, 1.84, 'marija.trajkovska@gmail.com'),
-('2026-01-19', 73.1, 168, 29, 1.90, 'marija.trajkovska@gmail.com'),
-('2026-01-26', 72.4, 168, 29, 1.95, 'marija.trajkovska@gmail.com'),
-('2026-01-06',  88.2, 182, 34, 2.10, 'stefan.petrov@gmail.com'),
-('2026-01-20',  87.0, 182, 34, 2.18, 'stefan.petrov@gmail.com'),
-('2026-01-27',  86.3, 182, 34, 2.24, 'stefan.petrov@gmail.com'),
-('2026-01-10',  61.0, 163, 41, 1.65, 'ana.jovanovska@yahoo.com'),
-('2026-01-24',  60.4, 163, 41, 1.70, 'ana.jovanovska@yahoo.com'),
-('2026-01-08',  95.7, 190, 27, 2.35, 'nikola.stojanov@gmail.com'),
-('2026-01-22',  94.9, 190, 27, 2.40, 'nikola.stojanov@gmail.com'),
-('2026-01-15',  68.3, 171, 36, 1.72, 'elena.dimitrova@hotmail.com'),
-('2026-01-29',  67.9, 171, 36, 1.75, 'elena.dimitrova@hotmail.com'),
-('2026-01-18',  79.0, 176, 31, 1.60, 'bojan.kostov@gmail.com')
-) AS v(d, weight, height, age, ratio, email)
-JOIN "user" u ON u.email = v.email;
Index: l/old/01_DDL_nutritioneer.sql
===================================================================
--- sql/old/01_DDL_nutritioneer.sql	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,296 +1,0 @@
--- ---------------------------------------------------------------------
--- Првобитни queries
--- ---------------------------------------------------------------------
-DROP TABLE IF EXISTS intake_planner_save_to_list_post   CASCADE;
-DROP TABLE IF EXISTS grocery_list_single_add_ingredient CASCADE;
-DROP TABLE IF EXISTS grocery_list_bulk_add_ingredient   CASCADE;
-DROP TABLE IF EXISTS recipe_contains_restriction        CASCADE;
-DROP TABLE IF EXISTS ingredient_contains_nutrient       CASCADE;
-DROP TABLE IF EXISTS recipe_contains_ingredient         CASCADE;
-DROP TABLE IF EXISTS biometrics                         CASCADE;
-DROP TABLE IF EXISTS intake_planner                     CASCADE;
-DROP TABLE IF EXISTS grocery_list                       CASCADE;
-DROP TABLE IF EXISTS comment                            CASCADE;
-DROP TABLE IF EXISTS post                               CASCADE;
-DROP TABLE IF EXISTS recipe                             CASCADE;
-DROP TABLE IF EXISTS restriction                        CASCADE;
-DROP TABLE IF EXISTS nutrient                           CASCADE;
-DROP TABLE IF EXISTS ingredient                         CASCADE;
-DROP TABLE IF EXISTS "user"                             CASCADE;
-
-DROP TYPE  IF EXISTS post_status      CASCADE;
-DROP TYPE  IF EXISTS restriction_type CASCADE;
-DROP TYPE  IF EXISTS nutrient_type    CASCADE;
-DROP TYPE  IF EXISTS nutrient_unit    CASCADE;
-DROP TYPE  IF EXISTS ingredient_type  CASCADE;
-DROP TYPE  IF EXISTS user_role        CASCADE;
-
-
-
-CREATE TYPE user_role AS ENUM (
-    'user',
-    'administrator',
-    'trainer'
-);
-
-CREATE TYPE ingredient_type AS ENUM (
-    'dairy',
-    'meat',
-    'fish',
-    'herb',
-    'carb',
-    'vegetable',
-    'fruit',
-    'legume',
-    'nut',
-    'seed',
-    'oil',
-    'spice',
-    'beverage',
-    'other'
-);
-
-CREATE TYPE nutrient_unit AS ENUM (
-    'milligram',
-    'gram',
-    'microgram'
-);
-
-CREATE TYPE nutrient_type AS ENUM (
-    'macro',
-    'micro'
-);
-
-CREATE TYPE restriction_type AS ENUM (
-    'vegan',
-    'vegetarian',
-    'halal',
-    'kosher',
-    'pescatarian',
-    'allergen',
-    'keto',
-    'paleo',
-    'gluten_free',
-    'lactose_free',
-    'diabetic',
-    'low_sodium',
-    'other'
-);
-
-CREATE TYPE post_status AS ENUM (
-    'published',
-    'draft',
-    'archived'
-);
-
-
-
-CREATE TABLE "user" (
-    email       TEXT        PRIMARY KEY,
-    username    TEXT        NOT NULL,
-    password    TEXT        NOT NULL,        -- BCrypt hash, never plaintext
-    role        user_role   NOT NULL DEFAULT 'user',
-
-    CONSTRAINT uq_user_username     UNIQUE (username),
-    CONSTRAINT ck_user_email_format CHECK  (email LIKE '%_@_%.__%'),
-    CONSTRAINT ck_user_username_len CHECK  (length(username) BETWEEN 3 AND 40),
-    CONSTRAINT ck_user_password_len CHECK  (length(password) >= 8)
-);
-
-CREATE TABLE ingredient (
-    name        TEXT                NOT NULL PRIMARY KEY,
-    description TEXT                NOT NULL,
-    energy      NUMERIC             NOT NULL,   -- kJ  / 100 g
-    kcal        NUMERIC             NOT NULL,   -- kcal / 100 g
-    type        ingredient_type     NOT NULL DEFAULT 'other',
-
-    CONSTRAINT ck_ingredient_energy CHECK (energy >= 0),
-    CONSTRAINT ck_ingredient_kcal   CHECK (kcal   >= 0),
-    CONSTRAINT ck_ingredient_name   CHECK (length(trim(name)) > 0)
-);
-
-CREATE TABLE nutrient (
-    id          BIGINT          PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    quantity    NUMERIC         NOT NULL,   -- reference daily intake
-    description TEXT            NOT NULL,   -- nutrient name, e.g. 'Protein'
-    unit        nutrient_unit   NOT NULL,
-    type        nutrient_type   NOT NULL,
-
-    CONSTRAINT uq_nutrient_description UNIQUE (description),
-    CONSTRAINT ck_nutrient_quantity    CHECK  (quantity > 0)
-);
-
-CREATE TABLE restriction (
-    id          BIGINT              PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    description TEXT                NOT NULL,
-    type        restriction_type    NOT NULL,
-
-    CONSTRAINT uq_restriction_description UNIQUE (description)
-);
-
-
-CREATE TABLE recipe (
-    id          BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    name        TEXT        NOT NULL,
-    guide       TEXT        NOT NULL,
-    kcal_sum    NUMERIC     NOT NULL,
-    servings    NUMERIC     NOT NULL DEFAULT 1,
-    user_email  TEXT        NOT NULL REFERENCES "user"(email) ON DELETE CASCADE,
-
-    CONSTRAINT uq_recipe_owner_name UNIQUE (user_email, name),
-    CONSTRAINT ck_recipe_kcal_sum   CHECK  (kcal_sum >= 0),
-    CONSTRAINT ck_recipe_servings   CHECK  (servings > 0)
-);
-
-CREATE TABLE post (
-    id           BIGINT          PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    media        BYTEA,
-    media_type   TEXT,
-    media_name   TEXT,
-    is_private   BOOLEAN         NOT NULL DEFAULT FALSE,
-    is_favourite BOOLEAN         NOT NULL DEFAULT FALSE,
-    status       post_status     NOT NULL DEFAULT 'draft',
-    created_at   TIMESTAMP       NOT NULL DEFAULT NOW(),
-    created_by   TEXT            NOT NULL REFERENCES "user"(email) ON DELETE CASCADE,
-    recipe_id    BIGINT          REFERENCES recipe(id) ON DELETE SET NULL,
-
-    CONSTRAINT ck_post_media_type CHECK (media IS NULL OR media_type IS NOT NULL),
-
-    CONSTRAINT uq_post_recipe UNIQUE (recipe_id)
-);
-
-CREATE TABLE comment (
-    id          BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    description TEXT        NOT NULL,
-    created_at  TIMESTAMP   NOT NULL DEFAULT NOW(),
-    post_id     BIGINT      NOT NULL REFERENCES post(id)     ON DELETE CASCADE,
-    created_by  TEXT        NOT NULL REFERENCES "user"(email) ON DELETE CASCADE,
-
-    CONSTRAINT ck_comment_not_empty CHECK (length(trim(description)) > 0)
-);
-
-CREATE TABLE grocery_list (
-    id          BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    date_time   TIMESTAMP   NOT NULL DEFAULT NOW(),
-    notes       TEXT,
-    is_bought   BOOLEAN     NOT NULL DEFAULT FALSE,
-    kcal        NUMERIC     NOT NULL,
-    user_email  TEXT        NOT NULL REFERENCES "user"(email) ON DELETE CASCADE,
-
-    CONSTRAINT ck_grocery_list_kcal CHECK (kcal >= 0)
-);
-
-CREATE TABLE intake_planner (
-    id          BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    date_time   TIMESTAMP   NOT NULL DEFAULT NOW(),
-    notes       TEXT,
-    kcal        NUMERIC     NOT NULL,
-    is_consumed BOOLEAN     NOT NULL DEFAULT FALSE,
-    user_email  TEXT        NOT NULL REFERENCES "user"(email) ON DELETE CASCADE,
-
-    CONSTRAINT ck_intake_planner_kcal CHECK (kcal >= 0)
-);
-
-
-CREATE TABLE biometrics (
-    id               BIGINT      PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
-    date             DATE        NOT NULL,
-    weight           NUMERIC,            -- kg
-    height           NUMERIC,            -- cm
-    age              NUMERIC,            -- years
-    muscle_fat_ratio NUMERIC,            -- lean mass / fat mass
-    user_email       TEXT        NOT NULL REFERENCES "user"(email) ON DELETE CASCADE,
-
-    CONSTRAINT uq_biometrics_user_date UNIQUE (user_email, date),
-    CONSTRAINT ck_biometrics_weight    CHECK  (weight IS NULL OR weight BETWEEN 20  AND 400),
-    CONSTRAINT ck_biometrics_height    CHECK  (height IS NULL OR height BETWEEN 50  AND 260),
-    CONSTRAINT ck_biometrics_age       CHECK  (age    IS NULL OR age    BETWEEN 5   AND 120),
-    CONSTRAINT ck_biometrics_ratio     CHECK  (muscle_fat_ratio IS NULL OR muscle_fat_ratio > 0)
-);
-
-
-
-CREATE TABLE recipe_contains_ingredient (
-    recipe_id           BIGINT      NOT NULL REFERENCES recipe(id)          ON DELETE CASCADE,
-    ingredient_name     TEXT        NOT NULL REFERENCES ingredient(name)    ON DELETE CASCADE,
-    ingredient_quantity NUMERIC     NOT NULL,
-    PRIMARY KEY (recipe_id, ingredient_name),
-
-    CONSTRAINT ck_rci_quantity CHECK (ingredient_quantity > 0)
-);
-
-
-CREATE TABLE ingredient_contains_nutrient (
-    ingredient_name TEXT        NOT NULL REFERENCES ingredient(name) ON DELETE CASCADE,
-    nutrient_id     BIGINT      NOT NULL REFERENCES nutrient(id)     ON DELETE CASCADE,
-    quantity        NUMERIC     NOT NULL,   -- amount per 100 g, in nutrient.unit
-    PRIMARY KEY (ingredient_name, nutrient_id),
-
-    CONSTRAINT ck_icn_quantity CHECK (quantity >= 0)
-);
-
-
-CREATE TABLE recipe_contains_restriction (
-    recipe_id       BIGINT      NOT NULL REFERENCES recipe(id)      ON DELETE CASCADE,
-    restriction_id  BIGINT      NOT NULL REFERENCES restriction(id) ON DELETE CASCADE,
-    PRIMARY KEY (recipe_id, restriction_id)
-);
-
-
-CREATE TABLE grocery_list_bulk_add_ingredient (
-    list_id     BIGINT      NOT NULL REFERENCES grocery_list(id) ON DELETE CASCADE,
-    recipe_id   BIGINT      NOT NULL REFERENCES recipe(id)       ON DELETE CASCADE,
-    PRIMARY KEY (list_id, recipe_id)
-);
-
-
-CREATE TABLE grocery_list_single_add_ingredient (
-    list_id             BIGINT      NOT NULL REFERENCES grocery_list(id)    ON DELETE CASCADE,
-    ingredient_name     TEXT        NOT NULL REFERENCES ingredient(name)    ON DELETE CASCADE,
-    buy_quantity        NUMERIC     NOT NULL,   -- grams to purchase
-    PRIMARY KEY (list_id, ingredient_name),
-
-    CONSTRAINT ck_glsai_quantity CHECK (buy_quantity > 0)
-);
-
-
-CREATE TABLE intake_planner_save_to_list_post (
-    planner_id  BIGINT      NOT NULL REFERENCES intake_planner(id) ON DELETE CASCADE,
-    post_id     BIGINT      NOT NULL REFERENCES post(id)           ON DELETE CASCADE,
-    PRIMARY KEY (planner_id, post_id)
-);
-
-
-
-CREATE INDEX idx_recipe_user            ON recipe(user_email);
-CREATE INDEX idx_post_user              ON post(created_by);
-CREATE INDEX idx_post_recipe            ON post(recipe_id);
-CREATE INDEX idx_post_feed              ON post(status, created_at DESC);
-CREATE INDEX idx_comment_post           ON comment(post_id);
-CREATE INDEX idx_comment_user           ON comment(created_by);
-CREATE INDEX idx_grocery_list_user      ON grocery_list(user_email);
-CREATE INDEX idx_intake_planner_user    ON intake_planner(user_email, date_time);
-CREATE INDEX idx_biometrics_user        ON biometrics(user_email, date DESC);
-CREATE INDEX idx_rci_ingredient         ON recipe_contains_ingredient(ingredient_name);
-CREATE INDEX idx_icn_nutrient           ON ingredient_contains_nutrient(nutrient_id);
-CREATE INDEX idx_rcr_restriction        ON recipe_contains_restriction(restriction_id);
-CREATE INDEX idx_glbai_recipe           ON grocery_list_bulk_add_ingredient(recipe_id);
-CREATE INDEX idx_glsai_ingredient       ON grocery_list_single_add_ingredient(ingredient_name);
-CREATE INDEX idx_ipsp_post              ON intake_planner_save_to_list_post(post_id);
-
-
-
-COMMENT ON TABLE  "user"            IS 'Every account of the system, regardless of role.';
-COMMENT ON COLUMN "user".password   IS 'BCrypt hash of the password - never store plaintext.';
-COMMENT ON COLUMN ingredient.energy IS 'Energy in kilojoules per 100 g of edible portion.';
-COMMENT ON COLUMN ingredient.kcal   IS 'Energy in kilocalories per 100 g of edible portion.';
-COMMENT ON COLUMN nutrient.quantity IS 'Reference daily intake (RDI) for an average adult.';
-COMMENT ON COLUMN recipe.kcal_sum   IS 'Derived: SUM(ingredient.kcal * quantity / 100) for the WHOLE recipe.';
-COMMENT ON COLUMN recipe.servings   IS 'Portions the recipe yields; kcal per portion = kcal_sum / servings.';
-COMMENT ON COLUMN post.recipe_id    IS 'Recipe this post is about; NULL for a plain photo/note post.';
-COMMENT ON COLUMN ingredient_contains_nutrient.quantity
-                                    IS 'Amount of the nutrient per 100 g of the ingredient, in nutrient.unit.';
-COMMENT ON COLUMN recipe_contains_ingredient.ingredient_quantity
-                                    IS 'Grams of the ingredient used by the whole recipe.';
-
-
Index: l/old/02_DML_nutritioneer.sql
===================================================================
--- sql/old/02_DML_nutritioneer.sql	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,639 +1,0 @@
--- ---------------------------------------------------------------------
--- Првобитни queries
--- ---------------------------------------------------------------------
-
-BEGIN;
-
-
-TRUNCATE "user", ingredient, nutrient, restriction, recipe, post, comment,
-         grocery_list, intake_planner, biometrics,
-         recipe_contains_ingredient, ingredient_contains_nutrient,
-         recipe_contains_restriction, grocery_list_bulk_add_ingredient,
-         grocery_list_single_add_ingredient, intake_planner_save_to_list_post
-    RESTART IDENTITY CASCADE;
-
-
-INSERT INTO "user" (email, username, password, role) VALUES
-('admin@nutritioneer.mk',           'admin_nutri',    '$2a$10$3fQ9kZ1vJ8sT2xLmN0oPqeRuVwXyZaBcDeFgHiJkLmNoPqRsTuVwX', 'administrator'),
-('viktor.ilievski@nutritioneer.mk', 'coach_viktor',   '$2a$10$3fQ9kZ1vJ8sT2xLmN0oPqeRuVwXyZaBcDeFgHiJkLmNoPqRsTuVwX', 'trainer'),
-('teodora.mihajlova@nutritioneer.mk','coach_teodora', '$2a$10$3fQ9kZ1vJ8sT2xLmN0oPqeRuVwXyZaBcDeFgHiJkLmNoPqRsTuVwX', 'trainer'),
-('marija.trajkovska@gmail.com',     'marija_t',       '$2a$10$3fQ9kZ1vJ8sT2xLmN0oPqeRuVwXyZaBcDeFgHiJkLmNoPqRsTuVwX', 'user'),
-('stefan.petrov@gmail.com',         'stefan_p',       '$2a$10$3fQ9kZ1vJ8sT2xLmN0oPqeRuVwXyZaBcDeFgHiJkLmNoPqRsTuVwX', 'user'),
-('ana.jovanovska@yahoo.com',        'ana_j',          '$2a$10$3fQ9kZ1vJ8sT2xLmN0oPqeRuVwXyZaBcDeFgHiJkLmNoPqRsTuVwX', 'user'),
-('nikola.stojanov@gmail.com',       'nikola_s',       '$2a$10$3fQ9kZ1vJ8sT2xLmN0oPqeRuVwXyZaBcDeFgHiJkLmNoPqRsTuVwX', 'user'),
-('elena.dimitrova@hotmail.com',     'elena_d',        '$2a$10$3fQ9kZ1vJ8sT2xLmN0oPqeRuVwXyZaBcDeFgHiJkLmNoPqRsTuVwX', 'user'),
-('bojan.kostov@gmail.com',          'bojan_k',        '$2a$10$3fQ9kZ1vJ8sT2xLmN0oPqeRuVwXyZaBcDeFgHiJkLmNoPqRsTuVwX', 'user'),
-('filip.angelov@gmail.com',         'filip_a',        '$2a$10$3fQ9kZ1vJ8sT2xLmN0oPqeRuVwXyZaBcDeFgHiJkLmNoPqRsTuVwX', 'user');
-
-
-INSERT INTO ingredient (name, description, energy, kcal, type) VALUES
-('Chicken breast',      'Skinless boneless chicken breast, raw',            690,  165, 'meat'),
-('Beef mince',          'Ground beef, 20 percent fat, raw',                1046,  250, 'meat'),
-('Pork shoulder',       'Pork shoulder cut, raw',                          1013,  242, 'meat'),
-('Trout fillet',        'Freshwater trout fillet, raw',                     619,  148, 'fish'),
-('Mackerel',            'Atlantic mackerel, raw',                           858,  205, 'fish'),
-('Egg',                 'Whole chicken egg, raw',                           598,  143, 'other'),
-('Cow milk',            'Pasteurised cow milk, 3.2 percent fat',            255,   61, 'dairy'),
-('Yogurt',              'Plain yogurt, 3.5 percent fat',                    264,   63, 'dairy'),
-('White brine cheese',  'Traditional white brine cheese (sirenje)',        1105,  264, 'dairy'),
-('Kashkaval',           'Semi-hard yellow cheese (kashkaval)',             1490,  356, 'dairy'),
-('Butter',              'Unsalted cow butter, 82 percent fat',             3000,  717, 'dairy'),
-('Sunflower oil',       'Refined sunflower oil',                           3699,  884, 'oil'),
-('Olive oil',           'Extra virgin olive oil',                          3699,  884, 'oil'),
-('White rice',          'Long grain white rice, uncooked',                 1527,  365, 'carb'),
-('Bulgur',              'Coarse bulgur wheat, uncooked',                   1431,  342, 'carb'),
-('White flour',         'Wheat flour type 500, all purpose',               1523,  364, 'carb'),
-('Spaghetti',           'Durum wheat spaghetti, dry',                      1552,  371, 'carb'),
-('White bread',         'White wheat bread, baked',                        1109,  265, 'carb'),
-('Oats',                'Rolled oat flakes',                               1628,  389, 'carb'),
-('Potato',              'White potato, raw, peeled',                        322,   77, 'vegetable'),
-('Dry white beans',     'Dry white beans (tetovec variety)',               1393,  333, 'legume'),
-('Lentils',             'Dry brown lentils',                               1473,  352, 'legume'),
-('Chickpeas',           'Dry chickpeas',                                   1523,  364, 'legume'),
-('Tomato',              'Fresh ripe tomato',                                 75,   18, 'vegetable'),
-('Red bell pepper',     'Fresh red bell pepper (kapija)',                   130,   31, 'vegetable'),
-('Onion',               'Yellow onion, raw',                                167,   40, 'vegetable'),
-('Garlic',              'Fresh garlic cloves',                              623,  149, 'vegetable'),
-('Cucumber',            'Fresh cucumber with skin',                          63,   15, 'vegetable'),
-('Aubergine',           'Fresh aubergine (eggplant)',                       105,   25, 'vegetable'),
-('Zucchini',            'Fresh green zucchini',                              71,   17, 'vegetable'),
-('Carrot',              'Fresh carrot, raw',                                172,   41, 'vegetable'),
-('Spinach',             'Fresh spinach leaves',                              96,   23, 'vegetable'),
-('Cabbage',             'White cabbage, raw',                               105,   25, 'vegetable'),
-('Leek',                'Fresh leek, raw',                                  255,   61, 'vegetable'),
-('Apple',               'Fresh apple with skin',                            218,   52, 'fruit'),
-('Banana',              'Fresh banana, peeled',                             372,   89, 'fruit'),
-('Strawberry',          'Fresh strawberries',                               134,   32, 'fruit'),
-('Grapes',              'Fresh table grapes',                               288,   69, 'fruit'),
-('Lemon',               'Fresh lemon, peeled',                              121,   29, 'fruit'),
-('Walnut',              'Shelled walnut kernels',                          2736,  654, 'nut'),
-('Almond',              'Raw almonds',                                     2423,  579, 'nut'),
-('Sunflower seed',      'Hulled sunflower seeds',                          2444,  584, 'seed'),
-('Chia seed',           'Whole chia seeds',                                2033,  486, 'seed'),
-('Parsley',             'Fresh flat leaf parsley',                          151,   36, 'herb'),
-('Dill',                'Fresh dill weed',                                  180,   43, 'herb'),
-('Salt',                'Refined table salt, iodised',                        0,    0, 'spice'),
-('Black pepper',        'Ground black pepper',                             1050,  251, 'spice'),
-('Paprika powder',      'Sweet red paprika powder',                        1180,  282, 'spice'),
-('Honey',               'Raw flower honey',                                1272,  304, 'other'),
-('Water',               'Drinking water',                                     0,    0, 'beverage');
-
-
-INSERT INTO nutrient (description, quantity, unit, type) VALUES
-('Protein',             50,   'gram',      'macro'),
-('Total carbohydrate', 260,   'gram',      'macro'),
-('Total fat',           70,   'gram',      'macro'),
-('Saturated fat',       20,   'gram',      'macro'),
-('Dietary fibre',       30,   'gram',      'macro'),
-('Sugars',              90,   'gram',      'macro'),
-('Vitamin C',           80,   'milligram', 'micro'),
-('Vitamin A',          800,   'microgram', 'micro'),
-('Vitamin D',            5,   'microgram', 'micro'),
-('Vitamin E',           12,   'milligram', 'micro'),
-('Vitamin K',           75,   'microgram', 'micro'),
-('Vitamin B12',        2.5,   'microgram', 'micro'),
-('Folate',             200,   'microgram', 'micro'),
-('Calcium',            800,   'milligram', 'micro'),
-('Iron',                14,   'milligram', 'micro'),
-('Magnesium',          375,   'milligram', 'micro'),
-('Potassium',         2000,   'milligram', 'micro'),
-('Zinc',                10,   'milligram', 'micro'),
-('Sodium',            2000,   'milligram', 'micro'),
-('Phosphorus',         700,   'milligram', 'micro'),
-('Selenium',            55,   'microgram', 'micro'),
-('Iodine',             150,   'microgram', 'micro');
-
-
-INSERT INTO restriction (description, type) VALUES
-('Contains no ingredient of animal origin',                 'vegan'),
-('Contains no meat and no fish',                            'vegetarian'),
-('Prepared according to Islamic dietary law',               'halal'),
-('Prepared according to Jewish dietary law',                'kosher'),
-('Fish and seafood allowed, no other meat',                 'pescatarian'),
-('Contains gluten from wheat, barley or rye',               'allergen'),
-('Contains milk protein and lactose',                       'allergen'),
-('Contains tree nuts',                                      'allergen'),
-('Contains egg',                                            'allergen'),
-('Contains fish',                                           'allergen'),
-('Low carbohydrate and high fat, ketogenic',                'keto'),
-('No grains, legumes or processed food',                    'paleo'),
-('Free of gluten containing cereals',                       'gluten_free'),
-('Free of lactose',                                         'lactose_free'),
-('Suitable for a diabetic meal plan, low glycaemic load',   'diabetic'),
-('Reduced sodium content, under 120 mg per 100 g',          'low_sodium');
-
-
-INSERT INTO ingredient_contains_nutrient (ingredient_name, nutrient_id, quantity)
-SELECT m.ing, n.id, v.qty
-FROM (VALUES
-    ('Chicken breast',     31.0,   0.0,   3.6,   0.0),
-    ('Beef mince',         26.0,   0.0,  15.0,   0.0),
-    ('Pork shoulder',      20.0,   0.0,  18.0,   0.0),
-    ('Trout fillet',       20.5,   0.0,   6.6,   0.0),
-    ('Mackerel',           19.0,   0.0,  13.9,   0.0),
-    ('Egg',                12.6,   0.7,   9.5,   0.0),
-    ('Cow milk',            3.3,   4.8,   3.2,   0.0),
-    ('Yogurt',              3.5,   4.7,   3.5,   0.0),
-    ('White brine cheese', 14.0,   2.0,  21.0,   0.0),
-    ('Kashkaval',          25.0,   1.5,  28.0,   0.0),
-    ('Butter',              0.9,   0.1,  81.0,   0.0),
-    ('Sunflower oil',       0.0,   0.0, 100.0,   0.0),
-    ('Olive oil',           0.0,   0.0, 100.0,   0.0),
-    ('White rice',          7.0,  80.0,   0.7,   1.3),
-    ('Bulgur',             12.0,  76.0,   1.3,  18.0),
-    ('White flour',        10.0,  76.0,   1.0,   2.7),
-    ('Spaghetti',          13.0,  75.0,   1.5,   3.2),
-    ('White bread',         9.0,  49.0,   3.2,   2.7),
-    ('Oats',               17.0,  66.0,   7.0,  11.0),
-    ('Potato',              2.0,  17.0,   0.1,   2.2),
-    ('Dry white beans',    21.0,  60.0,   1.2,  15.0),
-    ('Lentils',            25.0,  60.0,   1.1,  30.0),
-    ('Chickpeas',          19.0,  61.0,   6.0,  17.0),
-    ('Tomato',              0.9,   3.9,   0.2,   1.2),
-    ('Red bell pepper',     1.0,   6.0,   0.3,   2.1),
-    ('Onion',               1.1,   9.3,   0.1,   1.7),
-    ('Garlic',              6.4,  33.0,   0.5,   2.1),
-    ('Cucumber',            0.7,   3.6,   0.1,   0.5),
-    ('Aubergine',           1.0,   5.9,   0.2,   3.0),
-    ('Zucchini',            1.2,   3.1,   0.3,   1.0),
-    ('Carrot',              0.9,   9.6,   0.2,   2.8),
-    ('Spinach',             2.9,   3.6,   0.4,   2.2),
-    ('Cabbage',             1.3,   5.8,   0.1,   2.5),
-    ('Leek',                1.5,  14.0,   0.3,   1.8),
-    ('Apple',               0.3,  14.0,   0.2,   2.4),
-    ('Banana',              1.1,  23.0,   0.3,   2.6),
-    ('Strawberry',          0.7,   7.7,   0.3,   2.0),
-    ('Grapes',              0.7,  18.0,   0.2,   0.9),
-    ('Lemon',               1.1,   9.3,   0.3,   2.8),
-    ('Walnut',             15.0,  14.0,  65.0,   6.7),
-    ('Almond',             21.0,  22.0,  50.0,  12.5),
-    ('Sunflower seed',     21.0,  20.0,  51.0,   8.6),
-    ('Chia seed',          17.0,  42.0,  31.0,  34.0),
-    ('Parsley',             3.0,   6.3,   0.8,   3.3),
-    ('Dill',                3.5,   7.0,   1.1,   2.1),
-    ('Paprika powder',     14.0,  54.0,  13.0,  35.0),
-    ('Black pepper',       10.0,  64.0,   3.3,  25.0),
-    ('Honey',               0.3,  82.0,   0.0,   0.2)
-) AS m(ing, protein, carbohydrate, fat, fibre)
-CROSS JOIN LATERAL (VALUES
-    ('Protein',            m.protein),
-    ('Total carbohydrate', m.carbohydrate),
-    ('Total fat',          m.fat),
-    ('Dietary fibre',      m.fibre)
-) AS v(nut, qty)
-JOIN nutrient n ON n.description = v.nut;
-
-INSERT INTO ingredient_contains_nutrient (ingredient_name, nutrient_id, quantity)
-SELECT v.ing, n.id, v.qty
-FROM (VALUES
-    ('Red bell pepper', 'Vitamin C',    128.0),
-    ('Parsley',         'Vitamin C',    133.0),
-    ('Spinach',         'Vitamin C',     28.0),
-    ('Tomato',          'Vitamin C',     14.0),
-    ('Cabbage',         'Vitamin C',     36.0),
-    ('Strawberry',      'Vitamin C',     59.0),
-    ('Potato',          'Vitamin C',     20.0),
-    ('Lemon',           'Vitamin C',     53.0),
-    ('Carrot',          'Vitamin A',    835.0),
-    ('Spinach',         'Vitamin A',    469.0),
-    ('Red bell pepper', 'Vitamin A',    157.0),
-    ('Butter',          'Vitamin A',    684.0),
-    ('Trout fillet',    'Vitamin D',      6.5),
-    ('Mackerel',        'Vitamin D',     16.0),
-    ('Egg',             'Vitamin D',      2.0),
-    ('Beef mince',      'Vitamin B12',    2.6),
-    ('Trout fillet',    'Vitamin B12',    4.5),
-    ('Mackerel',        'Vitamin B12',    8.7),
-    ('Egg',             'Vitamin B12',    1.1),
-    ('Cow milk',        'Vitamin B12',    0.5),
-    ('Spinach',         'Folate',       194.0),
-    ('Lentils',         'Folate',       479.0),
-    ('Dry white beans', 'Folate',       388.0),
-    ('Parsley',         'Folate',       152.0),
-    ('Cow milk',        'Calcium',      120.0),
-    ('Yogurt',          'Calcium',      121.0),
-    ('White brine cheese','Calcium',    493.0),
-    ('Kashkaval',       'Calcium',      700.0),
-    ('Spinach',         'Calcium',       99.0),
-    ('Almond',          'Calcium',      269.0),
-    ('Chia seed',       'Calcium',      631.0),
-    ('Spinach',         'Iron',           2.7),
-    ('Lentils',         'Iron',           7.5),
-    ('Dry white beans', 'Iron',           5.5),
-    ('Beef mince',      'Iron',           2.6),
-    ('Oats',            'Iron',           4.7),
-    ('Chia seed',       'Iron',           7.7),
-    ('Almond',          'Magnesium',    270.0),
-    ('Oats',            'Magnesium',    177.0),
-    ('Spinach',         'Magnesium',     79.0),
-    ('Walnut',          'Magnesium',    158.0),
-    ('Potato',          'Potassium',    425.0),
-    ('Banana',          'Potassium',    358.0),
-    ('Spinach',         'Potassium',    558.0),
-    ('Tomato',          'Potassium',    237.0),
-    ('Dry white beans', 'Potassium',   1795.0),
-    ('Beef mince',      'Zinc',           4.8),
-    ('Lentils',         'Zinc',           3.3),
-    ('Oats',            'Zinc',           4.0),
-    ('Salt',            'Sodium',     38758.0),
-    ('White brine cheese','Sodium',    1200.0),
-    ('White bread',     'Sodium',       490.0),
-    ('Kashkaval',       'Sodium',       800.0),
-    ('Salt',            'Iodine',      2500.0),
-    ('Trout fillet',    'Selenium',      12.6),
-    ('Mackerel',        'Selenium',      44.1),
-    ('Chicken breast',  'Selenium',      22.8),
-    ('Chicken breast',  'Phosphorus',   210.0),
-    ('Cow milk',        'Phosphorus',    92.0),
-    ('Almond',          'Vitamin E',     25.6),
-    ('Sunflower oil',   'Vitamin E',     41.1),
-    ('Olive oil',       'Vitamin E',     14.4),
-    ('Spinach',         'Vitamin K',    483.0),
-    ('Parsley',         'Vitamin K',   1640.0)
-) AS v(ing, nut, qty)
-JOIN nutrient n ON n.description = v.nut;
-
-
-
-INSERT INTO recipe (name, guide, kcal_sum, servings, user_email) VALUES
-('Tavce Gravce',
- E'1. Soak the beans overnight, then boil until soft.\n2. Fry the chopped onion in oil, add paprika off the heat.\n3. Combine everything in a clay pan, season and bake at 200 C for 40 minutes.\n4. Serve hot with fresh bread.',
- 0, 6, 'marija.trajkovska@gmail.com'),
-
-('Shopska Salad',
- E'1. Dice the tomatoes and cucumbers, slice the onion thinly.\n2. Season with salt and olive oil and toss.\n3. Grate the white brine cheese generously over the top and serve immediately.',
- 0, 4, 'stefan.petrov@gmail.com'),
-
-('Turli Tava',
- E'1. Brown the beef mince with the onion.\n2. Cut all vegetables into large chunks and layer them in a deep tray.\n3. Add the meat, oil, spices and a cup of water.\n4. Bake covered at 190 C for 60 minutes, then 15 minutes uncovered.',
- 0, 6, 'ana.jovanovska@yahoo.com'),
-
-('Homemade Ajvar',
- E'1. Roast the peppers and aubergine until the skins blister, then peel them.\n2. Mince the flesh and cook it slowly in oil, stirring constantly for about two hours.\n3. Add garlic and salt near the end and jar while hot.',
- 0, 20, 'elena.dimitrova@hotmail.com'),
-
-('Grilled Trout with Parsley',
- E'1. Clean the fillets and pat them dry.\n2. Rub with olive oil, crushed garlic, salt and pepper.\n3. Grill 4 minutes per side and finish with chopped parsley and lemon.',
- 0, 2, 'nikola.stojanov@gmail.com'),
-
-('Chicken Bulgur Bowl',
- E'1. Boil the bulgur in twice its volume of water for 12 minutes.\n2. Grill the seasoned chicken breast and slice it.\n3. Assemble with diced tomato and cucumber, finish with olive oil.',
- 0, 3, 'viktor.ilievski@nutritioneer.mk'),
-
-('Oat Porridge with Banana and Walnuts',
- E'1. Simmer the oats in milk for 5 minutes, stirring.\n2. Pour into a bowl and top with sliced banana and crushed walnuts.\n3. Drizzle honey over the top.',
- 0, 2, 'marija.trajkovska@gmail.com'),
-
-('Lentil Soup',
- E'1. Saute the diced onion, carrot and garlic in olive oil.\n2. Add the rinsed lentils and 1.5 litres of water.\n3. Simmer for 35 minutes, season and blend half of the soup for a thicker texture.',
- 0, 5, 'bojan.kostov@gmail.com'),
-
-('Zelnik Spinach and Cheese Filling',
- E'1. Wilt the washed spinach and squeeze out all excess water.\n2. Crumble in the white brine cheese and mix with beaten egg and oil.\n3. Season lightly - the cheese is already salty. Use as filling for zelnik.',
- 0, 8, 'elena.dimitrova@hotmail.com'),
-
-('Tarator',
- E'1. Grate the cucumber and drain it briefly.\n2. Whisk the yogurt with crushed garlic, chopped dill, salt and olive oil.\n3. Fold in the cucumber and crushed walnuts, chill for one hour before serving.',
- 0, 4, 'ana.jovanovska@yahoo.com'),
-
-('Baked Mackerel with Vegetables',
- E'1. Slice the potatoes, carrots and onion and spread them on a tray with oil and salt.\n2. Bake at 200 C for 25 minutes.\n3. Place the cleaned mackerel on top and bake for another 20 minutes.',
- 0, 3, 'nikola.stojanov@gmail.com'),
-
-('Protein Pancakes',
- E'1. Blend the oats, eggs, milk and banana into a smooth batter.\n2. Rest the batter for 10 minutes.\n3. Fry small pancakes in a little butter over medium heat, about 2 minutes per side.',
- 0, 3, 'teodora.mihajlova@nutritioneer.mk'),
-
-('Grilled Chicken Salad',
- E'1. Grill the seasoned chicken breast and let it rest, then slice.\n2. Toss the spinach, tomato and cucumber with olive oil and salt.\n3. Arrange the chicken on top and serve at room temperature.',
- 0, 2, 'stefan.petrov@gmail.com'),
-
-('Stuffed Peppers with Rice',
- E'1. Core the peppers carefully without splitting them.\n2. Mix the rice, browned mince, onion, oil and spices.\n3. Fill the peppers, stand them upright in a pot with water and simmer for 50 minutes.',
- 0, 4, 'filip.angelov@gmail.com');
-
-
-INSERT INTO recipe_contains_ingredient (recipe_id, ingredient_name, ingredient_quantity)
-SELECT r.id, v.ing, v.qty
-FROM (VALUES
-    -- Tavce Gravce
-    ('Tavce Gravce','Dry white beans',500), ('Tavce Gravce','Onion',200),
-    ('Tavce Gravce','Red bell pepper',100), ('Tavce Gravce','Sunflower oil',60),
-    ('Tavce Gravce','Paprika powder',10),   ('Tavce Gravce','Salt',8),
-    ('Tavce Gravce','Black pepper',2),      ('Tavce Gravce','Parsley',15),
-    -- Shopska Salad
-    ('Shopska Salad','Tomato',400),         ('Shopska Salad','Cucumber',300),
-    ('Shopska Salad','Onion',80),           ('Shopska Salad','White brine cheese',150),
-    ('Shopska Salad','Olive oil',30),       ('Shopska Salad','Salt',5),
-    -- Turli Tava
-    ('Turli Tava','Beef mince',300),        ('Turli Tava','Potato',400),
-    ('Turli Tava','Aubergine',300),         ('Turli Tava','Zucchini',250),
-    ('Turli Tava','Red bell pepper',200),   ('Turli Tava','Tomato',250),
-    ('Turli Tava','Onion',150),             ('Turli Tava','Sunflower oil',50),
-    ('Turli Tava','Salt',10),               ('Turli Tava','Black pepper',3),
-    ('Turli Tava','Paprika powder',8),
-    -- Homemade Ajvar
-    ('Homemade Ajvar','Red bell pepper',2000), ('Homemade Ajvar','Aubergine',500),
-    ('Homemade Ajvar','Garlic',30),            ('Homemade Ajvar','Sunflower oil',200),
-    ('Homemade Ajvar','Salt',20),
-    -- Grilled Trout with Parsley
-    ('Grilled Trout with Parsley','Trout fillet',400), ('Grilled Trout with Parsley','Olive oil',20),
-    ('Grilled Trout with Parsley','Garlic',10),        ('Grilled Trout with Parsley','Parsley',20),
-    ('Grilled Trout with Parsley','Lemon',60),         ('Grilled Trout with Parsley','Salt',5),
-    ('Grilled Trout with Parsley','Black pepper',2),
-    -- Chicken Bulgur Bowl
-    ('Chicken Bulgur Bowl','Chicken breast',400), ('Chicken Bulgur Bowl','Bulgur',200),
-    ('Chicken Bulgur Bowl','Tomato',150),         ('Chicken Bulgur Bowl','Cucumber',100),
-    ('Chicken Bulgur Bowl','Olive oil',20),       ('Chicken Bulgur Bowl','Salt',5),
-    -- Oat Porridge with Banana and Walnuts
-    ('Oat Porridge with Banana and Walnuts','Oats',80),   ('Oat Porridge with Banana and Walnuts','Cow milk',250),
-    ('Oat Porridge with Banana and Walnuts','Banana',120),('Oat Porridge with Banana and Walnuts','Walnut',30),
-    ('Oat Porridge with Banana and Walnuts','Honey',20),
-    -- Lentil Soup
-    ('Lentil Soup','Lentils',300),   ('Lentil Soup','Carrot',150),
-    ('Lentil Soup','Onion',120),     ('Lentil Soup','Garlic',15),
-    ('Lentil Soup','Olive oil',30),  ('Lentil Soup','Salt',8),
-    ('Lentil Soup','Black pepper',2),
-    -- Zelnik Spinach and Cheese Filling
-    ('Zelnik Spinach and Cheese Filling','Spinach',500),  ('Zelnik Spinach and Cheese Filling','White brine cheese',300),
-    ('Zelnik Spinach and Cheese Filling','Egg',120),      ('Zelnik Spinach and Cheese Filling','Sunflower oil',40),
-    ('Zelnik Spinach and Cheese Filling','Salt',6),
-    -- Tarator
-    ('Tarator','Yogurt',500),   ('Tarator','Cucumber',300),
-    ('Tarator','Garlic',10),    ('Tarator','Dill',15),
-    ('Tarator','Olive oil',15), ('Tarator','Walnut',25),
-    ('Tarator','Salt',4),
-    -- Baked Mackerel with Vegetables
-    ('Baked Mackerel with Vegetables','Mackerel',500), ('Baked Mackerel with Vegetables','Potato',300),
-    ('Baked Mackerel with Vegetables','Carrot',150),   ('Baked Mackerel with Vegetables','Onion',100),
-    ('Baked Mackerel with Vegetables','Olive oil',25), ('Baked Mackerel with Vegetables','Salt',6),
-    ('Baked Mackerel with Vegetables','Black pepper',2),
-    -- Protein Pancakes
-    ('Protein Pancakes','Oats',100),    ('Protein Pancakes','Egg',150),
-    ('Protein Pancakes','Cow milk',150),('Protein Pancakes','Banana',100),
-    ('Protein Pancakes','Butter',15),
-    -- Grilled Chicken Salad
-    ('Grilled Chicken Salad','Chicken breast',300), ('Grilled Chicken Salad','Spinach',150),
-    ('Grilled Chicken Salad','Tomato',150),         ('Grilled Chicken Salad','Cucumber',100),
-    ('Grilled Chicken Salad','Olive oil',20),       ('Grilled Chicken Salad','Salt',4),
-    -- Stuffed Peppers with Rice
-    ('Stuffed Peppers with Rice','Red bell pepper',600), ('Stuffed Peppers with Rice','White rice',200),
-    ('Stuffed Peppers with Rice','Beef mince',250),      ('Stuffed Peppers with Rice','Onion',100),
-    ('Stuffed Peppers with Rice','Sunflower oil',40),    ('Stuffed Peppers with Rice','Salt',8),
-    ('Stuffed Peppers with Rice','Paprika powder',5)
-) AS v(recipe_name, ing, qty)
-JOIN recipe r ON r.name = v.recipe_name;
-
-INSERT INTO recipe_contains_restriction (recipe_id, restriction_id)
-SELECT r.id, res.id
-FROM (VALUES
-    ('Tavce Gravce','Contains no ingredient of animal origin'),
-    ('Tavce Gravce','Contains no meat and no fish'),
-    ('Tavce Gravce','Free of gluten containing cereals'),
-    ('Tavce Gravce','Free of lactose'),
-    ('Shopska Salad','Contains no meat and no fish'),
-    ('Shopska Salad','Contains milk protein and lactose'),
-    ('Shopska Salad','Free of gluten containing cereals'),
-    ('Turli Tava','Free of gluten containing cereals'),
-    ('Turli Tava','Free of lactose'),
-    ('Homemade Ajvar','Contains no ingredient of animal origin'),
-    ('Homemade Ajvar','Contains no meat and no fish'),
-    ('Homemade Ajvar','Free of gluten containing cereals'),
-    ('Homemade Ajvar','Free of lactose'),
-    ('Grilled Trout with Parsley','Fish and seafood allowed, no other meat'),
-    ('Grilled Trout with Parsley','Contains fish'),
-    ('Grilled Trout with Parsley','Free of gluten containing cereals'),
-    ('Grilled Trout with Parsley','Low carbohydrate and high fat, ketogenic'),
-    ('Chicken Bulgur Bowl','Contains gluten from wheat, barley or rye'),
-    ('Chicken Bulgur Bowl','Free of lactose'),
-    ('Oat Porridge with Banana and Walnuts','Contains no meat and no fish'),
-    ('Oat Porridge with Banana and Walnuts','Contains milk protein and lactose'),
-    ('Oat Porridge with Banana and Walnuts','Contains tree nuts'),
-    ('Lentil Soup','Contains no ingredient of animal origin'),
-    ('Lentil Soup','Contains no meat and no fish'),
-    ('Lentil Soup','Free of gluten containing cereals'),
-    ('Lentil Soup','Free of lactose'),
-    ('Zelnik Spinach and Cheese Filling','Contains no meat and no fish'),
-    ('Zelnik Spinach and Cheese Filling','Contains milk protein and lactose'),
-    ('Zelnik Spinach and Cheese Filling','Contains egg'),
-    ('Tarator','Contains no meat and no fish'),
-    ('Tarator','Contains milk protein and lactose'),
-    ('Tarator','Contains tree nuts'),
-    ('Tarator','Free of gluten containing cereals'),
-    ('Baked Mackerel with Vegetables','Fish and seafood allowed, no other meat'),
-    ('Baked Mackerel with Vegetables','Contains fish'),
-    ('Baked Mackerel with Vegetables','Free of lactose'),
-    ('Protein Pancakes','Contains no meat and no fish'),
-    ('Protein Pancakes','Contains milk protein and lactose'),
-    ('Protein Pancakes','Contains egg'),
-    ('Grilled Chicken Salad','Free of gluten containing cereals'),
-    ('Grilled Chicken Salad','Free of lactose'),
-    ('Grilled Chicken Salad','Suitable for a diabetic meal plan, low glycaemic load'),
-    ('Stuffed Peppers with Rice','Free of gluten containing cereals'),
-    ('Stuffed Peppers with Rice','Free of lactose')
-) AS v(recipe_name, restriction_description)
-JOIN recipe      r   ON r.name        = v.recipe_name
-JOIN restriction res ON res.description = v.restriction_description;
-
-
-INSERT INTO post (created_by, recipe_id, is_private, is_favourite, status, created_at, media, media_type, media_name)
-SELECT v.author, r.id, v.is_private, v.is_favourite, v.status::post_status, v.created_at::timestamp, NULL, NULL, v.media_name
-FROM (VALUES
-    ('marija.trajkovska@gmail.com',      'Tavce Gravce',                          FALSE, TRUE,  'published', '2026-01-12 18:20:00', 'tavce_gravce.jpg'),
-    ('stefan.petrov@gmail.com',          'Shopska Salad',                         FALSE, FALSE, 'published', '2026-01-14 12:05:00', 'shopska.jpg'),
-    ('ana.jovanovska@yahoo.com',         'Turli Tava',                            FALSE, TRUE,  'published', '2026-01-15 19:40:00', 'turli_tava.jpg'),
-    ('elena.dimitrova@hotmail.com',      'Homemade Ajvar',                        FALSE, TRUE,  'published', '2026-01-18 16:10:00', 'ajvar_jars.jpg'),
-    ('nikola.stojanov@gmail.com',        'Grilled Trout with Parsley',            FALSE, FALSE, 'published', '2026-01-20 20:00:00', 'trout.jpg'),
-    ('viktor.ilievski@nutritioneer.mk',  'Chicken Bulgur Bowl',                   FALSE, TRUE,  'published', '2026-01-21 13:30:00', 'bulgur_bowl.jpg'),
-    ('marija.trajkovska@gmail.com',      'Oat Porridge with Banana and Walnuts',  TRUE,  FALSE, 'published', '2026-01-22 08:15:00', 'porridge.jpg'),
-    ('bojan.kostov@gmail.com',           'Lentil Soup',                           FALSE, FALSE, 'draft',     '2026-01-23 17:45:00', NULL),
-    ('elena.dimitrova@hotmail.com',      'Zelnik Spinach and Cheese Filling',     FALSE, FALSE, 'archived',  '2026-01-05 11:00:00', 'zelnik.jpg'),
-    ('ana.jovanovska@yahoo.com',         'Tarator',                               FALSE, TRUE,  'published', '2026-01-25 14:20:00', 'tarator.jpg'),
-    ('nikola.stojanov@gmail.com',        'Baked Mackerel with Vegetables',        FALSE, FALSE, 'published', '2026-01-26 19:10:00', 'mackerel.jpg'),
-    ('teodora.mihajlova@nutritioneer.mk','Protein Pancakes',                      FALSE, TRUE,  'published', '2026-01-27 09:00:00', 'pancakes.jpg'),
-    ('stefan.petrov@gmail.com',          'Grilled Chicken Salad',                 TRUE,  FALSE, 'draft',     '2026-01-28 12:40:00', NULL),
-    ('filip.angelov@gmail.com',          'Stuffed Peppers with Rice',             FALSE, FALSE, 'published', '2026-01-29 18:55:00', 'peppers.jpg')
-) AS v(author, recipe_name, is_private, is_favourite, status, created_at, media_name)
-JOIN recipe r ON r.name = v.recipe_name;
-
-INSERT INTO post (created_by, recipe_id, is_private, is_favourite, status, created_at, media, media_type, media_name)
-VALUES ('marija.trajkovska@gmail.com', NULL, TRUE, FALSE, 'published', '2026-01-30 07:30:00',
-        decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64'),
-        'image/png', 'progress_week4.png');
-
-
-INSERT INTO comment (description, created_at, post_id, created_by)
-SELECT v.body, v.created_at::timestamp, p.id, v.author
-FROM (VALUES
-    ('Tavce Gravce',               'stefan.petrov@gmail.com',          'Exactly how my grandmother made it. Adding it to my planner.', '2026-01-12 19:02:00'),
-    ('Tavce Gravce',               'ana.jovanovska@yahoo.com',         'How long do the beans need to soak?',                          '2026-01-13 09:11:00'),
-    ('Tavce Gravce',               'marija.trajkovska@gmail.com',      'At least eight hours, overnight is best.',                     '2026-01-13 09:40:00'),
-    ('Shopska Salad',              'elena.dimitrova@hotmail.com',      'Simple and perfect. I use half the cheese to cut the sodium.',  '2026-01-14 13:20:00'),
-    ('Turli Tava',                 'bojan.kostov@gmail.com',           'Made it without the mince, still excellent.',                  '2026-01-16 20:15:00'),
-    ('Turli Tava',                 'viktor.ilievski@nutritioneer.mk',  'Good macro balance for a rest day.',                           '2026-01-16 21:00:00'),
-    ('Homemade Ajvar',             'marija.trajkovska@gmail.com',      'Two hours of stirring is no joke but worth every minute.',      '2026-01-19 10:05:00'),
-    ('Homemade Ajvar',             'filip.angelov@gmail.com',          'How many jars does this quantity fill?',                       '2026-01-19 11:30:00'),
-    ('Grilled Trout with Parsley', 'teodora.mihajlova@nutritioneer.mk','Very clean protein source, great for a cutting phase.',         '2026-01-21 08:45:00'),
-    ('Chicken Bulgur Bowl',        'stefan.petrov@gmail.com',          'This is now my default meal prep for the week.',                '2026-01-21 18:10:00'),
-    ('Chicken Bulgur Bowl',        'nikola.stojanov@gmail.com',        'Swapped bulgur for rice to keep it gluten free.',               '2026-01-22 12:25:00'),
-    ('Tarator',                    'stefan.petrov@gmail.com',          'Draining the cucumber really does make a difference.',          '2026-01-25 16:00:00'),
-    ('Baked Mackerel with Vegetables','ana.jovanovska@yahoo.com',      'The vitamin D content on this one is impressive.',              '2026-01-27 08:20:00'),
-    ('Protein Pancakes',           'marija.trajkovska@gmail.com',      'Thirty grams of protein in a breakfast, finally.',              '2026-01-27 10:15:00'),
-    ('Stuffed Peppers with Rice',  'elena.dimitrova@hotmail.com',      'Try it with a spoon of ajvar mixed into the filling.',          '2026-01-30 09:05:00')
-) AS v(recipe_name, author, body, created_at)
-JOIN recipe r ON r.name      = v.recipe_name
-JOIN post   p ON p.recipe_id = r.id;
-
-
-INSERT INTO grocery_list (date_time, notes, is_bought, kcal, user_email) VALUES
-('2026-01-11 10:00:00', 'Weekly shopping - Sunday cooking',        TRUE,  0, 'marija.trajkovska@gmail.com'),
-('2026-01-14 17:30:00', 'Vegetables for the tray bake',            TRUE,  0, 'ana.jovanovska@yahoo.com'),
-('2026-01-17 09:15:00', 'Ajvar season - green market',             TRUE,  0, 'elena.dimitrova@hotmail.com'),
-('2026-01-19 18:00:00', 'Fish market on Saturday morning',         FALSE, 0, 'nikola.stojanov@gmail.com'),
-('2026-01-22 11:45:00', 'Cheap protein week',                      FALSE, 0, 'bojan.kostov@gmail.com');
-
-INSERT INTO grocery_list_bulk_add_ingredient (list_id, recipe_id)
-SELECT g.id, r.id
-FROM (VALUES
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Tavce Gravce'),
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Oat Porridge with Banana and Walnuts'),
-    ('ana.jovanovska@yahoo.com',    '2026-01-14 17:30:00', 'Turli Tava'),
-    ('ana.jovanovska@yahoo.com',    '2026-01-14 17:30:00', 'Tarator'),
-    ('elena.dimitrova@hotmail.com', '2026-01-17 09:15:00', 'Homemade Ajvar'),
-    ('nikola.stojanov@gmail.com',   '2026-01-19 18:00:00', 'Grilled Trout with Parsley'),
-    ('nikola.stojanov@gmail.com',   '2026-01-19 18:00:00', 'Baked Mackerel with Vegetables'),
-    ('bojan.kostov@gmail.com',      '2026-01-22 11:45:00', 'Lentil Soup')
-) AS v(user_email, dt, recipe_name)
-JOIN grocery_list g ON g.user_email = v.user_email AND g.date_time = v.dt::timestamp
-JOIN recipe       r ON r.name       = v.recipe_name;
-
-INSERT INTO grocery_list_single_add_ingredient (list_id, ingredient_name, buy_quantity)
-SELECT g.id, v.ing, v.qty
-FROM (VALUES
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Sunflower oil',   1000),
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Salt',             500),
-    ('marija.trajkovska@gmail.com', '2026-01-11 10:00:00', 'Apple',           1500),
-    ('ana.jovanovska@yahoo.com',    '2026-01-14 17:30:00', 'Potato',          2000),
-    ('ana.jovanovska@yahoo.com',    '2026-01-14 17:30:00', 'White bread',      500),
-    ('elena.dimitrova@hotmail.com', '2026-01-17 09:15:00', 'Red bell pepper', 5000),
-    ('elena.dimitrova@hotmail.com', '2026-01-17 09:15:00', 'Garlic',           150),
-    ('nikola.stojanov@gmail.com',   '2026-01-19 18:00:00', 'Olive oil',        750),
-    ('nikola.stojanov@gmail.com',   '2026-01-19 18:00:00', 'Lemon',            300),
-    ('bojan.kostov@gmail.com',      '2026-01-22 11:45:00', 'Chickpeas',       1000),
-    ('bojan.kostov@gmail.com',      '2026-01-22 11:45:00', 'Carrot',           800),
-    ('bojan.kostov@gmail.com',      '2026-01-22 11:45:00', 'Onion',            700)
-) AS v(user_email, dt, ing, qty)
-JOIN grocery_list g ON g.user_email = v.user_email AND g.date_time = v.dt::timestamp;
-
-
-INSERT INTO intake_planner (date_time, notes, kcal, is_consumed, user_email) VALUES
-('2026-01-22 08:30:00', 'Breakfast',                         0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-22 13:00:00', 'Lunch at home',                     0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-22 19:30:00', 'Light dinner',                      0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-23 08:30:00', 'Breakfast',                         0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-23 13:15:00', 'Lunch',                             0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-24 09:00:00', 'Late breakfast after training',     0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-24 20:00:00', 'Dinner with family',                0, TRUE,  'marija.trajkovska@gmail.com'),
-('2026-01-25 12:30:00', 'Planned lunch - not eaten yet',     0, FALSE, 'marija.trajkovska@gmail.com'),
-('2026-01-22 12:45:00', 'Meal prep box 1',                   0, TRUE,  'stefan.petrov@gmail.com'),
-('2026-01-23 12:45:00', 'Meal prep box 2',                   0, TRUE,  'stefan.petrov@gmail.com'),
-('2026-01-26 19:45:00', 'Dinner',                            0, TRUE,  'nikola.stojanov@gmail.com'),
-('2026-01-27 09:30:00', 'Post workout breakfast',            0, TRUE,  'teodora.mihajlova@nutritioneer.mk'),
-('2026-01-28 13:00:00', 'Lunch',                             0, TRUE,  'ana.jovanovska@yahoo.com'),
-('2026-01-29 10:00:00', 'Coffee and fruit only',           180, TRUE,  'bojan.kostov@gmail.com');
-
-INSERT INTO intake_planner_save_to_list_post (planner_id, post_id)
-SELECT ip.id, p.id
-FROM (VALUES
-    ('marija.trajkovska@gmail.com',     '2026-01-22 08:30:00', 'Oat Porridge with Banana and Walnuts'),
-    ('marija.trajkovska@gmail.com',     '2026-01-22 13:00:00', 'Tavce Gravce'),
-    ('marija.trajkovska@gmail.com',     '2026-01-22 19:30:00', 'Shopska Salad'),
-    ('marija.trajkovska@gmail.com',     '2026-01-23 08:30:00', 'Protein Pancakes'),
-    ('marija.trajkovska@gmail.com',     '2026-01-23 13:15:00', 'Chicken Bulgur Bowl'),
-    ('marija.trajkovska@gmail.com',     '2026-01-24 09:00:00', 'Oat Porridge with Banana and Walnuts'),
-    ('marija.trajkovska@gmail.com',     '2026-01-24 20:00:00', 'Turli Tava'),
-    ('marija.trajkovska@gmail.com',     '2026-01-25 12:30:00', 'Grilled Trout with Parsley'),
-    ('stefan.petrov@gmail.com',         '2026-01-22 12:45:00', 'Chicken Bulgur Bowl'),
-    ('stefan.petrov@gmail.com',         '2026-01-23 12:45:00', 'Chicken Bulgur Bowl'),
-    ('nikola.stojanov@gmail.com',       '2026-01-26 19:45:00', 'Baked Mackerel with Vegetables'),
-    ('teodora.mihajlova@nutritioneer.mk','2026-01-27 09:30:00','Protein Pancakes'),
-    ('ana.jovanovska@yahoo.com',        '2026-01-28 13:00:00', 'Tarator'),
-    ('ana.jovanovska@yahoo.com',        '2026-01-28 13:00:00', 'Stuffed Peppers with Rice')
-) AS v(user_email, dt, recipe_name)
-JOIN intake_planner ip ON ip.user_email = v.user_email AND ip.date_time = v.dt::timestamp
-JOIN recipe          r ON r.name        = v.recipe_name
-JOIN post            p ON p.recipe_id   = r.id;
-
-
-INSERT INTO biometrics (date, weight, height, age, muscle_fat_ratio, user_email) VALUES
-('2026-01-05', 74.5, 168, 29, 1.80, 'marija.trajkovska@gmail.com'),
-('2026-01-12', 73.8, 168, 29, 1.84, 'marija.trajkovska@gmail.com'),
-('2026-01-19', 73.1, 168, 29, 1.90, 'marija.trajkovska@gmail.com'),
-('2026-01-26', 72.4, 168, 29, 1.95, 'marija.trajkovska@gmail.com'),
-('2026-01-06',  88.2, 182, 34, 2.10, 'stefan.petrov@gmail.com'),
-('2026-01-20',  87.0, 182, 34, 2.18, 'stefan.petrov@gmail.com'),
-('2026-01-27',  86.3, 182, 34, 2.24, 'stefan.petrov@gmail.com'),
-('2026-01-10',  61.0, 163, 41, 1.65, 'ana.jovanovska@yahoo.com'),
-('2026-01-24',  60.4, 163, 41, 1.70, 'ana.jovanovska@yahoo.com'),
-('2026-01-08',  95.7, 190, 27, 2.35, 'nikola.stojanov@gmail.com'),
-('2026-01-22',  94.9, 190, 27, 2.40, 'nikola.stojanov@gmail.com'),
-('2026-01-15',  68.3, 171, 36, 1.72, 'elena.dimitrova@hotmail.com'),
-('2026-01-29',  67.9, 171, 36, 1.75, 'elena.dimitrova@hotmail.com'),
-('2026-01-18',  79.0, 176, 31, 1.60, 'bojan.kostov@gmail.com');
-
-
-
-UPDATE recipe r
-SET kcal_sum = ROUND(COALESCE((
-        SELECT SUM(i.kcal * rci.ingredient_quantity / 100.0)
-        FROM recipe_contains_ingredient rci
-        JOIN ingredient i ON i.name = rci.ingredient_name
-        WHERE rci.recipe_id = r.id
-    ), 0), 2);
-
-UPDATE grocery_list g
-SET kcal = ROUND(
-      COALESCE((SELECT SUM(r.kcal_sum)
-                FROM grocery_list_bulk_add_ingredient b
-                JOIN recipe r ON r.id = b.recipe_id
-                WHERE b.list_id = g.id), 0)
-    + COALESCE((SELECT SUM(i.kcal * s.buy_quantity / 100.0)
-                FROM grocery_list_single_add_ingredient s
-                JOIN ingredient i ON i.name = s.ingredient_name
-                WHERE s.list_id = g.id), 0)
-    , 2);
-
-UPDATE intake_planner ip
-SET kcal = ROUND((
-        SELECT SUM(r.kcal_sum / r.servings)
-        FROM intake_planner_save_to_list_post sp
-        JOIN post   p ON p.id = sp.post_id
-        JOIN recipe r ON r.id = p.recipe_id
-        WHERE sp.planner_id = ip.id
-    ), 2)
-WHERE EXISTS (SELECT 1
-              FROM intake_planner_save_to_list_post sp
-              JOIN post p ON p.id = sp.post_id
-              WHERE sp.planner_id = ip.id AND p.recipe_id IS NOT NULL);
-
-COMMIT;
-
-
-SELECT 'user'                               AS table_name, count(*) AS rows FROM "user"
-UNION ALL SELECT 'ingredient',                 count(*) FROM ingredient
-UNION ALL SELECT 'nutrient',                   count(*) FROM nutrient
-UNION ALL SELECT 'restriction',                count(*) FROM restriction
-UNION ALL SELECT 'recipe',                     count(*) FROM recipe
-UNION ALL SELECT 'post',                       count(*) FROM post
-UNION ALL SELECT 'comment',                    count(*) FROM comment
-UNION ALL SELECT 'grocery_list',               count(*) FROM grocery_list
-UNION ALL SELECT 'intake_planner',             count(*) FROM intake_planner
-UNION ALL SELECT 'biometrics',                 count(*) FROM biometrics
-UNION ALL SELECT 'recipe_contains_ingredient', count(*) FROM recipe_contains_ingredient
-UNION ALL SELECT 'ingredient_contains_nutrient', count(*) FROM ingredient_contains_nutrient
-UNION ALL SELECT 'recipe_contains_restriction', count(*) FROM recipe_contains_restriction
-UNION ALL SELECT 'grocery_list_bulk_add_ingredient',   count(*) FROM grocery_list_bulk_add_ingredient
-UNION ALL SELECT 'grocery_list_single_add_ingredient', count(*) FROM grocery_list_single_add_ingredient
-UNION ALL SELECT 'intake_planner_save_to_list_post',   count(*) FROM intake_planner_save_to_list_post
-ORDER BY 1;
-
Index: nnel_scripta.cmd
===================================================================
--- tunnel_scripta.cmd	(revision 98cd519d004627b2c74881b58f5ffaae3de2622c)
+++ 	(revision )
@@ -1,15 +1,0 @@
-@echo off
-echo "PRED DA JA STARTUVATE SKRIPTATA TREBA VO NEA DA GI STAVITE"
-echo "VASHETO KORISNICHKO IME I LOZINKA ZA TUNELOT."
-echo "PO STARTUVANJETO, BAZATA KJE BIDE DOSTAPNA NA PORTA LOCALHOST:9999"
-echo "AKO SAKATE DA SLUSHA NA DRUGA PORTA, SMENETE JA SKRIPTATA SOODVETNO"
-echo "AKO SAKATE DA STOPIRATE (CTRL+C)"
-pause
-
-plink -v -ssh -2 -C -N -L 9999:localhost:5432 -pw 7253539c t_nutritioneer@194.149.135.130
-
-echo "VRSKATA TREBA DA STOI OTVORENA SE DODEKA IMATE PRISTAP DO SERVEROT"
-echo "OVAA PORAKA KAZHUVA DEKA TUNELOT VEKJE SE ZATVORIL"
-echo "AKO TOA SE SLUCHILO SAMO OD SEBE, IMATE PROBLEM SO VOSPOSTAVUVANJE"
-echo "NA VRSKATA DO SERVEROT (BLOKADA) ILI VI SE POGRESHNI PARAMETRITE"
-pause
