SSO Oauth 2 - and other improvements.

Single sign on has been added, it works in the same fashion as the Scipro application.

Application no longer supports self registration of users.

Users can now upload the same file again if they want too.

UI SU logotype added, "look and feel" a la stockholm university.
UI improved feedback of the uploaded files, now also shows when a file was uploaded together with the status
UI improved feedback of the transcribed files, files are grouped in collapsable divs based on the source file.
The transcribed files section also shows when the files where created which are based on the upload of the source file,
this to help differentiate if the user uploaded the same source file.
Bulk download zip file will have a folder structure resembling the grouped structure from the transcribed files section,
transcribed files belonging together will be in their correct folder.

Increased the varchar limit from the default value to a more appropriate value to handle long file names or paths.
When cleaning up files the application now cleans up empty folders when needed.

The application needs a bit more internal improvements and polish, for example in some places the application
both uses java.io and java.nio it would be better to use java.nio across the board.
This can be done at a later date.
This commit is contained in:
Nico Athanassiadis 2025-01-09 07:46:37 +01:00
parent 63301d578c
commit e4e2f75f94
22 changed files with 693 additions and 249 deletions

@ -10,5 +10,18 @@ services:
- '3306:3306' - '3306:3306'
volumes: volumes:
- mariadb_data:/var/lib/mysql - mariadb_data:/var/lib/mysql
oauth2:
build:
context: https://github.com/dsv-su/toker.git
dockerfile: embedded.Dockerfile
ports:
- '51337:8080'
environment:
- CLIENT_ID=seshat
- CLIENT_SECRET=n0tS3cr3t
- CLIENT_REDIRECT_URI=http://localhost:8181/login/oauth2/code/seshat
volumes: volumes:
mariadb_data: mariadb_data:

@ -39,6 +39,10 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> <artifactId>spring-boot-starter-security</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId> <artifactId>spring-boot-starter-validation</artifactId>

