diff --git a/.dockeringnore b/.dockeringnore new file mode 100644 index 0000000..b9f52ad --- /dev/null +++ b/.dockeringnore @@ -0,0 +1,6 @@ +target/ +.git/ +.idea/ +*.iml +*.log +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e4014f7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM gradle:8.5-jdk17 AS builder + +WORKDIR /app +COPY build.gradle.kts settings.gradle.kts ./ +COPY gradle gradle +RUN gradle dependencies || return 0 + +COPY . . + +RUN gradle bootJar --no-daemon + +FROM eclipse-temurin:17-jre-alpine + +RUN adduser -D spring +USER spring + +WORKDIR /home/spring + +COPY --from=builder /app/build/libs/*.jar app.jar + +EXPOSE 8080 + +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/build.gradle.kts b/build.gradle.kts index 9b3ab58..be5b126 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,7 @@ version = "0.0.2" java { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(17) } } @@ -45,6 +45,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-web") compileOnly("org.projectlombok:lombok") developmentOnly("org.springframework.boot:spring-boot-devtools") + implementation("org.postgresql:postgresql:42.7.3") runtimeOnly("org.postgresql:postgresql") annotationProcessor("org.projectlombok:lombok") testImplementation("org.springframework.boot:spring-boot-starter-test") diff --git a/src/main/java/com/m/linshor/controllers/LinShorController.java b/src/main/java/com/m/linshor/controllers/LinShorController.java index 365131f..6f428e1 100644 --- a/src/main/java/com/m/linshor/controllers/LinShorController.java +++ b/src/main/java/com/m/linshor/controllers/LinShorController.java @@ -14,17 +14,20 @@ import java.util.Optional; import jakarta.servlet.http.HttpServletRequest; -import lombok.AllArgsConstructor; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/linshor/v1") -@AllArgsConstructor @Tag(name = "URL Shortener API", description = "Operations for URL shortening and redirection") public class LinShorController { private final LinShorService linShorService; + public LinShorController(@Qualifier("dbLinShorService") LinShorService linShorService) { + this.linShorService = linShorService; + } + @PutMapping("/update") @Operation(summary = "Update a long URL", description = "Updates an existing shortened URL with a new long URL.") @@ -78,7 +81,7 @@ public ResponseEntity redirectToLongUrl(@PathVariable @Parameter( String longUrl = mapping.get().getLongUrl().trim().replaceAll("^\"|\"$", ""); try { - URI uri = new URL(longUrl).toURI(); // Convert URL to URI safely + URI uri = new URL(longUrl).toURI(); return ResponseEntity.status(302).location(uri).build(); } catch (Exception e) { return ResponseEntity.badRequest().body("Invalid URL stored in database: " + longUrl); diff --git a/src/main/java/com/m/linshor/entities/Mapping.java b/src/main/java/com/m/linshor/entities/Mapping.java index c9eeb3f..c7f4580 100644 --- a/src/main/java/com/m/linshor/entities/Mapping.java +++ b/src/main/java/com/m/linshor/entities/Mapping.java @@ -1,9 +1,6 @@ package com.m.linshor.entities; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; +import jakarta.persistence.*; import lombok.*; @AllArgsConstructor @@ -11,11 +8,15 @@ @Getter @Setter @Entity +@Table(name = "mapping") public class Mapping { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) int id; + @Column(unique = true, nullable = false) String longUrl; + + @Column(nullable = false, length = 2048) String shortUrl; } diff --git a/src/main/java/com/m/linshor/services/DbLinShorService.java b/src/main/java/com/m/linshor/services/DbLinShorService.java new file mode 100644 index 0000000..eefff41 --- /dev/null +++ b/src/main/java/com/m/linshor/services/DbLinShorService.java @@ -0,0 +1,63 @@ +package com.m.linshor.services; + +import com.m.linshor.entities.Mapping; +import com.m.linshor.repositories.MappingRepository; +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; + +import java.util.Optional; +import java.util.Random; + +@Service +@Primary +@AllArgsConstructor +public class DbLinShorService implements LinShorService { + private final MappingRepository repository; + private static final String BASE62 = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + private static final int SHORT_URL_LENGTH = 10; + + private String generateShor() { + Random random = new Random(); + StringBuilder sb = new StringBuilder(SHORT_URL_LENGTH); + for (int i = 0; i < SHORT_URL_LENGTH; i++) { + sb.append(BASE62.charAt(random.nextInt(BASE62.length()))); + } + return sb.toString(); + } + + public Mapping saveLink(String longUrl) { + String shortUrl; + do { + shortUrl = generateShor(); + } while (repository.findByShortUrl(shortUrl).isPresent()); + + Mapping urlMapping = new Mapping(); + urlMapping.setShortUrl(shortUrl); + urlMapping.setLongUrl(longUrl); + return repository.save(urlMapping); + } + + @Override + public Mapping updateLink(String longUrl) { + Mapping mapping = findByLongUrl(longUrl); + String shortUrl = generateShor(); + + mapping.setShortUrl(shortUrl); + return repository.save(mapping); + } + + public Optional findByShortUrl(String shortUrl) { + return repository.findByShortUrl(shortUrl); + } + + public void deleteByShortUrl(String shortUrl) { + repository.findByShortUrl(shortUrl).ifPresent(repository::delete); + } + + @Override + public Mapping findByLongUrl(String longUrl) { + return repository.findByLongUrl(longUrl); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9871d24..9c29ada 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,9 +1,11 @@ spring.application.name=LinShor -spring.datasource.url=jdbc:postgresql://localhost:5432/template1 +spring.datasource.url=jdbc:postgresql://linshor-db:5432/linshor spring.datasource.username=postgres spring.datasource.password=postgres -spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect -spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.datasource.driver-class-name=org.postgresql.Driver - +spring.datasource.hikari.minimum-idle=2 +spring.datasource.hikari.maximum-pool-size=10 +spring.datasource.hikari.idle-timeout=30000 +spring.datasource.hikari.pool-name=LinShorHikariCP