- Table of Contents
- MealFinderApp Guide
- MealFinderServer Guide
Clarification for User Authentication System: The user register and login system is implemented for server safety and security. We want to make sure that only authorized users can access certain services. We believe that's common practice in real-world applications.
API Usage Clarification: Because we are using the free version of Spoonacular API (a third-party API for recipe search), the API has a limit on the number of requests on a daily basis. If you encounter any usage issues, usually Failed to fetch instructions for recipe
, please inform us or replace the api key in the utils/RecipeService.swift
file.
Available API Keys:
eb7eed994fc64755be038c4e3f7cc3c6
93a2224a02154eefb3fb06b594e4b993
Server Availability Clarification: Although we have designed the server in a proper way and used several methods to ensure the server is available in most cases, there might be possibility that the server is not available due to vcm issues or other reasons. If you encounter any server issues, please inform us and we will try to fix it as soon as possible.
Accounts:
- Username:
Carl
, Password:111
- Username:
Kejia
, Password:222
- Username:
Ric
, Password:000
clone the repo and run the app on Xcode.
Use preregistred accounts to login or register a new account in the initial screen.
- Search Bar: Search for recipes by ingredients.
- Search By Image: Take a photo or upload an image to search for recipes.
- After searching, results will be displayed in a pop-up window.
- Swipe left or right to view different recipes and click on the recipe to view details.
- Click
Save
to save the recipe to your local favorite library. - Click
Share
Button to share the recipe on the forum as a post.
- Click list items to view recipes saved in the library.
- Swipe left to delete a recipe from the library.
- Detail page also has the
Share
button to share the recipe on the forum as a post.
- View posts from other users.
- Click on a post to view details.
- Click
Like
orDislike
to like or dislike a post or a comment. - Click
Add Comment
to add a comment to the post. - Click
Reply
to reply to a comment. - Click
Delete
to delete a post or a comment (only the author can delete).
- Click
Log out
to log out of the app.
A running instance contains all services is deployed on Duke VCM. For code review use, you can use mobile app to interact with the server directly.
To deploy the server on you own server or local machine, follow the steps below:
- Prerequisites: (ensure you have these services running and add
.env
file in the root directory)
- RabbitMQ
- Redis
- S3 Bucket
- PostgreSQL
- Set up Swift Web Server
- clone the server repo
- Install Swift (version 6.0)
- run
Swift run build
to build the project - run
Swift run App
to start the server
- Set up ML Server
- clone the server repo
- Setup and Install Poetry for Python 3.13
- run
poetry install
to install dependencies - run
python main.py
to start the server
vapor run migrate
vapor run migrate --revert
vapor run migrate --revert-all
- Microservice: Decoupled RESTful API service with Machine Learning model for image recognition
- 3 phase:
- Task registration (By Swift Web Server): save image to S3 bucket and register task to RabbitMQ
- Task processing (By Machine Learning Model Server): consume task from RabbitMQ, download image from S3 bucket, process image with ML model, save result to Redis
- Task result query (By Swift Web Server): query result from Redis by task id
- Technologies:
- Swift Web Server: Vapor
- Machine Learning Model Server: Python Flask with PyTorch
- Message Queue: RabbitMQ
- Image Storage: S3
- Cache: Redis
- Basic Structure:
For better scalability and flexibility, the ML consumer server is designed to be a separate service from the main web server.
- Written in Python
- Pika for RabbitMQ
- Poetry for dependency management
- Designed AI agents based on OpenAI GPT-4o-mini as Image Recognition Model
To run the ML server, ensure you have Python 3.13 installed and use poetry install
to install dependencies.
-
Create a new user
-
Request Body:
{ "username": "username", "email": "email", "password": "password" }
-
Response Body:
{ "id": "id", "username": "username", "email": "email" }
- Get user info by id
- Response Body:
{ "id": "id", "username": "username", "email": "email" }
- Login user to get token
- Request Body:
{ "username": "username", "password": "password" }
- Response Body:
{ "token": "token" }
- Logout user
- token required
- Get detailed user info
- token required
- Response Body:
{ "id": "id", "username": "username", "email": "email" }
- Update user info
- token required
- won't invalidate token
- Request Body:
{ "username": "username", "email": "email", "password": "password" }
- Response Body:
{ "id": "id", "username": "username", "email": "email" }
- Delete user
- token required
-
set posts by certain order in certain quantity
-
Request Body:
struct IndexByOrderWithQuantityRequest: Content { var order: PostOrder? var index: Int? var offset: Int? var direction: PostDirection? } enum PostOrder: String, Content { case createdAt = "created_at" case updatedAt = "updated_at" case likes = "likes" // case dislikes = "dislikes" } enum PostDirection: String, Content { case asc = "asc" case desc = "desc" }
- Get post by id
- Get direct comments of post by id
-
Create a new post
-
token required
-
Request Body:
struct CreatePostRequest: Content { var title: String var content: String var recipe: CreateRecipeRequest } struct CreateRecipeRequest: Content { var title: String var content: String var ingredients: [String] var image: String // url }
- Like a post
- token required
- Dislike a post
- token required
- Create a new comment on post
- token required
- Request Body:
struct CreateCommentRequest: Content { var title: String var content: String }
- Update post
- token required
- Request Body:
struct UpdatePostRequest: Content { var title: String? var content: String? }
- Delete post
- token required
- fetch all child comments of certain comment
- create a new comment on certain comment
- token required
- Request Body:
struct CreateCommentRequest: Content { var title: String var content: String }
- Like a comment
- token required
- Dislike a comment
- token required
- Delete comment
- token required
- Register a new task with an image
- token required
- Compress image before sending
- Request Body:
struct CreateTaskRequest: Content { var image: File }
- Response Body:
struct TaskDTO: Content { var taskID: UUID var key: String var url: String }
- Get task info by id
- token required
- Request Body:
struct CheckTaskResponse: Content { var taskID: UUID var status: TaskStatus var result: [String]? // the result of the task }
- Response Sample:
{ "taskID": "9F28A645-07E8-43F3-B646-AF766C88017D", "result": [ "flour, eggs, milk, butter, white sugar, brown sugar, baking powder, salt" ], "status": "completed" }
{ "taskID": "3B3A71BA-EAF5-4D6A-A574-E3E68DFFCC5B", "result": [ "cauliflower, carrots, broccoli, lettuce, garlic, yellow bell pepper, tomatoes, green bell pepper, zucchini, red bell pepper, celery, green onions, white radishes, parsley, green beans" ], "status": "completed" }