@ -6,24 +6,32 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.web.filter.ForwardedHeaderFilter;
import se.su.dsv.seshat.services.CustomOAuth2loginSuccessHandler;
@Configuration @Configuration
public class SecurityConfig { public class SecurityConfig {
@Bean @Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { public SecurityFilterChain securityFilterChain(
HttpSecurity httpSecurity,
CustomOAuth2loginSuccessHandler customOAuth2loginSuccessHandler
) throws Exception {
httpSecurity.csrf(AbstractHttpConfigurer::disable) httpSecurity.csrf(AbstractHttpConfigurer::disable)
.addFilterBefore(new ForwardedHeaderFilter(), WebAsyncManagerIntegrationFilter.class)
.authorizeHttpRequests(authorize -> authorize .authorizeHttpRequests(authorize -> authorize
.requestMatchers("/css/**", "/js/**", "/register", "/login").permitAll() .requestMatchers("/css/**", "/js/**", "/register", "/login").permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.formLogin(login -> login .oauth2Login(oauth2 -> oauth2
.loginPage("/login") .successHandler(customOAuth2loginSuccessHandler)
.defaultSuccessUrl("/files/manage", true)
.permitAll()
) )
.logout(logout -> logout .logout(logout -> logout
.logoutSuccessUrl("/login?logout") .logoutSuccessUrl("/")
.invalidateHttpSession(true)
.clearAuthentication(true)
.deleteCookies("JSESSIONID", "auth_code", "refresh_token", "Authorization")
.permitAll() .permitAll()
); );
return httpSecurity.build(); return httpSecurity.build();
@ -33,4 +41,5 @@ public class SecurityConfig {
public BCryptPasswordEncoder passwordEncoder() { public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();
} }
} }

@ -24,6 +24,8 @@ import se.su.dsv.seshat.services.UserService;
import java.io.File; import java.io.File;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Controller @Controller
@ -50,7 +52,19 @@ public class FileController {
.filter(file -> file.getJobStatus() != null) .filter(file -> file.getJobStatus() != null)
.toList(); .toList();
model.addAttribute("files", files); // Map<String, List<FileMetadata>> filesByDirectory = files.stream().collect(Collectors.groupingBy(FileMetadata::getOutputDirectory));
Map<FileMetadata, List<FileMetadata>> filesByDirectory = files.stream()
.collect(Collectors.groupingBy(outputFile -> {
return uploaded.stream()
.filter(uploadedFile -> uploadedFile.getOutputDirectory().equals(outputFile.getOutputDirectory()))
.filter(uploadedFile -> outputFile.getSourceFile().equals(uploadedFile.getFilePath()))
.findFirst()
.orElseThrow(/* Will never happen */);
}));
model.addAttribute("filesByDirectory", filesByDirectory);
model.addAttribute("statuses", statuses); model.addAttribute("statuses", statuses);
return "file-management"; return "file-management";
@ -79,7 +93,6 @@ public class FileController {
return "redirect:/files/manage"; return "redirect:/files/manage";
} }
// Browsers do not support DELETE method so for individual file deletion we use GET
@GetMapping("files/download/{id}") @GetMapping("files/download/{id}")
public ResponseEntity<Resource> downloadFile(@PathVariable("id") Long id) { public ResponseEntity<Resource> downloadFile(@PathVariable("id") Long id) {
try { try {
@ -116,7 +129,7 @@ public class FileController {
} }
// Browsers do not support DELETE method so for individual file deletion we use GET
@GetMapping("/files/delete/{id}") @GetMapping("/files/delete/{id}")
public String deleteFile(@PathVariable("id") Long id, Authentication authentication, Model model) { public String deleteFile(@PathVariable("id") Long id, Authentication authentication, Model model) {
try { try {

@ -0,0 +1,18 @@
package se.su.dsv.seshat.controllers;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LandingController {
@GetMapping("/")
public String showHomePage(Authentication authentication) {
if (authentication != null) {
return "redirect:/files/manage";
}
return "redirect:/login";
}
}

@ -1,33 +0,0 @@
package se.su.dsv.seshat.controllers;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/")
public String showHomePage(Authentication authentication) {
if (authentication != null) {
return "redirect:/files/manage";
}
return "redirect:/login";
}
@GetMapping("/login")
public String showLoginPage(Model model, String error, String logout) {
if (error != null) {
model.addAttribute("error", "Invalid username or password");
}
if (logout != null) {
model.addAttribute("message", "Logged out successfully");
}
return "login";
}
}

@ -1,37 +0,0 @@
package se.su.dsv.seshat.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import se.su.dsv.seshat.services.UserService;
@Controller
public class RegistrationController {
private final UserService userService;
public RegistrationController(UserService userService) {
this.userService = userService;
}
@GetMapping("/register")
public String showRegistrationForm() {
return "register";
}
@PostMapping("/register")
public String registerUser(@RequestParam String username,
@RequestParam String email,
@RequestParam String password,
Model model) {
try{
userService.registerUser(username, email, password);
return "redirect:/login";
} catch (IllegalArgumentException e) {
model.addAttribute("error", "Registration failed: " + e.getMessage());
return "register";
}
}
}

@ -25,9 +25,6 @@ public class AppUser {
@Column(nullable = false, unique = true) @Column(nullable = false, unique = true)
private String username; private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false, unique = true) @Column(nullable = false, unique = true)
private String email; private String email;
@ -42,9 +39,8 @@ public class AppUser {
public AppUser() {} public AppUser() {}
public AppUser(String username, String password, String email, String roles) { public AppUser(String username, String email, String roles) {
this.username = username; this.username = username;
this.password = password;
this.email = email; this.email = email;
this.roles = roles; this.roles = roles;
} }
@ -65,14 +61,6 @@ public class AppUser {
this.username = username; this.username = username;
} }
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() { public String getEmail() {
return email; return email;
} }
@ -111,14 +99,13 @@ public class AppUser {
if (!(o instanceof AppUser appUser)) return false; if (!(o instanceof AppUser appUser)) return false;
return Objects.equals(id, appUser.id) return Objects.equals(id, appUser.id)
&& Objects.equals(username, appUser.username) && Objects.equals(username, appUser.username)
&& Objects.equals(password, appUser.password)
&& Objects.equals(email, appUser.email) && Objects.equals(email, appUser.email)
&& Objects.equals(roles, appUser.roles); && Objects.equals(roles, appUser.roles);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id, username, password, email, roles); return Objects.hash(id, username, email, roles);
} }
@Override @Override
@ -126,7 +113,6 @@ public class AppUser {
return "AppUser{" + return "AppUser{" +
"id=" + id + "id=" + id +
", username='" + username + '\'' + ", username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' + ", email='" + email + '\'' +
", roles='" + roles + '\'' + ", roles='" + roles + '\'' +
", createdAt=" + createdAt + ", createdAt=" + createdAt +

@ -15,7 +15,7 @@ public class DeletedFile {
@GeneratedValue @GeneratedValue
private Long id; private Long id;
@Column(nullable = false) @Column(nullable = false, length = 1000)
private String filePath; private String filePath;
@Column(nullable = false) @Column(nullable = false)

@ -23,12 +23,15 @@ public class FileMetadata {
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@Column(nullable = false) @Column(nullable = false, length = 512)
private String fileName; private String fileName;
@Column(nullable = false) @Column(nullable = false, length = 1000)
private String filePath; private String filePath;
@Column(length = 1000)
private String sourceFile;
private String language; private String language;
@ManyToOne(fetch = FetchType.EAGER) @ManyToOne(fetch = FetchType.EAGER)
@ -36,13 +39,13 @@ public class FileMetadata {
private AppUser user; private AppUser user;
@Column(nullable = false) @Column(nullable = false)
private LocalDateTime uploadedAt = LocalDateTime.now(); private LocalDateTime uploadedAt;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name ="job_status", nullable = false, length = 20) @Column(name ="job_status", length = 20)
private JobStatus jobStatus = JobStatus.PENDING; private JobStatus jobStatus = JobStatus.PENDING;
@Column @Column(length = 1000)
private String outputDirectory; private String outputDirectory;
public FileMetadata() {} public FileMetadata() {}
@ -76,6 +79,14 @@ public class FileMetadata {
this.filePath = filePath; this.filePath = filePath;
} }
public String getSourceFile() {
return sourceFile;
}
public void setSourceFile(String sourceFile) {
this.sourceFile = sourceFile;
}
public String getLanguage() { public String getLanguage() {
return language; return language;
} }
@ -116,6 +127,8 @@ public class FileMetadata {
this.outputDirectory = outputDirectory; this.outputDirectory = outputDirectory;
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

@ -0,0 +1,43 @@
package se.su.dsv.seshat.services;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
public class CustomOAuth2loginSuccessHandler implements AuthenticationSuccessHandler {
private static final Logger logger = LoggerFactory.getLogger(CustomOAuth2loginSuccessHandler.class);
private final String redirectUrl;
private final UserService userService;
public CustomOAuth2loginSuccessHandler(@Value("${app.onSuccess-homepage}") String redirectUrl, UserService userService) {
this.redirectUrl = redirectUrl;
this.userService = userService;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
logger.info("OAuth2 attributes: {}", oAuth2User.getAttributes());
String username = oAuth2User.getName();
// If the user does not have an email, set it to "no-email". We will not send any eamil notifications to this user.
String email = oAuth2User.getAttribute("mail") != null ? oAuth2User.getAttribute("mail") : "no-email";
if(!userService.existsByUsername(oAuth2User.getAttribute("principal"))) {
userService.registerUser(username, email);
}
response.sendRedirect(redirectUrl);
}
}

@ -23,7 +23,6 @@ public class CustomUserDetailService implements UserDetailsService {
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
return User.builder() return User.builder()
.username(appUser.getUsername()) .username(appUser.getUsername())
.password(appUser.getPassword())
.roles(appUser.getRoles().split(",")) .roles(appUser.getRoles().split(","))
.build(); .build();
} }

@ -3,7 +3,8 @@ package se.su.dsv.seshat.services;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.EntityNotFoundException;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Value; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import se.su.dsv.seshat.entities.DeletedFile; import se.su.dsv.seshat.entities.DeletedFile;
@ -15,10 +16,9 @@ import se.su.dsv.seshat.repositories.FileMetadataRepository;
import java.io.File; import java.io.File;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service @Service
public class JobProcessorService { public class JobProcessorService {
@ -31,9 +31,6 @@ public class JobProcessorService {
private final StorageService storageService; private final StorageService storageService;
private final DeletedFileRepository deletedFileRepository; private final DeletedFileRepository deletedFileRepository;
@Value("${app.output-root}")
private String outputRoot;
public JobProcessorService(FileMetadataRepository fileMetadataRepository, public JobProcessorService(FileMetadataRepository fileMetadataRepository,
Transcriber transcriber, Transcriber transcriber,
StorageService storageService, StorageService storageService,
@ -88,9 +85,11 @@ public class JobProcessorService {
if (transcribe) { if (transcribe) {
logger.info("Transcription successful for file: {}", managedJob.getFileName()); logger.info("Transcription successful for file: {}", managedJob.getFileName());
storageService.addTranscribedFilesToDatabase(managedJob.getUser(), managedJob.getOutputDirectory()); managedJob.setJobStatus(JobStatus.COMPLETED);
fileMetadataRepository.saveAndFlush(managedJob);
//TODO: This method can just take the filemetadata object managedJob as arugument we will need further information from it.
storageService.addTranscribedFilesToDatabase(managedJob);
cleanupFile(managedJob); cleanupFile(managedJob);
fileMetadataRepository.delete(managedJob); // Delete the job after successful transcription
break; break;
} else { } else {
logger.info("Transcription failed for file: {}", managedJob.getFileName()); logger.info("Transcription failed for file: {}", managedJob.getFileName());
@ -111,20 +110,22 @@ public class JobProcessorService {
} }
private boolean cleanupFile(FileMetadata jobFile) { private void cleanupFile(FileMetadata jobFile) {
String filePath = jobFile.getFilePath(); String filePath = jobFile.getFilePath();
File file = new File(filePath); File file = new File(filePath);
File parentDir = file.getParentFile();
if(file.exists()) { if(file.exists()) {
if(file.delete()) { if(file.delete()) {
recordFileDeletion(filePath, "JobProcessorService"); recordFileDeletion(filePath, "JobProcessorService");
// Delete the parent directory if it is empty
if(parentDir != null && parentDir.isDirectory() && Objects.requireNonNull(parentDir.list()).length == 0) {
parentDir.delete();
}
logger.info("File deleted successfully: {}", filePath); logger.info("File deleted successfully: {}", filePath);
return true;
} else { } else {
logger.error("Failed to delete file: {}", filePath); logger.error("Failed to delete file: {}", filePath);
return false;
} }
} }
return false;
} }
private void recordFileDeletion(String filePath, String jobProcessorService) { private void recordFileDeletion(String filePath, String jobProcessorService) {

@ -1,6 +1,8 @@
package se.su.dsv.seshat.services; package se.su.dsv.seshat.services;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -18,7 +20,10 @@ import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@ -26,6 +31,8 @@ import java.util.zip.ZipOutputStream;
@Service @Service
public class StorageService { public class StorageService {
private final static Logger logger = LoggerFactory.getLogger(StorageService.class);
private final DeletedFileRepository deletedFileRepository; private final DeletedFileRepository deletedFileRepository;
@Value("${app.upload-root}") @Value("${app.upload-root}")
private String uploadRoot; private String uploadRoot;
@ -41,7 +48,9 @@ public class StorageService {
} }
@Transactional @Transactional
public void addTranscribedFilesToDatabase(AppUser user, String outputDirectory) { public void addTranscribedFilesToDatabase(FileMetadata sourceFile) {
String outputDirectory = sourceFile.getOutputDirectory();
AppUser user = sourceFile.getUser();
File userOutputDirectory = new File(outputDirectory); File userOutputDirectory = new File(outputDirectory);
if(!userOutputDirectory.exists() || !userOutputDirectory.isDirectory()) { if(!userOutputDirectory.exists() || !userOutputDirectory.isDirectory()) {
return; return;
@ -64,6 +73,9 @@ public class StorageService {
fileMetadata.setFilePath(filePath); fileMetadata.setFilePath(filePath);
fileMetadata.setUser(user); fileMetadata.setUser(user);
fileMetadata.setJobStatus(null); fileMetadata.setJobStatus(null);
fileMetadata.setUploadedAt(sourceFile.getUploadedAt());
fileMetadata.setOutputDirectory(sourceFile.getOutputDirectory());
fileMetadata.setSourceFile(sourceFile.getFilePath());
fileMetadataRepository.save(fileMetadata); fileMetadataRepository.save(fileMetadata);
} }
} }
@ -84,7 +96,7 @@ public class StorageService {
String sanitizedFilename = sanitizeFilename(originalFilename); String sanitizedFilename = sanitizeFilename(originalFilename);
// Users upload directory // Users upload directory
Path userUploadDir = Paths.get(uploadRoot, user.getUsername()); Path userUploadDir = Paths.get(uploadRoot, user.getUsername(), UUID.randomUUID().toString());
try { try {
if(!Files.exists(userUploadDir)) { if(!Files.exists(userUploadDir)) {
@ -107,7 +119,9 @@ public class StorageService {
} else { } else {
metadata.setLanguage("auto"); metadata.setLanguage("auto");
} }
metadata.setOutputDirectory(outputRoot + File.separator + user.getUsername()); metadata.setUploadedAt(LocalDateTime.now());
String fileFolder = fileNameAndUploadedTime(metadata);
metadata.setOutputDirectory(outputRoot + File.separator + user.getUsername() + File.separator + fileFolder);
metadata.setUser(user); metadata.setUser(user);
return fileMetadataRepository.save(metadata); return fileMetadataRepository.save(metadata);
} catch (IOException e) { } catch (IOException e) {
@ -126,6 +140,7 @@ public class StorageService {
return fileMetadataRepository.findByUserId(user.getId()) return fileMetadataRepository.findByUserId(user.getId())
.stream() .stream()
.filter(file -> file.getFilePath().startsWith(uploadRoot)) .filter(file -> file.getFilePath().startsWith(uploadRoot))
.sorted((file1, file2) -> file2.getUploadedAt().compareTo(file1.getUploadedAt()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -147,9 +162,21 @@ public class StorageService {
deletedFile.setDeletionTime(LocalDateTime.now()); deletedFile.setDeletionTime(LocalDateTime.now());
deletedFileRepository.save(deletedFile); deletedFileRepository.save(deletedFile);
fileMetadataRepository.delete(fileMetadata); fileMetadataRepository.delete(fileMetadata);
// Delete the directory if it is empty
Path outputDirectory = Paths.get(fileMetadata.getOutputDirectory());
if (Files.exists(outputDirectory) && Files.isDirectory(outputDirectory)) {
File[] files = outputDirectory.toFile().listFiles();
if (files != null && files.length == 0) {
Files.delete(outputDirectory);
}
}
return true; return true;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
return false; return false;
} catch (IOException e) {
logger.info("Failed to delete file or directory: " + e.getMessage());
} }
} else { } else {
return false; return false;
@ -166,6 +193,9 @@ public class StorageService {
} }
public File createZipFromFiles(List<Long> fileIds, AppUser user) throws IOException { public File createZipFromFiles(List<Long> fileIds, AppUser user) throws IOException {
// TODO: Use java.nio FileSystems.newFileSystem(Files.createTempFile("selected_files", ".zip"));
// FileSystems.newFileSystem(Files.createTempFile("selected_files", ".zip"));
// Temporary ZIP file // Temporary ZIP file
File zipFile = File.createTempFile("selected_files", ".zip"); File zipFile = File.createTempFile("selected_files", ".zip");
@ -183,9 +213,9 @@ public class StorageService {
File file = new File(fileMetadata.getFilePath()); File file = new File(fileMetadata.getFilePath());
if (file.exists()) { if (file.exists()) {
Path filePath = file.toPath(); Path filePath = file.toPath();
String closestParentDir = filePath.getName(filePath.getNameCount() - 2).toString();
// Add new entry for the file in the ZIP // Add new entry for the file in the ZIP
zos.putNextEntry(new ZipEntry(file.getName())); zos.putNextEntry(new ZipEntry(closestParentDir + File.separator + file.getName()));
// Copy file contents to the ZIP // Copy file contents to the ZIP
Files.copy(filePath, zos); Files.copy(filePath, zos);
@ -200,4 +230,19 @@ public class StorageService {
private String sanitizeFilename(String filename) { private String sanitizeFilename(String filename) {
return filename.replaceAll("[^a-zA-Z0-9.-]", "_"); return filename.replaceAll("[^a-zA-Z0-9.-]", "_");
} }
private String fileNameAndUploadedTime(FileMetadata file) {
String fileName = file.getFileName();
LocalDateTime uploadedAt = file.getUploadedAt();
String uploadedToSeconds = uploadedAt.truncatedTo(ChronoUnit.SECONDS).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
return fileNameWithoutExtension(file.getFileName()) + "_" + uploadedToSeconds.replaceAll(":|-", "");
}
private String fileNameWithoutExtension(String fileName) {
int lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex != -1) {
return fileName.substring(0, lastDotIndex);
}
return fileName;
}
} }

@ -1,11 +1,11 @@
package se.su.dsv.seshat.services; package se.su.dsv.seshat.services;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service @Service
public class Transcriber { public class Transcriber {

@ -1,6 +1,5 @@
package se.su.dsv.seshat.services; package se.su.dsv.seshat.services;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import se.su.dsv.seshat.entities.AppUser; import se.su.dsv.seshat.entities.AppUser;
import se.su.dsv.seshat.repositories.AppUserRepository; import se.su.dsv.seshat.repositories.AppUserRepository;
@ -9,14 +8,12 @@ import se.su.dsv.seshat.repositories.AppUserRepository;
public class UserService { public class UserService {
private final AppUserRepository appUserRepository; private final AppUserRepository appUserRepository;
private final PasswordEncoder passwordEncoder;
public UserService(AppUserRepository appUserRepository, PasswordEncoder passwordEncoder) { public UserService(AppUserRepository appUserRepository) {
this.appUserRepository = appUserRepository; this.appUserRepository = appUserRepository;
this.passwordEncoder = passwordEncoder;
} }
public void registerUser(String username, String email,String password) { public void registerUser(String username, String email) {
if (appUserRepository.existsByUsername(username)) { if (appUserRepository.existsByUsername(username)) {
throw new IllegalArgumentException("Username already exists"); throw new IllegalArgumentException("Username already exists");
} }
@ -24,7 +21,7 @@ public class UserService {
throw new IllegalArgumentException("Email already exists"); throw new IllegalArgumentException("Email already exists");
} }
AppUser newUser = new AppUser(username, passwordEncoder.encode(password), email, "USER"); AppUser newUser = new AppUser(username, email, "USER");
appUserRepository.save(newUser); appUserRepository.save(newUser);
} }
@ -32,4 +29,8 @@ public class UserService {
return appUserRepository.findByUsername(username) return appUserRepository.findByUsername(username)
.orElseThrow(() -> new IllegalArgumentException("User not found")); .orElseThrow(() -> new IllegalArgumentException("User not found"));
} }
public boolean existsByUsername(String username) {
return appUserRepository.existsByUsername(username);
}
} }

@ -6,7 +6,7 @@ spring.servlet.multipart.max-request-size=5GB
app.upload-root=/seshat/uploads app.upload-root=/seshat/uploads
app.output-root=/seshat/outputs app.output-root=/seshat/outputs
app.api-url=http://localhost:8181/seshat/api app.onSuccess-homepage=/files/manage
# Database properties (local development) # Database properties (local development)
spring.datasource.url=jdbc:mariadb://localhost:3306/seshat spring.datasource.url=jdbc:mariadb://localhost:3306/seshat
@ -16,4 +16,15 @@ spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
# JPA properties # JPA properties
spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false spring.jpa.show-sql=false
# OAuth2 properties, remember if you change the registration.provider the provider properties must be updated
spring.security.oauth2.client.provider.docker.authorization-uri=http://localhost:51337/authorize
spring.security.oauth2.client.provider.docker.token-uri=http://localhost:51337/exchange
spring.security.oauth2.client.provider.docker.user-info-uri=http://localhost:51337/verify
spring.security.oauth2.client.provider.docker.user-name-attribute=sub
spring.security.oauth2.client.registration.seshat.client-id=seshat
spring.security.oauth2.client.registration.seshat.client-secret=n0tS3cr3t
spring.security.oauth2.client.registration.seshat.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.seshat.provider=docker
spring.security.oauth2.client.registration.seshat.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}

@ -1,3 +1,7 @@
:root {
--bs-primary-rgb: 0, 47, 95;
--bs-btn-bg: #002F5F;
}
/* Sticky footer layout */ /* Sticky footer layout */
/* Header Styling */ /* Header Styling */
.header { .header {
@ -7,8 +11,9 @@
} }
.header .app-title { .header .app-title {
font-size: 1.5rem; font-family: 'PMN Caecilia', serif;
font-weight: bold; font-size: 2rem;
font-weight: normal;
margin: 0; margin: 0;
} }
@ -23,8 +28,27 @@
font-size: 1.5rem; font-size: 1.5rem;
} }
.logo {
width: 12rem;
margin-right: 1rem;
}
.table-wrapper {
max-height: 160px;
}
.table thead th {
position: sticky;
top: 0;
z-index: 1;
background-color: #f9fafb;
}
footer {
margin-top: 1rem;
}
html, body { html, body {
height: 100%; /* Ensure the height of the body is at least the viewport height */ min-height: 100dvh;
} }
body { body {

@ -0,0 +1,379 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Logotyper" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 671.54 283.1" style="enable-background:new 0 0 671.54 283.1;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g>
<g id="Group_33" transform="translate(-337.004 -221)">
<g id="Group_27" transform="translate(143.967)">
<path id="Path_1187" class="st0" d="M388.85,289.02c0.14,0.16,0.07,0.27-0.08,0.4c-0.27,0.22-1.77,2.43-1.95,2.7
c-0.11,0.18-0.13-0.14-0.07-0.32c0.51-1.48,0.08-3.13-1.08-4.18c-1.21-0.92-2.93-0.68-3.84,0.53c-0.01,0.02-0.03,0.04-0.04,0.06
c-0.94,1.4-0.97,2.67,0,6.33c1.01,3.81,0.59,5.21-0.8,6.83c-1.87,1.85-4.82,2.05-6.92,0.47c-2.71-1.89-2.82-2.32-3.24-2.49
c-0.33-0.14-0.32-0.4,0-0.8c0.48-0.57,2.09-2.75,2.36-3.16c0.18-0.28,0.22,0.04,0.03,0.45c-0.84,1.64-0.4,3.64,1.05,4.77
c1.18,1.1,3.03,1.03,4.13-0.15c0.06-0.07,0.12-0.14,0.18-0.21c0.8-0.96,1.27-1.89,0.14-6.26c-1.2-4.61-0.37-6.12,1.37-7.48
c1.79-1.46,4.37-1.44,6.14,0.04c0.7,0.55,1.35,1.14,1.97,1.78c0.22,0.22,0.34,0.46,0.46,0.57S388.78,288.93,388.85,289.02z"/>
<path id="Path_1188" class="st0" d="M244.24,341.13c0.06-0.16,0.24-0.08,0.47,0c0.95,0.26,1.92,0.43,2.9,0.51
c0.49,0-0.34,0.37-0.59,0.4c-0.69,0.09-2.05,0.49-2.67,2.77c-0.42,1.3,0.29,2.69,1.59,3.11c0.07,0.02,0.13,0.04,0.2,0.06
c2,0.4,3.06-0.36,5.99-2.72c1.78-1.92,4.44-2.75,6.99-2.19c2.41,0.86,3.79,3.4,3.2,5.89c-0.27,1.31-0.79,2.56-1.54,3.68
c-0.12,0.34-0.28,0.46-0.78,0.32c-0.64-0.18-3.05-0.53-3.48-0.58c-0.78-0.09-0.45-0.4,0-0.44c1.24-0.16,3.94-0.5,4.48-3.15
c0.36-1.38-0.47-2.78-1.84-3.14c-0.08-0.02-0.17-0.04-0.25-0.05c-1.23-0.3-2.57-0.26-6.03,2.61c-3.73,3.09-5.32,3.08-7.33,2.17
c-2.1-0.92-3.19-3.26-2.55-5.47c0.21-1.14,0.59-2.24,1.13-3.26C244.24,341.52,244.2,341.24,244.24,341.13z"/>
<path id="Path_1189" class="st0" d="M358.57,449.92c-0.26,0-0.21-0.22-0.25-0.4c0,0-0.66-2.19-0.72-2.37
c-0.06-0.17,0.13-0.24,0.26-0.08c0.95,1.05,2.39,1.51,3.77,1.2c1.48-0.35,2.41-1.82,2.09-3.31c-0.52-1.6-2.09-2.35-5.53-3.93
c-3.1-1.43-4.1-2.86-4.4-4.64c-0.26-2.68,1.41-5.17,4-5.95c2.59-0.8,3.2-0.72,3.72-0.98c0.4-0.21,0.45,0.61,0.5,0.76
c0.25,0.69,0.92,2.84,1.08,3.24c0.16,0.4-0.14,0.51-0.33,0.1c-0.89-1.57-2.69-2.38-4.45-2c-2.31,0.75-2.65,2.4-2.33,3.49
c0.36,1.2,0.57,2.17,4.92,4c4.23,1.78,5.06,3.6,5.08,5.65c-0.14,2.34-1.88,4.28-4.2,4.66c-0.68,0.18-1.38,0.28-2.09,0.31
c-0.31,0.05-0.55,0.07-0.71,0.09C358.82,449.78,358.78,449.92,358.57,449.92z"/>
<path id="Path_1190" class="st0" d="M390.91,293.8c0.62-0.27,1.21-0.61,1.74-1.03c0.22-0.18,0.64,0,0.9,0.26
c0.17,0.17,3.95,3.91,4.69,4.71c1.01,1.07,4.36,4.15,4.79,4.57c0.43,0.42,0.84,0.64,0.4,0.93c-0.44,0.28-1.36,0.8-1.64,0.95
c-0.28,0.15-0.19,0.03-0.19-0.28c0-0.32-0.22-0.8-1.16-1.77c-0.94-0.97-2.02-2.1-2.22-2.26c-0.2-0.16-0.27-0.19-0.44-0.04
c-0.27,0.24-3.97,4-4.79,4.86c-2.07,1.94-3.99,4.04-5.74,6.27c-0.15,0.77,0.1,0.93,0.2,1.07c0.1,0.14,0.22,0.77-0.02,0.53
c-0.24-0.24-1.79-1.93-2.15-2.27c-0.36-0.34-1.87-1.6-2.09-1.81s-0.06-0.32,0.31-0.17c0.51,0.29,1.13,0.3,1.65,0.03
c2.07-1.66,4.02-3.48,5.83-5.42c0.59-0.59,4.6-4.77,4.71-4.89c0.18-0.2,0.25-0.3,0-0.62c-1.02-1.07-2.11-2.08-3.25-3.02
c-0.32-0.26-0.72-0.41-1.13-0.43C390.98,293.97,390.56,293.96,390.91,293.8z"/>
<path id="Path_1191" class="st0" d="M253.1,323.87c-0.35-0.4-1.2-1.11-1.67-1.51c-0.22-0.18-0.1-0.42,0.1-0.74
c0.12-0.2,2.77-5.07,3.38-5.99c0.8-1.24,3.2-5.23,3.49-5.75c0.3-0.52,0.52-0.91,0.77-0.68c0.43,0.43,0.83,0.9,1.2,1.39
c0.21,0.3,0.09,0.15-0.22,0.15s-0.72,0.63-1.45,1.77c-0.52,0.83-0.99,1.68-1.41,2.56c-0.15,0.32,0.06,0.4,0.24,0.54
c0.31,0.2,4.69,2.91,5.73,3.52c2.4,1.48,4.93,2.75,7.54,3.8c0.8-0.04,0.78-0.17,0.92-0.27c0.22-0.16,0.67-0.23,0.45,0
c-0.58,0.75-1.1,1.53-1.57,2.35c-0.37,0.66-1.22,2.24-1.33,2.49s-0.12-0.16-0.1-0.5c0.02-0.4,0.27-0.77-0.33-1.26
c-1.06-0.87-6.1-3.82-6.79-4.25c-0.69-0.43-5.63-3.22-5.99-3.34c-0.26-0.09-0.47-0.4-0.67-0.03c-0.88,1.36-1.63,2.8-2.23,4.31
c-0.09,0.33-0.09,0.69-0.02,1.02C253.25,323.89,253.27,324.05,253.1,323.87z"/>
<path id="Path_1192" class="st0" d="M275.67,294.27c-0.24-0.67-0.56-1.31-0.94-1.91c-0.16-0.32-0.17-0.31,0.26-0.66
c0.19-0.15,4.6-3.41,5.47-4.08c1.2-0.92,5.25-3.65,5.73-4c0.48-0.35,1.02-0.56,1.2-0.12c0.18,0.44,0.66,1.63,0.75,1.93
c0.12,0.44-0.06,0.11-0.38-0.16c-0.24-0.22-0.68-0.18-1.81,0.58c-1.13,0.76-2.71,1.8-2.92,2s-0.18,0.2-0.08,0.4
c0.25,0.5,3.16,4.77,3.98,5.85c1.65,2.43,3.48,4.74,5.46,6.91c0.3,0.15,0.65,0.16,0.96,0.04c0.24-0.05,0.53,0.03,0.13,0.26
c-0.4,0.23-1.6,1.04-2.04,1.33c-0.44,0.29-1.57,1.16-1.96,1.51c-0.47,0.4-0.34-0.03-0.13-0.31c0.21-0.28,0.36-0.46,0.06-1.16
c-1.28-2.28-2.7-4.49-4.25-6.6c-0.48-0.68-4.31-5.8-4.56-6.13c-0.14-0.18-0.18-0.24-0.52,0s-3.32,2.51-3.76,2.92
c-0.34,0.33-0.52,0.4-0.56,0.84C275.74,293.96,275.75,294.6,275.67,294.27z"/>
<path id="Path_1193" class="st0" d="M410.65,311.56c3.75,3.82,3.69,9.95-0.13,13.7c-0.18,0.18-0.38,0.36-0.58,0.52
c-3.91,3.33-9.72,3.13-13.38-0.47c-3.67-3.9-3.49-10.03,0.4-13.7c3.66-3.79,9.7-3.9,13.5-0.24
C410.52,311.44,410.59,311.5,410.65,311.56z M399.28,313.9c-3.31,2.4-4.04,7.03-1.63,10.34c0.05,0.07,0.1,0.13,0.15,0.2
c3.26,2.47,7.91,1.89,10.46-1.32c3.34-2.57,3.97-7.35,1.4-10.69c-0.1-0.13-0.2-0.26-0.31-0.38
C407.5,310.43,403.39,309.9,399.28,313.9L399.28,313.9z"/>
<path id="Path_1194" class="st0" d="M416.12,403.32c-1.72,4.99-7.17,7.65-12.16,5.92c-0.34-0.12-0.67-0.25-0.99-0.41
c-4.51-2.07-6.79-7.17-5.31-11.91c1.8-4.95,7.26-7.52,12.23-5.75C414.94,392.85,417.72,398.25,416.12,403.32z M408.98,394.02
c-3.48-1.91-7.85-0.64-9.77,2.84c-0.08,0.14-0.15,0.29-0.22,0.44c-0.86,2.53,0.14,6.33,5.74,8.43c5.43,2.03,9.51,0,10.44-3.64
C415.52,400.73,415.6,396.39,408.98,394.02z"/>
<path id="Path_1195" class="st0" d="M409.07,347.58c-0.82-0.03-1.64-0.12-2.45-0.26c-0.56-0.12-0.64-0.13-0.8-0.36
c-1.62-2.1-2.51-4.68-2.51-7.34c-0.37-4.49,2.45-8.63,6.76-9.95c3.84-1.05,7.94,0.33,10.36,3.5c1.34,1.9,2.1,4.15,2.17,6.47
c0.09,1.37,0.21,2.19,0.27,2.63c0.12,0.74,0.11,0.94,0.27,1.14c0.16,0.2-0.07,0.4-0.35,0.4s-1.36,0.18-1.91,0.31
c-0.4,0.1-1.25,0.33-1.36,0.36c-0.59,0.14-0.57,0-0.11-0.36c1-0.52,1.69-1.48,1.88-2.59c0.49-2.57-0.22-5.22-1.94-7.19
c-2.32-2.4-5.94-3-8.92-1.49c-3.3,1.24-5.52,4.36-5.59,7.89c-0.18,2.79,1.47,5.38,4.08,6.39
C409.54,347.37,409.9,347.56,409.07,347.58z"/>
<path id="Path_1196" class="st0" d="M424.46,349.87c0,0.72,0.33,3.97,0.46,4.6c0.16,0.76-0.18,0.4-0.22,0.14
c-0.12-0.42-0.48-0.72-0.91-0.76c-2.65,0.03-5.3,0.23-7.92,0.59c2.18,1.95,4.47,3.76,6.88,5.43c0.54,0.45,1.15,0.8,1.8,1.04
c0.17,0,0.08,0.13,0.16-0.09c0.1-0.3,0.4-1.27,0.36,0.2c0,0.35-0.03,1.41,0,1.68c0.03,0.27,0.13,1.01,0.15,1.17
c0.11,0.8-0.02,0.63-0.61-0.09c-1.04-1.08-2.18-2.04-3.42-2.88c-0.65-0.47-4.77-3.6-4.77-3.6s-5.92,5.62-7.04,6.85
c-0.88,0.88-1.65,1.86-2.31,2.92c-0.19,0.43-0.35,0.64-0.28,0.23c0.05-0.26,0.23-1.88,0.27-2.59c0.04-0.62,0.1-2.54,0.11-2.73
c0.02-0.4,0.18-0.1,0.4-0.04c1.42-0.9,2.7-2.01,3.81-3.28c1.39-1.37,3.7-3.88,3.88-4.12c-1.93,0.13-3.85,0.36-5.75,0.68
c-0.73,0.09-1.43,0.34-2.05,0.74c-0.22,0.2-0.36,0.46-0.4,0.76c-0.03,0.24-0.26,0.35-0.3,0.04c-0.04-0.32-0.11-2-0.15-2.6
c-0.04-0.6-0.24-2-0.28-2.2c-0.04-0.2,0.16-0.36,0.28-0.08c0.08,0.27,0.29,0.47,0.55,0.55c0.66,0.26,1.55,0.27,8.08-0.5
c2.59-0.18,5.17-0.55,7.7-1.11c0.69-0.18,1.12-0.44,1.2-1.02C424.18,349.47,424.48,348.81,424.46,349.87z"/>
<path id="Path_1197" class="st0" d="M424.58,372.31c-0.13,0.59-0.66,3.81-0.66,4.46c0,0.65-0.3,0.66-0.38,0.22
c-0.02-0.38-0.26-0.72-0.61-0.87c-1.04-0.43-2.13-0.7-3.25-0.8c-2.26-0.36-3.74-0.62-3.74-0.62s-0.7,2.98-1.09,4.79
c-0.49,2.27-1.03,5.27-1.03,5.27l4.36,1.23c1.17,0.32,2.96,1.06,3.53,0.1c0.4-0.68,0.65-0.53,0.27,0.2
c-0.13,0.26-0.93,3.35-1.05,3.97c-0.24,1.15-0.4,0.42-0.34,0.15c0.06-0.27,0.27-0.74-0.74-1.14c-1.01-0.4-4.42-1.55-7.43-2.4
c-2.52-0.9-5.14-1.51-7.8-1.84c-0.68,0.06-0.86,0.21-1.02,0.44c-0.23,0.32-0.63,0.42-0.31-0.11c0.53-1.34,0.93-2.72,1.2-4.13
c0.2-1.34,0.4-0.4,0.38-0.15c-0.04,0.44,0.21,0.86,0.62,1.03c2.25,0.91,4.56,1.66,6.91,2.25c0,0,0.7-2.75,1.16-4.87
c0.55-2.5,0.92-4.97,0.92-4.97s-1.38-0.22-2.36-0.4c-1.68-0.31-3.38-0.5-5.09-0.55c-0.35,0.1-0.64,0.35-0.78,0.69
c-0.07,0.23-0.52,0.66-0.27-0.16c0.3-1.35,0.5-2.71,0.59-4.09c0.06-1.15,0.36-0.65,0.37-0.32c0.05,0.33,0.24,0.62,0.52,0.8
c2.54,0.75,5.14,1.25,7.78,1.49c2.75,0.55,5.54,0.85,8.33,0.9c0.64-0.22,0.6-0.46,0.72-0.73
C424.41,371.88,424.76,371.47,424.58,372.31z"/>
<path id="Path_1198" class="st0" d="M406.64,417.68c-1.1,1.09-2.11,2.27-3.02,3.51c-0.47,0.64-0.4,0.13-0.28-0.12
c0.23-0.46,0.09-0.61-0.09-1.01c-1.66-1.83-3.5-3.48-5.48-4.95c-1.87-1.83-3.98-3.4-6.28-4.67c-0.4,0-0.73-0.05-1.54,0.69
c-1.12,1.04-2.12,2.21-2.98,3.47c-0.3,0.57-0.43,1.22-0.37,1.87c0,0.44-0.13,0.4-0.28,0c-0.26-0.8-0.46-1.62-0.61-2.45
c-0.03-0.25,0.07-0.49,0.26-0.65c0.45-0.49,4.28-4.83,4.84-5.45c0.64-0.69,1.23-1.42,1.78-2.19c0.36-0.62,0.48-0.18,0.37,0.14
c-0.23,0.38-0.29,0.84-0.18,1.27c1.76,2.1,3.81,3.94,6.08,5.48c2.02,1.96,4.29,3.65,6.74,5.02c0.33,0.05,0.66-0.06,0.9-0.28
C406.68,417.2,407.16,417.32,406.64,417.68z"/>
<path id="Path_1199" class="st0" d="M383.18,417.49c0.2,0.5,2.15,3.85,4.18,7.26c2.11,3.52,4.4,7.19,4.7,7.75
c0.62,1.08,0.64,1.34-0.43,0.8c-1.55-0.77-7.76-3.9-8.79-4.4c-1.03-0.5-6.11-3.08-6.11-3.08s1.1,8.29,1.2,8.75
c0.1,0.47,0.98,7.37,1.03,7.72c0.05,0.35,0.05,0.9-0.58,0.31c-0.62-0.59-10.43-13.1-10.92-13.67c-0.35-0.45-0.87-0.75-1.43-0.84
c-0.26-0.05-0.52-0.04-0.77,0.04c-0.25,0.09-0.44-0.14-0.08-0.28c0.44-0.17,3.94-1.97,4.28-2.19c0.22-0.13,0.52,0.04,0.18,0.25
c-0.44,0.27-0.5,0.46-0.4,0.86c0.22,0.95,7.32,9.77,7.32,9.77s-1.2-7.92-1.41-8.93c-0.21-1.01-0.9-5.68-0.9-5.68s0-0.3,0.3-0.14
c0.3,0.16,7.24,3.7,7.99,4.06s5.19,2.56,5.19,2.56s-3.9-6.33-5.51-8.85c-0.23-0.44-0.6-0.78-1.05-0.97
c-0.55-0.11-0.72,0.12-1.04,0.26c-0.16,0.07-0.4-0.13,0.07-0.4c0.57-0.35,1.11-0.74,1.62-1.17c0.4-0.31,1.4-1.27,1.56-1.43
c0.16-0.16,0.4,0.04,0.23,0.21C383.12,416.36,382.93,416.98,383.18,417.49z"/>
<path id="Path_1200" class="st0" d="M340.34,441.57c-0.04,0.33,0.06,4.16,0.1,4.64c0,0.22-0.18,0.16-0.31-0.33
c-0.12-0.38-0.42-0.67-0.8-0.78c-1.42-0.18-2.86-0.21-4.3-0.08c-0.09,1.66,0.04,3.32,0.38,4.95c0.44,0.48,0.51,0.43,0.7,0.47
c0.2,0.04,0.67,0.29,0.36,0.26c-0.6-0.06-3.94-0.06-4.69,0.02c-0.55,0.06-0.27-0.2,0.19-0.3c0.46-0.1,0.7-0.36,0.87-0.94
c0.13-1.49,0.16-2.99,0.09-4.49c-1.53-0.05-3.07,0.03-4.59,0.24c-0.33,0.19-0.54,0.54-0.55,0.92c-0.04,0.32-0.38,0.6-0.33,0.17
c0.1-1.38,0.11-2.76,0.04-4.14c-0.08-0.55,0.05-0.47,0.23-0.14c0.22,0.4,0.15,0.63,0.62,0.82c1.52,0.15,3.05,0.21,4.58,0.16
c0,0,0.03-3.77-0.08-4.35c-0.07-0.5-0.46-0.89-0.97-0.96c-0.27-0.02-0.52-0.27-0.16-0.23c1.49,0.11,2.99,0.11,4.48,0
c0.48-0.05,0.2,0.21-0.16,0.3c-0.47,0.09-0.85,0.45-0.95,0.92c-0.15,0.58-0.13,4.29-0.13,4.29s3.51,0.04,4.15-0.09
c0.48-0.08,0.86-0.46,0.94-0.94C340.13,441.64,340.38,441.25,340.34,441.57z"/>
<path id="Path_1201" class="st0" d="M308.51,450.59c-1.62-0.79-3.33-1.4-5.09-1.82c-0.56-0.09-0.04-0.24,0.31-0.21
c0.46,0.04,0.85,0.1,1.2-0.25c1.96-3.73,3.55-7.63,4.77-11.66c0.91-2.44-0.32-5.15-2.76-6.06c-0.18-0.07-0.36-0.12-0.55-0.17
c-4-1.43-5.59,0.75-7.77,4.84c-1.58,2.86-2.95,5.84-4.11,8.89c-0.03,0.33,0.12,0.65,0.4,0.83c0.36,0.29,0.56,0.76,0.22,0.49
c-1.39-0.86-2.86-1.58-4.4-2.13c-0.34-0.11-0.07-0.3,0.36-0.21c0.36,0.08,0.52,0,0.87-0.25c2.12-3.33,4.05-6.76,5.81-10.3
c1.05-1.94,2.89-3.34,5.04-3.82c3.29-0.33,6.5,1.08,8.5,3.71c1.19,1.86,1.49,4.16,0.8,6.27c-0.4,1.65-4,9.69-4,10.79
c-0.02,0.31,0.13,0.6,0.4,0.76C308.73,450.45,308.86,450.8,308.51,450.59z"/>
<path id="Path_1202" class="st0" d="M283.31,439.86c0,0-0.75-8.14-0.98-10.95c-0.26-3.27-1.08-9.59-1.08-9.59
s-6.39,6.92-7.22,7.76c-0.83,0.84-1.6,1.67-1.6,2.24c0,0.33,0.13,0.65,0.36,0.9c0.08,0.16,0.22,0.72,0.06,0.52
c-0.55-0.63-1.14-1.22-1.75-1.78c-0.54-0.49-1.1-0.95-1.69-1.37c-0.44-0.29,0.1-0.24,0.34-0.09c0.25,0.19,0.57,0.26,0.88,0.19
c2.46-2.14,4.77-4.45,6.91-6.91c1.71-1.78,5.54-5.99,5.94-6.54c0.28-0.4,0.28-0.22,0.31,0.2c0.04,0.52,0.71,8.89,0.85,9.86
c0.13,0.9,0.56,6.59,0.62,7.23s0.25,2.77,0.25,2.77s2.94-4.22,3.92-5.75c1.19-1.53,2.2-3.2,3.02-4.96
c0.01-0.5-0.24-0.96-0.66-1.23c-0.13-0.14-0.24-0.48,0.06-0.22c1.25,1.05,2.61,1.95,4.05,2.71c0.62,0.29-0.31,0.11-0.47,0.04
c-0.39-0.12-0.81-0.12-1.2,0c-1.87,2.19-3.57,4.52-5.08,6.97c-0.76,1.11-2.98,4.22-3.5,5.07c-0.52,0.84-1.71,2.59-1.95,3.03
C283.47,440.36,283.37,440.33,283.31,439.86z"/>
<path id="Path_1203" class="st0" d="M275.07,405.78c0.44,0.76,0.94,1.48,1.49,2.17c0.57,0.71,1.18,1.37,1.84,2
c0.35,0.33-0.14,0.21-0.46-0.03c-0.32-0.24-0.69-0.35-1.71,0.2c-1.2,0.64-11.68,9.47-12.08,9.99c-0.49,0.6-0.45,0.9-0.24,1.36
c0.16,0.36,0.05,0.67-0.09,0.44c-0.91-1.41-1.95-2.73-3.11-3.94c-0.46-0.34,0.02-0.26,0.32-0.08c0.3,0.17,0.4,0.17,0.76,0
c4.64-3.28,9.06-6.87,13.22-10.75c0.12-0.34,0.08-0.72-0.1-1.03C274.9,405.84,274.84,405.38,275.07,405.78z"/>
<path id="Path_1204" class="st0" d="M265.98,334.45c-0.83,1.65-1.51,3.36-2.03,5.13c-0.1,0.46-0.33-0.02-0.26-0.36
c0.12-0.58,0.2-0.88-0.76-1.52c-4.82-2.22-9.79-4.07-14.88-5.56c-0.42-0.04-0.82,0.18-1.01,0.55c-0.2,0.26-0.62,0.37-0.36,0
c0.79-1.65,1.47-3.35,2.03-5.09c0.09-0.56,0.31,0,0.25,0.32c-0.14,0.37-0.05,0.78,0.23,1.05c5,2.47,10.22,4.48,15.58,5.99
c0.68-0.11,0.61-0.34,0.84-0.55C265.83,334.2,266.3,333.97,265.98,334.45z"/>
<path id="Path_1205" class="st0" d="M261.32,402.92l3.97-5.3c0,0-6.96,0.38-8.45,0.5c-1.49,0.12-5.82,0.31-6.31,1.01
c-0.32,0.47,0,0.99,0,1.15c0.06,0.32,0,0.82-0.16,0.44c-0.08-0.24-0.62-1.65-1.05-2.63c-0.4-0.88-0.9-1.95-1.03-2.15
c-0.22-0.32,0.15-0.24,0.36,0c0.23,0.29,0.57,0.49,0.94,0.56c2.9,0.11,5.81,0.07,8.7-0.12c1.25-0.05,10.45-0.65,11.22-0.58
c0.4,0.04,0.78-0.16,0.28,0.6c-0.28,0.43-5.77,7.62-6.19,8.2c-2.04,2.56-3.92,5.24-5.63,8.02c-0.06,0.36-0.04,0.72,0.06,1.07
c0.06,0.28,0.02,1.05-0.08,0.8c-0.9-1.74-1.93-3.4-3.09-4.99c-0.4-0.51,0.13-0.16,0.44-0.05c0.31,0.12,0.5,0.04,0.85-0.2
C258.04,407.27,259.76,405.15,261.32,402.92z"/>
<path id="Path_1206" class="st0" d="M261.9,376.76c0.26,0.75,1.07,4.05,1.48,5.49c0.14,0.51,1.05,3.87,1.33,4.43
c0.13,0.27-0.47-0.16-0.59-0.44c-0.04-0.22-0.16-0.41-0.35-0.54c-0.19-0.11-0.4-0.17-0.62-0.18c-2.73,0.54-5.42,1.29-8.04,2.22
c-2.63,0.67-5.2,1.59-7.66,2.75c-0.32,0.28-0.43,0.73-0.29,1.13c0.04,0.26-0.16,1.2-0.26,0.61c-0.15-1.04-0.37-2.07-0.64-3.08
c-0.4-1.38-2-7.08-2.07-7.32c-0.13-0.32-0.15-0.44,0.09-0.53c0.72-0.24,1.43-0.52,2.13-0.84c0.5-0.3,0.31,0.13,0.03,0.42
c-0.28,0.29-0.4,0.43-0.36,1.32c0.34,1.68,0.78,3.34,1.33,4.96c0,0,2.35-0.62,3.12-0.9s3.32-1.03,3.32-1.03
c-0.4-1.61-0.9-3.19-1.51-4.74c-0.37-0.49-0.62-0.4-1-0.36c-0.28,0.02-0.76-0.18-0.4-0.26c1.38-0.3,2.72-0.72,4.02-1.25
c0.45-0.22,0,0.34-0.21,0.46c-0.38,0.2-0.6,0.61-0.56,1.04c0.08,0.86,0.28,1.71,0.58,2.53c0.17,0.48,0.65,2.12,0.65,2.12
s4.25-1.23,4.96-1.56c0.71-0.34,1.26-0.34,1.11-1.75c-0.24-1.68-0.75-3.31-1.51-4.83c-0.26-0.41-0.6-0.77-0.99-1.05
c-0.16-0.13-0.72-0.6-0.4-0.49c0.98,0.31,1.94,0.68,2.88,1.1C261.66,376.3,261.82,376.51,261.9,376.76z"/>
<path id="Path_1207" class="st0" d="M282.78,309.83c-0.51,0.54-4.24,4.58-5.29,5.76c-0.35,0.4-1.2,1.35-1.43,1.64
c-0.19,0.22-0.28-0.22-0.12-0.49c0.25-0.29,0.29-0.7,0.12-1.04c-1.98-2.22-4.16-4.24-6.51-6.06c-1.86-1.81-3.89-3.44-6.07-4.86
c-0.4-0.09-0.81-0.03-1.16,0.19c-0.24,0.12-0.8,0.36-0.4-0.03c0.4-0.39,1.02-1.07,1.91-2.04c0.97-1.05,4.96-5.19,5.12-5.41
s0.32-0.28,0.49-0.1c0.5,0.53,1.03,1.03,1.6,1.49c0.6,0.36,0.06,0.32-0.25,0.25c-0.32-0.07-0.43-0.17-1.08,0.21
c-1.27,1.06-2.43,2.24-3.47,3.52l4.77,4.4c0,0,3.32-3.09,3.38-3.72c0.05-0.27,0.03-0.54-0.07-0.8c-0.08-0.25-0.05-0.69,0.16-0.4
c0.77,0.95,1.65,1.82,2.61,2.57c0.4,0.3-0.07,0.3-0.3,0.2c-0.35-0.15-0.48-0.4-1.13-0.08c-1.19,1.06-2.29,2.21-3.31,3.44
c0,0,3.4,2.92,4.04,3.38c0.83,0.61,1.43,0.76,2.26,0c1.23-1.06,2.3-2.28,3.2-3.64c0.2-0.46,0.28-0.97,0.23-1.47
c0.02-0.3,0.2-0.66,0.25-0.42c0.05,0.24,0.75,2.4,0.8,2.75C283.17,309.42,283.09,309.5,282.78,309.83z"/>
<path id="Path_1208" class="st0" d="M260.58,367.65c0.03,0.4,0.13,2.31,0.26,3.16c0.07,0.48-0.22-0.12-0.29-0.36
c-0.04-0.3-0.23-0.56-0.5-0.69c-2.67-0.18-5.35-0.07-7.99,0.34c-2.98,0.1-5.94,0.5-8.84,1.2c-0.31,0.32-0.46,0.76-0.4,1.2
c0,0.26-0.46,1.11-0.45,0.47c0-0.4,0-2.1-0.1-2.7c-0.29-2.05-0.39-4.12-0.3-6.18c0.46-2.23,2.37-3.88,4.65-4
c1.97-0.01,3.76,1.12,4.61,2.89c0.33-0.44,0.74-0.82,1.2-1.12c2.7-1.58,5.24-3.4,7.59-5.46c0.19-0.38,0.24-0.81,0.15-1.23
c-0.06-0.72,0.1-1.23,0.45,0c0.34,1.36,0.15,2.8-0.53,4.03c-1.57,1.59-3.34,2.96-5.27,4.08c-0.8,0.55-2.67,1.6-2.67,2.7
c-0.01,0.54,0.04,1.07,0.15,1.6c0,0,2.74-0.28,3.48-0.36c1.37-0.03,2.73-0.26,4.03-0.7c0.22-0.2,0.35-0.5,0.34-0.8
c0.03-0.21,0.47-1.13,0.44,0.06C260.52,366.18,260.54,367.25,260.58,367.65z M249.41,363.17c-1.94-1.24-4.51-0.73-5.83,1.16
c-0.48,1.23-0.61,2.56-0.37,3.86l3.94-0.26c0.62-0.04,4.06-0.28,4.06-0.28s0.35-3.2-1.8-4.49V363.17z"/>
<path id="Path_1209" class="st0" d="M328.95,307.11c-2.39-2.85-3.7-6.45-3.71-10.16c0.09-2.34,0.98-4.59,2.52-6.36l0.92-1.13
c2.28-2.26,3.59-5.33,3.63-8.55c0.12-2.01-0.11-4.03-0.67-5.96c-0.82,6.44-4.1,12.31-9.16,16.38c-2.28,1.98-6.09,5.28-6.09,9.41
c0.26,4.61,2.07,8.99,5.12,12.45c-0.91-2.36-1.38-4.86-1.38-7.39c-0.02-1.72,0.19-3.43,0.62-5.09l0.07-0.23l0.12,0.21
C322.71,303.76,325.57,306.05,328.95,307.11z"/>
<path id="Path_1210" class="st0" d="M330.13,289.41c2-0.77,9.17-3.91,9.17-8.94c-0.28-3.2-1.64-6.22-3.87-8.54
C337.54,278.29,335.42,285.29,330.13,289.41z"/>
<path id="Path_1211" class="st0" d="M357.06,279.77c-2.69,2.53-5.86,4.47-9.33,5.72l-0.12-0.17c2.95-3.43,4.81-7.66,5.35-12.15
c-5.67,8.93-9.13,10.47-13.13,12.23l-1.75,0.8c-4.79,2.27-10.29,5.51-10.29,10.6c-0.38,3.7,0.72,7.4,3.06,10.29
c-0.43-1.47-0.65-2.98-0.67-4.51c-0.03-1.03,0.1-2.07,0.4-3.06l0.15-0.4l0.06,0.4c1.05,3.22,3.15,6,5.97,7.89
c-0.08-0.8-0.13-1.61-0.12-2.42c0-3.75,0.87-10.39,6.69-12.88C353.83,287.62,356.45,281.82,357.06,279.77z"/>
<path id="Path_1212" class="st0" d="M348.36,300.57c-0.41-3.63,0.3-7.3,2.03-10.52c-7.5,2.91-12.46,10.11-12.51,18.15
c0,0.45,0,0.8,0.04,1.05c1.28-3.54,3.47-6.69,6.35-9.12l0.07-0.06l0.07,0.04c0,0,0.04,0.03,0.04,0.13
c-0.44,2.78-1.02,5.54-1.75,8.26C343.72,307.86,348.36,304.64,348.36,300.57z"/>
<path id="Path_1213" class="st0" d="M358.27,299.89c0-1.47-0.27-2.94-0.8-4.32c-0.48,1.66-1.42,3.15-2.71,4.3l0,0l-0.15-0.15
c0.92-1.44,1.34-3.15,1.17-4.86c-0.01-1.21-0.12-2.41-0.34-3.6c-0.09-0.51-0.14-1.03-0.14-1.55c0.06-2.13,0.69-4.2,1.83-5.99
c-2.9,2.21-4.97,5.33-5.88,8.86c-0.62,2.18-1.11,4.39-1.48,6.62c-0.11,0.79-0.28,1.58-0.51,2.35c-0.8,2.81-1.89,5.54-3.25,8.13
c2.09-1.32,3.75-3.21,4.79-5.45l0.15-0.3l0.06,0.33c0.09,0.68,0.13,1.36,0.12,2.05c0.02,1.94-0.36,3.87-1.11,5.66
C351.51,310.78,358.27,305.09,358.27,299.89z"/>
<path id="Path_1214" class="st0" d="M370.27,288.65c-2.3-1.69-5.24-2.24-7.99-1.49l-0.34,0.11l0.22-0.28
c1.45-1.76,3.21-3.24,5.19-4.36c-3.06-0.62-6.22,0.44-8.29,2.76c-1.27,2.26-1.88,4.83-1.77,7.42c0,0.54,0,0.95,0.04,1.23
c2.32-4.03,2.59-4.02,2.67-4l0.1,0.04v0.08c0.09,0.51,0.13,1.03,0.12,1.55c-0.05,1.76-0.22,3.51-0.52,5.24
C363.83,289.06,368.9,288.61,370.27,288.65z"/>
<path id="Path_1215" class="st0" d="M336.4,428.59l0.43-19c0.29,0.41,0.56,0.84,0.8,1.29c0.33,0.72,0.6,1.48,0.8,2.25
c0.41,0.44,0.69,0.98,0.8,1.57c0.14,0.93,0.58,4.06,0.74,4.79c0.15,0.74,0.14,1.32,0.58,1.32c2.54,0,3.96-4.2,3.42-5.53
c-0.52-1.06-1.18-2.05-1.96-2.93c0,0-1.37-4.35-1.6-4.89c-0.23-0.54-1.22-1.03-1.51-1.22c-0.15-0.1-1.04-0.98-1.9-1.84l0.12-5.33
c-0.28,0.02-0.56,0.02-0.84,0c-2.28,0.03-4.48-0.87-6.09-2.49l0.93,31.99L336.4,428.59z M342.8,418.2
c-1.12,1.56-1.27,1.6-1.66,1.6s-0.4-1.71-0.14-2.59c0.26-0.88,0.88-1.81,1.71-1.76C343.39,415.51,343.93,416.64,342.8,418.2
L342.8,418.2z"/>
<path id="Path_1216" class="st0" d="M378.59,356.33c2.06,0.65,4.2,1.04,6.35,1.17c3.63-0.51,6.98-2.21,9.53-4.84
c1.31-0.91,2.68-1.72,4.1-2.44c0,0-2.64-0.05-5.96-0.34c-3.09-0.25-6.2,0.11-9.14,1.07c-1.67,0.61-3.19,1.58-4.45,2.83
c-0.5,0.57-1.14,0.99-1.86,1.22c-0.98,0.24-1.81,0.44-1.81,0.44c0.23-0.86,0.69-1.63,1.32-2.25c1.72-1.18,3.31-2.54,4.74-4.06
c2.29-2.74,4.23-5.76,5.77-8.98c0.24-0.93,2.25-5.43,2.25-5.43c-1.64,1.17-3.36,2.24-5.13,3.2c-2.82,1.49-5.22,3.65-6.99,6.31
c-1.2,2.26-1.84,4.78-1.86,7.34c-0.14,2.42-1.15,4.71-2.83,6.45c-1.57,1.7-3.27,3.29-5.08,4.74c-0.89,0.68-1.9,1.19-2.98,1.51
c-0.67,0.14-1.37,0.18-2.05,0.1c0,0,1.03-1.32,1.66-1.51c1.74-0.59,3.31-1.58,4.59-2.88c2.04-2.53,3.45-5.51,4.1-8.7
c0.2-0.93,1.07-3.76,1.07-3.76c-1.28,1.22-2.67,2.31-4.16,3.28c-2.62,1.74-4.84,4.02-6.5,6.7c-0.96,1.56-1.26,3.45-0.83,5.23
c0,0.4-0.14,0.64-0.8,1.27c-0.62,0.49-1.36,0.82-2.15,0.93c-1.63,0.6-3.35,0.94-5.09,1.03c-2.03-0.2-4.03-0.57-5.99-1.12
c-0.68-0.05-1.76,0.59-2.2,0.64c-0.41-0.02-0.82-0.07-1.22-0.14c-0.44,0-1.81,0.05-1.81,0.05c0.26-0.72,0.4-1.48,0.44-2.25
c0.05-0.34,0.15-0.83,0.64-0.93c0.83-0.37,1.63-0.79,2.4-1.27c0.44-0.22,0.93-0.32,1.42-0.29c1.89-0.04,3.73-0.55,5.38-1.47
c2.79-1.83,5.13-4.27,6.84-7.14c1.17-1.2,2.45-2.3,3.81-3.28c0,0-2.49,0.68-5.33,1.51c-3.03,0.89-5.88,2.28-8.45,4.11
c-1.11,1.01-2.05,2.18-2.8,3.47c-0.4,0.86-0.9,1.68-1.47,2.44c-0.44,0.34-1.86,1.07-1.86,0.88c0.23-0.63,0.52-1.24,0.88-1.81
c0.34-0.59,1.42-2.3,2.1-3.32c0.53-0.83,0.96-1.71,1.27-2.64c0.29-0.91,0.72-1.76,1.27-2.54c1.08-1.59,2.87-2.56,4.79-2.59
c2.02,0.02,4.05-0.08,6.06-0.3c2.63-0.42,5.13-1.43,7.33-2.93c0.8-0.44,4.25-2.98,4.25-2.98s-3.37,0.59-4.45,0.73
c-2.56,0.11-5.1,0.38-7.63,0.8c-2.34,0.75-4.54,1.89-6.5,3.37c-1.44,0.99-2.83,2.06-4.15,3.2c0-0.62,0.05-1.24,0.15-1.86
c0.11-0.82,0.6-1.55,1.32-1.96c2.01-1.1,3.88-2.44,5.57-4c1.77-1.95,3.25-4.13,4.4-6.5c0.3-0.88,2.05-4.4,2.05-4.4
c-1.18,1-2.49,1.82-3.91,2.45c-2.89,1.41-5.46,3.4-7.53,5.87c-1.14,1.39-1.82,3.1-1.95,4.89c-0.03,0.8-0.16,1.58-0.4,2.35
c-0.39,0.77-0.59,1.63-0.59,2.49c-0.05,0.96-0.26,1.91-0.64,2.8c-0.15,0.61-0.36,1.2-0.64,1.76c-0.26,0.34-0.48,0.72-0.64,1.12
c-0.68,1.15-1.45,2.24-2.3,3.28c0,0-0.64-0.8,0.34-2.88c1.12-2.23,1.8-4.65,2-7.14c-0.1-2.03-0.48-4.04-1.13-5.96
c-0.44-1.17-1.91-5.33-1.91-5.33c-0.13,1.32-0.41,2.61-0.83,3.86c-1.16,2.76-1.92,5.67-2.25,8.65c0.1,2.41,0.82,4.75,2.11,6.79
c0.62,0.75,0.89,1.73,0.73,2.69c-0.11,0.86-0.47,1.68-1.03,2.35c-0.36,0.72-0.6,1.5-0.68,2.3c-0.43,2.01-1.67,3.76-3.42,4.84
c-0.15,0.12-0.34,0.26-0.55,0.4l0.69-26.82c-0.01-6.13,1.5-12.17,4.4-17.58c0.47-0.68,0.72-1.01,0.72-1.01
c2.17-2.98,5.52-4.9,9.19-5.27v-2.19c0,0-16.85-0.03-20.13-0.03h-0.06c-3.26,0-18.44,0.03-18.44,0.03v2.19
c3.67,0.37,7.02,2.29,9.19,5.27c2.88,4,4.66,6.37,5.17,19.07l0.93,30.65c-1.35,0.86-2.27,2.26-2.53,3.85
c-0.4-0.59-0.49-1.33-0.24-2c0.84-1.83,1.13-3.87,0.83-5.87c-0.94-2.95-2.41-5.7-4.35-8.12c-0.93-1.07-2.64-3.71-2.64-3.71
s0.05,2.45-0.1,4.4c-0.53,2.69-0.41,5.47,0.34,8.11c0.77,2.19,2.21,4.09,4.1,5.43c0.61,0.49,1.01,1.19,1.12,1.96
c0.15,0.66,0.11,1.35-0.1,2c-0.02,0.72-0.11,1.44-0.24,2.15c-0.2,0.72-0.8,1.12-0.88,1.76c-0.17,1.01-0.41,2.01-0.73,2.98
c-0.15,0.54-0.49,0.64-0.93,1.37c-0.25,0.36-0.37,0.79-0.34,1.22c-0.94-0.76-1.8-1.63-2.54-2.59c-1.08-2.96-3.64-5.14-6.74-5.72
c-2.1-0.34-4.24-0.38-6.35-0.14c-1.78-0.04-3.56-0.25-5.31-0.63c1.53,0.49,2.89,1.43,3.89,2.69c1.89,2.1,4.2,3.77,6.79,4.89
c1.68,0.5,3.47,0.48,5.13-0.05c0.73-0.19,1.27-0.49,1.91-0.24c0.54,0.31,1,0.72,1.37,1.22c0,0-1.13,0.64-1.96,1.03
c-2.86,1-5.32,2.9-6.99,5.43c-0.88,2.14-1.52,4.37-1.91,6.65c-0.28,1.27-0.62,2.53-1.03,3.76c0,0,1.66-1.42,3.03-2.35
c2.13-1.27,4.02-2.9,5.57-4.84c1.55-2.38,2.44-5.13,2.59-7.97c0-0.57,0.42-1.05,0.98-1.13c0.58,0.18,0.99,0.71,1.03,1.32
c0.01,0.7,0.23,1.39,0.64,1.96c0.29,0.44,0.98,1.03,1.57,1.86c0.55,1.13,1.02,2.29,1.42,3.47c0.4,0.8,0.59,0.68,1.17,1.76
c0.59,1.07,0.49,1.66,0.98,2.49c0.49,0.83,0.73,0.74,0.73,0.74l-0.29-5.19c-0.15-0.09-0.29-0.2-0.4-0.34
c-0.19-0.28-0.35-0.57-0.49-0.88c0,0,0.88-0.59,1.47-0.88c0.42-0.25,0.94-0.21,1.32,0.1c1.78,1.42,3.94,2.28,6.21,2.49
c3.65-0.72,7.15-2.04,10.36-3.91c1.32-0.56,2.68-1.04,4.06-1.42c0,0-2.93-0.29-4.5-0.64c-2.37-0.53-4.77-0.94-7.19-1.22
c-0.67,0.01-1.34,0.09-2,0.24l0.13-1.57c0,0-1.29,1.09-1.73,1.43c-1.01,0.66-2.09,1.2-3.23,1.6c-0.76,0.29-1.55,0.52-2.35,0.68
c0,0-0.2-0.2-0.34,0.54s-0.24,0.64-1.07,0.83c-0.54,0.13-1.06,0.31-1.56,0.54c0,0,0.68-1.76,0.8-2.05
c0.12-0.29,0.24-0.58,1.27-0.8c2.61-0.53,5.03-1.76,6.99-3.57c2.16-2.06,3.87-4.54,5.03-7.28c0.93-1.6,2.1-3.05,3.47-4.3
c0,0-2.25,0.88-6.5,2.35c-0.2,0.07-0.4,0.14-0.59,0.22l0.15-5.61l0,0v-0.4l-0.29,0.14c-0.64,0.29-2.49,0.83-3.22,1.17
c-0.73,0.34-1.32,1.12-1.91,1.42c-1.03,0.38-1.97,0.96-2.76,1.71l0.05,0.92l0,0l0.19,5.99c-1.1,1.43-1.77,3.15-1.92,4.94
c-0.2,1.12-0.34,1.66-0.49,2.25c-0.21,0.66-0.45,1.32-0.73,1.96c-0.46-0.89-1.02-1.73-1.66-2.49c-0.57-0.62-0.74-1.51-0.44-2.3
c0.23-0.87,0.54-1.73,0.93-2.54c0.44-1.12,1.03-3.28,1.37-3.96c0.34-0.68,0.2,0.64,0.93-3.37c0.43-2.45,2.18-4.46,4.55-5.23
c0.59-0.3,1.17-1.07,1.91-1.42c0.73-0.34,2.59-0.88,3.22-1.17c1.39-0.53,2.64-1.37,3.67-2.45c0.54-0.84,1.28-1.52,2.15-2
c1.05-0.36,2.16-0.53,3.27-0.49c1.6-0.1,1.95,1.17,2.3,1.6c0.29,0.43,0.43,0.95,0.4,1.47c-0.15,2.49,0.93,4.9,2.88,6.45
c2.56,1.91,5.58,3.11,8.75,3.47c1.47,0.4,5.13,1.91,5.13,1.91s-0.64-0.24-3.13-4.11c-1.61-2.77-3.79-5.17-6.39-7.03
c-1.56-0.74-3.21-1.29-4.9-1.63c-0.58-0.1-1.13-0.33-1.6-0.68c-0.45-0.49-0.85-1.01-1.22-1.57c0.21-0.03,0.42-0.03,0.64,0
c1.54,0.67,3.21,0.99,4.89,0.93c1.97,0.01,3.92-0.46,5.67-1.37c0.63-0.25,1.34-0.18,1.91,0.2c0.64,0.4,1.42,0.93,2.98,1.91
c2.16,1.46,4.68,2.32,7.28,2.49c3.15-0.19,6.25-0.86,9.19-2c1.23-0.5,2.51-0.9,3.81-1.17c0,0-2.44-0.44-3.81-0.74
c-2.9-0.98-5.9-1.63-8.95-1.95c-2.01,0.01-3.97,0.59-5.67,1.66c-0.52,0.54-1.31,0.7-2,0.4c-0.71-0.14-1.28-0.64-1.51-1.32
c2.01-0.09,3.94-0.81,5.52-2.05c1.32-1.06,2.54-2.26,3.62-3.57c0.63-0.6,1.14-1.35,2.49-2.59
C375.76,356.13,377.27,355.8,378.59,356.33z M385.7,351.25c3.16-0.63,6.39-0.75,9.59-0.36c-0.8,0.2-5.67,2.3-9.09,3.2
c-2.43,0.6-4.9,0.99-7.4,1.17l-0.05-0.1C380.74,353.36,383.12,352.02,385.7,351.25z M378.35,344.26c2.09-2.87,4.93-5.1,8.22-6.43
c0,0-4.19,5.57-5.51,7.35c-1.33,1.79-4.75,6.84-4.75,6.84c-0.11-2.73,0.61-5.44,2.06-7.76H378.35z M364.66,356.77
c1.82-2.64,4.26-4.79,7.1-6.28c0,0-2.91,4.9-4.54,7.15c-1.63,2.25-4,5-4,5s-0.94-1.94,1.46-5.87H364.66z M351.85,354.32
c2.79-1.51,5.69-2.81,8.68-3.88c0,0-5,4.44-7.5,6.23c-1.75,1.21-3.6,2.29-5.51,3.22c0.82-2.27,2.35-4.22,4.36-5.57L351.85,354.32
z M359.97,344.36c2.9-0.57,5.84-0.89,8.79-0.97c0,0-5.77,2.04-8.12,2.8c-2.13,0.6-4.3,1.06-6.48,1.38
c1.76-1.37,3.73-2.46,5.83-3.21L359.97,344.36z M352.25,340.08c2.44-2.6,5.33-4.74,8.53-6.33c0,0-3.73,3.72-6.33,6.79
c-2.6,3.07-4.34,5.16-4.34,5.16l-0.05,0.1c0.34-2.04,1.09-3.99,2.21-5.73L352.25,340.08z M343.37,355.8
c-1.25-1.97-1.91-4.25-1.89-6.58c0.16-2.96,0.96-5.85,2.35-8.47c0,0-0.1,4.79,0.05,7.5c0.01,2.52-0.15,5.05-0.5,7.55
L343.37,355.8z M311.81,384.09c-2.02-0.85-3.95-1.89-5.77-3.12c2.84-0.4,5.73-0.26,8.53,0.4c2.17,0.87,3.84,2.67,4.54,4.9
c0,0-4.93-1.36-7.27-2.19L311.81,384.09z M310.8,402.47c0.31-2.93,1.17-5.78,2.54-8.39c1.3-1.77,3.15-3.06,5.27-3.65
L310.8,402.47z M338.47,391.52c2.83,0.11,5.64,0.5,8.39,1.15c-3.22,0.78-6.48,1.37-9.77,1.77c-2.06,0.14-4.13,0.11-6.19-0.08
c0,0,1.56-2.26,7.6-2.86L338.47,391.52z M332.12,383.39c2.86-1.96,6.01-3.46,9.34-4.44c0,0-5.61,4.79-7.86,6.79
c-1.81,1.7-3.51,3.51-5.1,5.41c-0.01-3.01,1.33-5.86,3.66-7.78L332.12,383.39z M325.9,374.99c-1.1-1.94-1.97-3.99-2.6-6.13
c-0.68-2.48-1.69-8.47-1.69-8.47c1.72,2.42,3.27,4.96,4.65,7.59c0.65,2.31,0.53,4.77-0.33,7L325.9,374.99z M350.15,368.87
c2.12,0.47,4.16,1.23,6.07,2.25c2.74,2.11,4.96,4.81,6.48,7.91c-2.44-1.33-4.8-2.81-7.04-4.44c-2.04-1.64-3.89-3.51-5.51-5.57
V368.87z M371.89,365.45c3.08,0.22,6.12,0.84,9.03,1.84c0,0-6.84,0.87-9.03,0.87c-1.97-0.07-3.93-0.26-5.87-0.56
c1.74-1.22,3.78-1.96,5.9-2.15L371.89,365.45z"/>
<path id="Path_1217" class="st0" d="M361.37,338.3c-0.91,0.5-1.44,1.51-1.33,2.55c-0.12,0.71,0.36,1.39,1.08,1.51
c0.23,0.04,0.48,0.01,0.7-0.07c1.01-0.3,1.66-1.27,1.55-2.32c0.04-0.89-0.65-1.66-1.54-1.7
C361.67,338.25,361.52,338.27,361.37,338.3z"/>
<path id="Path_1218" class="st0" d="M385.58,345.63c-0.91,0.5-1.44,1.5-1.33,2.54c-0.12,0.71,0.35,1.39,1.06,1.51
c0.24,0.04,0.48,0.02,0.71-0.07c1.01-0.3,1.67-1.28,1.55-2.33c0.04-0.9-0.65-1.65-1.55-1.7
C385.88,345.58,385.73,345.6,385.58,345.63z"/>
<path id="Path_1219" class="st0" d="M344.59,368.63c-0.91,0.51-1.43,1.51-1.33,2.55c-0.12,0.71,0.35,1.38,1.06,1.51
c0.24,0.04,0.48,0.02,0.71-0.08c1.01-0.3,1.66-1.27,1.55-2.32c0.04-0.9-0.65-1.66-1.55-1.71
C344.89,368.58,344.74,368.6,344.59,368.63z"/>
<path id="Path_1220" class="st0" d="M373.24,360.94c-0.33,0.64-0.08,1.43,0.56,1.77c0.21,0.11,0.45,0.16,0.68,0.14
c1.05,0.02,1.97-0.71,2.18-1.74c0.32-0.84-0.1-1.77-0.94-2.09c-0.15-0.06-0.3-0.09-0.45-0.1
C374.26,359.12,373.45,359.92,373.24,360.94z"/>
<path id="Path_1221" class="st0" d="M360.92,368.61c-0.91,0.51-1.43,1.51-1.33,2.55c-0.12,0.71,0.36,1.39,1.07,1.51
c0.24,0.04,0.48,0.02,0.7-0.07c1.01-0.3,1.66-1.28,1.55-2.33c0.04-0.9-0.65-1.66-1.55-1.7
C361.22,368.57,361.07,368.58,360.92,368.61z"/>
<path id="Path_1222" class="st0" d="M341.44,385.33c-0.91,0.5-1.44,1.5-1.33,2.54c-0.13,0.71,0.35,1.39,1.06,1.52
c0.24,0.04,0.48,0.02,0.71-0.07c1.01-0.3,1.66-1.28,1.55-2.33c0.04-0.9-0.65-1.65-1.55-1.7
C341.73,385.28,341.58,385.3,341.44,385.33z"/>
<path id="Path_1223" class="st0" d="M311.04,392.41c1.01-0.3,1.66-1.28,1.55-2.33c0.04-0.9-0.65-1.66-1.55-1.7
c-0.15-0.01-0.3,0.01-0.45,0.04c-0.91,0.51-1.44,1.51-1.33,2.55c-0.12,0.71,0.36,1.39,1.07,1.51
C310.57,392.52,310.82,392.5,311.04,392.41z"/>
<path id="Path_1224" class="st0" d="M320.23,271.21c1.67,2.3,2.61,5.05,2.72,7.89c0,1.07-0.12,2.8-0.12,2.8
c-0.17,1.67-0.48,3.32-0.93,4.93c1.49-2.11,2.64-4.43,3.43-6.89l0.06-0.27l0.13,0.24c0.54,1.04,0.89,2.17,1.02,3.34
c0.63-1.45,0.98-3,1.02-4.58C327.24,274.74,324.16,271.6,320.23,271.21z"/>
<path id="Path_1225" class="st0" d="M321.12,283.36L321.12,283.36c-0.07-1.23-0.21-2.46-0.43-3.68c-1.7,3.43-4.15,6.43-7.16,8.79
c-2.79,1.95-4.55,5.06-4.79,8.45c0.56,5.03,3.14,9.61,7.15,12.69c-1.24-3.34-1.89-6.88-1.93-10.44c0.12-1.87,1.04-3.59,2.53-4.73
l0.3-0.3c1.12-1.06,2.14-2.21,3.05-3.46l0,0c0.48-0.72,0.84-1.51,1.06-2.34c0.09-0.39,0.15-0.8,0.19-1.2l0,0
C321.18,285.88,321.2,284.62,321.12,283.36z"/>
<path id="Path_1226" class="st0" d="M290.99,341.24c-0.91,0-1.65,0.74-1.65,1.65c0,0.01,0,0.01,0,0.02
c0.01,0.41,0.18,0.79,0.47,1.08c-0.19-0.06-0.39-0.1-0.59-0.1c-1.15,0-2.07,0.94-2.07,2.08c0,1.14,0.93,2.06,2.07,2.07
c0.18-0.01,0.37-0.03,0.55-0.08c-0.46,1.51-1.87,2.53-3.44,2.51c-3.05,0-4.27-3.29-4.4-4.07c0.32,1.08,1.45,1.71,2.54,1.39
c1.08-0.32,1.71-1.45,1.39-2.54c-0.26-0.87-1.06-1.47-1.97-1.47c-0.21,0.01-0.41,0.05-0.61,0.12c0.84-0.75,0.92-2.04,0.17-2.89
c-0.75-0.84-2.04-0.92-2.89-0.17c-0.43,0.38-0.68,0.93-0.69,1.51c0.01,0.6,0.27,1.17,0.72,1.57c-0.21-0.08-0.44-0.13-0.67-0.14
c-1.13-0.01-2.06,0.9-2.07,2.04c-0.01,1.13,0.9,2.06,2.04,2.07c0.92,0.01,1.74-0.6,1.99-1.49c-0.12,0.94-1.84,4.07-4.4,4.07
c-1.59,0.06-3.02-0.97-3.46-2.51c0.18,0.05,0.36,0.07,0.55,0.08c1.15,0,2.07-0.94,2.07-2.08c0-1.14-0.93-2.06-2.07-2.07
c-0.2,0-0.4,0.03-0.59,0.1c0.29-0.28,0.46-0.67,0.47-1.08c0.01-0.91-0.73-1.66-1.64-1.66c-0.01,0-0.02,0-0.02,0
c-0.36-0.04-0.72,0.03-1.04,0.2c0.43,1.7,0.94,5.11,1.02,5.59l1.43,9.31h15.38c0,0,0-0.16,0.04-0.28l1.39-9.04
c0.31-2.11,0.68-4.34,1-5.59C291.7,341.27,291.34,341.2,290.99,341.24z"/>
<path id="Path_1227" class="st0" d="M320.05,341.24c-0.91,0-1.64,0.74-1.64,1.65c0,0,0,0.01,0,0.01
c0.01,0.41,0.18,0.79,0.47,1.08c-0.19-0.06-0.39-0.1-0.59-0.1c-1.15,0-2.07,0.94-2.07,2.08c0,1.14,0.93,2.06,2.07,2.07
c0.18-0.01,0.37-0.03,0.55-0.08c-0.46,1.51-1.87,2.53-3.44,2.51c-3.05,0-4.27-3.29-4.4-4.07c0.32,1.08,1.45,1.71,2.54,1.39
c1.08-0.32,1.71-1.45,1.39-2.54c-0.26-0.87-1.06-1.47-1.97-1.47c-0.21,0.01-0.41,0.05-0.61,0.12c0.84-0.75,0.92-2.04,0.17-2.89
c-0.75-0.84-2.04-0.92-2.89-0.17c-0.43,0.38-0.68,0.93-0.69,1.51c0.01,0.6,0.27,1.17,0.72,1.57c-0.21-0.08-0.44-0.13-0.67-0.14
c-1.13-0.01-2.06,0.9-2.07,2.04c-0.01,1.13,0.9,2.06,2.04,2.07c0.92,0.01,1.74-0.6,1.99-1.49c-0.12,0.94-1.84,4.07-4.4,4.07
c-1.59,0.06-3.02-0.97-3.46-2.51c0.18,0.05,0.36,0.07,0.55,0.08c1.15,0,2.07-0.94,2.07-2.08c0-1.14-0.93-2.06-2.07-2.07
c-0.2,0-0.4,0.03-0.59,0.1c0.29-0.28,0.46-0.67,0.47-1.08c0.01-0.91-0.73-1.66-1.64-1.66c-0.01,0-0.02,0-0.03,0
c-0.36-0.04-0.72,0.03-1.04,0.2c0.43,1.7,0.94,5.11,1.02,5.59l1.43,9.31h15.38c0,0,0-0.16,0.04-0.28l1.39-9.04
c0.31-2.11,0.72-4.47,1-5.59C320.77,341.27,320.41,341.2,320.05,341.24z"/>
<path id="Path_1228" class="st0" d="M306.66,359.85c-1,0-1.81,0.81-1.81,1.81c0,0.01,0,0.01,0,0.02c0.01,0.45,0.19,0.88,0.52,1.2
c-1.21-0.36-2.48,0.32-2.84,1.53c-0.36,1.21,0.32,2.48,1.53,2.84c0.21,0.06,0.44,0.1,0.66,0.1c0.2-0.01,0.41-0.04,0.6-0.09
c-0.51,1.66-2.06,2.79-3.8,2.76c-3.37,0-4.71-3.63-4.83-4.49c0.35,1.2,1.61,1.88,2.8,1.53c1.2-0.35,1.88-1.61,1.53-2.8
c-0.28-0.96-1.17-1.63-2.17-1.62c-0.23,0.01-0.45,0.05-0.67,0.13c0.93-0.83,1.02-2.25,0.19-3.19c-0.83-0.93-2.25-1.02-3.19-0.19
c-0.48,0.42-0.75,1.03-0.76,1.67c0.01,0.66,0.3,1.29,0.8,1.73c-0.23-0.09-0.48-0.14-0.73-0.15c-1.25-0.01-2.27,0.99-2.28,2.25
c-0.01,1.25,0.99,2.27,2.25,2.28c1.02,0.01,1.91-0.66,2.2-1.64c-0.13,1.03-2.03,4.49-4.85,4.49c-1.76,0.06-3.33-1.07-3.82-2.76
c0.2,0.05,0.4,0.08,0.6,0.09c1.26,0.05,2.33-0.93,2.38-2.19c0.05-1.26-0.93-2.33-2.19-2.38c-0.06,0-0.13,0-0.19,0
c-0.22,0-0.44,0.04-0.65,0.11c0.33-0.31,0.51-0.75,0.52-1.2c0.01-1-0.8-1.83-1.8-1.83c-0.01,0-0.02,0-0.03,0
c-0.39-0.05-0.79,0.03-1.14,0.22c0.48,1.88,1.03,5.63,1.12,6.15l1.57,10.27h16.96c0,0,0.02-0.17,0.04-0.3l1.53-9.99
c0.34-2.33,0.8-4.94,1.1-6.15C307.45,359.86,307.05,359.79,306.66,359.85z"/>
</g>
</g>
<g>
<path class="st0" d="M292.05,156.13h-5.16c-0.07,6.74-0.14,13.42-0.14,24.7c0,5.99,0.41,9.36,2.41,11.83
c1.93,2.41,5.43,3.44,10.39,3.44c4.82,0,8.53-1.17,10.73-4.06c2-2.62,2.06-6.74,2.13-13.28l0.14-22.63h-5.99v-4.34h16.65v4.34
h-5.3l-0.14,26.08c0,5.5-0.62,9.49-3.78,13.35c-2.75,3.37-7.84,5.23-14.65,5.23c-8.39,0-13-2.13-15.82-5.99
c-2.27-3.1-2.68-6.47-2.68-14.52c0-7.84,0.07-16.79,0.14-24.15h-5.57v-4.34h16.65V156.13z"/>
<path class="st0" d="M350.99,195.62h4.54c0-5.5,0.28-11.28,0.28-16.99c0-3.44-0.14-10.32-8.05-10.32
c-5.02,0-10.53,3.03-10.53,12.66l-0.14,14.65h5.16v4.33h-15.41v-4.33h4.54l0.28-26.83h-5.71v-4.33h11.35
c0,1.38-0.07,3.3-0.21,4.27l-0.55,3.58h0.14c1.51-5.37,7.22-8.67,12.59-8.67c9.77,0,12.18,6.6,12.18,13.35
c0,6.05-0.27,13.14-0.27,18.65h5.23v4.33h-15.41V195.62z"/>
<path class="st0" d="M376.65,168.79h-6.47v-4.33h12.11l-0.28,31.17h5.64v4.33h-16.92v-4.33h5.64L376.65,168.79z M378.79,146.5
c2.34,0,4.27,1.93,4.27,4.27c0,2.34-1.93,4.27-4.27,4.27c-2.34,0-4.27-1.93-4.27-4.27C374.52,148.42,376.45,146.5,378.79,146.5z"
/>
<path class="st0" d="M387.85,164.45h14.59v4.33h-4.27l8.19,22.77c0.34,0.96,0.55,2,0.76,3.03h0.14c0.21-1.03,0.41-2.06,0.76-3.03
l8.39-22.77h-4.61v-4.33h14.17v4.33h-3.92l-12.25,31.17h-5.64l-12.32-31.17h-3.99V164.45z"/>
<path class="st0" d="M433.97,182.68c0.21,7.29,3.37,13.42,13.48,13.42c3.85,0,7.7-1.17,10.66-2.68l-0.21,4.88
c-2.61,1.31-8.12,2.48-11.7,2.48c-11.21,0-18.16-6.12-18.16-18.51c0-10.46,5.64-18.64,16.44-18.64
c14.04,0,15.34,12.73,15.34,19.06H433.97z M453.92,178.35c0-5.57-3.51-10.39-9.43-10.39c-6.05,0-10.18,4.54-10.52,10.39H453.92z"
/>
<path class="st0" d="M470.71,168.79h-6.05v-4.33h11.7v1.93c0,2.06-0.41,6.26-0.76,9.15h0.07h0.14c1.38-5.23,3.23-11.7,12.04-11.7
c0.48,0,3.3,0.28,3.58,0.55l-0.55,5.09c-0.96-0.34-2.48-0.55-3.85-0.55c-7.29,0-10.53,7.16-10.66,13.42l-0.28,13.28h5.99v4.33
h-16.65v-4.33h5.02L470.71,168.79z"/>
<path class="st0" d="M519.81,174.29h-5.09l-0.28-5.02c-1.31-1.1-4.61-1.31-6.26-1.31c-3.58,0-8.19,1.17-8.19,5.44
c0,3.85,3.44,5.02,8.67,5.92c9.01,1.51,12.8,4.27,12.8,10.11c0,6.33-4.27,11.35-14.52,11.35c-3.85,0-9.97-0.96-12.18-2.13
l-0.27-9.08h5.09l0.27,5.3c2.13,1.03,5.71,1.58,8.05,1.58c3.72,0,8.19-1.58,8.19-5.92c0-3.51-3.65-4.75-7.5-5.57
c-9.49-2.06-14.24-3.72-14.24-10.66c0-5.71,3.79-10.66,13.97-10.66c2.96,0,8.46,0.96,11.21,2.13L519.81,174.29z"/>
<path class="st0" d="M532.86,168.79h-6.47v-4.33h12.11l-0.28,31.17h5.64v4.33h-16.92v-4.33h5.64L532.86,168.79z M534.99,146.5
c2.34,0,4.27,1.93,4.27,4.27c0,2.34-1.93,4.27-4.27,4.27c-2.34,0-4.27-1.93-4.27-4.27C530.72,148.42,532.65,146.5,534.99,146.5z"
/>
<path class="st0" d="M546.4,164.45h5.71l0.27-8.67l5.64-0.34l-0.27,9.01h11.15v4.33h-11.22c-0.14,5.92-0.27,11.9-0.27,17.82
c0,5.09,0,9.5,5.44,9.5c2.89,0,5.5-0.83,7.02-1.44l-0.21,4.47c-1.79,0.89-5.09,1.65-7.98,1.65c-9.91,0-9.91-7.09-9.91-11.63
c0-4.34,0.21-15.21,0.28-20.37h-5.64V164.45z"/>
<path class="st0" d="M571.76,164.45h15v4.33h-4.47l8.46,22.08c0.41,1.24,0.83,2.89,0.96,3.72h0.14c0.14-0.83,0.55-2.48,0.96-3.72
l7.91-22.08h-5.02v-4.33h14.17v4.33h-3.92l-10.87,29.93c-5.02,13.97-9.08,19.33-18.03,19.33c-1.72,0-3.16-0.27-4.2-0.62v-4.68
c0.83,0.28,2.61,0.62,3.99,0.62c4.06,0,8.26-2.48,10.94-8.94l1.17-2.89l-13-32.75h-4.2V164.45z"/>
<path class="st0" d="M276.94,127.75h5.66v6.56c2.21,1.31,6.42,1.79,9.46,1.79c6.9,0,11.46-3.18,11.46-8.98
c0-12.91-26.52-5.25-26.52-22.58c0-5.87,3.45-13.74,17.88-13.74c3.52,0,9.94,0.9,12.98,2.28v9.74h-5.38v-6.01
c-1.8-0.76-5.8-1.31-8.08-1.31c-5.18,0-11.05,2-11.05,8.36c0,12.29,26.52,4.63,26.52,22.44c0,10.63-9.12,14.5-18.44,14.5
c-7.94,0-11.81-1.86-14.5-2.42V127.75z"/>
<path class="st0" d="M314.23,104.34h5.73l0.28-8.7l5.66-0.34l-0.28,9.04h11.19v4.35h-11.25c-0.14,5.94-0.28,11.95-0.28,17.88
c0,5.11,0,9.53,5.46,9.53c2.9,0,5.52-0.83,7.04-1.45l-0.21,4.49c-1.79,0.9-5.11,1.66-8.01,1.66c-9.94,0-9.94-7.11-9.94-11.67
c0-4.35,0.21-15.26,0.28-20.44h-5.66V104.34z"/>
<path class="st0" d="M358.07,103.51c10.84,0,17.26,7.46,17.26,17.68c0,11.33-6.42,19.61-17.26,19.61
c-10.84,0-17.26-7.46-17.26-17.67C340.8,111.79,347.23,103.51,358.07,103.51z M358.07,136.1c7.6,0,11.33-6.21,11.33-13.95
c0-7.73-3.73-13.95-11.33-13.95c-7.59,0-11.32,6.21-11.32,13.95C346.74,129.89,350.47,136.1,358.07,136.1z"/>
<path class="st0" d="M409.78,139.07c-2.76,0.83-6.35,1.73-10.29,1.73c-14.09,0-18.58-9.32-18.58-18.64
c0-13.6,9.87-18.64,17.82-18.64c3.94,0,7.87,0.48,11.53,1.79v9.32h-5.11v-5.59c-1.52-0.55-4.35-0.83-5.94-0.83
c-10.29,0-12.36,8.15-12.36,13.95c0,1.93,0.28,13.95,13.53,13.95c3.52,0,6.35-0.97,9.67-2L409.78,139.07z"/>
<path class="st0" d="M420.15,91.22h-6.91v-4.35h12.57l-0.14,33.14l13.6-11.19v-0.14h-4.97v-4.35h15.47v4.35h-3.66l-14.78,12.02
l16.09,14.92h3.87v4.35h-15.47v-4.35h4.28v-0.14l-14.43-13.74l-0.14,13.88h4.7v4.35h-15.47v-4.35h5.11L420.15,91.22z"/>
<path class="st0" d="M460.4,91.22h-6.91v-4.35h12.57L466,105.58c0,1.59-0.41,5.11-0.69,6.63h0.14c1.52-5.39,7.25-8.7,12.64-8.7
c9.81,0,12.22,6.63,12.22,13.39c0,6.08-0.28,13.19-0.28,18.71h5.25v4.35h-15.47v-4.35h4.56c0-5.53,0.27-11.33,0.27-17.06
c0-3.45-0.14-10.36-8.08-10.36c-5.04,0-10.57,3.04-10.57,12.71l-0.21,14.71h5.25v4.35h-16.02v-4.35h5.11L460.4,91.22z"/>
<path class="st0" d="M516.3,103.51c10.84,0,17.26,7.46,17.26,17.68c0,11.33-6.42,19.61-17.26,19.61
c-10.84,0-17.26-7.46-17.26-17.67C499.04,111.79,505.46,103.51,516.3,103.51z M516.3,136.1c7.6,0,11.32-6.21,11.32-13.95
c0-7.73-3.73-13.95-11.32-13.95c-7.6,0-11.32,6.21-11.32,13.95C504.98,129.89,508.7,136.1,516.3,136.1z"/>
<path class="st0" d="M542.77,91.22h-6.91v-4.35h12.57l-0.28,48.75h5.66v4.35h-16.43v-4.35h5.11L542.77,91.22z"/>
<path class="st0" d="M558.84,135.62h4.56l0.27-26.93h-5.73v-4.35h11.39c0,1.38-0.07,3.32-0.21,4.28l-0.55,3.59h0.14
c1.52-5.39,7.25-8.7,12.64-8.7c6.49,0,9.87,3.73,10.77,8.35h0.14c0.69-1.52,2.9-8.35,12.08-8.35c9.8,0,12.22,6.63,12.22,13.39
c0,6.08-0.28,13.19-0.28,18.71h5.25v4.35h-15.47v-4.35h4.56c0.07-5.8,0.27-11.53,0.27-17.26c0-5.18-1.11-10.15-7.73-10.15
c-4.63,0-10.15,3.04-10.22,12.71l-0.14,14.71h5.11v4.35h-15.54v-4.35h4.55c0.07-5.8,0.28-11.53,0.28-17.26
c0-5.18-1.1-10.15-7.73-10.15c-4.63,0-10.15,3.04-10.22,12.71l-0.14,14.71h5.18v4.35h-15.47V135.62z"/>
</g>
</g>
</svg>

After

(image error) Size: 40 KiB

@ -5,17 +5,20 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Management - Seshat App</title> <title>File Management - Seshat App</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.9.1/font/bootstrap-icons.min.css"> <link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.9.1/font/bootstrap-icons.min.css">
<link rel="stylesheet" th:href="@{/css/styles.css}"> <link rel="stylesheet" th:href="@{/css/styles.css}">
</head> </head>
<body> <body>
<header class="header bg-primary text-white py-3"> <header class="header bg-primary text-white py-3">
<div class="container d-flex justify-content-between align-items-center"> <div class="container d-flex justify-content-between align-items-center">
<img th:src="@{/images/SU_logotyp_Landscape_Invert.svg}" alt="Stockholm University Logotype" class="logo">
<h1 class="app-title mb-0">Seshat Audio Transcriber</h1> <h1 class="app-title mb-0">Seshat Audio Transcriber</h1>
<div th:if="${#authentication.name != 'anonymousUser'}" class="dropdown"> <div th:if="${#authentication.name != 'anonymousUser'}" class="dropdown">
<a class="user-menu text-white text-decoration-none dropdown-toggle" href="#" id="userMenu" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <a class="user-menu text-white text-decoration-none dropdown-toggle" href="#" id="userMenu" role="button"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-person-circle"></i> <i class="bi bi-person-circle"></i>
<span th:text="${#authentication.name}">Username</span> <span th:text="${#authentication.getName()}">Username</span>
</a> </a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userMenu"> <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userMenu">
<li><a class="dropdown-item" th:href="@{/logout}">Logout</a></li> <li><a class="dropdown-item" th:href="@{/logout}">Logout</a></li>
@ -55,59 +58,89 @@
<!-- File Status Section --> <!-- File Status Section -->
<section th:if="${statuses != null && !statuses.isEmpty()}" class="mt-5"> <section th:if="${statuses != null && !statuses.isEmpty()}" class="mt-5">
<h3>File Upload Statuses</h3> <h3>File Upload Statuses</h3>
<ul class="list-group"> <div class="table-wrapper">
<li th:each="status : ${statuses}" class="list-group-item d-flex justify-content-between align-items-center"> <table class="table">
<span th:text="${status.fileName}">File Name</span> <thead>
<span class="badge bg-primary" th:text="${status.jobStatus}">Status</span> <tr>
</li> <th>File Name</th>
</ul> <th>Upload Date</th>
<th>Upload time</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr th:each="status : ${statuses}">
<td th:text="${status.fileName}">File Name</td>
<td th:text="${#temporals.format(status.getUploadedAt(), 'yyyy-MM-dd')}">Upload Date</td>
<td th:text="${#temporals.format(status.getUploadedAt(), 'HH:mm:ss')}">Upload time</td>
<td>
<span class="badge bg-primary" th:text="${status.jobStatus}">Status</span>
</td>
</tr>
</tbody>
</table>
</div>
</section> </section>
<hr> <hr>
<!-- File Browsing Section --> <!-- File Browsing Section -->
<section> <section>
<h3>Your Files</h3> <h3>Your Transcribed Files</h3>
<form id="bulk-actions-form" method="post"> <form id="bulk-actions-form" method="post">
<table class="table"> <div th:each="entry : ${filesByDirectory}">
<thead>
<tr> <a data-bs-toggle="collapse"
<th> th:href="'#collapse-' + ${entry.key.id}">
<input type="checkbox" id="select-all" /> <i class="bi bi-folder"></i> <span th:text="${entry.key.fileName}">Directory Name</span>
</th> </a>
<th>File Name</th> (uploaded at: <span th:text="${#temporals.format(entry.key.getUploadedAt(), 'yyyy-MM-dd HH:mm:ss')}"></span>)
<th>Actions</th>
</tr> <div th:id="'collapse-' + ${entry.key.id}" class="collapse">
</thead> <table class="table">
<tbody> <thead>
<tr th:each="file : ${files}"> <tr>
<td> <th>
<input type="checkbox" name="selectedFiles" th:value="${file.id}" /> <input type="checkbox" th:id="'select-all-' + ${entry.key.id}"/>
</td> </th>
<td th:text="${file.fileName}"></td> <th>File Name</th>
<td> <th>Actions</th>
<a th:href="@{/files/download/{id}(id=${file.id})}" class="btn btn-primary">Download</a> </tr>
<a th:href="@{/files/delete/{id}(id=${file.id})}" class="btn btn-danger">Delete</a> </thead>
</td> <tbody>
</tr> <tr th:each="file : ${entry.value}">
</tbody> <td>
</table> <input type="checkbox" name="selectedFiles" th:value="${file.id}"/>
</td>
<td>
<a th:href="@{/files/download/{id}(id=${file.id})}" th:text="${file.fileName}"></a>
</td>
<td>
<a th:href="@{/files/delete/{id}(id=${file.id})}" class="btn btn-sm btn-outline-danger">Delete</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="d-flex justify-content-between mt-3"> <div class="d-flex justify-content-between mt-3">
<button type="button" class="btn btn-primary" onclick="downloadSelected()">Download Selected</button> <button type="button" class="btn btn-sm btn-primary" onclick="downloadSelected()">Download Selected</button>
<button type="button" class="btn btn-danger" onclick="deleteSelected()">Delete Selected</button> <button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteSelected()">Delete Selected</button>
</div> </div>
</form> </form>
</section> </section>
</main> </main>
<footer class="bg-dark text-white text-center py-3"> <footer class="bg-primary text-white text-center py-3">
<p>&copy; 2024 Seshat App</p> <p>&copy; 2024 Seshat App</p>
</footer> </footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<script> <script>
// Select/Deselect all checkboxes // Select/Deselect all checkboxes
document.getElementById('select-all').addEventListener('change', function() { document.querySelectorAll('[id^=select-all]').forEach(function (element) {
const checkboxes = document.querySelectorAll('input[name="selectedFiles"]'); element.addEventListener('change', function () {
checkboxes.forEach(checkbox => checkbox.checked = this.checked); const checkboxes = element.closest('table').querySelectorAll('input[name="selectedFiles"]');
checkboxes.forEach(checkbox => checkbox.checked = this.checked);
});
}); });
// Handle bulk download // Handle bulk download

@ -1,40 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Seshat App</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
<link rel="stylesheet" th:href="@{/css/styles.css}">
</head>
<body>
<header class="bg-primary text-white text-center py-3">
<div class="container d-flex justify-content-between align-items-center">
<h1 class="app-title mb-0">Seshat Audio Transcriber</h1>
</div>
</header>
<main class="container mt-4">
<h2>Login</h2>
<form th:action="@{/login}" method="post" class="mb-4">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" id="username" name="username" class="form-control" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" id="password" name="password" class="form-control" required>
</div>
<div class="d-flex justify-content-between">
<!-- Login Button -->
<button type="submit" class="btn btn-primary">Login</button>
<!-- Register Button -->
<a href="/register" class="btn btn-secondary ms-auto">Register</a>
</div>
</form>
</main>
<footer class="bg-dark text-white text-center py-3">
<p>&copy; 2024 Seshat App</p>
</footer>
</body>
</html>

@ -1,38 +0,0 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register - Seshat App</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
<link rel="stylesheet" th:href="@{/css/styles.css}">
</head>
<body>
<header class="bg-primary text-white text-center py-3">
<div class="container d-flex justify-content-between align-items-center">
<h1 class="app-title mb-0">Seshat Audio Transcriber</h1>
</div>
</header>
<main class="container mt-4">
<h2>Register</h2>
<form th:action="@{/register}" method="post">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" id="username" name="username" class="form-control" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" id="email" name="email" class="form-control" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" id="password" name="password" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</main>
<footer class="bg-dark text-white text-center py-3">
<p>&copy; 2024 Seshat App</p>
</footer>
</body>
</html>