Skip to content

Instantly share code, notes, and snippets.

@hoangsonww
Last active December 20, 2024 15:02
Show Gist options
  • Select an option

  • Save hoangsonww/4715b7cb1fd24217cdaa2d38a8001c8a to your computer and use it in GitHub Desktop.

Select an option

Save hoangsonww/4715b7cb1fd24217cdaa2d38a8001c8a to your computer and use it in GitHub Desktop.
A Ruby on Rails API for a Recipe Sharing Platform that supports user authentication, recipe management, commenting, and admin controls with token-based security.
# frozen_string_literal: true
require 'rails/all'
# Basic Application Setup
class RecipeSharingApp < Rails::Application
config.load_defaults 6.1
config.api_only = true
end
# Models
class User < ApplicationRecord
has_secure_password
has_many :recipes
has_many :comments
before_create :generate_auth_token
def generate_auth_token
self.auth_token = SecureRandom.hex(10)
end
end
class Recipe < ApplicationRecord
belongs_to :user
has_many :ingredients, dependent: :destroy
has_many :steps, dependent: :destroy
has_many :comments, dependent: :destroy
accepts_nested_attributes_for :ingredients, :steps
end
class Ingredient < ApplicationRecord
belongs_to :recipe
end
class Step < ApplicationRecord
belongs_to :recipe
end
class Comment < ApplicationRecord
belongs_to :recipe
belongs_to :user
end
# Controllers
class ApplicationController < ActionController::API
before_action :authenticate_user
private
def authenticate_user
token = request.headers['Authorization']
@current_user = User.find_by(auth_token: token)
render json: { error: 'Unauthorized' }, status: :unauthorized if @current_user.nil?
end
def current_user
@current_user
end
end
class UsersController < ApplicationController
skip_before_action :authenticate_user, only: [:create]
def create
user = User.new(user_params)
if user.save
render json: { message: 'User created successfully', token: user.auth_token }, status: :created
else
render json: user.errors, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password, :password_confirmation)
end
end
class SessionsController < ApplicationController
skip_before_action :authenticate_user, only: [:create]
def create
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
render json: { token: user.auth_token }, status: :ok
else
render json: { error: 'Invalid credentials' }, status: :unauthorized
end
end
end
class RecipesController < ApplicationController
before_action :set_recipe, only: [:show, :update, :destroy]
before_action :authorize_admin, only: [:destroy]
def index
recipes = Recipe.all.includes(:ingredients, :steps)
render json: recipes
end
def create
recipe = current_user.recipes.build(recipe_params)
if recipe.save
render json: recipe, status: :created
else
render json: recipe.errors, status: :unprocessable_entity
end
end
def show
render json: @recipe, include: [:ingredients, :steps, :comments]
end
def update
if @recipe.update(recipe_params)
render json: @recipe
else
render json: @recipe.errors, status: :unprocessable_entity
end
end
def destroy
@recipe.destroy
head :no_content
end
private
def set_recipe
@recipe = Recipe.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: 'Recipe not found' }, status: :not_found
end
def recipe_params
params.require(:recipe).permit(:title, :description, ingredients_attributes: [:name, :quantity], steps_attributes: [:description])
end
def authorize_admin
render json: { error: 'Forbidden' }, status: :forbidden unless current_user&.admin?
end
end
class CommentsController < ApplicationController
def index
recipe = Recipe.find(params[:recipe_id])
render json: recipe.comments
end
def create
recipe = Recipe.find(params[:recipe_id])
comment = recipe.comments.build(comment_params.merge(user: current_user))
if comment.save
render json: comment, status: :created
else
render json: comment.errors, status: :unprocessable_entity
end
end
private
def comment_params
params.require(:comment).permit(:content)
end
end
# Database Migration Setup
ActiveRecord::Schema.define do
create_table :users, force: :cascade do |t|
t.string :name
t.string :email
t.string :password_digest
t.boolean :admin, default: false
t.string :auth_token
t.timestamps
end
create_table :recipes, force: :cascade do |t|
t.string :title
t.text :description
t.references :user, foreign_key: true
t.timestamps
end
create_table :ingredients, force: :cascade do |t|
t.string :name
t.string :quantity
t.references :recipe, foreign_key: true
t.timestamps
end
create_table :steps, force: :cascade do |t|
t.text :description
t.references :recipe, foreign_key: true
t.timestamps
end
create_table :comments, force: :cascade do |t|
t.text :content
t.references :recipe, foreign_key: true
t.references :user, foreign_key: true
t.timestamps
end
end
Rails.application.routes.draw do
# User registration and login routes
post '/register', to: 'users#create'
post '/login', to: 'sessions#create'
# Routes for recipe management
resources :recipes, only: [:index, :create, :show, :update, :destroy] do
resources :comments, only: [:create, :index] # Nested route for comments on recipes
end
# Admin-specific routes
namespace :admin do
resources :recipes, only: [:destroy] # Admin can delete any recipe
end
end
@hoangsonww
Copy link
Author

Recipe Sharing Platform API

This project is a Recipe Sharing Platform built using the Ruby on Rails framework. It provides a backend API that allows users to register and log in, create and manage recipes, add ingredients and steps to each recipe, and comment on recipes. The platform supports role-based access control for admin users to manage content effectively.

Features

  • User Authentication: Secure user registration and login using token-based authentication.
  • Recipe Management: Create, update, delete, and retrieve recipes with associated ingredients and steps.
  • Commenting: Users can comment on recipes.
  • Admin Controls: Admin users have additional privileges, such as deleting any recipe.
  • RESTful API: Provides a clean and well-structured RESTful API for managing all aspects of the recipe-sharing platform.
  • Single-File Consolidation: All core logic is consolidated in a single Ruby script for simplicity.

