Skip to content
Snippets Groups Projects
Commit d61b3a0d authored by veronikaovsyannikova's avatar veronikaovsyannikova
Browse files

readme updated

parent e8843a41
Branches main
No related tags found
No related merge requests found
# Recipe Site
## Description
The platform will allow users to select ingredients they have available, and the system will suggest recipes that can be made using those ingredients.
The platform will allow users to select ingredients they have available, and the system will suggest recipes that can be
made using those ingredients.
Users can search for recipes, save their favorite ones, and rate/review the recipes. The platform will feature:
- An ingredient filter
- User accounts
- Recipe browsing
......@@ -10,34 +13,180 @@ Users can search for recipes, save their favorite ones, and rate/review the reci
- Personalized recommendations based on user activity
## Subject Area
The main goal of this project is to create a **backend** connected to **multiple databases** with a **simple frontend**.
## Functional Requirements
- **FQ0:** Users must be able to register and log in.
- **FQ1:** Users should be able to search for recipes by selecting multiple ingredients.
- **FQ2:** Users should be able to filter recipes by rating and date of publication.
- **FQ3:** Users can save favorite recipes and view their history of saved and viewed recipes.
- **FQ4:** The system should suggest recipes based on a user’s activity, such as previously saved recipes or highly rated recipes.
- **FQ4:** The system should suggest recipes based on a user’s activity, such as previously saved recipes or highly
rated recipes.
- **FQ5:** Users can leave ratings and reviews on recipes.
---
## Database System Selection
### **PostgreSQL**
- **Use Case**: Ingredients, Recipes, User Accounts, and Profiles
- **Reason**: Recipes, ingredients, and user information have a structured format. Using foreign keys and indexes will ensure efficient search and filtering of recipes.
- **Reason**: Recipes, ingredients, and user information have a structured format. Using foreign keys and indexes will
ensure efficient search and filtering of recipes.
#### **Implemented queries**
In the PostgreSQL database, information about users and recipes is stored.
To find recipes based on parameters, this query is used:
```sql
SELECT r
FROM Recipe r
LEFT JOIN r.ingredients ing
WHERE
(LOWER(r.title) LIKE LOWER(CONCAT('%', :title, '%')) OR :title IS NULL)
AND (:categories IS NULL OR r.category IN :categories)
AND (:ingredientIds IS NULL OR ing.id IN :ingredientIds)
ORDER BY
CASE WHEN :sortParam = 'rating_desc' THEN r.averageRating END DESC,
CASE WHEN :sortParam = 'updated_desc' THEN r.updatedAt END DESC,
CASE WHEN :sortParam = 'updated_asc' THEN r.updatedAt END ASC,
CASE WHEN :sortParam = 'rating_asc' THEN r.averageRating END ASC
```
### **MongoDB**
- **Use Case**: Search History. It is displayed in the user account page.
- **Reason**: MongoDB is suited for semi-structured data (e.g., user search history). It can store a flexible format of
search data.
#### **Implemented Queries**
In MongoDB I save search history with provided parameters and a list of results with recipe ids.
I am using `UserSearchRepository` provided by Spring Boot for storing search results.
To find top3 search result I am using `MongoTemplate` and this query.
```kotlin
val agg = newAggregation(
unwind("searches"),
unwind("searches.results"),
group("searches.results").count().`as`("count"),
sort(Sort.by(Sort.Direction.DESC, "count")),
limit(3),
project()
.andExpression("_id").`as`("recipeId")
.and("count").`as`("count")
)
val results = mongoTemplate.aggregate(agg, "user_searches", Document::class.java)
return results.mappedResults.mapNotNull {
it.getLong("recipeId")
}
```
### **Redis**
- **Use Case**: Caching Frequently Accessed Data. Top 3 most frequently searched recipes are cached in Redis.
- **Reason**: Redis handles high-speed data access, making it ideal for session management. It can improve load times and reduce database queries by caching popular searches or recipes.
- **Reason**: Redis handles high-speed data access, making it ideal for session management. It can improve load times
and reduce database queries by caching popular searches or recipes.
### **MongoDB**
- **Use Case**: Search History. It is displayed in user account page.
- **Reason**: MongoDB is suited for semi-structured data (e.g., user search history). It can store a flexible format of search data.
#### **Implemented Queries**
I am using search result data from MongoDB to define the top-3 recipes that are displayed in most searches.
And I store these recipes in the cache.
For queries and cache management, I am using `RedisTemplate` provided by Spring Boot.
### **Cassandra**
- **Use Case**: Activity Log. It is accessible through user account page.
- **Reason**: Cassandra offers high write throughput, which is useful for recording every user action (recipe searches, clicks, interactions). It can efficiently handle large volumes of log entries across multiple nodes.
- **Use Case**: Activity Log. It is accessible through the user account page.
- **Reason**: Cassandra offers high write throughput, which is useful for recording every user action (recipe searches,
clicks, interactions). It can efficiently handle large volumes of log entries across multiple nodes.
#### **Implemented Queries**
When a user performs actions such as adding/removing a friend, opening a recipe, adding/removing a favorite, rating a
recipe, etc., the information about it is inserted into the `user_actions` table.
Later, I display logs for the user in their account page.
For managing user action rows, I used the `CassandraRepository` provided by Spring Boot.
### **Neo4j**
- **Use Case**: Personalized Recipe Recommendations. User can display personal recommendation by clicking button on home page. It will show recipes that are in favorites or were rated no less than 4 by user's friends or their friends.
- **Reason**: Neo4j excels at managing complex relationships. Ideal for generating recommendations based on user behavior, as it can traverse relationship graphs effectively.
- **Use Case**: Personalized Recipe Recommendations. A user can display personal recommendations by clicking a button
on the home page. It will show recipes that are in favorites or were rated no less than 4 by the user’s friends
or their friends.
- **Reason**: Neo4j excels at managing complex relationships. It is ideal for generating recommendations based on user
behavior, as it can traverse relationship graphs effectively.
#### **Implemented Queries**
Query for searching recommendations for a user. Returns information about recipes that were added to favorites by the
user’s friends or their friends, or rated no less than 4.
```cypher
MATCH path=(u:User {userName: '$userName'})-[:FRIEND*1..2]->(friend:User)
WITH friend, size(relationships(path)) AS hopCount
MATCH (friend)-[fav:FAVORITED]->(r:Recipe)
WHERE r IS NOT NULL
WITH friend, r, hopCount,
CASE hopCount
WHEN 1 THEN 'friend'
ELSE 'friend of friend'
END AS relationshipReason
RETURN friend.userName AS friendName,
r.recipeId AS recipeId,
r.title AS recipeTitle,
relationshipReason + ' favorited' AS reason
UNION
MATCH path=(u:User {userName: '$userName'})-[:FRIEND*1..2]->(friend:User)
WITH friend, size(relationships(path)) AS hopCount
MATCH (friend)-[rat:RATED]->(r2:Recipe)
WHERE r2 IS NOT NULL AND rat.value >= 4
WITH friend, r2, rat, hopCount,
CASE hopCount
WHEN 1 THEN 'friend'
ELSE 'friend of friend'
END AS relationshipReason
RETURN friend.userName AS friendName,
r2.recipeId AS recipeId,
r2.title AS recipeTitle,
relationshipReason + ' rated ' + toString(rat.value) AS reason
```
Query for managing friendship, favorite, and rated relations, based on which recommendations are returned.
```cypher
MATCH (u:User {userName: ?#{#userName}})-[rel:RATED]->(r:Recipe {recipeId: ?#{#recipeId}})
DELETE rel
```
```cypher
MERGE (u:User {userName: ?#{#userName}})
MERGE (f:User {userName: ?#{#friendName}})
MERGE (u)-[:FRIEND]->(f)
```
```cypher
MATCH (u:User {userName: ?#{#userName}}), (r:Recipe {recipeId: ?#{#recipeId}})
MERGE (u)-[:FAVORITED]->(r)
```
```cypher
MATCH (u:User {userName: ?#{#userName}})-[rel:FRIEND]->(f:User {userName: ?#{#friendName}})
DELETE rel
```
```cypher
MATCH (u:User {userName: ?#{#userName}})-[rel:FAVORITED]->(r:Recipe {recipeId: ?#{#recipeId}})
DELETE rel
```
```cypher
MATCH (u:User {userName: ?#{#userName}}), (r:Recipe {recipeId: ?#{#recipeId}})
MERGE (u)-[rel:RATED]->(r)
SET rel.value = ?#{#value}
```
......@@ -95,7 +95,6 @@ class HomeController(
userActionLoggingService.logSearch(it.userName, title, categories, ingredientIds, sort)
}
}
}
......
......@@ -57,8 +57,7 @@ class UserController(
if (principal == null) return "redirect:/login"
val user = userRepository.findByEmail(principal.username) ?: return "redirect:/login"
val userLogs = userActionLogRepository.findAll().filter { it.userId == user.userName }
.sortedByDescending { it.timestamp }
val userLogs = userActionLogRepository.findByUserId(user.userName)
model.addAttribute("isLogsEmpty", userLogs.isEmpty())
model.addAttribute("username", user.userName)
......
......@@ -7,5 +7,5 @@ import java.util.*
@Repository
interface UserActionLogRepository : CassandraRepository<UserActionLog, UUID> {
fun findByUserId(userId: String): List<UserActionLog>
}
\ No newline at end of file
......@@ -11,28 +11,6 @@ import org.springframework.data.repository.query.Param
@Repository
interface UserNodeRepository : Neo4jRepository<UserNode, String> {
@Query(
value = """
MATCH (u:User {userName: ?#{#userName}})-[:FRIEND*1..2]->(friend:User)
OPTIONAL MATCH (friend)-[fav:FAVORITED]->(r:Recipe)
WHERE r IS NOT NULL
RETURN friend.userName AS friendName,
r.recipeId AS recipeId,
r.title AS recipeTitle,
'favorited' AS reason
UNION
MATCH (u:User {userName: ?#{#userName}})-[:FRIEND*1..2]->(friend:User)
OPTIONAL MATCH (friend)-[rat:RATED]->(r2:Recipe)
WHERE r2 IS NOT NULL AND rat.value >= 4
RETURN friend.userName AS friendName,
r2.recipeId AS recipeId,
r2.title AS recipeTitle,
'rated ' + toString(rat.value) AS reason
"""
)
fun getRecommendations(@Param("userName") userName: String): List<RecommendationProjection>
@Query(
"""
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment