Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 76 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,77 @@
# Java URL Shortener
# Shorten

See full README content in project documentation.
A lightweight URL shortener built using plain Java with `com.sun.net.httpserver.HttpServer`, H2 Database, and vanilla HTML/CSS/JS frontend.

## ✅ Features

* 🔗 **Anonymous URL shortening**: Convert long URLs into short ones instantly.
* 🧑‍💻 **User login**: Create an account and log in.
* 🌐 **Custom short URLs**: Logged-in users can create their own alias URLs.
* ↪️ **Redirection**: Short URLs automatically redirect to the long ones.

## 🛠️ Tech Stack

| Layer | Technology |
| -------- | ----------------------------------------- |
| Frontend | HTML, CSS, JavaScript (no framework) |
| Backend | Java, `com.sun.net.httpserver.HttpServer` |
| Database | H2 using JDBC |
| Logging | SLF4J |
| Testing | JUnit 5, Mockito |
| CI/CD | GitHub Actions |

```

## 🚀 Getting Started

### Prerequisites

* Java 17+
* Maven

### Run Application

```bash
mvn clean package
java -jar target/shorten.jar
```

Visit `http://localhost:8080` in your browser.

### Database

* H2 in-memory mode used.
* JDBC with prepared statements.

### GitHub Actions

* CI configured in `.github/workflows/ci.yml` to run tests on every PR and merge to `main`.

## 🔌 API Endpoints

| Endpoint | Method | Description |
| --------------- | ------ | ------------------------------ |
| `/shorten` | POST | Shortens a given long URL |
| `/s/{shortUrl}` | GET | Redirects to original long URL |
| `/register` | POST | Register a new user |
| `/login` | POST | Log in existing user |

## 🧪 Testing

* All core components are tested with JUnit 5
* Mocked database and auth dependencies with Mockito

## 📌 Project Management

* [ ] GitHub **Issues** created for each task
* [ ] Separate **branches** for every feature
* [ ] Pull Requests with self-review
* [ ] CI runs on every PR and merge

## 📖 License

This project is licensed under the MIT License.

---

Built with ❤️ using pure Java.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@
<version>5.12.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.38</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/urlshortener/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import java.net.InetSocketAddress;
import java.sql.*;

import lombok.extern.slf4j.Slf4j;
import urlshortener.config.AppConfig;
import urlshortener.service.*;

@Slf4j
public class Server {

private static Connection conn;
Expand All @@ -29,7 +31,7 @@ public static void main(String[] args) throws Exception {
server.setExecutor(null);
server.start();

System.out.println("Server running on http://localhost:8080/");
log.info("Server running on http://localhost:8080/");
}

public static Connection getConn() {
Expand Down
8 changes: 5 additions & 3 deletions src/main/java/urlshortener/service/LoginHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import lombok.extern.slf4j.Slf4j;
import urlshortener.utils.HelperMethods;

import java.io.File;
Expand All @@ -15,6 +16,7 @@
import java.sql.ResultSet;
import java.sql.SQLException;

@Slf4j
public class LoginHandler implements HttpHandler {

private static Connection conn;
Expand Down Expand Up @@ -52,16 +54,16 @@ public void handle(HttpExchange exchange) throws IOException {
ResultSet rs = ps.executeQuery();

if (rs.next()) {
System.out.println("Login successful for user: " + username);
log.info("Login successful for user: {}", username);
String encodedUsername = URLEncoder.encode(username, StandardCharsets.UTF_8.toString());
exchange.getResponseHeaders().set("Location", "/index?username=" + encodedUsername);
exchange.sendResponseHeaders(302, -1);
} else {
System.out.println("Invalid credentials for user: " + username);
log.info("Invalid credentials for user: {}", username);
HelperMethods.respond(exchange, 401, "Invalid credentials");
}
} catch (SQLException e) {
System.err.println("Error querying user: " + e.getMessage());
log.error("Error querying user: {}", e.getMessage());
HelperMethods.respond(exchange, 500, "Database error");
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/main/java/urlshortener/service/RegisterHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import lombok.extern.slf4j.Slf4j;
import urlshortener.utils.HelperMethods;

import java.io.File;
Expand All @@ -12,6 +13,7 @@
import java.sql.PreparedStatement;
import java.sql.SQLException;

@Slf4j
public class RegisterHandler implements HttpHandler {

private static Connection conn;
Expand Down Expand Up @@ -48,14 +50,15 @@ public void handle(HttpExchange exchange) throws IOException {
ps.setString(2, password);
int rowsAffected = ps.executeUpdate();
if (rowsAffected > 0) {
System.out.println("User registered successfully: " + username);
log.info("User registered successfully: {}", username);
exchange.getResponseHeaders().set("Location", "/index");
exchange.sendResponseHeaders(302, -1);
} else {
log.error("Failed to register user {}", username);
HelperMethods.respond(exchange, 500, "Failed to register user");
}
} catch (SQLException e) {
System.err.println("Error inserting user: " + e.getMessage());
log.error("Error inserting user: {}", e.getMessage());
HelperMethods.respond(exchange, 400, "User already exists");
}
}
Expand Down