diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..beee0ca --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..3b30419 --- /dev/null +++ b/pom.xml @@ -0,0 +1,78 @@ + + 4.0.0 + + com.fitlife + fitlife-smart-fitness-tracker + 1.0-SNAPSHOT + war + + FitLife Smart Fitness Tracker + + + 17 + 17 + UTF-8 + + + + + + javax.servlet + javax.servlet-api + 4.0.1 + provided + + + + + javax.servlet.jsp.jstl + jstl-api + 1.2 + + + taglibs + standard + 1.1.2 + + + + + mysql + mysql-connector-java + 8.0.33 + + + + + nz.ac.waikato.cms.weka + weka-stable + 3.8.0 + + + + + fitlife-smart-fitness-tracker + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + + + + + org.apache.maven.plugins + maven-war-plugin + 3.4.0 + + false + + + + + diff --git a/src/main/java/com/fitlife/config/DBConnection.java b/src/main/java/com/fitlife/config/DBConnection.java new file mode 100644 index 0000000..62aaa20 --- /dev/null +++ b/src/main/java/com/fitlife/config/DBConnection.java @@ -0,0 +1,24 @@ +package com.fitlife.config; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class DBConnection { + + private static final String URL = "jdbc:mysql://localhost:3306/fitlife_db"; + private static final String USER = "root"; + private static final String PASSWORD = "password"; + + static { + try { + Class.forName("com.mysql.cj.jdbc.Driver"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + public static Connection getConnection() throws SQLException { + return DriverManager.getConnection(URL, USER, PASSWORD); + } +} diff --git a/src/main/java/com/fitlife/dao/UserDAO.java b/src/main/java/com/fitlife/dao/UserDAO.java new file mode 100644 index 0000000..6b3685c --- /dev/null +++ b/src/main/java/com/fitlife/dao/UserDAO.java @@ -0,0 +1,84 @@ +package com.fitlife.dao; + +import com.fitlife.config.DBConnection; +import com.fitlife.model.User; + +import java.sql.*; +import java.time.LocalDateTime; + +public class UserDAO { + + public User findByUsername(String username) throws SQLException { + // IMPORTANT: use password_hash (NOT password) + String sql = "SELECT id, username, password_hash, email, created_at FROM users WHERE username = ?"; + + try (Connection conn = DBConnection.getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + + ps.setString(1, username); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + return mapRow(rs); + } + } + } + return null; + } + + public boolean usernameExists(String username) throws SQLException { + return findByUsername(username) != null; + } + + public User createUser(String username, String rawPassword, String email) throws SQLException { + // IMPORTANT: insert into password_hash (NOT password) + String sql = "INSERT INTO users (username, password_hash, email) VALUES (?, ?, ?)"; + + try (Connection conn = DBConnection.getConnection(); + PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + + ps.setString(1, username); + ps.setString(2, rawPassword); // for coursework, plain text is fine + ps.setString(3, email); + ps.executeUpdate(); + + try (ResultSet keys = ps.getGeneratedKeys()) { + if (keys.next()) { + int id = keys.getInt(1); + User u = new User(); + u.setId(id); + u.setUsername(username); + u.setPasswordHash(rawPassword); + u.setEmail(email); + u.setCreatedAt(LocalDateTime.now()); + return u; + } + } + } + return null; + } + + public User validateLogin(String username, String rawPassword) throws SQLException { + User u = findByUsername(username); + if (u == null) return null; + + if (u.getPasswordHash() != null && u.getPasswordHash().equals(rawPassword)) { + return u; + } + return null; + } + + private User mapRow(ResultSet rs) throws SQLException { + User u = new User(); + u.setId(rs.getInt("id")); + u.setUsername(rs.getString("username")); + // IMPORTANT: map password_hash column → passwordHash field + u.setPasswordHash(rs.getString("password_hash")); + u.setEmail(rs.getString("email")); + + Timestamp ts = rs.getTimestamp("created_at"); + if (ts != null) { + u.setCreatedAt(ts.toLocalDateTime()); + } + return u; + } +} diff --git a/src/main/java/com/fitlife/dao/WorkoutDAO.java b/src/main/java/com/fitlife/dao/WorkoutDAO.java new file mode 100644 index 0000000..806dbcb --- /dev/null +++ b/src/main/java/com/fitlife/dao/WorkoutDAO.java @@ -0,0 +1,165 @@ +package com.fitlife.dao; + +import com.fitlife.config.DBConnection; +import com.fitlife.model.Workout; + +import java.sql.*; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +public class WorkoutDAO { + + public void addWorkout(Workout w) throws SQLException { + String sql = "INSERT INTO workouts (user_id, activity_type, duration_minutes, distance_km, " + + "calories_burned, date, notes) VALUES (?, ?, ?, ?, ?, ?, ?)"; + try (Connection conn = DBConnection.getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + + ps.setInt(1, w.getUserId()); + ps.setString(2, w.getActivityType()); + ps.setInt(3, w.getDurationMinutes()); + ps.setDouble(4, w.getDistanceKm()); + ps.setInt(5, w.getCaloriesBurned()); + ps.setDate(6, Date.valueOf(w.getDate())); + ps.setString(7, w.getNotes()); + ps.executeUpdate(); + } + } + + public void updateWorkout(Workout w) throws SQLException { + String sql = "UPDATE workouts SET activity_type=?, duration_minutes=?, distance_km=?, " + + "calories_burned=?, date=?, notes=? WHERE id=? AND user_id=?"; + try (Connection conn = DBConnection.getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + + ps.setString(1, w.getActivityType()); + ps.setInt(2, w.getDurationMinutes()); + ps.setDouble(3, w.getDistanceKm()); + ps.setInt(4, w.getCaloriesBurned()); + ps.setDate(5, Date.valueOf(w.getDate())); + ps.setString(6, w.getNotes()); + ps.setInt(7, w.getId()); + ps.setInt(8, w.getUserId()); + ps.executeUpdate(); + } + } + + public void deleteWorkout(int id, int userId) throws SQLException { + String sql = "DELETE FROM workouts WHERE id=? AND user_id=?"; + try (Connection conn = DBConnection.getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setInt(1, id); + ps.setInt(2, userId); + ps.executeUpdate(); + } + } + + public Workout findById(int id, int userId) throws SQLException { + String sql = "SELECT * FROM workouts WHERE id=? AND user_id=?"; + try (Connection conn = DBConnection.getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setInt(1, id); + ps.setInt(2, userId); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + return mapRow(rs); + } + } + } + return null; + } + + public List findByUser(int userId, + String activityFilter, + LocalDate fromDate, + LocalDate toDate) throws SQLException { + + StringBuilder sql = new StringBuilder("SELECT * FROM workouts WHERE user_id=?"); + List params = new ArrayList<>(); + params.add(userId); + + if (activityFilter != null && !activityFilter.isBlank()) { + sql.append(" AND activity_type = ?"); + params.add(activityFilter); + } + if (fromDate != null) { + sql.append(" AND date >= ?"); + params.add(Date.valueOf(fromDate)); + } + if (toDate != null) { + sql.append(" AND date <= ?"); + params.add(Date.valueOf(toDate)); + } + sql.append(" ORDER BY date DESC"); + + List list = new ArrayList<>(); + try (Connection conn = DBConnection.getConnection(); + PreparedStatement ps = conn.prepareStatement(sql.toString())) { + + for (int i = 0; i < params.size(); i++) { + ps.setObject(i + 1, params.get(i)); + } + + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + list.add(mapRow(rs)); + } + } + } + return list; + } + + public WorkoutStats getDashboardStatsForUser(int userId) throws SQLException { + String sql = "SELECT COUNT(*) AS total_workouts, " + + "COALESCE(SUM(duration_minutes), 0) AS total_minutes, " + + "COALESCE(SUM(calories_burned), 0) AS total_calories " + + "FROM workouts WHERE user_id = ?"; + + try (Connection conn = DBConnection.getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + + ps.setInt(1, userId); + + try (ResultSet rs = ps.executeQuery()) { + WorkoutStats stats = new WorkoutStats(); + if (rs.next()) { + stats.setTotalWorkouts(rs.getInt("total_workouts")); + stats.setTotalMinutes(rs.getInt("total_minutes")); + stats.setTotalCalories(rs.getInt("total_calories")); + } + return stats; + } + } + } + + + private Workout mapRow(ResultSet rs) throws SQLException { + Workout w = new Workout(); + w.setId(rs.getInt("id")); + w.setUserId(rs.getInt("user_id")); + w.setActivityType(rs.getString("activity_type")); + w.setDurationMinutes(rs.getInt("duration_minutes")); + w.setDistanceKm(rs.getDouble("distance_km")); + w.setCaloriesBurned(rs.getInt("calories_burned")); + w.setDate(rs.getDate("date").toLocalDate()); + w.setNotes(rs.getString("notes")); + return w; + } + + // simple inner POJO or make it a separate class + public static class WorkoutStats { + private int totalWorkouts; + private int totalMinutes; + private int totalCalories; + + public int getTotalWorkouts() { return totalWorkouts; } + public void setTotalWorkouts(int totalWorkouts) { this.totalWorkouts = totalWorkouts; } + + public int getTotalMinutes() { return totalMinutes; } + public void setTotalMinutes(int totalMinutes) { this.totalMinutes = totalMinutes; } + + public int getTotalCalories() { return totalCalories; } + public void setTotalCalories(int totalCalories) { this.totalCalories = totalCalories; } + } +} diff --git a/src/main/java/com/fitlife/eval/ModelEvaluator.java b/src/main/java/com/fitlife/eval/ModelEvaluator.java new file mode 100644 index 0000000..54bccfd --- /dev/null +++ b/src/main/java/com/fitlife/eval/ModelEvaluator.java @@ -0,0 +1,43 @@ +package com.fitlife.eval; + +import weka.classifiers.Evaluation; +import weka.classifiers.trees.J48; +import weka.core.Instances; +import weka.core.converters.ConverterUtils.DataSource; + +import java.util.Random; + +public class ModelEvaluator { + + public static void main(String[] args) throws Exception { + + // 1. Load the dataset (path is from the project root) + String arffPath = "src/main/webapp/WEB-INF/dataset/fitness.arff"; + DataSource source = new DataSource(arffPath); + Instances data = source.getDataSet(); + + // 2. Set the class attribute (last column = activity) + if (data.classIndex() == -1) { + data.setClassIndex(data.numAttributes() - 1); + } + + // 3. Create the J48 classifier + J48 tree = new J48(); + tree.setUnpruned(false); // use pruning (default) + + // 4. Run 10-fold cross validation + int folds = 10; + Evaluation eval = new Evaluation(data); + eval.crossValidateModel(tree, data, folds, new Random(1)); + + // 5. Print results to console + System.out.println("=== Summary (10-fold cross-validation) ==="); + System.out.println(eval.toSummaryString()); + + System.out.println("=== Class details (precision, recall, F1) ==="); + System.out.println(eval.toClassDetailsString()); + + System.out.println("=== Confusion Matrix ==="); + System.out.println(eval.toMatrixString()); + } +} diff --git a/src/main/java/com/fitlife/model/User.java b/src/main/java/com/fitlife/model/User.java new file mode 100644 index 0000000..9b9ef18 --- /dev/null +++ b/src/main/java/com/fitlife/model/User.java @@ -0,0 +1,51 @@ +package com.fitlife.model; + +import java.time.LocalDateTime; + +public class User { + private int id; + private String username; + private String passwordHash; + private String email; + private LocalDateTime createdAt; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPasswordHash() { + return passwordHash; + } + + public void setPasswordHash(String passwordHash) { + this.passwordHash = passwordHash; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } +} diff --git a/src/main/java/com/fitlife/model/Workout.java b/src/main/java/com/fitlife/model/Workout.java new file mode 100644 index 0000000..c6ee3cf --- /dev/null +++ b/src/main/java/com/fitlife/model/Workout.java @@ -0,0 +1,78 @@ +package com.fitlife.model; + +import java.time.LocalDate; + +public class Workout { + private int id; + private int userId; + private String activityType; + private int durationMinutes; + private double distanceKm; + private int caloriesBurned; + private LocalDate date; + private String notes; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + public String getActivityType() { + return activityType; + } + + public void setActivityType(String activityType) { + this.activityType = activityType; + } + + public int getDurationMinutes() { + return durationMinutes; + } + + public void setDurationMinutes(int durationMinutes) { + this.durationMinutes = durationMinutes; + } + + public double getDistanceKm() { + return distanceKm; + } + + public void setDistanceKm(double distanceKm) { + this.distanceKm = distanceKm; + } + + public int getCaloriesBurned() { + return caloriesBurned; + } + + public void setCaloriesBurned(int caloriesBurned) { + this.caloriesBurned = caloriesBurned; + } + + public LocalDate getDate() { + return date; + } + + public void setDate(LocalDate date) { + this.date = date; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } +} diff --git a/src/main/java/com/fitlife/servlet/AuthServlet.java b/src/main/java/com/fitlife/servlet/AuthServlet.java new file mode 100644 index 0000000..42e57b1 --- /dev/null +++ b/src/main/java/com/fitlife/servlet/AuthServlet.java @@ -0,0 +1,48 @@ +package com.fitlife.servlet; + +import com.fitlife.dao.UserDAO; +import com.fitlife.model.User; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.*; +import java.io.IOException; +import java.sql.SQLException; + +@WebServlet(name = "AuthServlet", urlPatterns = {"/login"}) +public class AuthServlet extends HttpServlet { + + private final UserDAO userDAO = new UserDAO(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + req.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(req, resp); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + String username = req.getParameter("username"); + String password = req.getParameter("password"); + + try { + User user = userDAO.validateLogin(username, password); + if (user == null) { + req.setAttribute("error", "Invalid username or password."); + req.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(req, resp); + return; + } + + HttpSession session = req.getSession(); + session.setAttribute("username", user.getUsername()); + session.setAttribute("userId", user.getId()); + + resp.sendRedirect(req.getContextPath() + "/dashboard"); + + } catch (SQLException e) { + throw new ServletException(e); + } + } +} diff --git a/src/main/java/com/fitlife/servlet/DashboardServlet.java b/src/main/java/com/fitlife/servlet/DashboardServlet.java new file mode 100644 index 0000000..74417ed --- /dev/null +++ b/src/main/java/com/fitlife/servlet/DashboardServlet.java @@ -0,0 +1,46 @@ +package com.fitlife.servlet; + +import com.fitlife.dao.WorkoutDAO; +import com.fitlife.dao.WorkoutDAO.WorkoutStats; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.*; +import java.io.IOException; +import java.sql.SQLException; + +@WebServlet(name = "DashboardServlet", urlPatterns = {"/dashboard"}) +public class DashboardServlet extends HttpServlet { + + private final WorkoutDAO workoutDAO = new WorkoutDAO(); + + private int getCurrentUserId(HttpServletRequest req) { + return 1; // later map to real user + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + HttpSession session = req.getSession(false); + if (session == null || session.getAttribute("userId") == null) { + resp.sendRedirect(req.getContextPath() + "/login"); + return; + } + + int userId = (int) session.getAttribute("userId"); + + try { + WorkoutStats stats = workoutDAO.getDashboardStatsForUser(userId); + + req.setAttribute("totalWorkouts", stats.getTotalWorkouts()); + req.setAttribute("totalMinutes", stats.getTotalMinutes()); + req.setAttribute("totalCalories", stats.getTotalCalories()); + + req.getRequestDispatcher("/WEB-INF/views/dashboard.jsp").forward(req, resp); + } catch (SQLException e) { + throw new ServletException(e); + } + } + +} diff --git a/src/main/java/com/fitlife/servlet/LogoutServlet.java b/src/main/java/com/fitlife/servlet/LogoutServlet.java new file mode 100644 index 0000000..ae91c6c --- /dev/null +++ b/src/main/java/com/fitlife/servlet/LogoutServlet.java @@ -0,0 +1,28 @@ +package com.fitlife.servlet; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.*; +import java.io.IOException; + + + +@WebServlet(name = "LogoutServlet", urlPatterns = {"/logout"}) +public class LogoutServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException { + + HttpSession session = req.getSession(false); + if (session != null) { + session.invalidate(); + } + resp.sendRedirect(req.getContextPath() + "/login"); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws IOException { + doGet(req, resp); + } +} diff --git a/src/main/java/com/fitlife/servlet/PredictionServlet.java b/src/main/java/com/fitlife/servlet/PredictionServlet.java new file mode 100644 index 0000000..61f96ff --- /dev/null +++ b/src/main/java/com/fitlife/servlet/PredictionServlet.java @@ -0,0 +1,85 @@ +package com.fitlife.servlet; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.*; + +import weka.core.Instance; +import weka.core.Instances; +import weka.core.DenseInstance; +import weka.classifiers.trees.J48; + + + +@WebServlet(name = "PredictionServlet", urlPatterns = {"/predict"}) +public class PredictionServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + req.getRequestDispatcher("/WEB-INF/views/prediction.jsp").forward(req, resp); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + String durationStr = req.getParameter("durationMinutes"); + String distanceStr = req.getParameter("distanceKm"); + String caloriesStr = req.getParameter("caloriesBurned"); + + double duration = Double.parseDouble(durationStr); + double distance = Double.parseDouble(distanceStr); + double calories = Double.parseDouble(caloriesStr); + + // 1) Load ARFF from WEB-INF/dataset + String arffPath = "/WEB-INF/dataset/fitness.arff"; + InputStream is = getServletContext().getResourceAsStream(arffPath); + if (is == null) { + throw new ServletException("Could not find ARFF file at " + arffPath); + } + + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + Instances data = new Instances(reader); + reader.close(); + data.setClassIndex(data.numAttributes() - 1); + + // 2) Train J48 + J48 tree = new J48(); + try { + tree.buildClassifier(data); + } catch (Exception e) { + throw new ServletException("Failed to build J48 model", e); + } + + // 3) Build instance to classify + Instance inst = new DenseInstance(data.numAttributes()); + inst.setDataset(data); + + inst.setValue(0, duration); + inst.setValue(1, distance); + inst.setValue(2, calories); + + String predictedActivity; + try { + double labelIndex = tree.classifyInstance(inst); + predictedActivity = data.classAttribute().value((int) labelIndex); + } catch (Exception e) { + throw new ServletException("Failed to classify instance", e); + } + + // 4) Send result to JSP + req.setAttribute("inputDuration", durationStr); + req.setAttribute("inputDistance", distanceStr); + req.setAttribute("inputCalories", caloriesStr); + req.setAttribute("predictedActivity", predictedActivity); + + req.getRequestDispatcher("/WEB-INF/views/prediction.jsp").forward(req, resp); + } + + +} diff --git a/src/main/java/com/fitlife/servlet/RegisterServlet.java b/src/main/java/com/fitlife/servlet/RegisterServlet.java new file mode 100644 index 0000000..58720e0 --- /dev/null +++ b/src/main/java/com/fitlife/servlet/RegisterServlet.java @@ -0,0 +1,51 @@ +package com.fitlife.servlet; + +import com.fitlife.dao.UserDAO; +import com.fitlife.model.User; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.*; +import java.io.IOException; +import java.sql.SQLException; + +@WebServlet(name = "RegisterServlet", urlPatterns = {"/register"}) +public class RegisterServlet extends HttpServlet { + + private final UserDAO userDAO = new UserDAO(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + req.getRequestDispatcher("/WEB-INF/views/register.jsp").forward(req, resp); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + String username = req.getParameter("username"); + String password = req.getParameter("password"); + String email = req.getParameter("email"); + + try { + if (userDAO.usernameExists(username)) { + req.setAttribute("error", "Username already taken. Please choose another one."); + req.getRequestDispatcher("/WEB-INF/views/register.jsp").forward(req, resp); + return; + } + + User created = userDAO.createUser(username, password, email); + + // auto-login after successful registration + HttpSession session = req.getSession(); + session.setAttribute("username", created.getUsername()); + session.setAttribute("userId", created.getId()); + + resp.sendRedirect(req.getContextPath() + "/dashboard"); + + } catch (SQLException e) { + throw new ServletException(e); + } + } +} diff --git a/src/main/java/com/fitlife/servlet/WorkoutServlet.java b/src/main/java/com/fitlife/servlet/WorkoutServlet.java new file mode 100644 index 0000000..1991f69 --- /dev/null +++ b/src/main/java/com/fitlife/servlet/WorkoutServlet.java @@ -0,0 +1,174 @@ +package com.fitlife.servlet; + +import com.fitlife.dao.WorkoutDAO; +import com.fitlife.model.Workout; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.*; +import java.io.IOException; +import java.sql.SQLException; +import java.time.LocalDate; +import java.util.List; + +@WebServlet(name = "WorkoutServlet", urlPatterns = {"/workouts"}) +public class WorkoutServlet extends HttpServlet { + + private final WorkoutDAO workoutDAO = new WorkoutDAO(); + + private int getCurrentUserId(HttpServletRequest req) { + HttpSession session = req.getSession(false); + if (session == null || session.getAttribute("userId") == null) { + // fallback to 1 if somehow missing, but normally this should not happen + return 1; + } + return (int) session.getAttribute("userId"); + } + + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + HttpSession session = req.getSession(false); + if (session == null || session.getAttribute("username") == null) { + resp.sendRedirect(req.getContextPath() + "/login"); + return; + } + + String action = req.getParameter("action"); + if (action == null) action = "list"; + + try { + switch (action) { + case "add": + showAddForm(req, resp); + break; + case "edit": + showEditForm(req, resp); + break; + case "delete": + deleteWorkout(req, resp); + break; + default: + listWorkouts(req, resp); + } + } catch (SQLException e) { + throw new ServletException(e); + } + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + String action = req.getParameter("action"); + if (action == null) action = "save"; + + try { + switch (action) { + case "save": + saveWorkout(req, resp); + break; + case "update": + updateWorkout(req, resp); + break; + default: + resp.sendRedirect(req.getContextPath() + "/workouts"); + } + } catch (SQLException e) { + throw new ServletException(e); + } + } + + private void listWorkouts(HttpServletRequest req, HttpServletResponse resp) + throws SQLException, ServletException, IOException { + + int userId = getCurrentUserId(req); + + String activity = req.getParameter("activity"); + String from = req.getParameter("fromDate"); + String to = req.getParameter("toDate"); + + LocalDate fromDate = (from != null && !from.isBlank()) ? LocalDate.parse(from) : null; + LocalDate toDate = (to != null && !to.isBlank()) ? LocalDate.parse(to) : null; + + List workouts = workoutDAO.findByUser(userId, activity, fromDate, toDate); + req.setAttribute("workouts", workouts); + req.getRequestDispatcher("/WEB-INF/views/workouts.jsp").forward(req, resp); + } + + private void showAddForm(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + req.setAttribute("mode", "add"); + req.getRequestDispatcher("/WEB-INF/views/workout-form.jsp").forward(req, resp); + } + + private void showEditForm(HttpServletRequest req, HttpServletResponse resp) + throws SQLException, ServletException, IOException { + int id = Integer.parseInt(req.getParameter("id")); + int userId = getCurrentUserId(req); + Workout w = workoutDAO.findById(id, userId); + if (w == null) { + resp.sendRedirect(req.getContextPath() + "/workouts"); + return; + } + req.setAttribute("mode", "edit"); + req.setAttribute("workout", w); + req.getRequestDispatcher("/WEB-INF/views/workout-form.jsp").forward(req, resp); + } + + private void saveWorkout(HttpServletRequest req, HttpServletResponse resp) + throws SQLException, IOException, ServletException { + + int userId = getCurrentUserId(req); + Workout w = buildWorkoutFromRequest(req, userId, false); + workoutDAO.addWorkout(w); + resp.sendRedirect(req.getContextPath() + "/workouts"); + } + + private void updateWorkout(HttpServletRequest req, HttpServletResponse resp) + throws SQLException, IOException, ServletException { + + int userId = getCurrentUserId(req); + Workout w = buildWorkoutFromRequest(req, userId, true); + workoutDAO.updateWorkout(w); + resp.sendRedirect(req.getContextPath() + "/workouts"); + } + + private void deleteWorkout(HttpServletRequest req, HttpServletResponse resp) + throws SQLException, IOException { + int id = Integer.parseInt(req.getParameter("id")); + int userId = getCurrentUserId(req); + workoutDAO.deleteWorkout(id, userId); + resp.sendRedirect(req.getContextPath() + "/workouts"); + } + + private Workout buildWorkoutFromRequest(HttpServletRequest req, int userId, boolean includeId) + throws ServletException { + + String activityType = req.getParameter("activityType"); + int duration = Integer.parseInt(req.getParameter("durationMinutes")); + double distance = Double.parseDouble(req.getParameter("distanceKm")); + int calories = Integer.parseInt(req.getParameter("caloriesBurned")); + LocalDate date = LocalDate.parse(req.getParameter("date")); + String notes = req.getParameter("notes"); + + if (duration <= 0 || calories <= 0 || distance < 0) { + throw new ServletException("Invalid workout values"); + } + + Workout w = new Workout(); + if (includeId) { + w.setId(Integer.parseInt(req.getParameter("id"))); + } + w.setUserId(userId); + w.setActivityType(activityType); + w.setDurationMinutes(duration); + w.setDistanceKm(distance); + w.setCaloriesBurned(calories); + w.setDate(date); + w.setNotes(notes); + return w; + } +} diff --git a/src/main/webapp/WEB-INF/dataset/fitness.arff b/src/main/webapp/WEB-INF/dataset/fitness.arff new file mode 100644 index 0000000..c54c417 --- /dev/null +++ b/src/main/webapp/WEB-INF/dataset/fitness.arff @@ -0,0 +1,43 @@ +@relation fitness + +@attribute duration numeric +@attribute distance numeric +@attribute calories numeric +@attribute activity {Running,Cycling,Walking,Gym} + +@data +30,5.2,320,Running +20,3.2,220,Running +40,8.0,480,Running +50,10.0,650,Running +25,4.0,260,Running +35,6.8,360,Running +28,5.0,300,Running +22,3.5,210,Running + +45,15.0,450,Cycling +70,20.0,650,Cycling +55,18.0,520,Cycling +90,30.0,1100,Cycling +40,12.0,380,Cycling +80,25.0,900,Cycling +65,22.0,700,Cycling +100,35.0,1200,Cycling + +60,4.0,200,Walking +40,3.0,150,Walking +70,5.0,260,Walking +55,3.8,190,Walking +45,2.5,140,Walking +80,6.0,300,Walking +50,3.2,170,Walking +65,5.5,230,Walking + +50,0,380,Gym +30,0,250,Gym +60,0,420,Gym +45,0,330,Gym +35,0,260,Gym +70,0,550,Gym +25,0,200,Gym +80,0,600,Gym diff --git a/src/main/webapp/WEB-INF/views/dashboard.jsp b/src/main/webapp/WEB-INF/views/dashboard.jsp new file mode 100644 index 0000000..85dfb3d --- /dev/null +++ b/src/main/webapp/WEB-INF/views/dashboard.jsp @@ -0,0 +1,116 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ include file="includes/header.jspf" %> + +
+ + +
+
+

Dashboard

+

Quick overview of your recent activity.

+
+ +
+ + +
+
+
+
+

Total Workouts

+

${totalWorkouts}

+
+
+
+ +
+
+
+

Total Minutes

+

${totalMinutes}

+
+
+
+ +
+
+
+

Total Calories

+

${totalCalories}

+
+
+
+
+ + +
+ +
+
+
+ Quick Actions +
+
+

Use these shortcuts to manage your activity.

+ +
+
+
+ + +
+
+
+ Activity Summary +
+
+ + +

+ You haven’t logged any workouts yet. +

+

+ Start by adding your first workout. The dashboard will then show + useful stats like total time and calories burned. +

+
+ +

+ You have logged ${totalWorkouts} workouts + so far. +

+
    +
  • Total training time: ${totalMinutes} minutes
  • +
  • Total calories burned: ${totalCalories}
  • +
  • Use the Predict Activity page to estimate + activity type for a new session. +
  • +
+
+
+
+
+
+
+
+ +<%@ include file="includes/footer.jspf" %> diff --git a/src/main/webapp/WEB-INF/views/header.jsp b/src/main/webapp/WEB-INF/views/header.jsp new file mode 100644 index 0000000..e2b4319 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/header.jsp @@ -0,0 +1,37 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> + + + + + FitLife Smart Fitness Tracker + + + + + +
diff --git a/src/main/webapp/WEB-INF/views/includes/footer.jspf b/src/main/webapp/WEB-INF/views/includes/footer.jspf new file mode 100644 index 0000000..bf460d6 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/includes/footer.jspf @@ -0,0 +1,6 @@ +
+ + + diff --git a/src/main/webapp/WEB-INF/views/includes/header.jspf b/src/main/webapp/WEB-INF/views/includes/header.jspf new file mode 100644 index 0000000..e2b4319 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/includes/header.jspf @@ -0,0 +1,37 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> + + + + + FitLife Smart Fitness Tracker + + + + + +
diff --git a/src/main/webapp/WEB-INF/views/login.jsp b/src/main/webapp/WEB-INF/views/login.jsp new file mode 100644 index 0000000..cdb4040 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/login.jsp @@ -0,0 +1,28 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ include file="includes/header.jspf" %> + +
+
+

Login

+ + +
${error}
+
+ +
+
+ + +
+
+ + +
+ +
+ +

No account? Register here

+
+
+ +<%@ include file="includes/footer.jspf" %> diff --git a/src/main/webapp/WEB-INF/views/prediction.jsp b/src/main/webapp/WEB-INF/views/prediction.jsp new file mode 100644 index 0000000..a230589 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/prediction.jsp @@ -0,0 +1,38 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ include file="includes/header.jspf" %> + +

Predict Activity Type

+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+ Predicted Activity: ${predictedActivity} +
+
+ +

+ Note: At the moment this is using a dummy prediction. You will replace this with a real J48 model + using WEKA in your coursework. +

+ +<%@ include file="includes/footer.jspf" %> diff --git a/src/main/webapp/WEB-INF/views/register.jsp b/src/main/webapp/WEB-INF/views/register.jsp new file mode 100644 index 0000000..ecb5e1e --- /dev/null +++ b/src/main/webapp/WEB-INF/views/register.jsp @@ -0,0 +1,39 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ include file="includes/header.jspf" %> + +
+
+

Register

+ + +
${error}
+
+ +
${message}
+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + +

Already have an account? Login

+
+
+ +<%@ include file="includes/footer.jspf" %> diff --git a/src/main/webapp/WEB-INF/views/workout-form.jsp b/src/main/webapp/WEB-INF/views/workout-form.jsp new file mode 100644 index 0000000..b0dc5a4 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/workout-form.jsp @@ -0,0 +1,69 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ include file="includes/header.jspf" %> + + + +

+ + Edit Workout + Add Workout + +

+ +
+ + + + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + Cancel +
+ +<%@ include file="includes/footer.jspf" %> diff --git a/src/main/webapp/WEB-INF/views/workouts.jsp b/src/main/webapp/WEB-INF/views/workouts.jsp new file mode 100644 index 0000000..68bc257 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/workouts.jsp @@ -0,0 +1,75 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ include file="includes/header.jspf" %> + +

My Workouts

+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + Clear +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DateActivityDuration (min)Distance (km)CaloriesNotesActions
${w.date}${w.activityType}${w.durationMinutes}${w.distanceKm}${w.caloriesBurned}${w.notes} + Edit + Delete +
+ + +

No workouts found. Add your first workout.

+
+ +<%@ include file="includes/footer.jspf" %> diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..cd0f7be --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + + FitLife Smart Fitness Tracker + + + index.jsp + + diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp new file mode 100644 index 0000000..cd17339 --- /dev/null +++ b/src/main/webapp/index.jsp @@ -0,0 +1,4 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<% + response.sendRedirect(request.getContextPath() + "/login"); +%>