USER
my code
package nsomatrix.ui;
import org.nsomatrix.supabase.SupabaseStorageClient;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.io.File;
import java.util.List;
public class DashboardPanel extends JPanel {
private String userEmail;
private SupabaseStorageClient storageClient;
private JLabel userLabel;
private DefaultListModel<String> fileListModel = new DefaultListModel<>();
private JList<String> fileList;
private JButton uploadButton;
private JButton downloadButton;
private JButton deleteButton;
private JButton refreshButton;
public DashboardPanel() {
setLayout(new BorderLayout(10, 10));
setBorder(new EmptyBorder(10, 10, 10, 10));
userLabel = new JLabel("Logged in as: ");
add(userLabel, BorderLayout.NORTH);
fileList = new JList<>(fileListModel);
JScrollPane scrollPane = new JScrollPane(fileList);
add(scrollPane, BorderLayout.CENTER);
JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10));
uploadButton = new JButton("Upload");
downloadButton = new JButton("Download");
deleteButton = new JButton("Delete");
refreshButton = new JButton("Refresh");
buttonsPanel.add(uploadButton);
buttonsPanel.add(downloadButton);
buttonsPanel.add(deleteButton);
buttonsPanel.add(refreshButton);
add(buttonsPanel, BorderLayout.SOUTH);
uploadButton.addActionListener(e -> onUpload());
refreshButton.addActionListener(e -> fetchFileList());
downloadButton.addActionListener(e -> onDownload());
deleteButton.addActionListener(e -> onDelete());
}
public void setUserEmail(String email) {
this.userEmail = email;
userLabel.setText("Logged in as: " + email);
}
public void setStorageClient(SupabaseStorageClient storageClient) {
this.storageClient = storageClient;
fetchFileList();
}
public void clearState() {
userLabel.setText("Logged out.");
fileListModel.clear();
storageClient = null;
}
private void fetchFileList() {
if (storageClient == null) return;
fileListModel.clear();
SwingWorker<List<String>, Void> worker = new SwingWorker<>() {
@Override
protected List<String> doInBackground() throws Exception {
return storageClient.listFiles();
}
@Override
protected void done() {
try {
List<String> files = get();
fileListModel.clear();
if (files.isEmpty()) {
fileListModel.addElement("<no files>");
} else {
files.forEach(fileListModel::addElement);
}
} catch (Exception e) {
// Print full stack trace to console for debugging
e.printStackTrace();
// Show detailed error message to user
String msg = e.getMessage();
if (e.getCause() != null) {
msg += "\nCause: " + e.getCause().getMessage();
}
JOptionPane.showMessageDialog(DashboardPanel.this,
"Failed to fetch file list:\n" + msg,
"Error",
JOptionPane.ERROR_MESSAGE);
}
}
};
worker.execute();
}
// onUpload, onDownload and onDelete methods unchanged, or similarly add try/catch with e.printStackTrace()
private void onUpload() {
if (storageClient == null) return;
JFileChooser chooser = new JFileChooser();
int result = chooser.showOpenDialog(this);
if (result == JFileChooser.APPROVE_OPTION) {
File selectedFile = chooser.getSelectedFile();
SwingWorker<Void, Void> worker = new SwingWorker<>() {
@Override
protected Void doInBackground() throws Exception {
storageClient.uploadFile(selectedFile);
return null;
}
@Override
protected void done() {
try {
get();
fetchFileList();
JOptionPane.showMessageDialog(DashboardPanel.this,
"Upload completed.", "Success", JOptionPane.INFORMATION_MESSAGE);
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(DashboardPanel.this,
"Upload failed:\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
};
worker.execute();
}
}
private void onDownload() {
if (storageClient == null) return;
String selected = fileList.getSelectedValue();
if (selected == null || selected.equals("<no files>")) {
JOptionPane.showMessageDialog(this,
"Please select a file first.", "Warning", JOptionPane.WARNING_MESSAGE);
return;
}
JFileChooser chooser = new JFileChooser();
chooser.setSelectedFile(new File(selected));
int result = chooser.showSaveDialog(this);
if (result == JFileChooser.APPROVE_OPTION) {
File saveFile = chooser.getSelectedFile();
SwingWorker<Void, Void> worker = new SwingWorker<>() {
@Override
protected Void doInBackground() throws Exception {
storageClient.downloadFile(selected, saveFile);
return null;
}
@Override
protected void done() {
try {
get();
JOptionPane.showMessageDialog(DashboardPanel.this,
"Download completed.", "Success", JOptionPane.INFORMATION_MESSAGE);
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(DashboardPanel.this,
"Download failed:\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
};
worker.execute();
}
}
private void onDelete() {
if (storageClient == null) return;
String selected = fileList.getSelectedValue();
if (selected == null || selected.equals("<no files>")) {
JOptionPane.showMessageDialog(this,
"Please select a file first.", "Warning", JOptionPane.WARNING_MESSAGE);
return;
}
int confirm = JOptionPane.showConfirmDialog(this,
"Are you sure you want to delete '" + selected + "'?",
"Confirm Delete",
JOptionPane.YES_NO_OPTION);
if (confirm == JOptionPane.YES_OPTION) {
SwingWorker<Void, Void> worker = new SwingWorker<>() {
@Override
protected Void doInBackground() throws Exception {
storageClient.deleteFile(selected);
return null;
}
@Override
protected void done() {
try {
get();
fetchFileList();
JOptionPane.showMessageDialog(DashboardPanel.this,
"Deleted successfully.", "Success", JOptionPane.INFORMATION_MESSAGE);
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(DashboardPanel.this,
"Delete failed:\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
};
worker.execute();
}
}
}
package nsomatrix.supabase;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.http.*;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.file.Files;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* SupabaseStorageClient - handles file operations in a Supabase storage bucket,
* isolating files per user by prefixing paths with user UUID,
* using user JWT for authenticated requests.
*/
public class SupabaseStorageClient {
private final HttpClient httpClient;
private final ObjectMapper objectMapper;
private String accessToken; // User JWT token
private String userId; // User UUID (used as folder prefix)
/**
* Constructor initializes HttpClient and ObjectMapper.
*/
public SupabaseStorageClient() {
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(15))
.build();
this.objectMapper = new ObjectMapper();
}
/**
* Sets the user JWT token for authenticated requests.
* @param token JWT access token obtained upon user login
*/
public void setAccessToken(String token) {
this.accessToken = token;
}
/**
* Sets the current user's UUID used for prefixing file paths.
* @param userId UUID string representing the current user
*/
public void setUserId(String userId) {
this.userId = userId;
}
/**
* Prepends the userId folder prefix to a given file path.
* @param path File path relative to the user folder (can be empty)
* @return Fully prefixed file path in storage bucket
*/
private String prefixPath(String path) {
if (userId == null || userId.isBlank()) {
throw new IllegalStateException("User ID must be set before calling storage methods");
}
if (path == null || path.isBlank()) {
return userId + "/";
}
return userId + "/" + path;
}
/**
* Lists files in the logged-in user's folder.
* @return List of file names (without userId prefix)
* @throws IOException on network issues or bad response
* @throws InterruptedException if request is interrupted
*/
public List<String> listFiles() throws IOException, InterruptedException {
String prefix = prefixPath("");
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(SupabaseConfig.SUPABASE_URL + "/storage/v1/object/list/" + SupabaseConfig.STORAGE_BUCKET + "?prefix=" + prefix))
.GET()
.header("apikey", SupabaseConfig.SUPABASE_ANON_KEY)
.header("Authorization", "Bearer " + accessToken)
.build();
// Debug output
System.out.println("[SupabaseStorageClient] List files request URL: " + request.uri());
System.out.println("[SupabaseStorageClient] Bucket: " + SupabaseConfig.STORAGE_BUCKET);
System.out.println("[SupabaseStorageClient] Access token present: " + (accessToken != null && !accessToken.isBlank()));
HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IOException("Failed to list files, status: " + response.statusCode() + ", body: " + response.body());
}
JsonNode root = objectMapper.readTree(response.body());
List<String> files = new ArrayList<>();
if (root.isArray()) {
for (JsonNode item : root) {
String fullName = item.get("name").asText();
files.add(fullName.substring(prefix.length())); // Remove userId prefix
}
}
return files;
}
/**
* Uploads a file to the user's folder in the storage bucket.
* @param file Local file to upload
* @throws IOException on network or file IO errors
* @throws InterruptedException if request is interrupted
*/
public void uploadFile(File file) throws IOException, InterruptedException {
String path = prefixPath(file.getName());
HttpRequest.BodyPublisher bodyPublisher = HttpRequest.BodyPublishers.ofFile(file.toPath());
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(SupabaseConfig.SUPABASE_URL + "/storage/v1/object/" + SupabaseConfig.STORAGE_BUCKET + "/" + path))
.header("apikey", SupabaseConfig.SUPABASE_ANON_KEY)
.header("Authorization", "Bearer " + accessToken)
.header("Content-Type", Files.probeContentType(file.toPath()))
.PUT(bodyPublisher)
.build();
System.out.println("[SupabaseStorageClient] Upload file request URL: " + request.uri());
System.out.println("[SupabaseStorageClient] Uploading file: " + file.getAbsolutePath());
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IOException("Failed to upload file, status: " + response.statusCode() + ", body: " + response.body());
}
}
/**
* Downloads a file from the user's folder in the storage bucket.
* @param remoteFileName Filename in storage (without userId prefix)
* @param localFile Local file to save the data
* @throws IOException on network or file IO errors
* @throws InterruptedException if request is interrupted
*/
public void downloadFile(String remoteFileName, File localFile) throws IOException, InterruptedException {
String path = prefixPath(remoteFileName);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(SupabaseConfig.SUPABASE_URL + "/storage/v1/object/" + SupabaseConfig.STORAGE_BUCKET + "/" + path))
.GET()
.header("apikey", SupabaseConfig.SUPABASE_ANON_KEY)
.header("Authorization", "Bearer " + accessToken)
.build();
System.out.println("[SupabaseStorageClient] Download file request URL: " + request.uri());
System.out.println("[SupabaseStorageClient] Downloading to: " + localFile.getAbsolutePath());
HttpResponse<InputStream> response = httpClient.send(request, BodyHandlers.ofInputStream());
if (response.statusCode() != 200) {
String body = new String(response.body().readAllBytes());
throw new IOException("Failed to download file, status: " + response.statusCode() + ", body: " + body);
}
try (InputStream is = response.body();
OutputStream os = Files.newOutputStream(localFile.toPath())) {
is.transferTo(os);
}
}
/**
* Deletes a file from the user's folder in the storage bucket.
* @param remoteFileName Filename in storage (without userId prefix)
* @throws IOException on network errors
* @throws InterruptedException if request is interrupted
*/
public void deleteFile(String remoteFileName) throws IOException, InterruptedException {
String path = prefixPath(remoteFileName);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(SupabaseConfig.SUPABASE_URL + "/storage/v1/object/" + SupabaseConfig.STORAGE_BUCKET + "/" + path))
.header("apikey", SupabaseConfig.SUPABASE_ANON_KEY)
.header("Authorization", "Bearer " + accessToken)
.DELETE()
.build();
System.out.println("[SupabaseStorageClient] Delete file request URL: " + request.uri());
System.out.println("[SupabaseStorageClient] Deleting file: " + remoteFileName);
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 204) {
throw new IOException("Failed to delete file, status: " + response.statusCode() + ", body: " + response.body());
}
}
}
is it wrong?