Getting Started

Prerequisites

  • Ruby (version 2.7 or later)
  • Rails (version 6.1 or later)
  • SQLite3 (or any preferred database)

Installation

  1. Clone the repository:

    git clone https://github.com/yourusername/recipe-sharing-app.git
    cd recipe-sharing-app
  2. Install required gems:

    Install the required gems by running:

    bundle install
  3. Set up the database:

    Run the database migrations to set up the SQLite3 database:

    rails db:migrate
  4. Run the Rails server:

    Start the Rails server:

    rails server

    The API will be available at http://localhost:3000.

API Endpoints

User Management

  • Register a User

    • Endpoint: POST /register
    • Description: Creates a new user account.
    • Request Body:
      {
        "name": "John Doe",
        "email": "[email protected]",
        "password": "password123",
        "password_confirmation": "password123"
      }
    • Response:
      • 201 Created: User created successfully with an authentication token.
      • 422 Unprocessable Entity: Validation errors for missing or incorrect parameters.
  • Login a User

    • Endpoint: POST /login
    • Description: Authenticates a user and returns an authentication token.
    • Request Body:
      {
        "email": "[email protected]",
        "password": "password123"
      }
    • Response:
      • 200 OK: Returns a JSON object with the user's authentication token.
      • 401 Unauthorized: Invalid credentials.

Recipe Management

  • List All Recipes

    • Endpoint: GET /recipes
    • Description: Retrieves all recipes with their associated ingredients and steps.
    • Response:
      • 200 OK: Returns a list of all recipes.
  • Create a New Recipe

    • Endpoint: POST /recipes
    • Description: Creates a new recipe. Requires authentication.
    • Request Headers:
      • Authorization: Bearer <auth_token>
    • Request Body:
      {
        "title": "Chocolate Cake",
        "description": "A delicious chocolate cake recipe.",
        "ingredients_attributes": [
          {"name": "Flour", "quantity": "2 cups"},
          {"name": "Cocoa Powder", "quantity": "1 cup"}
        ],
        "steps_attributes": [
          {"description": "Mix all dry ingredients."},
          {"description": "Add wet ingredients and mix well."}
        ]
      }
    • Response:
      • 201 Created: Recipe created successfully.
      • 401 Unauthorized: User is not authenticated.
      • 422 Unprocessable Entity: Validation errors.
  • Get a Recipe by ID

    • Endpoint: GET /recipes/:id
    • Description: Retrieves a specific recipe by its ID, including ingredients, steps, and comments.
    • Response:
      • 200 OK: Returns the recipe details.
      • 404 Not Found: Recipe not found.
  • Update a Recipe

    • Endpoint: PUT /recipes/:id
    • Description: Updates an existing recipe. Requires authentication.
    • Request Headers:
      • Authorization: Bearer <auth_token>
    • Request Body:
      {
        "title": "Updated Chocolate Cake",
        "description": "An even more delicious chocolate cake recipe."
      }
    • Response:
      • 200 OK: Recipe updated successfully.
      • 401 Unauthorized: User is not authenticated.
      • 422 Unprocessable Entity: Validation errors.
  • Delete a Recipe (Admin Only)

    • Endpoint: DELETE /recipes/:id
    • Description: Deletes a recipe. Only accessible by admin users.
    • Request Headers:
      • Authorization: Bearer <auth_token>
    • Response:
      • 204 No Content: Recipe deleted successfully.
      • 401 Unauthorized: User is not authenticated.
      • 403 Forbidden: User is not an admin.
      • 404 Not Found: Recipe not found.

Comment Management

  • List All Comments for a Recipe

    • Endpoint: GET /recipes/:recipe_id/comments
    • Description: Retrieves all comments for a specific recipe.
    • Response:
      • 200 OK: Returns a list of comments for the recipe.
      • 404 Not Found: Recipe not found.
  • Add a Comment to a Recipe

    • Endpoint: POST /recipes/:recipe_id/comments
    • Description: Adds a comment to a recipe. Requires authentication.
    • Request Headers:
      • Authorization: Bearer <auth_token>
    • Request Body:
      {
        "content": "This is a great recipe!"
      }
    • Response:
      • 201 Created: Comment added successfully.
      • 401 Unauthorized: User is not authenticated.
      • 404 Not Found: Recipe not found.

Testing the API

Use tools like Postman or curl to test the endpoints:

  • Example: Register a New User

    curl -X POST http://localhost:3000/register -H "Content-Type: application/json" -d '{"name": "John Doe", "email": "[email protected]", "password": "password123", "password_confirmation": "password123"}'
  • Example: Create a New Recipe

    curl -X POST http://localhost:3000/recipes -H "Content-Type: application/json" -H "Authorization: Bearer <auth_token>" -d '{"title": "Chocolate Cake", "description": "A delicious chocolate cake recipe."}'

Security Considerations

  • Ensure that HTTPS is enabled in production to secure data in transit.
  • Tokens should be stored securely on the client side (e.g., in localStorage or cookies with the HttpOnly flag).
  • Passwords are hashed using bcrypt for secure storage.

Contributing

Contributions are welcome! Please follow these steps to contribute:

  1. Fork the repository.
  2. Create a new branch (git checkout -b feature/your-feature).
  3. Commit your changes (git commit -m 'Add your feature').
  4. Push to the branch (git push origin feature/your-feature).
  5. Open a pull request.

License

This project is open-source and available under the MIT License.


By following this guide, you should be able to set up and run the Recipe Sharing Platform API using Ruby on Rails. Feel free to extend and modify the code to add more features or improve functionality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment