From 47be194210dc20d06127f547aa01f18cfe7a4b1a Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Sat, 18 Apr 2026 18:14:01 +0100 Subject: [PATCH 01/25] Added application entry point class --- .../urlshortener/UrlShortenerApplication.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/java/com/tpximpact/urlshortener/UrlShortenerApplication.java diff --git a/src/main/java/com/tpximpact/urlshortener/UrlShortenerApplication.java b/src/main/java/com/tpximpact/urlshortener/UrlShortenerApplication.java new file mode 100644 index 00000000..cdf3eb69 --- /dev/null +++ b/src/main/java/com/tpximpact/urlshortener/UrlShortenerApplication.java @@ -0,0 +1,13 @@ +package com.tpximpact.urlshortener; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +class UrlShortenerApplication { + + public static void main(String[] args) { + SpringApplication.run(UrlShortenerApplication.class, args); + } + +} From 806efbf7b73cb0e9aa4d3dc47124903583e1acdc Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Sat, 18 Apr 2026 20:50:36 +0100 Subject: [PATCH 02/25] Added UrlController class --- .../urlshortener/controller/UrlController.java | 10 ++++++++++ .../com/tpximpact/urlshortener/service/UrlService.java | 4 ++++ .../tpximpact/urlshortener/service/UrlServiceImpl.java | 4 ++++ 3 files changed, 18 insertions(+) create mode 100644 src/main/java/com/tpximpact/urlshortener/controller/UrlController.java create mode 100644 src/main/java/com/tpximpact/urlshortener/service/UrlService.java create mode 100644 src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java diff --git a/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java b/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java new file mode 100644 index 00000000..ef47ae87 --- /dev/null +++ b/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java @@ -0,0 +1,10 @@ +package com.tpximpact.urlshortener.controller; + +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestMapping; + +@RestController +@RequestMapping("/api/url") +class UrlController { + +} diff --git a/src/main/java/com/tpximpact/urlshortener/service/UrlService.java b/src/main/java/com/tpximpact/urlshortener/service/UrlService.java new file mode 100644 index 00000000..0a6e0e8e --- /dev/null +++ b/src/main/java/com/tpximpact/urlshortener/service/UrlService.java @@ -0,0 +1,4 @@ +package com.tpximpact.urlshortener.service; + +public interface UrlService { +} diff --git a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java new file mode 100644 index 00000000..8ae78e1e --- /dev/null +++ b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java @@ -0,0 +1,4 @@ +package com.tpximpact.urlshortener.service; + +public class UrlServiceImpl { +} From 572c1bc69849d7678a6ea52369bde8631389848d Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Sat, 18 Apr 2026 20:53:02 +0100 Subject: [PATCH 03/25] Added serviceImpl class and implemnetents with interface UrlService --- .../com/tpximpact/urlshortener/service/UrlServiceImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java index 8ae78e1e..a5dfe5b3 100644 --- a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java +++ b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java @@ -1,4 +1,7 @@ package com.tpximpact.urlshortener.service; -public class UrlServiceImpl { +import org.springframework.stereotype.Service; + +@Service +public class UrlServiceImpl implements UrlService{ } From 7eec3eeece1d6d83fe9bdfc45fc46c492df7adab Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Sat, 18 Apr 2026 21:04:30 +0100 Subject: [PATCH 04/25] Used constructor-based dependency injection in controller to inject UrlService --- .../com/tpximpact/urlshortener/controller/UrlController.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java b/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java index ef47ae87..7f8f7d1b 100644 --- a/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java +++ b/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java @@ -1,10 +1,15 @@ package com.tpximpact.urlshortener.controller; +import com.tpximpact.urlshortener.service.UrlService; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestMapping; @RestController @RequestMapping("/api/url") class UrlController { + private final UrlService urlService; + public UrlController(UrlService urlService) { + this.urlService = urlService; + } } From 5d35b34a15a6ff445e0c0e0a3b0c7bfaed32a699 Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Sat, 18 Apr 2026 21:44:11 +0100 Subject: [PATCH 05/25] Integrated Spring Data JPA in DAO layer for database operations and added Lombok to reduce boilerplate code --- pom.xml | 18 +++++++++++++++++- .../tpximpact/urlshortener/dto/UrlRequest.java | 4 ++++ .../urlshortener/dto/UrlResponse.java | 4 ++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/tpximpact/urlshortener/dto/UrlRequest.java create mode 100644 src/main/java/com/tpximpact/urlshortener/dto/UrlResponse.java diff --git a/pom.xml b/pom.xml index 6c34601c..e205b4d5 100644 --- a/pom.xml +++ b/pom.xml @@ -21,12 +21,28 @@ org.springframework.boot spring-boot-starter - org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.projectlombok + lombok + true + + + org.springframework + spring-web + 6.1.13 + compile + diff --git a/src/main/java/com/tpximpact/urlshortener/dto/UrlRequest.java b/src/main/java/com/tpximpact/urlshortener/dto/UrlRequest.java new file mode 100644 index 00000000..a9e6b830 --- /dev/null +++ b/src/main/java/com/tpximpact/urlshortener/dto/UrlRequest.java @@ -0,0 +1,4 @@ +package com.tpximpact.urlshortener.dto; + +public class UrlRequest { +} diff --git a/src/main/java/com/tpximpact/urlshortener/dto/UrlResponse.java b/src/main/java/com/tpximpact/urlshortener/dto/UrlResponse.java new file mode 100644 index 00000000..6214b76e --- /dev/null +++ b/src/main/java/com/tpximpact/urlshortener/dto/UrlResponse.java @@ -0,0 +1,4 @@ +package com.tpximpact.urlshortener.dto; + +public class UrlResponse { +} From e33e59c9ab1a22d2fa6488496d0d4b5b5e95c9ae Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Sat, 18 Apr 2026 21:56:04 +0100 Subject: [PATCH 06/25] Added PostgreSQL dependency to enable persistent data storage --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index e205b4d5..d4a36c80 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,12 @@ org.springframework.boot spring-boot-starter-data-jpa + + + org.postgresql + postgresql + runtime + org.projectlombok From 17489868f215ba81ce3daf17daa6e15e272ead59 Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Tue, 21 Apr 2026 22:07:05 +0100 Subject: [PATCH 07/25] Added H2 DB and removed unused dependency --- pom.xml | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index d4a36c80..8f7d60aa 100644 --- a/pom.xml +++ b/pom.xml @@ -31,24 +31,28 @@ org.springframework.boot spring-boot-starter-data-jpa - + - org.postgresql - postgresql + com.h2database + h2 runtime + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-actuator + org.projectlombok lombok true - - org.springframework - spring-web - 6.1.13 - compile - From 481f8c798ca7f374f513283c4ab9765419f23ca7 Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Tue, 21 Apr 2026 22:12:00 +0100 Subject: [PATCH 08/25] Added Spring boot application yaml file for the dev and required configuration for the url shorten application --- src/main/resources/application-dev.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/resources/application-dev.yaml diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml new file mode 100644 index 00000000..8223f691 --- /dev/null +++ b/src/main/resources/application-dev.yaml @@ -0,0 +1,16 @@ +spring: + datasource: + url: jdbc:h2:file:./data/mydb + driver-class-name: org.h2.Driver + username: sa + password: + + h2: + console: + enabled: true + path: /h2-console + + jpa: + hibernate: + ddl-auto: update + show-sql: true \ No newline at end of file From 52e46587b72aa508510169d3928d10b660487f80 Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Tue, 21 Apr 2026 22:34:39 +0100 Subject: [PATCH 09/25] Added JPA Entity class to create a table with columns, Request and Response dto classes to carry data --- .../urlshortener/dto/UrlRequest.java | 8 +++-- .../urlshortener/dto/UrlResponse.java | 5 +-- .../tpximpact/urlshortener/entity/Url.java | 35 +++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/tpximpact/urlshortener/entity/Url.java diff --git a/src/main/java/com/tpximpact/urlshortener/dto/UrlRequest.java b/src/main/java/com/tpximpact/urlshortener/dto/UrlRequest.java index a9e6b830..c3068b89 100644 --- a/src/main/java/com/tpximpact/urlshortener/dto/UrlRequest.java +++ b/src/main/java/com/tpximpact/urlshortener/dto/UrlRequest.java @@ -1,4 +1,8 @@ package com.tpximpact.urlshortener.dto; -public class UrlRequest { -} +import jakarta.validation.constraints.NotBlank; + +public record UrlRequest( + @NotBlank String fullUrl, + String customAlias +) {} diff --git a/src/main/java/com/tpximpact/urlshortener/dto/UrlResponse.java b/src/main/java/com/tpximpact/urlshortener/dto/UrlResponse.java index 6214b76e..9c9d7d9e 100644 --- a/src/main/java/com/tpximpact/urlshortener/dto/UrlResponse.java +++ b/src/main/java/com/tpximpact/urlshortener/dto/UrlResponse.java @@ -1,4 +1,5 @@ package com.tpximpact.urlshortener.dto; -public class UrlResponse { -} +public record UrlResponse( + String shortUrl +) {} diff --git a/src/main/java/com/tpximpact/urlshortener/entity/Url.java b/src/main/java/com/tpximpact/urlshortener/entity/Url.java new file mode 100644 index 00000000..8dff4267 --- /dev/null +++ b/src/main/java/com/tpximpact/urlshortener/entity/Url.java @@ -0,0 +1,35 @@ +package com.tpximpact.urlshortener.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Table(name = "urls", uniqueConstraints = { + @UniqueConstraint(columnNames = "alias") +}) +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Url { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String fullUrl; + + @Column(nullable = false, unique = true) + private String alias; +} From 2637b68bca11bea0959bf725bf6eb16beff381d2 Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Tue, 21 Apr 2026 22:46:15 +0100 Subject: [PATCH 10/25] Implemented POST endpoint to create shorten url as per api spec --- .../controller/UrlController.java | 15 +++++-- .../repository/UrlRepository.java | 9 +++++ .../urlshortener/service/UrlService.java | 5 +++ .../urlshortener/service/UrlServiceImpl.java | 39 ++++++++++++++++++- 4 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/tpximpact/urlshortener/repository/UrlRepository.java diff --git a/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java b/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java index 7f8f7d1b..515755d9 100644 --- a/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java +++ b/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java @@ -1,15 +1,24 @@ package com.tpximpact.urlshortener.controller; +import com.tpximpact.urlshortener.dto.UrlRequest; +import com.tpximpact.urlshortener.dto.UrlResponse; import com.tpximpact.urlshortener.service.UrlService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestMapping; @RestController -@RequestMapping("/api/url") +@RequiredArgsConstructor class UrlController { private final UrlService urlService; - public UrlController(UrlService urlService) { - this.urlService = urlService; + @PostMapping("/shorten") + public ResponseEntity shorten(@RequestBody @Valid UrlRequest request) { + UrlResponse response = urlService.shorten(request); + return ResponseEntity.status(201).body(response); } } diff --git a/src/main/java/com/tpximpact/urlshortener/repository/UrlRepository.java b/src/main/java/com/tpximpact/urlshortener/repository/UrlRepository.java new file mode 100644 index 00000000..bfcd2507 --- /dev/null +++ b/src/main/java/com/tpximpact/urlshortener/repository/UrlRepository.java @@ -0,0 +1,9 @@ +package com.tpximpact.urlshortener.repository; + +import com.tpximpact.urlshortener.entity.Url; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UrlRepository extends JpaRepository { + + boolean existsByAlias(String alias); +} diff --git a/src/main/java/com/tpximpact/urlshortener/service/UrlService.java b/src/main/java/com/tpximpact/urlshortener/service/UrlService.java index 0a6e0e8e..1b33f271 100644 --- a/src/main/java/com/tpximpact/urlshortener/service/UrlService.java +++ b/src/main/java/com/tpximpact/urlshortener/service/UrlService.java @@ -1,4 +1,9 @@ package com.tpximpact.urlshortener.service; +import com.tpximpact.urlshortener.dto.UrlRequest; +import com.tpximpact.urlshortener.dto.UrlResponse; + public interface UrlService { + + UrlResponse shorten(UrlRequest request); } diff --git a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java index a5dfe5b3..91e2f8b6 100644 --- a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java +++ b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java @@ -1,7 +1,44 @@ package com.tpximpact.urlshortener.service; +import com.tpximpact.urlshortener.dto.UrlRequest; +import com.tpximpact.urlshortener.dto.UrlResponse; +import com.tpximpact.urlshortener.entity.Url; +import com.tpximpact.urlshortener.exception.BadRequestException; +import com.tpximpact.urlshortener.repository.UrlRepository; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.UUID; + @Service -public class UrlServiceImpl implements UrlService{ +@RequiredArgsConstructor +public class UrlServiceImpl implements UrlService { + + private static final String BASE_URL = "http://localhost:8080/"; + private final UrlRepository urlRepository; + + @Override + public UrlResponse shorten(UrlRequest request) { + + String alias = request.customAlias() != null + ? request.customAlias() : generateAlias(); + + if (urlRepository.existsByAlias(alias)) { + throw new BadRequestException("Url already exists"); + } + + Url url = Url + .builder() + .fullUrl(request.fullUrl()) + .alias(alias) + .build(); + + urlRepository.save(url); + + return new UrlResponse(BASE_URL + alias); + } + + private String generateAlias() { + return UUID.randomUUID().toString().substring(0, 6); + } } From b162940a4c8342484e5e17ca52efdc531b971729 Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Tue, 21 Apr 2026 22:50:47 +0100 Subject: [PATCH 11/25] Added custom exception classes and global exception handlers for the application --- .../exception/BadRequestException.java | 5 +++++ .../exception/GlobalExceptionHandler.java | 14 ++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/main/java/com/tpximpact/urlshortener/exception/BadRequestException.java create mode 100644 src/main/java/com/tpximpact/urlshortener/exception/GlobalExceptionHandler.java diff --git a/src/main/java/com/tpximpact/urlshortener/exception/BadRequestException.java b/src/main/java/com/tpximpact/urlshortener/exception/BadRequestException.java new file mode 100644 index 00000000..0490e90c --- /dev/null +++ b/src/main/java/com/tpximpact/urlshortener/exception/BadRequestException.java @@ -0,0 +1,5 @@ +package com.tpximpact.urlshortener.exception; + +public class BadRequestException extends RuntimeException { + public BadRequestException(String message) { super(message); } +} diff --git a/src/main/java/com/tpximpact/urlshortener/exception/GlobalExceptionHandler.java b/src/main/java/com/tpximpact/urlshortener/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..76115d35 --- /dev/null +++ b/src/main/java/com/tpximpact/urlshortener/exception/GlobalExceptionHandler.java @@ -0,0 +1,14 @@ +package com.tpximpact.urlshortener.exception; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(BadRequestException.class) + public ResponseEntity handleBadRequest(BadRequestException ex) { + return ResponseEntity.badRequest().body(ex.getMessage()); + } +} From 3d4f6ab924d2f442a986525d2fc5a078e6841b8f Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Wed, 22 Apr 2026 06:36:50 +0100 Subject: [PATCH 12/25] Implemented GET endpoint to redirect to full url by alias name --- .../urlshortener/controller/UrlController.java | 17 +++++++++++++++++ .../exception/GlobalExceptionHandler.java | 5 +++++ .../exception/NotFoundException.java | 7 +++++++ .../urlshortener/repository/UrlRepository.java | 6 ++++++ .../urlshortener/service/UrlService.java | 2 ++ .../urlshortener/service/UrlServiceImpl.java | 12 ++++++++++++ 6 files changed, 49 insertions(+) create mode 100644 src/main/java/com/tpximpact/urlshortener/exception/NotFoundException.java diff --git a/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java b/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java index 515755d9..ef7b8a6c 100644 --- a/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java +++ b/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java @@ -5,14 +5,20 @@ import com.tpximpact.urlshortener.service.UrlService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestMapping; +import java.net.URI; + @RestController @RequiredArgsConstructor +@Slf4j class UrlController { private final UrlService urlService; @@ -21,4 +27,15 @@ public ResponseEntity shorten(@RequestBody @Valid UrlRequest reques UrlResponse response = urlService.shorten(request); return ResponseEntity.status(201).body(response); } + + @GetMapping("/{alias}") + public ResponseEntity redirect(@PathVariable String alias) { + log.info("Contacting urlService to full url with: {}", alias); + String fullUrl = urlService.getFullUrl(alias); + log.info("Received Full url to redirect: {}", fullUrl); + return ResponseEntity.status(302) + .location(URI.create(fullUrl)) + .build(); + } + } diff --git a/src/main/java/com/tpximpact/urlshortener/exception/GlobalExceptionHandler.java b/src/main/java/com/tpximpact/urlshortener/exception/GlobalExceptionHandler.java index 76115d35..01c41b00 100644 --- a/src/main/java/com/tpximpact/urlshortener/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/tpximpact/urlshortener/exception/GlobalExceptionHandler.java @@ -11,4 +11,9 @@ public class GlobalExceptionHandler { public ResponseEntity handleBadRequest(BadRequestException ex) { return ResponseEntity.badRequest().body(ex.getMessage()); } + + @ExceptionHandler(NotFoundException.class) + public ResponseEntity handleNotFound(NotFoundException ex) { + return ResponseEntity.status(404).body(ex.getMessage()); + } } diff --git a/src/main/java/com/tpximpact/urlshortener/exception/NotFoundException.java b/src/main/java/com/tpximpact/urlshortener/exception/NotFoundException.java new file mode 100644 index 00000000..5042cfb3 --- /dev/null +++ b/src/main/java/com/tpximpact/urlshortener/exception/NotFoundException.java @@ -0,0 +1,7 @@ +package com.tpximpact.urlshortener.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/tpximpact/urlshortener/repository/UrlRepository.java b/src/main/java/com/tpximpact/urlshortener/repository/UrlRepository.java index bfcd2507..877be95e 100644 --- a/src/main/java/com/tpximpact/urlshortener/repository/UrlRepository.java +++ b/src/main/java/com/tpximpact/urlshortener/repository/UrlRepository.java @@ -2,8 +2,14 @@ import com.tpximpact.urlshortener.entity.Url; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.Optional; + +@Repository public interface UrlRepository extends JpaRepository { boolean existsByAlias(String alias); + + Optional findByAlias(String alias); } diff --git a/src/main/java/com/tpximpact/urlshortener/service/UrlService.java b/src/main/java/com/tpximpact/urlshortener/service/UrlService.java index 1b33f271..eff8ea29 100644 --- a/src/main/java/com/tpximpact/urlshortener/service/UrlService.java +++ b/src/main/java/com/tpximpact/urlshortener/service/UrlService.java @@ -6,4 +6,6 @@ public interface UrlService { UrlResponse shorten(UrlRequest request); + + String getFullUrl(String alias); } diff --git a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java index 91e2f8b6..45891932 100644 --- a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java +++ b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java @@ -4,12 +4,16 @@ import com.tpximpact.urlshortener.dto.UrlResponse; import com.tpximpact.urlshortener.entity.Url; import com.tpximpact.urlshortener.exception.BadRequestException; +import com.tpximpact.urlshortener.exception.NotFoundException; import com.tpximpact.urlshortener.repository.UrlRepository; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.crossstore.ChangeSetPersister; import org.springframework.stereotype.Service; import java.util.UUID; +@Slf4j @Service @RequiredArgsConstructor public class UrlServiceImpl implements UrlService { @@ -38,6 +42,14 @@ public UrlResponse shorten(UrlRequest request) { return new UrlResponse(BASE_URL + alias); } + @Override + public String getFullUrl(String alias) { + log.info("Getting full url for alias ' {} ' from the DB", alias); + return urlRepository.findByAlias(alias) + .map(Url::getFullUrl) + .orElseThrow(() -> new NotFoundException("Alias not found")); + } + private String generateAlias() { return UUID.randomUUID().toString().substring(0, 6); } From 7581b9b98aee789393f5e87f368a67d7edd22a5f Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Wed, 22 Apr 2026 06:41:43 +0100 Subject: [PATCH 13/25] To see hibenate generated queries on the console for the testing --- src/main/resources/application-dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 8223f691..819ffce0 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -11,6 +11,6 @@ spring: path: /h2-console jpa: + show_sql: true hibernate: ddl-auto: update - show-sql: true \ No newline at end of file From 3c2896fa22ca777b081b41b8f7b394d30ea99ad9 Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Wed, 22 Apr 2026 07:03:29 +0100 Subject: [PATCH 14/25] Added helper methods to generate alias and extract bsse url from the full url --- .../tpximpact/urlshortener/util/UrlUtils.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java diff --git a/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java b/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java new file mode 100644 index 00000000..9857bccd --- /dev/null +++ b/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java @@ -0,0 +1,25 @@ +package com.tpximpact.urlshortener.util; + +import java.net.URI; +import java.util.UUID; + +public class UrlUtils { + + public static String getBaseUrl(String fullUrl) { + URI uri = URI.create(fullUrl); + + String scheme = uri.getScheme(); // http / https + String host = uri.getHost(); // example.com + int port = uri.getPort(); // -1 if default + + if (port == -1) { + return scheme + "://" + host; + } + + return scheme + "://" + host + ":" + port; + } + + public static String generateAlias() { + return UUID.randomUUID().toString().substring(0, 6); + } +} From 7e827f1bd4d22ffecab0d01fba0cb2f42b9a1bfb Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Wed, 22 Apr 2026 07:05:53 +0100 Subject: [PATCH 15/25] Updated service with util --- .../com/tpximpact/urlshortener/service/UrlServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java index 45891932..b87e0ffe 100644 --- a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java +++ b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java @@ -6,6 +6,7 @@ import com.tpximpact.urlshortener.exception.BadRequestException; import com.tpximpact.urlshortener.exception.NotFoundException; import com.tpximpact.urlshortener.repository.UrlRepository; +import com.tpximpact.urlshortener.util.UrlUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.crossstore.ChangeSetPersister; @@ -25,7 +26,7 @@ public class UrlServiceImpl implements UrlService { public UrlResponse shorten(UrlRequest request) { String alias = request.customAlias() != null - ? request.customAlias() : generateAlias(); + ? request.customAlias() : UrlUtils.generateAlias(); if (urlRepository.existsByAlias(alias)) { throw new BadRequestException("Url already exists"); From 76b93c4356ef87c1454cb568c8faf17662863675 Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Wed, 22 Apr 2026 11:23:23 +0100 Subject: [PATCH 16/25] Tweak util class to return proper base url with slash --- .../java/com/tpximpact/urlshortener/util/UrlUtils.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java b/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java index 9857bccd..4cb60673 100644 --- a/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java +++ b/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java @@ -1,22 +1,24 @@ package com.tpximpact.urlshortener.util; +import lombok.extern.slf4j.Slf4j; + import java.net.URI; import java.util.UUID; +@Slf4j public class UrlUtils { public static String getBaseUrl(String fullUrl) { URI uri = URI.create(fullUrl); - String scheme = uri.getScheme(); // http / https String host = uri.getHost(); // example.com int port = uri.getPort(); // -1 if default if (port == -1) { - return scheme + "://" + host; + return scheme + "://" + host + "/"; } - - return scheme + "://" + host + ":" + port; + log.info("baseUrl: {}", scheme + "://" + host + ":" + port + "/"); + return scheme + "://" + host + ":" + port + "/"; } public static String generateAlias() { From 45ee07f57679feaba8018f2f609abc54fc42d713 Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Wed, 22 Apr 2026 11:33:57 +0100 Subject: [PATCH 17/25] Implemented GET endpoint to get list of urls --- .../com/tpximpact/urlshortener/dto/UrlListResponse.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/com/tpximpact/urlshortener/dto/UrlListResponse.java diff --git a/src/main/java/com/tpximpact/urlshortener/dto/UrlListResponse.java b/src/main/java/com/tpximpact/urlshortener/dto/UrlListResponse.java new file mode 100644 index 00000000..f61f25a6 --- /dev/null +++ b/src/main/java/com/tpximpact/urlshortener/dto/UrlListResponse.java @@ -0,0 +1,7 @@ +package com.tpximpact.urlshortener.dto; + +public record UrlListResponse( + String alias, + String fullUrl, + String shortUrl +) {} \ No newline at end of file From 4c84cb8595664314e30c013e1c71f4f1176dbfc4 Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Wed, 22 Apr 2026 11:36:04 +0100 Subject: [PATCH 18/25] Implemented GET endpoint to return list of urls --- .../urlshortener/controller/UrlController.java | 7 +++++++ .../urlshortener/service/UrlService.java | 5 +++++ .../urlshortener/service/UrlServiceImpl.java | 18 ++++++++++++------ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java b/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java index ef7b8a6c..ee4aff4f 100644 --- a/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java +++ b/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java @@ -1,5 +1,6 @@ package com.tpximpact.urlshortener.controller; +import com.tpximpact.urlshortener.dto.UrlListResponse; import com.tpximpact.urlshortener.dto.UrlRequest; import com.tpximpact.urlshortener.dto.UrlResponse; import com.tpximpact.urlshortener.service.UrlService; @@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import java.net.URI; +import java.util.List; @RestController @RequiredArgsConstructor @@ -38,4 +40,9 @@ public ResponseEntity redirect(@PathVariable String alias) { .build(); } + @GetMapping("/urls") + public ResponseEntity> getAll() { + return ResponseEntity.ok(urlService.getAll()); + } + } diff --git a/src/main/java/com/tpximpact/urlshortener/service/UrlService.java b/src/main/java/com/tpximpact/urlshortener/service/UrlService.java index eff8ea29..2a6106a4 100644 --- a/src/main/java/com/tpximpact/urlshortener/service/UrlService.java +++ b/src/main/java/com/tpximpact/urlshortener/service/UrlService.java @@ -1,11 +1,16 @@ package com.tpximpact.urlshortener.service; +import com.tpximpact.urlshortener.dto.UrlListResponse; import com.tpximpact.urlshortener.dto.UrlRequest; import com.tpximpact.urlshortener.dto.UrlResponse; +import java.util.List; + public interface UrlService { UrlResponse shorten(UrlRequest request); String getFullUrl(String alias); + + List getAll(); } diff --git a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java index b87e0ffe..24958f80 100644 --- a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java +++ b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java @@ -1,5 +1,6 @@ package com.tpximpact.urlshortener.service; +import com.tpximpact.urlshortener.dto.UrlListResponse; import com.tpximpact.urlshortener.dto.UrlRequest; import com.tpximpact.urlshortener.dto.UrlResponse; import com.tpximpact.urlshortener.entity.Url; @@ -9,17 +10,15 @@ import com.tpximpact.urlshortener.util.UrlUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.crossstore.ChangeSetPersister; import org.springframework.stereotype.Service; -import java.util.UUID; +import java.util.List; @Slf4j @Service @RequiredArgsConstructor public class UrlServiceImpl implements UrlService { - private static final String BASE_URL = "http://localhost:8080/"; private final UrlRepository urlRepository; @Override @@ -40,7 +39,7 @@ public UrlResponse shorten(UrlRequest request) { urlRepository.save(url); - return new UrlResponse(BASE_URL + alias); + return new UrlResponse(UrlUtils.getBaseUrl(url.getFullUrl()) + alias); } @Override @@ -51,7 +50,14 @@ public String getFullUrl(String alias) { .orElseThrow(() -> new NotFoundException("Alias not found")); } - private String generateAlias() { - return UUID.randomUUID().toString().substring(0, 6); + @Override + public List getAll() { + return urlRepository.findAll().stream() + .map(url -> new UrlListResponse( + url.getAlias(), + url.getFullUrl(), + UrlUtils.getBaseUrl(url.getFullUrl()) + url.getAlias() + )) + .toList(); } } From 595096174ab4b90cbfce2d8209d76f1937b4ec27 Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Wed, 22 Apr 2026 11:40:04 +0100 Subject: [PATCH 19/25] Removed unused imports --- .../com/tpximpact/urlshortener/controller/UrlController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java b/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java index ee4aff4f..c088062f 100644 --- a/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java +++ b/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java @@ -13,8 +13,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.bind.annotation.RequestMapping; - import java.net.URI; import java.util.List; From 99cac5f15511209b07720f775175dc1e4f2dd26e Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Wed, 22 Apr 2026 12:29:01 +0100 Subject: [PATCH 20/25] Removed baseUrl method from the util class. Moved back to static base url to use for the shorten url endpoint as per spec --- .../urlshortener/service/UrlServiceImpl.java | 6 +++--- .../com/tpximpact/urlshortener/util/UrlUtils.java | 13 ------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java index 24958f80..c96fc36b 100644 --- a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java +++ b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java @@ -18,7 +18,7 @@ @Service @RequiredArgsConstructor public class UrlServiceImpl implements UrlService { - + private static final String BASE_URL = "http://localhost:8080/"; private final UrlRepository urlRepository; @Override @@ -39,7 +39,7 @@ public UrlResponse shorten(UrlRequest request) { urlRepository.save(url); - return new UrlResponse(UrlUtils.getBaseUrl(url.getFullUrl()) + alias); + return new UrlResponse(BASE_URL + alias); } @Override @@ -56,7 +56,7 @@ public List getAll() { .map(url -> new UrlListResponse( url.getAlias(), url.getFullUrl(), - UrlUtils.getBaseUrl(url.getFullUrl()) + url.getAlias() + BASE_URL + url.getAlias() )) .toList(); } diff --git a/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java b/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java index 4cb60673..ebeb913e 100644 --- a/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java +++ b/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java @@ -8,19 +8,6 @@ @Slf4j public class UrlUtils { - public static String getBaseUrl(String fullUrl) { - URI uri = URI.create(fullUrl); - String scheme = uri.getScheme(); // http / https - String host = uri.getHost(); // example.com - int port = uri.getPort(); // -1 if default - - if (port == -1) { - return scheme + "://" + host + "/"; - } - log.info("baseUrl: {}", scheme + "://" + host + ":" + port + "/"); - return scheme + "://" + host + ":" + port + "/"; - } - public static String generateAlias() { return UUID.randomUUID().toString().substring(0, 6); } From b4cc71a3d3dcecc333fe2e433e4ba7514f7e71bf Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Wed, 22 Apr 2026 12:29:23 +0100 Subject: [PATCH 21/25] Changed error message as per spec --- .../java/com/tpximpact/urlshortener/service/UrlServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java index c96fc36b..a507d0f9 100644 --- a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java +++ b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java @@ -28,7 +28,7 @@ public UrlResponse shorten(UrlRequest request) { ? request.customAlias() : UrlUtils.generateAlias(); if (urlRepository.existsByAlias(alias)) { - throw new BadRequestException("Url already exists"); + throw new BadRequestException("Invalid input or alias already taken"); } Url url = Url From 5826a562d82781357b17be3628b0b50aeb053d4f Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Wed, 22 Apr 2026 12:57:19 +0100 Subject: [PATCH 22/25] Implemented delete endpoint --- .../tpximpact/urlshortener/controller/UrlController.java | 7 +++++++ .../com/tpximpact/urlshortener/service/UrlService.java | 2 ++ .../tpximpact/urlshortener/service/UrlServiceImpl.java | 8 ++++++++ 3 files changed, 17 insertions(+) diff --git a/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java b/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java index c088062f..a46cb266 100644 --- a/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java +++ b/src/main/java/com/tpximpact/urlshortener/controller/UrlController.java @@ -8,6 +8,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -43,4 +44,10 @@ public ResponseEntity> getAll() { return ResponseEntity.ok(urlService.getAll()); } + @DeleteMapping("/{alias}") + public ResponseEntity delete(@PathVariable String alias) { + urlService.delete(alias); + return ResponseEntity.noContent().build(); + } + } diff --git a/src/main/java/com/tpximpact/urlshortener/service/UrlService.java b/src/main/java/com/tpximpact/urlshortener/service/UrlService.java index 2a6106a4..7b948ed4 100644 --- a/src/main/java/com/tpximpact/urlshortener/service/UrlService.java +++ b/src/main/java/com/tpximpact/urlshortener/service/UrlService.java @@ -13,4 +13,6 @@ public interface UrlService { String getFullUrl(String alias); List getAll(); + + void delete(String alias); } diff --git a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java index a507d0f9..c9efd817 100644 --- a/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java +++ b/src/main/java/com/tpximpact/urlshortener/service/UrlServiceImpl.java @@ -60,4 +60,12 @@ public List getAll() { )) .toList(); } + + public void delete(String alias) { + + Url url = urlRepository.findByAlias(alias) + .orElseThrow(() -> new NotFoundException("Alias not found")); + log.info("Deleting shorten url for alias '{}' from the DB", url.getAlias()); + urlRepository.delete(url); + } } From 47727978a6323864cdd967d71abb75a9befe8a2b Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Wed, 22 Apr 2026 12:57:50 +0100 Subject: [PATCH 23/25] Removed unused import --- src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java b/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java index ebeb913e..46e50cfe 100644 --- a/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java +++ b/src/main/java/com/tpximpact/urlshortener/util/UrlUtils.java @@ -2,7 +2,6 @@ import lombok.extern.slf4j.Slf4j; -import java.net.URI; import java.util.UUID; @Slf4j From 1307bf9e59408831f66e48be0f109ae9dbec5aaf Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Wed, 22 Apr 2026 17:55:50 +0100 Subject: [PATCH 24/25] A little cleanup --- pom.xml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 8f7d60aa..8b415da1 100644 --- a/pom.xml +++ b/pom.xml @@ -2,8 +2,8 @@ 4.0.0 com.tpximpact - url_shortener_api - 1.0 + url_shortener + 1.0.0 org.springframework.boot @@ -16,37 +16,37 @@ - - - org.springframework.boot - spring-boot-starter - org.springframework.boot spring-boot-starter-web + org.springframework.boot spring-boot-starter-data-jpa + com.h2database h2 runtime + org.springframework.boot spring-boot-starter-validation + org.springframework.boot spring-boot-starter-actuator + org.projectlombok @@ -54,5 +54,4 @@ true - From addc39138dba3ac0b1ad29a0e2f71972b372b5e1 Mon Sep 17 00:00:00 2001 From: Upendra Kumbham Date: Wed, 22 Apr 2026 17:56:14 +0100 Subject: [PATCH 25/25] Format code --- src/main/java/com/tpximpact/urlshortener/dto/UrlRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/tpximpact/urlshortener/dto/UrlRequest.java b/src/main/java/com/tpximpact/urlshortener/dto/UrlRequest.java index c3068b89..79bb75b4 100644 --- a/src/main/java/com/tpximpact/urlshortener/dto/UrlRequest.java +++ b/src/main/java/com/tpximpact/urlshortener/dto/UrlRequest.java @@ -2,7 +2,7 @@ import jakarta.validation.constraints.NotBlank; -public record UrlRequest( +public record UrlRequest ( @NotBlank String fullUrl, String customAlias ) {}