commit 4107d7a2486f26b1fe4d82a89cc1530c79da0aa8 Author: Shay Patel Date: Mon Dec 22 18:43:19 2025 +0000 Initialise Spring API with ShortLink endpoints diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..aadf122 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,3 @@ +target/ + +.idea/ diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 0000000..792956d --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + services.shay + halflink + 1.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 4.0.1 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-devtools + true + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.postgresql + postgresql + runtime + + + org.springframework.boot + spring-boot-starter-validation + 3.3.5 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/backend/src/main/java/services/shay/Halflink.java b/backend/src/main/java/services/shay/Halflink.java new file mode 100644 index 0000000..ad4358a --- /dev/null +++ b/backend/src/main/java/services/shay/Halflink.java @@ -0,0 +1,21 @@ +package services.shay; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@SpringBootApplication +public class Halflink { + + @GetMapping("/") + String home() { + return "Halflink API says hello!"; + } + + public static void main(String[] args) { + SpringApplication.run(Halflink.class, args); + } + +} diff --git a/backend/src/main/java/services/shay/shortlink/ShortCodeGenerator.java b/backend/src/main/java/services/shay/shortlink/ShortCodeGenerator.java new file mode 100644 index 0000000..5c760a7 --- /dev/null +++ b/backend/src/main/java/services/shay/shortlink/ShortCodeGenerator.java @@ -0,0 +1,24 @@ +package services.shay.shortlink; + +import java.security.SecureRandom; + +public class ShortCodeGenerator { + + private static final String BASE_58_CHARS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + + private static final SecureRandom RANDOM = new SecureRandom(); + + public static String generate(int length) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append(BASE_58_CHARS.charAt(RANDOM.nextInt(BASE_58_CHARS.length()))); + } + return sb.toString(); + } + + public static String generate() { + return "abc"; + // return generate(7); + } + +} diff --git a/backend/src/main/java/services/shay/shortlink/ShortLink.java b/backend/src/main/java/services/shay/shortlink/ShortLink.java new file mode 100644 index 0000000..da33a15 --- /dev/null +++ b/backend/src/main/java/services/shay/shortlink/ShortLink.java @@ -0,0 +1,58 @@ +package services.shay.shortlink; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Null; + +import java.util.Date; +import java.util.UUID; + +@Entity +@Table(name = "shortlink", uniqueConstraints = { @UniqueConstraint(columnNames = { "code" }) }) +public class ShortLink { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @NotNull + private String url; + + @NotNull + private String title; + + private Date createdAt; + + private Integer clicks; + + private String code; + + protected ShortLink() {} + + public ShortLink(String url, String title) { + this.url = url; + this.title = title; + } + + @PrePersist + protected void onCreate() { + createdAt = new Date(); + clicks = 0; + code = ShortCodeGenerator.generate(); + } + + public UUID getId() { return id; } + public String getUrl() { return url; } + public String getTitle() { return title; } + public Date getCreatedAt() { return createdAt; } + public Integer getClicks() { return clicks; } + public String getCode() { return code; } + + public void setId(UUID id) { this.id = id; } + public void setUrl(String url) { this.url = url; } + public void setTitle(String title) { this.title = title; } + public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; } + public void setClicks(Integer clicks) { this.clicks = clicks; } + public void setCode(String code) { this.code = code; } + +} diff --git a/backend/src/main/java/services/shay/shortlink/ShortLinkController.java b/backend/src/main/java/services/shay/shortlink/ShortLinkController.java new file mode 100644 index 0000000..61ec6f4 --- /dev/null +++ b/backend/src/main/java/services/shay/shortlink/ShortLinkController.java @@ -0,0 +1,47 @@ +package services.shay.shortlink; + +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.net.URI; +import java.util.UUID; + +@RestController +@RequestMapping("/sl") +public class ShortLinkController { + + private final ShortLinkRepository shortLinkRepository; + + public ShortLinkController(ShortLinkRepository shortLinkRepository) { + this.shortLinkRepository = shortLinkRepository; + } + + @GetMapping("/{code}") + public ResponseEntity redirectToUrl(@PathVariable String code) { + ShortLink sl = shortLinkRepository.findByCode(code).orElse(null); + if (sl != null) { + sl.setClicks(sl.getClicks() + 1); + shortLinkRepository.save(sl); + return ResponseEntity + .status(HttpStatus.FOUND) + .location(URI.create(sl.getUrl())) + .build(); + } + return ResponseEntity.notFound().build(); + } + + @PostMapping + public ShortLink createShortLink(@Valid @RequestBody ShortLink shortLink) { + ShortLink savedShortLink = shortLinkRepository.save(shortLink); + System.out.println("savedShortLink = " + savedShortLink); + return savedShortLink; + } + + @DeleteMapping("/{id}") + public void deleteShortLink(@PathVariable UUID id) { + shortLinkRepository.deleteById(id); + } + +} diff --git a/backend/src/main/java/services/shay/shortlink/ShortLinkRepository.java b/backend/src/main/java/services/shay/shortlink/ShortLinkRepository.java new file mode 100644 index 0000000..087e2c4 --- /dev/null +++ b/backend/src/main/java/services/shay/shortlink/ShortLinkRepository.java @@ -0,0 +1,14 @@ +package services.shay.shortlink; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface ShortLinkRepository extends JpaRepository { + + Optional findByCode(String code); + +} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties new file mode 100644 index 0000000..5c95e1c --- /dev/null +++ b/backend/src/main/resources/application.properties @@ -0,0 +1,8 @@ +# Database connection +spring.datasource.url=jdbc:postgresql://localhost:5432/halflink +spring.datasource.username=springchatuser +spring.datasource.password=abcdef + +# JPA/Hibernate settings +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true diff --git a/backend/src/main/resources/sandbox.http b/backend/src/main/resources/sandbox.http new file mode 100644 index 0000000..14a482d --- /dev/null +++ b/backend/src/main/resources/sandbox.http @@ -0,0 +1,11 @@ +POST http://localhost:8080/sl +Content-Type: application/json + +{ + "url": "https://docs.spring.io/spring-security/reference/servlet/authentication/index.html", + "title": "Spring Security Servlet Authentication" +} + +### + +DELETE http://localhost:8080/sl/a0232bf3-245d-4515-8a8b-c10031d21766 \ No newline at end of file