Archive:

Fitness-Tracker App w/ User Authentication


The idea for this app came to me fairly quickly. It’s a new years resolution of mine to lose weight and hit the gym more this year, so I naturally thought of something that can help motivate me towards that goal. For this project I had to showcase the manipulation of a database written in Ruby through a front end written in React.

Frontend

This was fun because I actually put this frontend together using Figma. There was a slight learning curve but luckily my girlfriend uses it for her job and was nice enough to teach me a bit. From there I created the components and rendered them inside App.js. I then wrote up divs for the css I conjured up in Figma and placed them in the appropriate components.

The difference between this version of the project and my last is that it includes a route for logging the user in as well as a sign up page.

You can see how I set up the App component below:

function App() {
  const [user, setUser] = useState(null);
  let navigate = useNavigate();

  useEffect(() => {
    // auto-login
    fetch("/me").then((r) => {
      if (r.ok) {
        r.json().then((user) => setUser(user));
      } else {
        navigate("/signup");
      }
    });
  }, []);

  function handleLogout() {
    setUser(null);
    navigate("/signup");
  }
  return (
    <>
      {user ? (
        <Routes>
          <Route
            path="/"
            element={
              <Home user={user} setUser={setUser} handleLogout={handleLogout} />
            }
          />
        </Routes>
      ) : (
        <Routes>
          <Route
            path="/login"
            element={<Login setUser={setUser} user={user} />}
          />
          <Route
            path="/signup"
            element={
              <SignUp
                user={user}
                setUser={setUser}
                handleLogout={handleLogout}
              />
            }
          />
        </Routes>
      )}
    </>
  );
}
export default App;

When a user exists it authorizes the website to start with the Home component, otherwise it routes to the sign up page. From there you have the option of either signing up with a new account or logging in with one that already exists.

Backend

The backend for this version of the Fitness Tracker was done with rails. This made my life much easier than when I managed it manually. With a simple command: ‘rails g model ___’ rails auto generates the migration file as well as the controllers.

I also had to edit the routes file by including the routes to “login, logout, signup, me” You can see how the database is set up below:

---------------routes---------------------
  resources :workouts
  resources :days
  resources :routines

  post "/login", to: "sessions#create"
  delete "/logout", to: "sessions#destroy"
  post "/signup", to: "users#create"
  get "/me", to: "users#show"

---------------controllers---------------------
class DaysController < ApplicationController
  def index
    @days = Day.all
    render json: @days, include: [{ :routines => { :include => :workout , except: [:workout_id, :day_id, :created_at, :updated_at] }}]
  end

  def create
    day = Day.create(name: params[:name])
    render json: day
  end
end
------------------------------------------
class RoutinesController < ApplicationController
    def index
        routines = Routine.all
        render json: routines
    end

    def create
        routines = Routine.create(name: params[:name], day_id: params[:day_id], workout_id: params[:workout_id], user_id: params[:user_id] )
        render json: routines
    end

    def destroy
        routine = Routine.find(params[:id])
        # routine.delete_if { |hash| id.include?(hash[:id]) }
        routine.destroy
        render json: routine
    end

    def show
        routine = Routine.find(params[:id])
        render json: routine
    end
end
------------------------------------------
class SessionsController < ApplicationController
    def create
        user = User.find_by(username: params[:username])
        if user&.authenticate(params[:password])
          session[:user_id] = user.id
          render json: user, status: :created
        else
          render json: { error: "Invalid username or password" }, status: :unauthorized
        end
      end

      def destroy
        session.delete :user_id
        head :no_content
      end
end
------------------------------------------
class UsersController < ApplicationController
    before_action :authorize, only: [:show]
    def create
      user = User.create(user_params)
      if user.valid?
        session[:user_id] = user.id
        render json: user, status: :created
      else
        render json: { error: user.errors.full_messages }, status: :unprocessable_entity
      end
    end

    def show
      user = User.find_by(id: session[:user_id])
    end

    private

    def authorize
      return render json: { error: "Not authorized" }, status: :unauthorized unless session.include? :user_id
    end

    def user_params
      params.permit(:username, :password, :password_confirmation)
    end
end
------------------------------------------
class WorkoutsController < ApplicationController

    def index
            workouts = Workout.all
            render json: workouts
    end

    def create
        workouts = Workout.create(group: params[:group], body: params[:body], name: params[:name])
        render json: workouts

    end

    def update
        workout = Workout.find(params[:id])
        workout.update(body: params[:body])
        render json: workout
    end

    def destroy
        workout = Workout.find(params[:id])
        workout.destroy
        render json: workout
    end
end

The models and migrations for the project are set up as follows:

---------------models---------------------

class Day < ApplicationRecord
    has_many :routines
    has_many :workouts, through: :routines
end
------------------------------------------
class Workout < ApplicationRecord
    has_many :routines, dependent: :destroy
    has_many :days, through: :routines, dependent: :destroy
end
-------------------------------------------
class Routine < ApplicationRecord
    belongs_to :workout
    belongs_to :day
    belongs_to :user

end
-------------------------------------------
class User < ApplicationRecord
    has_secure_password
    has_many :routines
end
---------------migrations-------------------
class CreateRoutines < ActiveRecord::Migration[6.1]
  def change
    create_table :routines do |t|
      t.string :name
      t.integer :workout_id
      t.integer :day_id
      t.integer :user_id
      t.timestamps
    end
  end
end
-------------------------------------------
class CreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :users do |t|
      t.string :username
      t.string :password_digest
      t.timestamps
    end
  end
end

You can see in the UsersController that when a user is created, a session for that user is also created. This is what allows me to discriminate data between users.

In order to associate the Users to the routines being made, I had to create a foreign-key in the routines table.(user_id) Click the gif below to see the deployed version of the app.

The dummy login for the deployed heroku app is as follows:

username: username

password: password

You can check out a video demo of the website, or check out the repo by clicking the gif below.



Video Demo

lofi