diff --git a/src/main/java/com/note0/simple/AdminPanel.java b/src/main/java/com/note0/simple/AdminPanel.java index caa02b3..1f0c364 100644 --- a/src/main/java/com/note0/simple/AdminPanel.java +++ b/src/main/java/com/note0/simple/AdminPanel.java @@ -10,15 +10,17 @@ public class AdminPanel extends JPanel { private final SubjectDAO subjectDAO; private final MaterialDAO materialDAO; + private final CloudinaryService cloudinaryService; private JTable subjectTable; private DefaultTableModel subjectTableModel; private JTable materialTable; private DefaultTableModel materialTableModel; - public AdminPanel(SubjectDAO subjectDAO, MaterialDAO materialDAO) { + public AdminPanel(SubjectDAO subjectDAO, MaterialDAO materialDAO, CloudinaryService cloudinaryService) { this.subjectDAO = subjectDAO; this.materialDAO = materialDAO; + this.cloudinaryService = cloudinaryService; setLayout(new GridLayout(2, 1, 10, 10)); // Two main sections: Subjects and Materials setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); @@ -31,14 +33,24 @@ public AdminPanel(SubjectDAO subjectDAO, MaterialDAO materialDAO) { subjectTable = new JTable(subjectTableModel); subjectsPanel.add(new JScrollPane(subjectTable), BorderLayout.CENTER); + // Subject management buttons + JPanel subjectButtonPanel = new JPanel(new FlowLayout()); + JButton addSubjectButton = new JButton("Add New Subject"); JButton deleteSubjectButton = new JButton("Delete Selected Subject"); - subjectsPanel.add(deleteSubjectButton, BorderLayout.SOUTH); + JButton refreshSubjectsButton = new JButton("Refresh"); + + subjectButtonPanel.add(addSubjectButton); + subjectButtonPanel.add(deleteSubjectButton); + subjectButtonPanel.add(refreshSubjectsButton); + subjectsPanel.add(subjectButtonPanel, BorderLayout.SOUTH); + addSubjectButton.addActionListener(e -> addSubject()); deleteSubjectButton.addActionListener(e -> deleteSubject()); + refreshSubjectsButton.addActionListener(e -> loadSubjects()); // --- Materials Panel --- JPanel materialsPanel = new JPanel(new BorderLayout(10, 10)); - materialsPanel.setBorder(BorderFactory.createTitledBorder("Review Pending Materials")); + materialsPanel.setBorder(BorderFactory.createTitledBorder("Manage Materials (Pending & All Uploaded Notes)")); materialTableModel = new DefaultTableModel(new String[]{"ID", "Title", "Uploader", "Subject", "Status"}, 0); materialTable = new JTable(materialTableModel); @@ -48,17 +60,23 @@ public AdminPanel(SubjectDAO subjectDAO, MaterialDAO materialDAO) { JButton approveButton = new JButton("Approve Selected"); JButton rejectButton = new JButton("Reject Selected"); JButton deleteMaterialButton = new JButton("Delete Selected"); + JButton showPendingButton = new JButton("Show Pending"); + JButton showAllButton = new JButton("Show All"); JButton refreshButton = new JButton("Refresh"); materialButtonPanel.add(approveButton); materialButtonPanel.add(rejectButton); materialButtonPanel.add(deleteMaterialButton); + materialButtonPanel.add(showPendingButton); + materialButtonPanel.add(showAllButton); materialButtonPanel.add(refreshButton); materialsPanel.add(materialButtonPanel, BorderLayout.SOUTH); approveButton.addActionListener(e -> approveMaterial()); rejectButton.addActionListener(e -> rejectMaterial()); deleteMaterialButton.addActionListener(e -> deleteMaterial()); + showPendingButton.addActionListener(e -> loadPendingMaterials()); + showAllButton.addActionListener(e -> loadAllMaterials()); refreshButton.addActionListener(e -> loadPendingMaterials()); // Add both main panels to the AdminPanel @@ -92,6 +110,87 @@ private void loadPendingMaterials() { JOptionPane.showMessageDialog(this, "Error loading pending materials: " + e.getMessage(), "DB Error", JOptionPane.ERROR_MESSAGE); } } + + private void loadAllMaterials() { + materialTableModel.setRowCount(0); + try { + List materials = materialDAO.getAllMaterialsForAdmin(); + for (Material material : materials) { + materialTableModel.addRow(new Object[]{material.getId(), material.getTitle(), material.getUploaderName(), material.getSubjectName(), material.getApprovalStatus()}); + } + } catch (SQLException e) { + JOptionPane.showMessageDialog(this, "Error loading all materials: " + e.getMessage(), "DB Error", JOptionPane.ERROR_MESSAGE); + } + } + + private void addSubject() { + // Create a dialog for adding a new subject + JDialog addSubjectDialog = new JDialog((Frame) SwingUtilities.getWindowAncestor(this), "Add New Subject", true); + addSubjectDialog.setLayout(new BorderLayout()); + addSubjectDialog.setSize(400, 300); + addSubjectDialog.setLocationRelativeTo(this); + + // Form panel + JPanel formPanel = new JPanel(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + formPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // Subject name field + gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST; + formPanel.add(new JLabel("Subject Name:"), gbc); + gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0; + JTextField nameField = new JTextField(20); + formPanel.add(nameField, gbc); + + // Branch field + gbc.gridx = 0; gbc.gridy = 1; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0; + formPanel.add(new JLabel("Branch:"), gbc); + gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0; + JTextField branchField = new JTextField(20); + formPanel.add(branchField, gbc); + + // Semester field + gbc.gridx = 0; gbc.gridy = 2; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0; + formPanel.add(new JLabel("Semester:"), gbc); + gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0; + JSpinner semesterSpinner = new JSpinner(new SpinnerNumberModel(1, 1, 8, 1)); + formPanel.add(semesterSpinner, gbc); + + addSubjectDialog.add(formPanel, BorderLayout.CENTER); + + // Button panel + JPanel buttonPanel = new JPanel(new FlowLayout()); + JButton saveButton = new JButton("Save"); + JButton cancelButton = new JButton("Cancel"); + buttonPanel.add(saveButton); + buttonPanel.add(cancelButton); + addSubjectDialog.add(buttonPanel, BorderLayout.SOUTH); + + // Event handlers + saveButton.addActionListener(e -> { + String name = nameField.getText().trim(); + String branch = branchField.getText().trim(); + int semester = (Integer) semesterSpinner.getValue(); + + if (name.isEmpty() || branch.isEmpty()) { + JOptionPane.showMessageDialog(addSubjectDialog, "Please fill in all fields.", "Validation Error", JOptionPane.ERROR_MESSAGE); + return; + } + + try { + subjectDAO.addSubject(name, branch, semester); + JOptionPane.showMessageDialog(addSubjectDialog, "Subject added successfully!", "Success", JOptionPane.INFORMATION_MESSAGE); + addSubjectDialog.dispose(); + loadSubjects(); // Refresh the subjects list + } catch (SQLException ex) { + JOptionPane.showMessageDialog(addSubjectDialog, "Error adding subject: " + ex.getMessage(), "Database Error", JOptionPane.ERROR_MESSAGE); + } + }); + + cancelButton.addActionListener(e -> addSubjectDialog.dispose()); + + addSubjectDialog.setVisible(true); + } private void deleteSubject() { int selectedRow = subjectTable.getSelectedRow(); @@ -100,7 +199,11 @@ private void deleteSubject() { return; } Long id = (Long) subjectTableModel.getValueAt(selectedRow, 0); - int confirm = JOptionPane.showConfirmDialog(this, "Are you sure you want to delete this subject?", "Confirm Deletion", JOptionPane.YES_NO_OPTION); + String subjectName = (String) subjectTableModel.getValueAt(selectedRow, 1); + int confirm = JOptionPane.showConfirmDialog(this, + "Are you sure you want to delete the subject '" + subjectName + "'?\n\n" + + "This will also delete all materials associated with this subject.", + "Confirm Deletion", JOptionPane.YES_NO_OPTION); if (confirm == JOptionPane.YES_OPTION) { try { subjectDAO.deleteSubject(id); @@ -119,11 +222,20 @@ private void deleteMaterial() { return; } Long id = (Long) materialTableModel.getValueAt(selectedRow, 0); - int confirm = JOptionPane.showConfirmDialog(this, "Are you sure you want to delete this material?", "Confirm Deletion", JOptionPane.YES_NO_OPTION); + String materialTitle = (String) materialTableModel.getValueAt(selectedRow, 1); + int confirm = JOptionPane.showConfirmDialog(this, + "Are you sure you want to delete the material '" + materialTitle + "'?\n\n" + + "This will remove the material from the database.\n" + + "The uploaded file will remain in Cloudinary storage.", + "Confirm Deletion", JOptionPane.YES_NO_OPTION); if (confirm == JOptionPane.YES_OPTION) { try { - materialDAO.deleteMaterial(id); - JOptionPane.showMessageDialog(this, "Material deleted successfully!", "Success", JOptionPane.INFORMATION_MESSAGE); + boolean success = materialDAO.deleteMaterialWithFile(id, cloudinaryService); + if (success) { + JOptionPane.showMessageDialog(this, "Material deleted from database successfully!", "Success", JOptionPane.INFORMATION_MESSAGE); + } else { + JOptionPane.showMessageDialog(this, "Error deleting material from database.", "Error", JOptionPane.ERROR_MESSAGE); + } loadPendingMaterials(); // Refresh the list } catch (Exception ex) { JOptionPane.showMessageDialog(this, "Error deleting material: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); diff --git a/src/main/java/com/note0/simple/CloudinaryService.java b/src/main/java/com/note0/simple/CloudinaryService.java index 920c843..b03fee2 100644 --- a/src/main/java/com/note0/simple/CloudinaryService.java +++ b/src/main/java/com/note0/simple/CloudinaryService.java @@ -15,11 +15,12 @@ public CloudinaryService() { // Prefer standard CLOUDINARY_URL env var: cloudinary://:@ String cloudinaryUrl = System.getenv("CLOUDINARY_URL"); if (cloudinaryUrl == null || cloudinaryUrl.isBlank()) { - // Use default configuration for development/testing + // Replace these with your actual Cloudinary credentials + // Get these from https://cloudinary.com/console this.cloudinary = new Cloudinary(ObjectUtils.asMap( - "cloud_name", "demo", - "api_key", "demo", - "api_secret", "demo" + "cloud_name", "your_cloud_name_here", + "api_key", "your_api_key_here", + "api_secret", "your_api_secret_here" )); } else { this.cloudinary = new Cloudinary(cloudinaryUrl); @@ -27,30 +28,194 @@ public CloudinaryService() { } public String uploadFile(File file, String folder, String publicIdHint) throws IOException { - String safeFolder = (folder == null || folder.isBlank()) ? "note0" : folder; - String name = file.getName().toLowerCase(); - String resourceType; - if (name.endsWith(".pdf")) { - // PDFs should be uploaded as raw and explicitly set to public upload type - resourceType = "raw"; - } else if (name.endsWith(".doc") || name.endsWith(".docx") || name.endsWith(".ppt") || name.endsWith(".pptx") || name.endsWith(".xls") || name.endsWith(".xlsx")) { - resourceType = "raw"; - } else { - resourceType = "auto"; + try { + String safeFolder = (folder == null || folder.isBlank()) ? "note0" : folder; + String name = file.getName().toLowerCase(); + String resourceType; + if (name.endsWith(".pdf")) { + // PDFs should be uploaded as raw and explicitly set to public upload type + resourceType = "raw"; + } else if (name.endsWith(".doc") || name.endsWith(".docx") || name.endsWith(".ppt") || name.endsWith(".pptx") || name.endsWith(".xls") || name.endsWith(".xlsx")) { + resourceType = "raw"; + } else { + resourceType = "auto"; + } + Map uploadParams = ObjectUtils.asMap( + "folder", safeFolder, + "resource_type", resourceType, + "type", "upload", // ensure public delivery + "access_mode", "public", // avoid authenticated/private assets + "public_id", publicIdHint + ); + Map result = cloudinary.uploader().upload(file, uploadParams); + Object secureUrl = result.get("secure_url"); + if (secureUrl == null) { + throw new IOException("Cloudinary did not return a secure_url"); + } + return secureUrl.toString(); + } catch (Exception e) { + if (e.getMessage().contains("Invalid API key") || e.getMessage().contains("demo")) { + throw new IOException("Cloudinary API credentials are invalid. Please set up your Cloudinary account:\n" + + "1. Go to https://cloudinary.com and create a free account\n" + + "2. Get your credentials from the dashboard\n" + + "3. Set CLOUDINARY_URL environment variable or update CloudinaryService.java"); + } + throw new IOException("Cloudinary upload failed: " + e.getMessage()); } - Map uploadParams = ObjectUtils.asMap( - "folder", safeFolder, + } + + /** + * Deletes a file from Cloudinary using its URL. + * @param fileUrl The Cloudinary URL of the file to delete + * @return true if deletion was successful, false otherwise + */ + public boolean deleteFile(String fileUrl) { + try { + if (fileUrl == null || fileUrl.isBlank()) { + System.err.println("CloudinaryService: File URL is null or blank"); + return false; + } + + // Extract public_id from Cloudinary URL + String publicId = extractPublicIdFromUrl(fileUrl); + if (publicId == null) { + System.err.println("CloudinaryService: Could not extract public_id from URL: " + fileUrl); + return false; + } + + System.out.println("CloudinaryService: Attempting to delete public_id: " + publicId); + + // Determine resource type based on file extension + String resourceType = determineResourceType(fileUrl); + System.out.println("CloudinaryService: Using resource_type: " + resourceType); + + // Try with the determined resource type first + Map deleteParams = ObjectUtils.asMap( "resource_type", resourceType, - "type", "upload", // ensure public delivery - "access_mode", "public", // avoid authenticated/private assets - "public_id", publicIdHint - ); - Map result = cloudinary.uploader().upload(file, uploadParams); - Object secureUrl = result.get("secure_url"); - if (secureUrl == null) { - throw new IOException("Cloudinary did not return a secure_url"); + "type", "upload" + ); + + Map result = cloudinary.uploader().destroy(publicId, deleteParams); + System.out.println("CloudinaryService: Delete result with " + resourceType + ": " + result); + + String resultValue = (String) result.get("result"); + boolean success = "ok".equals(resultValue); + + if (success) { + System.out.println("CloudinaryService: File deleted successfully"); + return true; + } + + // If failed and it's a PDF/document, try with "raw" resource type + if (!success && (fileUrl.toLowerCase().contains(".pdf") || fileUrl.toLowerCase().contains(".doc"))) { + System.out.println("CloudinaryService: Retrying with 'raw' resource type..."); + Map rawDeleteParams = ObjectUtils.asMap( + "resource_type", "raw", + "type", "upload" + ); + + result = cloudinary.uploader().destroy(publicId, rawDeleteParams); + System.out.println("CloudinaryService: Delete result with raw: " + result); + + resultValue = (String) result.get("result"); + success = "ok".equals(resultValue); + + if (success) { + System.out.println("CloudinaryService: File deleted successfully with raw resource type"); + return true; + } + } + + System.err.println("CloudinaryService: Delete failed with result: " + resultValue); + return false; + + } catch (Exception e) { + System.err.println("CloudinaryService: Error deleting file from Cloudinary: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + /** + * Extracts the public_id from a Cloudinary URL. + * @param url The Cloudinary URL + * @return The public_id or null if extraction fails + */ + private String extractPublicIdFromUrl(String url) { + try { + // Expected formats: + // https://res.cloudinary.com/{cloud}/{resource_type}/upload/v{version}/{folder(s)}/{public_id}.{ext} + // We need: {folder(s)}/{public_id} (without extension and without version) + + if (url == null || !url.contains("cloudinary.com")) { + return null; + } + + int uploadIdx = url.indexOf("/upload/"); + if (uploadIdx < 0) { + return null; + } + + // Part after '/upload/' + String afterUpload = url.substring(uploadIdx + "/upload/".length()); + + // Strip query/hash if any + int qIdx = afterUpload.indexOf('?'); + if (qIdx >= 0) afterUpload = afterUpload.substring(0, qIdx); + int hashIdx = afterUpload.indexOf('#'); + if (hashIdx >= 0) afterUpload = afterUpload.substring(0, hashIdx); + + // Remove leading version segment if present (e.g., v1761134242/...) + if (afterUpload.startsWith("v") ) { + int slashIdx = afterUpload.indexOf('/'); + if (slashIdx > 0) { + // Ensure it's actually a version like v123456... + String possibleVersion = afterUpload.substring(1, slashIdx); + if (possibleVersion.matches("\\d+")) { + afterUpload = afterUpload.substring(slashIdx + 1); + } + } + } + + // Now afterUpload should be: {folder(s)}/{fileName}.{ext} + // Remove the extension from the last path segment only + int lastSlash = afterUpload.lastIndexOf('/'); + if (lastSlash < 0) { + // No folders, just filename + int dotIdx = afterUpload.lastIndexOf('.'); + return (dotIdx > 0) ? afterUpload.substring(0, dotIdx) : afterUpload; + } else { + String folders = afterUpload.substring(0, lastSlash); + String fileName = afterUpload.substring(lastSlash + 1); + int dotIdx = fileName.lastIndexOf('.'); + String fileBase = (dotIdx > 0) ? fileName.substring(0, dotIdx) : fileName; + return folders + "/" + fileBase; + } + + } catch (Exception e) { + System.err.println("Error extracting public_id from URL: " + e.getMessage()); + return null; + } + } + + /** + * Determines the resource type based on the file URL. + * @param url The Cloudinary URL + * @return The resource type (raw, image, video, etc.) + */ + private String determineResourceType(String url) { + String lowerUrl = url.toLowerCase(); + + if (lowerUrl.contains("/raw/")) { + return "raw"; + } else if (lowerUrl.contains("/video/")) { + return "video"; + } else if (lowerUrl.contains("/image/")) { + return "image"; + } else { + // Default to auto, but Cloudinary will handle it + return "auto"; } - return secureUrl.toString(); } } diff --git a/src/main/java/com/note0/simple/DashboardPanel.java b/src/main/java/com/note0/simple/DashboardPanel.java index ab116f3..6f7e143 100644 --- a/src/main/java/com/note0/simple/DashboardPanel.java +++ b/src/main/java/com/note0/simple/DashboardPanel.java @@ -23,6 +23,7 @@ public class DashboardPanel extends JPanel { private JTable materialsTable; private DefaultTableModel tableModel; + private List currentMaterials = new ArrayList<>(); public DashboardPanel(MainFrame mainFrame, User user, MaterialDAO materialDAO, SubjectDAO subjectDAO, CloudinaryService cloudinaryService) { this.mainFrame = mainFrame; @@ -36,6 +37,7 @@ public DashboardPanel(MainFrame mainFrame, User user, MaterialDAO materialDAO, S JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.addTab("Browse Materials", createBrowsePanel()); tabbedPane.addTab("Upload Material", createUploadPanel()); + tabbedPane.addTab("My Uploads", createMyUploadsPanel()); add(tabbedPane, BorderLayout.CENTER); @@ -94,8 +96,20 @@ public void mouseClicked(java.awt.event.MouseEvent evt) { filterButton.addActionListener(e -> loadMaterials(searchField.getText(), (String) subjectFilterComboBox.getSelectedItem())); + // Action buttons panel + JPanel actionPanel = new JPanel(new FlowLayout()); + JButton rateButton = new JButton("Rate Selected"); + JButton viewButton = new JButton("View Selected"); + + rateButton.addActionListener(e -> rateSelectedMaterial()); + viewButton.addActionListener(e -> viewSelectedMaterial()); + + actionPanel.add(rateButton); + actionPanel.add(viewButton); + panel.add(filterPanel, BorderLayout.NORTH); panel.add(new JScrollPane(materialsTable), BorderLayout.CENTER); + panel.add(actionPanel, BorderLayout.SOUTH); return panel; } @@ -164,15 +178,12 @@ private void populateSubjectFilter(JComboBox comboBox) { private void loadMaterials(String titleFilter, String subjectFilter) { tableModel.setRowCount(0); // Clear existing data + currentMaterials.clear(); // Clear current materials list try { List materials = materialDAO.getMaterials(titleFilter, subjectFilter); + currentMaterials.addAll(materials); // Store materials for later access for (Material material : materials) { - // Add a hidden column for the ID - Object[] rowData = {material.getTitle(), material.getSubjectName(), String.format("%.1f", material.getAverageRating()), material.getUploaderName(), material.getId()}; - - // We need a way to add the ID without displaying it. A custom table model is one way. - // For simplicity here, we'll just have to query it again on click. - tableModel.addRow(new Object[]{material.getTitle(), material.getSubjectName(), String.format("%.1f", material.getAverageRating()), material.getUploaderName()}); + tableModel.addRow(new Object[]{material.getTitle(), material.getSubjectName(), String.format("%.1f", material.getAverageRating()), material.getUploaderName()}); } } catch (SQLException e) { JOptionPane.showMessageDialog(this, "Could not load materials: " + e.getMessage(), "Database Error", JOptionPane.ERROR_MESSAGE); @@ -218,4 +229,132 @@ private void handleUpload(String title, String subjectName, File file) { JOptionPane.showMessageDialog(this, "Upload failed: " + e.getMessage(), "Upload Error", JOptionPane.ERROR_MESSAGE); } } + + private void rateSelectedMaterial() { + int selectedRow = materialsTable.getSelectedRow(); + if (selectedRow < 0) { + JOptionPane.showMessageDialog(this, "Please select a material to rate.", "Info", JOptionPane.INFORMATION_MESSAGE); + return; + } + + try { + Material material = currentMaterials.get(selectedRow); + long materialId = material.getId(); + + // Get current user rating + int currentRating = materialDAO.getUserRating(materialId, loggedInUser.getId()); + + // Show rating dialog + String[] options = {"1 Star", "2 Stars", "3 Stars", "4 Stars", "5 Stars"}; + String message = "Rate: " + material.getTitle() + + "\nCurrent rating: " + (currentRating > 0 ? currentRating + " stars" : "Not rated"); + + int choice = JOptionPane.showOptionDialog(this, message, "Rate Material", + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); + + if (choice >= 0) { + int rating = choice + 1; + materialDAO.addOrUpdateRating(materialId, loggedInUser.getId(), rating); + JOptionPane.showMessageDialog(this, "Rating saved successfully!", "Success", JOptionPane.INFORMATION_MESSAGE); + + // Refresh the materials list to show updated rating + loadMaterials("", "All Subjects"); + } + + } catch (SQLException e) { + JOptionPane.showMessageDialog(this, "Error rating material: " + e.getMessage(), "Database Error", JOptionPane.ERROR_MESSAGE); + } + } + + private void viewSelectedMaterial() { + int selectedRow = materialsTable.getSelectedRow(); + if (selectedRow < 0) { + JOptionPane.showMessageDialog(this, "Please select a material to view.", "Info", JOptionPane.INFORMATION_MESSAGE); + return; + } + + Material material = currentMaterials.get(selectedRow); + openMaterial(material); + } + + private JPanel createMyUploadsPanel() { + JPanel panel = new JPanel(new BorderLayout(10, 10)); + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + // My uploads table + String[] columnNames = {"Title", "Subject", "Rating", "Status"}; + DefaultTableModel myUploadsModel = new DefaultTableModel(columnNames, 0) { + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + }; + JTable myUploadsTable = new JTable(myUploadsModel); + myUploadsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + // Action buttons for my uploads + JPanel myUploadsActionPanel = new JPanel(new FlowLayout()); + JButton deleteMyUploadButton = new JButton("Delete Selected"); + JButton refreshMyUploadsButton = new JButton("Refresh"); + + deleteMyUploadButton.addActionListener(e -> deleteMyUpload(myUploadsTable, myUploadsModel)); + refreshMyUploadsButton.addActionListener(e -> loadMyUploads(myUploadsModel)); + + myUploadsActionPanel.add(deleteMyUploadButton); + myUploadsActionPanel.add(refreshMyUploadsButton); + + panel.add(new JScrollPane(myUploadsTable), BorderLayout.CENTER); + panel.add(myUploadsActionPanel, BorderLayout.SOUTH); + + // Load initial data + loadMyUploads(myUploadsModel); + + return panel; + } + + private void loadMyUploads(DefaultTableModel model) { + model.setRowCount(0); + try { + List myMaterials = materialDAO.getMaterialsByUser(loggedInUser.getId()); + for (Material material : myMaterials) { + model.addRow(new Object[]{ + material.getTitle(), + material.getSubjectName(), + String.format("%.1f", material.getAverageRating()), + material.getApprovalStatus() + }); + } + } catch (SQLException e) { + JOptionPane.showMessageDialog(this, "Could not load your uploads: " + e.getMessage(), "Database Error", JOptionPane.ERROR_MESSAGE); + } + } + + private void deleteMyUpload(JTable table, DefaultTableModel model) { + int selectedRow = table.getSelectedRow(); + if (selectedRow < 0) { + JOptionPane.showMessageDialog(this, "Please select an upload to delete.", "Info", JOptionPane.INFORMATION_MESSAGE); + return; + } + + try { + List myMaterials = materialDAO.getMaterialsByUser(loggedInUser.getId()); + if (selectedRow < myMaterials.size()) { + Material material = myMaterials.get(selectedRow); + + int confirm = JOptionPane.showConfirmDialog(this, + "Are you sure you want to delete '" + material.getTitle() + "'?\n\n" + + "This will remove the material from the database.\n" + + "The uploaded file will remain in Cloudinary storage.", + "Confirm Deletion", JOptionPane.YES_NO_OPTION); + + if (confirm == JOptionPane.YES_OPTION) { + materialDAO.deleteMaterial(material.getId()); + JOptionPane.showMessageDialog(this, "Upload deleted successfully!", "Success", JOptionPane.INFORMATION_MESSAGE); + loadMyUploads(model); // Refresh the list + } + } + } catch (SQLException e) { + JOptionPane.showMessageDialog(this, "Error deleting upload: " + e.getMessage(), "Database Error", JOptionPane.ERROR_MESSAGE); + } + } } diff --git a/src/main/java/com/note0/simple/FeedPanel.java b/src/main/java/com/note0/simple/FeedPanel.java index ed301bd..9d166e9 100644 --- a/src/main/java/com/note0/simple/FeedPanel.java +++ b/src/main/java/com/note0/simple/FeedPanel.java @@ -81,11 +81,18 @@ private JPanel createSimpleMaterialPanel(Material material) { infoPanel.add(new JLabel("Subject: " + material.getSubjectName())); // Simplified infoPanel.add(new JLabel("Rating: " + String.format("%.1f", material.getAverageRating()))); + JPanel buttonPanel = new JPanel(new FlowLayout()); JButton viewButton = new JButton("View"); + JButton rateButton = new JButton("Rate"); + viewButton.addActionListener(e -> handleMaterialClick(material)); + rateButton.addActionListener(e -> rateMaterial(material)); + + buttonPanel.add(viewButton); + buttonPanel.add(rateButton); panel.add(infoPanel, BorderLayout.CENTER); - panel.add(viewButton, BorderLayout.EAST); + panel.add(buttonPanel, BorderLayout.EAST); return panel; } @@ -112,4 +119,33 @@ private void handleMaterialClick(Material material) { JOptionPane.showMessageDialog(this, "Could not open file: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); } } + + private void rateMaterial(Material material) { + try { + long materialId = material.getId(); + + // Get current user rating + int currentRating = materialDAO.getUserRating(materialId, loggedInUser.getId()); + + // Show rating dialog + String[] options = {"1 Star", "2 Stars", "3 Stars", "4 Stars", "5 Stars"}; + String message = "Rate: " + material.getTitle() + + "\nCurrent rating: " + (currentRating > 0 ? currentRating + " stars" : "Not rated"); + + int choice = JOptionPane.showOptionDialog(this, message, "Rate Material", + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); + + if (choice >= 0) { + int rating = choice + 1; + materialDAO.addOrUpdateRating(materialId, loggedInUser.getId(), rating); + JOptionPane.showMessageDialog(this, "Rating saved successfully!", "Success", JOptionPane.INFORMATION_MESSAGE); + + // Refresh the feed to show updated ratings + mainFrame.showFeedPanel(loggedInUser); + } + + } catch (SQLException e) { + JOptionPane.showMessageDialog(this, "Error rating material: " + e.getMessage(), "Database Error", JOptionPane.ERROR_MESSAGE); + } + } } \ No newline at end of file diff --git a/src/main/java/com/note0/simple/MainFrame.java b/src/main/java/com/note0/simple/MainFrame.java index c9f0b73..3e1aa0e 100644 --- a/src/main/java/com/note0/simple/MainFrame.java +++ b/src/main/java/com/note0/simple/MainFrame.java @@ -67,7 +67,7 @@ public void showFeedPanel(User user) { tabbedPane.addTab("Profile", profilePanel); if ("ADMIN".equals(user.getRole())) { - AdminPanel adminPanel = new AdminPanel(subjectDAO, materialDAO); + AdminPanel adminPanel = new AdminPanel(subjectDAO, materialDAO, cloudinaryService); tabbedPane.addTab("Admin", adminPanel); } diff --git a/src/main/java/com/note0/simple/MaterialDAO.java b/src/main/java/com/note0/simple/MaterialDAO.java index d2d2a79..a7d8845 100644 --- a/src/main/java/com/note0/simple/MaterialDAO.java +++ b/src/main/java/com/note0/simple/MaterialDAO.java @@ -84,6 +84,18 @@ public void deleteMaterial(long materialId) throws SQLException { pstmt.executeUpdate(); } } + + /** + * Deletes a material from the database only (keeps the file in Cloudinary). + * @param materialId The ID of the material to delete + * @param cloudinaryService The CloudinaryService instance (not used, kept for compatibility) + * @return true if database deletion was successful + */ + public boolean deleteMaterialWithFile(long materialId, CloudinaryService cloudinaryService) throws SQLException { + // Delete from database only - keep the file in Cloudinary + deleteMaterial(materialId); + return true; + } public Material getMaterialById(long id) throws SQLException { String sql = "SELECT m.id, m.title, m.file_path, m.average_rating, m.approval_status, u.full_name, s.name AS subject_name " + @@ -233,4 +245,164 @@ public void updateApprovalStatus(long materialId, String status) throws SQLExcep pstmt.executeUpdate(); } } + + public List getAllMaterialsForAdmin() throws SQLException { + String sql = "SELECT m.id, m.title, m.file_path, m.average_rating, m.approval_status, u.full_name, s.name AS subject_name " + + "FROM materials m " + + "JOIN users u ON m.uploader_id = u.id " + + "JOIN subjects s ON m.subject_id = s.id " + + "ORDER BY m.id DESC"; + + List materials = new ArrayList<>(); + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + try (ResultSet rs = pstmt.executeQuery()) { + while (rs.next()) { + Material material = new Material(); + material.setId(rs.getLong("id")); + material.setTitle(rs.getString("title")); + material.setFilePath(rs.getString("file_path")); + material.setUploaderName(rs.getString("full_name")); + material.setSubjectName(rs.getString("subject_name")); + material.setAverageRating(rs.getDouble("average_rating")); + try { + material.setApprovalStatus(rs.getString("approval_status")); + } catch (SQLException e) { + // If approval_status column doesn't exist, set to APPROVED + material.setApprovalStatus("APPROVED"); + } + materials.add(material); + } + } + } + return materials; + } + + /** + * Adds or updates a rating for a material by a user. + * @param materialId The ID of the material being rated + * @param userId The ID of the user giving the rating + * @param rating The rating score (1-5) + * @throws SQLException if a database error occurs + */ + public void addOrUpdateRating(long materialId, long userId, int rating) throws SQLException { + // First, check if user has already rated this material + String checkSql = "SELECT id FROM ratings WHERE material_id = ? AND user_id = ?"; + boolean hasRated = false; + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(checkSql)) { + pstmt.setLong(1, materialId); + pstmt.setLong(2, userId); + try (ResultSet rs = pstmt.executeQuery()) { + hasRated = rs.next(); + } + } + + if (hasRated) { + // Update existing rating + String updateSql = "UPDATE ratings SET score = ? WHERE material_id = ? AND user_id = ?"; + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(updateSql)) { + pstmt.setInt(1, rating); + pstmt.setLong(2, materialId); + pstmt.setLong(3, userId); + pstmt.executeUpdate(); + } + } else { + // Insert new rating + String insertSql = "INSERT INTO ratings (material_id, user_id, score) VALUES (?, ?, ?)"; + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(insertSql)) { + pstmt.setLong(1, materialId); + pstmt.setLong(2, userId); + pstmt.setInt(3, rating); + pstmt.executeUpdate(); + } + } + + // Update the average rating for the material + updateAverageRating(materialId); + } + + /** + * Updates the average rating for a material based on all its ratings. + * @param materialId The ID of the material + * @throws SQLException if a database error occurs + */ + private void updateAverageRating(long materialId) throws SQLException { + String sql = "UPDATE materials SET average_rating = " + + "(SELECT COALESCE(AVG(score), 0) FROM ratings WHERE material_id = ?) " + + "WHERE id = ?"; + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setLong(1, materialId); + pstmt.setLong(2, materialId); + pstmt.executeUpdate(); + } + } + + /** + * Gets the rating given by a specific user for a material. + * @param materialId The ID of the material + * @param userId The ID of the user + * @return The rating score (1-5) or 0 if not rated + * @throws SQLException if a database error occurs + */ + public int getUserRating(long materialId, long userId) throws SQLException { + String sql = "SELECT score FROM ratings WHERE material_id = ? AND user_id = ?"; + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + pstmt.setLong(1, materialId); + pstmt.setLong(2, userId); + try (ResultSet rs = pstmt.executeQuery()) { + if (rs.next()) { + return rs.getInt("score"); + } + } + } + return 0; // No rating found + } + + /** + * Gets materials uploaded by a specific user. + * @param userId The ID of the user + * @return List of materials uploaded by the user + * @throws SQLException if a database error occurs + */ + public List getMaterialsByUser(long userId) throws SQLException { + String sql = "SELECT m.id, m.title, m.file_path, m.average_rating, m.approval_status, u.full_name, s.name AS subject_name " + + "FROM materials m " + + "JOIN users u ON m.uploader_id = u.id " + + "JOIN subjects s ON m.subject_id = s.id " + + "WHERE m.uploader_id = ? " + + "ORDER BY m.id DESC"; + + List materials = new ArrayList<>(); + try (Connection conn = DatabaseManager.getConnection(); + PreparedStatement pstmt = conn.prepareStatement(sql)) { + + pstmt.setLong(1, userId); + + try (ResultSet rs = pstmt.executeQuery()) { + while (rs.next()) { + Material material = new Material(); + material.setId(rs.getLong("id")); + material.setTitle(rs.getString("title")); + material.setFilePath(rs.getString("file_path")); + material.setUploaderName(rs.getString("full_name")); + material.setSubjectName(rs.getString("subject_name")); + material.setAverageRating(rs.getDouble("average_rating")); + try { + material.setApprovalStatus(rs.getString("approval_status")); + } catch (SQLException e) { + // If approval_status column doesn't exist, set to APPROVED + material.setApprovalStatus("APPROVED"); + } + materials.add(material); + } + } + } + return materials; + } } \ No newline at end of file