Skip to content

Commit

Permalink
Development: Remove deprecated file upload endpoint (#7453)
Browse files Browse the repository at this point in the history
  • Loading branch information
julian-christl authored Oct 28, 2023
1 parent 434c814 commit 475754f
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 339 deletions.
101 changes: 36 additions & 65 deletions src/main/java/de/tum/in/www1/artemis/web/rest/FileResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.regex.Pattern;

import javax.activation.MimetypesFileTypeMap;
import javax.annotation.Nullable;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
Expand Down Expand Up @@ -43,7 +44,6 @@
import de.tum.in.www1.artemis.service.ResourceLoaderService;
import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException;
import de.tum.in.www1.artemis.web.rest.errors.EntityNotFoundException;
import de.tum.in.www1.artemis.web.rest.lecture.AttachmentUnitResource;

/**
* REST controller for managing Files.
Expand All @@ -58,8 +58,6 @@ public class FileResource {

private final FileService fileService;

private final FilePathService filePathService;

private final ResourceLoaderService resourceLoaderService;

private final LectureRepository lectureRepository;
Expand Down Expand Up @@ -88,12 +86,13 @@ public class FileResource {

private final CourseRepository courseRepository;

public FileResource(FilePathService filePathService, SlideRepository slideRepository, AuthorizationCheckService authorizationCheckService, FileService fileService,
ResourceLoaderService resourceLoaderService, LectureRepository lectureRepository, FileUploadSubmissionRepository fileUploadSubmissionRepository,
FileUploadExerciseRepository fileUploadExerciseRepository, AttachmentRepository attachmentRepository, AttachmentUnitRepository attachmentUnitRepository,
AuthorizationCheckService authCheckService, UserRepository userRepository, ExamUserRepository examUserRepository, QuizQuestionRepository quizQuestionRepository,
DragItemRepository dragItemRepository, CourseRepository courseRepository) {
this.filePathService = filePathService;
private final FilePathService filePathService;

public FileResource(SlideRepository slideRepository, AuthorizationCheckService authorizationCheckService, FileService fileService, ResourceLoaderService resourceLoaderService,
LectureRepository lectureRepository, FileUploadSubmissionRepository fileUploadSubmissionRepository, FileUploadExerciseRepository fileUploadExerciseRepository,
AttachmentRepository attachmentRepository, AttachmentUnitRepository attachmentUnitRepository, AuthorizationCheckService authCheckService, UserRepository userRepository,
ExamUserRepository examUserRepository, QuizQuestionRepository quizQuestionRepository, DragItemRepository dragItemRepository, CourseRepository courseRepository,
FilePathService filePathService) {
this.fileService = fileService;
this.resourceLoaderService = resourceLoaderService;
this.lectureRepository = lectureRepository;
Expand All @@ -109,44 +108,7 @@ public FileResource(FilePathService filePathService, SlideRepository slideReposi
this.quizQuestionRepository = quizQuestionRepository;
this.dragItemRepository = dragItemRepository;
this.courseRepository = courseRepository;
}

/**
* POST /fileUpload : Upload a new file.
*
* @param file The file to save
* @param keepFileName specifies if original file name should be kept
* @return The path of the file
* @throws URISyntaxException if response path can't be converted into URI
* @deprecated Implement your own usage of {@link FileService#handleSaveFile(MultipartFile, boolean, boolean)} with a mixed multipart request instead. An example for this is
* {@link AttachmentUnitResource#updateAttachmentUnit(Long, Long, AttachmentUnit, Attachment, MultipartFile, boolean, String)}
*/
@Deprecated
@PostMapping("fileUpload")
@EnforceAtLeastTutor
public ResponseEntity<String> saveFile(@RequestParam(value = "file") MultipartFile file, @RequestParam(defaultValue = "false") boolean keepFileName) throws URISyntaxException {
log.debug("REST request to upload file : {}", file.getOriginalFilename());
String responsePath = fileService.handleSaveFile(file, keepFileName, false).toString();

// return path for getting the file
String responseBody = "{\"path\":\"" + responsePath + "\"}";

return ResponseEntity.created(new URI(responsePath)).body(responseBody);

}

/**
* GET /files/temp/:filename : Get the temporary file with the given filename
*
* @param filename The filename of the file to get
* @return The requested file, or 404 if the file doesn't exist
*/
@GetMapping("files/temp/{filename:.+}")
@EnforceAtLeastTutor
public ResponseEntity<byte[]> getTempFile(@PathVariable String filename) {
log.debug("REST request to get file : {}", filename);
sanitizeFilenameElseThrow(filename);
return responseEntityForFilePath(FilePathService.getTempFilePath().resolve(filename));
this.filePathService = filePathService;
}

/**
Expand Down Expand Up @@ -237,7 +199,7 @@ public ResponseEntity<byte[]> getDragAndDropBackgroundFile(@PathVariable Long qu
DragAndDropQuestion question = quizQuestionRepository.findDnDQuestionByIdOrElseThrow(questionId);
Course course = question.getExercise().getCourseViaExerciseGroupOrCourseMember();
authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.STUDENT, course, null);
return responseEntityForFilePath(filePathService.actualPathForPublicPath(URI.create(question.getBackgroundFilePath())));
return responseEntityForFilePath(getActualPathFromPublicPathString(question.getBackgroundFilePath()));
}

/**
Expand All @@ -256,7 +218,7 @@ public ResponseEntity<byte[]> getDragItemFile(@PathVariable Long dragItemId) {
if (dragItem.getPictureFilePath() == null) {
throw new EntityNotFoundException("Drag item " + dragItemId + " has no picture file");
}
return responseEntityForFilePath(filePathService.actualPathForPublicPath(URI.create(dragItem.getPictureFilePath())));
return responseEntityForFilePath(getActualPathFromPublicPathString(dragItem.getPictureFilePath()));
}

/**
Expand Down Expand Up @@ -290,7 +252,7 @@ public ResponseEntity<byte[]> getFileUploadSubmission(@PathVariable Long exercis
throw new AccessForbiddenException();
}

return buildFileResponse(filePathService.actualPathForPublicPath(URI.create(submission.getFilePath())), false);
return buildFileResponse(getActualPathFromPublicPathString(submission.getFilePath()), false);
}

/**
Expand All @@ -305,7 +267,7 @@ public ResponseEntity<byte[]> getCourseIcon(@PathVariable Long courseId) {
log.debug("REST request to get icon for course : {}", courseId);
Course course = courseRepository.findByIdElseThrow(courseId);
authCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.STUDENT, course, null);
return responseEntityForFilePath(filePathService.actualPathForPublicPath(URI.create(course.getCourseIcon())));
return responseEntityForFilePath(getActualPathFromPublicPathString(course.getCourseIcon()));
}

/**
Expand Down Expand Up @@ -335,7 +297,7 @@ public ResponseEntity<byte[]> getUserSignature(@PathVariable Long examUserId) {
ExamUser examUser = examUserRepository.findWithExamById(examUserId).orElseThrow();
authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, examUser.getExam().getCourse(), null);

return buildFileResponse(filePathService.actualPathForPublicPath(URI.create(examUser.getSigningImagePath())), false);
return buildFileResponse(getActualPathFromPublicPathString(examUser.getSigningImagePath()), false);
}

/**
Expand All @@ -351,7 +313,7 @@ public ResponseEntity<byte[]> getExamUserImage(@PathVariable Long examUserId) {
ExamUser examUser = examUserRepository.findWithExamById(examUserId).orElseThrow();
authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.INSTRUCTOR, examUser.getExam().getCourse(), null);

return buildFileResponse(filePathService.actualPathForPublicPath(URI.create(examUser.getStudentImagePath())), true);
return buildFileResponse(getActualPathFromPublicPathString(examUser.getStudentImagePath()), true);
}

/**
Expand All @@ -378,7 +340,7 @@ public ResponseEntity<byte[]> getLectureAttachment(@PathVariable Long lectureId,
// check if the user is authorized to access the requested attachment unit
checkAttachmentAuthorizationOrThrow(course, attachment);

return buildFileResponse(filePathService.actualPathForPublicPath(URI.create(attachment.getLink())), false);
return buildFileResponse(getActualPathFromPublicPathString(attachment.getLink()), false);
}

/**
Expand Down Expand Up @@ -435,7 +397,7 @@ public ResponseEntity<byte[]> getAttachmentUnitAttachment(@PathVariable Long att
// check if the user is authorized to access the requested attachment unit
checkAttachmentAuthorizationOrThrow(course, attachment);

return buildFileResponse(filePathService.actualPathForPublicPath(URI.create(attachment.getLink())), false);
return buildFileResponse(getActualPathFromPublicPathString(attachment.getLink()), false);
}

/**
Expand Down Expand Up @@ -521,16 +483,7 @@ private ResponseEntity<byte[]> buildFileResponse(Path path, String filename, boo
: "inline";
headers.setContentDisposition(ContentDisposition.builder(contentType).filename(filename).build());

FileNameMap fileNameMap = URLConnection.getFileNameMap();
String mimeType = fileNameMap.getContentTypeFor(filename);

// If we were unable to find mimeType with previous method, try another one, which returns application/octet-stream mime type,
// if it also can't determine mime type
if (mimeType == null) {
MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap();
mimeType = fileTypeMap.getContentType(filename);
}
var response = ResponseEntity.ok().headers(headers).contentType(MediaType.parseMediaType(mimeType)).header("filename", filename);
var response = ResponseEntity.ok().headers(headers).contentType(getMediaTypeFromFilename(filename)).header("filename", filename);
if (cache) {
var cacheControl = CacheControl.maxAge(Duration.ofDays(DAYS_TO_CACHE)).cachePublic();
response = response.cacheControl(cacheControl);
Expand All @@ -543,6 +496,24 @@ private ResponseEntity<byte[]> buildFileResponse(Path path, String filename, boo
}
}

private Path getActualPathFromPublicPathString(@Nullable String publicPath) {
if (publicPath == null) {
throw new EntityNotFoundException("No file linked");
}
return filePathService.actualPathForPublicPathOrThrow(URI.create(publicPath));
}

private MediaType getMediaTypeFromFilename(String filename) {
FileNameMap fileNameMap = URLConnection.getFileNameMap();
String mimeType = fileNameMap.getContentTypeFor(filename);
if (mimeType != null) {
return MediaType.parseMediaType(mimeType);
}
MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap();

return MediaType.parseMediaType(fileTypeMap.getContentType(filename));
}

/**
* Checks if the user is authorized to access an attachment
*
Expand Down
30 changes: 7 additions & 23 deletions src/main/webapp/app/shared/http/file-uploader.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { MAX_FILE_SIZE } from 'app/shared/constants/input.constants';
import { lastValueFrom } from 'rxjs';
import { FILE_EXTENSIONS, MARKDOWN_FILE_EXTENSIONS } from 'app/shared/constants/file-extensions.constants';
import { MARKDOWN_FILE_EXTENSIONS } from 'app/shared/constants/file-extensions.constants';

export interface FileUploadResponse {
path?: string;
Expand All @@ -15,38 +15,22 @@ type Options = {
@Injectable({ providedIn: 'root' })
export class FileUploaderService {
readonly acceptedMarkdownFileExtensions = MARKDOWN_FILE_EXTENSIONS;
readonly acceptedFileExtensions = FILE_EXTENSIONS;

constructor(private http: HttpClient) {}

/**
* Uploads a generic file to the server.
*/
uploadFile(file: Blob | File, fileName?: string, options?: Options): Promise<FileUploadResponse> {
return this.handleFileUpload('/api/fileUpload', this.acceptedFileExtensions, file, fileName, options);
}

/**
* Uploads a file for the markdown editor to the server.
*/
uploadMarkdownFile(file: Blob | File, fileName?: string, options?: Options): Promise<FileUploadResponse> {
return this.handleFileUpload('/api/markdown-file-upload', this.acceptedMarkdownFileExtensions, file, fileName, options);
}

/**
* Uploads a file to the server. It verifies the file extensions and file size.
* @param endpoint The API endpoint to upload the file to
* @param allowedExtensions The allowed extensions for the file
* @param file The file to upload
* @param fileName The name of the file
* @param options The options dictionary (e.g, { keepFileName: true })
* @return A promise with the response from the server or an error
*/
private handleFileUpload(endpoint: string, allowedExtensions: string[], file: Blob | File, fileName?: string, options?: Options): Promise<FileUploadResponse> {
uploadMarkdownFile(file: Blob | File, fileName?: string, options?: Options): Promise<FileUploadResponse> {
const fileExtension = fileName ? fileName.split('.').pop()!.toLocaleLowerCase() : (file as File).name.split('.').pop()!.toLocaleLowerCase();
if (!allowedExtensions.includes(fileExtension)) {
if (!this.acceptedMarkdownFileExtensions.includes(fileExtension)) {
return Promise.reject(
new Error('Unsupported file type! Only the following file extensions are allowed: ' + allowedExtensions.map((extension) => `.${extension}`).join(', ')),
new Error(
'Unsupported file type! Only the following file extensions are allowed: ' + this.acceptedMarkdownFileExtensions.map((extension) => `.${extension}`).join(', '),
),
);
}

Expand All @@ -57,6 +41,6 @@ export class FileUploaderService {
const keepFileName = !!options?.keepFileName;
const formData = new FormData();
formData.append('file', file, fileName);
return lastValueFrom(this.http.post<FileUploadResponse>(endpoint + `?keepFileName=${keepFileName}`, formData));
return lastValueFrom(this.http.post<FileUploadResponse>(`/api/markdown-file-upload?keepFileName=${keepFileName}`, formData));
}
}
Loading

0 comments on commit 475754f

Please sign in to comment.