Implement user-specific share link management for files
This commit is contained in:
@@ -40,9 +40,10 @@ class _ShareFileDialogState extends State<ShareFileDialog> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final apiClient = getIt<ApiClient>();
|
final apiClient = getIt<ApiClient>();
|
||||||
final response = await apiClient.getRaw(
|
final path = widget.orgId == 'files'
|
||||||
'/orgs/${widget.orgId}/files/${widget.fileId}/share',
|
? '/user/files/${widget.fileId}/share'
|
||||||
);
|
: '/orgs/${widget.orgId}/files/${widget.fileId}/share';
|
||||||
|
final response = await apiClient.getRaw(path);
|
||||||
|
|
||||||
if (response['exists'] == true) {
|
if (response['exists'] == true) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -67,10 +68,10 @@ class _ShareFileDialogState extends State<ShareFileDialog> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final apiClient = getIt<ApiClient>();
|
final apiClient = getIt<ApiClient>();
|
||||||
final response = await apiClient.postRaw(
|
final path = widget.orgId == 'files'
|
||||||
'/orgs/${widget.orgId}/files/${widget.fileId}/share',
|
? '/user/files/${widget.fileId}/share'
|
||||||
data: {},
|
: '/orgs/${widget.orgId}/files/${widget.fileId}/share';
|
||||||
);
|
final response = await apiClient.postRaw(path, data: {});
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_shareUrl = response['url'];
|
_shareUrl = response['url'];
|
||||||
@@ -92,9 +93,10 @@ class _ShareFileDialogState extends State<ShareFileDialog> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final apiClient = getIt<ApiClient>();
|
final apiClient = getIt<ApiClient>();
|
||||||
await apiClient.delete(
|
final path = widget.orgId == 'files'
|
||||||
'/orgs/${widget.orgId}/files/${widget.fileId}/share',
|
? '/user/files/${widget.fileId}/share'
|
||||||
);
|
: '/orgs/${widget.orgId}/files/${widget.fileId}/share';
|
||||||
|
await apiClient.delete(path);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_shareUrl = null;
|
_shareUrl = null;
|
||||||
|
|||||||
BIN
go_cloud/api
BIN
go_cloud/api
Binary file not shown.
@@ -210,6 +210,16 @@ func NewRouter(cfg *config.Config, db *database.DB, jwtManager *jwt.Manager, aut
|
|||||||
r.Get("/user/files/{fileId}/collabora-proxy", func(w http.ResponseWriter, req *http.Request) {
|
r.Get("/user/files/{fileId}/collabora-proxy", func(w http.ResponseWriter, req *http.Request) {
|
||||||
collaboraProxyHandler(w, req, db, jwtManager, "https://of.b0esche.cloud")
|
collaboraProxyHandler(w, req, db, jwtManager, "https://of.b0esche.cloud")
|
||||||
})
|
})
|
||||||
|
// Share link management for user files
|
||||||
|
r.Get("/user/files/{fileId}/share", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
getUserFileShareLinkHandler(w, req, db)
|
||||||
|
})
|
||||||
|
r.Post("/user/files/{fileId}/share", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
createUserFileShareLinkHandler(w, req, db)
|
||||||
|
})
|
||||||
|
r.Delete("/user/files/{fileId}/share", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
revokeUserFileShareLinkHandler(w, req, db)
|
||||||
|
})
|
||||||
|
|
||||||
// Org routes
|
// Org routes
|
||||||
r.Get("/orgs", func(w http.ResponseWriter, req *http.Request) {
|
r.Get("/orgs", func(w http.ResponseWriter, req *http.Request) {
|
||||||
@@ -3028,3 +3038,156 @@ func publicFileDownloadHandler(w http.ResponseWriter, r *http.Request, db *datab
|
|||||||
// Copy body
|
// Copy body
|
||||||
io.Copy(w, resp.Body)
|
io.Copy(w, resp.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getUserFileShareLinkHandler(w http.ResponseWriter, r *http.Request, db *database.DB) {
|
||||||
|
userIDStr, _ := middleware.GetUserID(r.Context())
|
||||||
|
userID, _ := uuid.Parse(userIDStr)
|
||||||
|
fileId := chi.URLParam(r, "fileId")
|
||||||
|
|
||||||
|
fileUUID, err := uuid.Parse(fileId)
|
||||||
|
if err != nil {
|
||||||
|
errors.WriteError(w, errors.CodeInvalidArgument, "Invalid file ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if file exists and belongs to user
|
||||||
|
file, err := db.GetFileByID(r.Context(), fileUUID)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(r, err, "Failed to get file")
|
||||||
|
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if file.UserID == nil || *file.UserID != userID {
|
||||||
|
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
link, err := db.GetFileShareLinkByFileID(r.Context(), fileUUID)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
// No share link exists
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"exists": false,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errors.LogError(r, err, "Failed to get share link")
|
||||||
|
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build full URL
|
||||||
|
scheme := "https"
|
||||||
|
if proto := r.Header.Get("X-Forwarded-Proto"); proto != "" {
|
||||||
|
scheme = proto
|
||||||
|
} else if r.TLS == nil {
|
||||||
|
scheme = "http"
|
||||||
|
}
|
||||||
|
host := r.Host
|
||||||
|
if host == "" {
|
||||||
|
host = "go.b0esche.cloud"
|
||||||
|
}
|
||||||
|
fullURL := fmt.Sprintf("%s://%s/public/share/%s", scheme, host, link.Token)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"exists": true,
|
||||||
|
"url": fullURL,
|
||||||
|
"token": link.Token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func createUserFileShareLinkHandler(w http.ResponseWriter, r *http.Request, db *database.DB) {
|
||||||
|
userIDStr, _ := middleware.GetUserID(r.Context())
|
||||||
|
userID, _ := uuid.Parse(userIDStr)
|
||||||
|
fileId := chi.URLParam(r, "fileId")
|
||||||
|
|
||||||
|
fileUUID, err := uuid.Parse(fileId)
|
||||||
|
if err != nil {
|
||||||
|
errors.WriteError(w, errors.CodeInvalidArgument, "Invalid file ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if file exists and belongs to user
|
||||||
|
file, err := db.GetFileByID(r.Context(), fileUUID)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(r, err, "Failed to get file")
|
||||||
|
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if file.UserID == nil || *file.UserID != userID {
|
||||||
|
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revoke existing link if any
|
||||||
|
db.RevokeFileShareLink(r.Context(), fileUUID) // Ignore error
|
||||||
|
|
||||||
|
// Generate token
|
||||||
|
token, err := storage.GenerateSecurePassword(32)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(r, err, "Failed to generate token")
|
||||||
|
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
link, err := db.CreateFileShareLink(r.Context(), token, fileUUID, userID, userID)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(r, err, "Failed to create share link")
|
||||||
|
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build full URL
|
||||||
|
scheme := "https"
|
||||||
|
if proto := r.Header.Get("X-Forwarded-Proto"); proto != "" {
|
||||||
|
scheme = proto
|
||||||
|
} else if r.TLS == nil {
|
||||||
|
scheme = "http"
|
||||||
|
}
|
||||||
|
host := r.Host
|
||||||
|
if host == "" {
|
||||||
|
host = "go.b0esche.cloud"
|
||||||
|
}
|
||||||
|
fullURL := fmt.Sprintf("%s://%s/public/share/%s", scheme, host, link.Token)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"url": fullURL,
|
||||||
|
"token": link.Token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func revokeUserFileShareLinkHandler(w http.ResponseWriter, r *http.Request, db *database.DB) {
|
||||||
|
userIDStr, _ := middleware.GetUserID(r.Context())
|
||||||
|
userID, _ := uuid.Parse(userIDStr)
|
||||||
|
fileId := chi.URLParam(r, "fileId")
|
||||||
|
|
||||||
|
fileUUID, err := uuid.Parse(fileId)
|
||||||
|
if err != nil {
|
||||||
|
errors.WriteError(w, errors.CodeInvalidArgument, "Invalid file ID", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if file exists and belongs to user
|
||||||
|
file, err := db.GetFileByID(r.Context(), fileUUID)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(r, err, "Failed to get file")
|
||||||
|
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if file.UserID == nil || *file.UserID != userID {
|
||||||
|
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.RevokeFileShareLink(r.Context(), fileUUID)
|
||||||
|
if err != nil {
|
||||||
|
errors.LogError(r, err, "Failed to revoke share link")
|
||||||
|
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user