├── .gitignore ├── README.md ├── pom.xml └── src └── main ├── java └── dev │ └── jlkeesh │ └── httpserver │ ├── Application.java │ ├── annotations │ └── Domain.java │ ├── config │ ├── DataSourceConfig.java │ └── SettingsConfig.java │ ├── exception │ ├── DataAccessException.java │ └── NotFoundException.java │ ├── todo │ ├── Priority.java │ ├── Todo.java │ ├── TodoController.java │ ├── TodoCreateController.java │ ├── TodoDAO.java │ ├── TodoRowMapper.java │ ├── TodoService.java │ └── dto │ │ ├── BaseResponse.java │ │ ├── TodoCreateDto.java │ │ └── TodoUpdateDto.java │ └── utils │ └── GsonUtil.java └── resources ├── application.properties └── templates ├── todo_add.html └── todo_list.html /.gitignore: -------------------------------------------------------------------------------- 1 | ### Java template 2 | # Compiled class file 3 | *.class 4 | 5 | # Log file 6 | *.log 7 | 8 | # BlueJ files 9 | *.ctxt 10 | 11 | # Mobile Tools for Java (J2ME) 12 | .mtj.tmp/ 13 | 14 | # Package Files # 15 | *.jar 16 | *.war 17 | *.nar 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 24 | hs_err_pid* 25 | replay_pid* 26 | 27 | ### Maven template 28 | target/ 29 | pom.xml.tag 30 | pom.xml.releaseBackup 31 | pom.xml.versionsBackup 32 | pom.xml.next 33 | release.properties 34 | dependency-reduced-pom.xml 35 | buildNumber.properties 36 | .mvn/timing.properties 37 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 38 | .mvn/wrapper/maven-wrapper.jar 39 | 40 | # Eclipse m2e generated files 41 | # Eclipse Core 42 | .project 43 | # JDT-specific (Eclipse Java Development Tools) 44 | .classpath 45 | 46 | ### JetBrains template 47 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 48 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 49 | 50 | # User-specific stuff 51 | .idea/**/workspace.xml 52 | .idea/**/tasks.xml 53 | .idea/**/usage.statistics.xml 54 | .idea/**/dictionaries 55 | .idea/**/shelf 56 | 57 | # AWS User-specific 58 | .idea/**/aws.xml 59 | 60 | # Generated files 61 | .idea/**/contentModel.xml 62 | 63 | # Sensitive or high-churn files 64 | .idea/**/dataSources/ 65 | .idea/**/dataSources.ids 66 | .idea/**/dataSources.local.xml 67 | .idea/**/sqlDataSources.xml 68 | .idea/**/dynamic.xml 69 | .idea/**/uiDesigner.xml 70 | .idea/**/dbnavigator.xml 71 | 72 | # Gradle 73 | .idea/**/gradle.xml 74 | .idea/**/libraries 75 | 76 | # Gradle and Maven with auto-import 77 | # When using Gradle or Maven with auto-import, you should exclude module files, 78 | # since they will be recreated, and may cause churn. Uncomment if using 79 | # auto-import. 80 | # .idea/artifacts 81 | # .idea/compiler.xml 82 | # .idea/jarRepositories.xml 83 | # .idea/modules.xml 84 | # .idea/*.iml 85 | # .idea/modules 86 | # *.iml 87 | # *.ipr 88 | 89 | # CMake 90 | cmake-build-*/ 91 | 92 | # Mongo Explorer plugin 93 | .idea/**/mongoSettings.xml 94 | 95 | # File-based project format 96 | *.iws 97 | 98 | # IntelliJ 99 | out/ 100 | 101 | # mpeltonen/sbt-idea plugin 102 | .idea_modules/ 103 | 104 | # JIRA plugin 105 | atlassian-ide-plugin.xml 106 | 107 | # Cursive Clojure plugin 108 | .idea/replstate.xml 109 | 110 | # SonarLint plugin 111 | .idea/sonarlint/ 112 | 113 | # Crashlytics plugin (for Android Studio and IntelliJ) 114 | com_crashlytics_export_strings.xml 115 | crashlytics.properties 116 | crashlytics-build.properties 117 | fabric.properties 118 | 119 | # Editor-based Rest Client 120 | .idea/httpRequests 121 | 122 | # Android studio 3.1+ serialized cache file 123 | .idea/caches/build_file_checksums.ser 124 | 125 | /.idea/.gitignore 126 | /.idea/compiler.xml 127 | /.idea/httpserver.iml 128 | /.idea/jarRepositories.xml 129 | /.idea/misc.xml 130 | /.idea/vcs.xml 131 | /.idea/git_toolbox_blame.xml 132 | /.idea/git_toolbox_prj.xml 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Todo HTTP Server in Java 2 | 3 | This project is a simple HTTP server for managing todo items, built using Java's standard HttpServer API. It allows users to create, read, update, and delete (CRUD) todo items through a RESTful API. 4 | 5 | ## Features 6 | 7 | - Create a new todo item 8 | - Read all todo items or a specific item 9 | - Update an existing todo item 10 | - Delete a todo item 11 | 12 | ## Getting Started 13 | 14 | ### Prerequisites 15 | 16 | - Java Development Kit (JDK) 8 or higher 17 | - postgresql 18 | - ```sql 19 | create database httpserver; 20 | 21 | create table if not exists todos( 22 | id bigserial primary key, 23 | title varchar not null, 24 | description varchar not null, 25 | user_id bigint not null, 26 | priority varchar not null default 'LOW', 27 | done boolean default 'f' not null, 28 | created_at timestamp not null default current_timestamp 29 | ); 30 | ``` 31 | --- 32 | # build jar 33 | 34 | ```shell 35 | mvn clean package 36 | cd target 37 | java -jar httpserverexec.jar 38 | ``` 39 | 40 | --- 41 | 42 | ## API 43 | 44 | - reads all todos 45 | ``` 46 | GET localhost:8080/todo 47 | ``` 48 | 49 | - reads todo which has id 1 50 | ``` 51 | GET localhost:8080/todo/1 52 | ``` 53 | 54 | - create todo 55 | ``` 56 | POST localhost:8080/todo 57 | 58 | Content-Type : application-json 59 | { 60 | "title":"Title for todo", 61 | "description":"Description for todo", 62 | "user_id":"User id which todo belongs to", 63 | "priority":"HIGH" 64 | } 65 | ``` 66 | 67 | - update todo 68 | ``` 69 | PUT localhost:8080/todo # update todo 70 | 71 | Content-Type : application-json 72 | { 73 | "id":1, 74 | "title":"Title for todo", 75 | "description":"Description for todo", 76 | "priority":"HIGH", 77 | "completed":true 78 | } 79 | ``` -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | dev.jlkeesh 5 | httpserverweb 6 | 0.0.3 7 | Simple Java HttpServer 8 | 9 | creating Http Server using simple java SE 10 | 11 | 12 | 21 13 | 21 14 | 21 15 | 16 | 17 | 18 | 19 | com.google.code.gson 20 | gson 21 | 2.11.0 22 | 23 | 24 | com.sun.mail 25 | javax.mail 26 | 1.6.2 27 | 28 | 29 | javax.activation 30 | activation 31 | 1.1.1 32 | 33 | 34 | 35 | org.postgresql 36 | postgresql 37 | 42.7.3 38 | 39 | 40 | 41 | org.projectlombok 42 | lombok 43 | 1.18.34 44 | provided 45 | 46 | 47 | 48 | 49 | org.junit.jupiter 50 | junit-jupiter-api 51 | 5.10.3 52 | test 53 | 54 | 55 | 56 | org.junit.jupiter 57 | junit-jupiter-engine 58 | 5.10.3 59 | test 60 | 61 | 62 | 63 | org.junit.jupiter 64 | junit-jupiter-params 65 | 5.10.3 66 | test 67 | 68 | 69 | 70 | org.assertj 71 | assertj-core 72 | 3.26.0 73 | test 74 | 75 | 76 | 77 | 78 | httpserver 79 | 80 | 81 | org.apache.maven.plugins 82 | maven-shade-plugin 83 | 3.6.0 84 | 85 | 86 | 87 | shade 88 | 89 | 90 | true 91 | 92 | 94 | dev.jlkeesh.httpserver.Application 95 | 96 | 97 | httpserver-exec 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/Application.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver; 2 | 3 | import com.sun.net.httpserver.HttpServer; 4 | import dev.jlkeesh.httpserver.config.SettingsConfig; 5 | import dev.jlkeesh.httpserver.todo.TodoController; 6 | import dev.jlkeesh.httpserver.todo.TodoCreateController; 7 | import dev.jlkeesh.httpserver.todo.TodoDAO; 8 | import dev.jlkeesh.httpserver.todo.TodoService; 9 | 10 | import java.io.IOException; 11 | import java.net.InetSocketAddress; 12 | import java.util.concurrent.Executors; 13 | import java.util.logging.Logger; 14 | 15 | public class Application { 16 | private static final Logger logger = Logger.getLogger(Application.class.getName()); 17 | public static void main(String[] args) throws IOException { 18 | int port = Integer.parseInt(SettingsConfig.get("server.port")); 19 | int concurrentRequest = Integer.parseInt(SettingsConfig.get("concurrent.request")); 20 | HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); 21 | TodoDAO todoDAO = new TodoDAO(); 22 | TodoService todoService = new TodoService(todoDAO); 23 | server.createContext("/todo", new TodoController(todoService)); 24 | server.createContext("/todo/add", new TodoCreateController(todoService)); 25 | server.setExecutor(Executors.newFixedThreadPool(concurrentRequest)); 26 | server.start(); 27 | logger.info("server started on port : " + port); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/annotations/Domain.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.annotations; 2 | 3 | public @interface Domain { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/config/DataSourceConfig.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.config; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DriverManager; 5 | import java.sql.SQLException; 6 | 7 | public class DataSourceConfig { 8 | public static Connection connection; 9 | private static final String url = SettingsConfig.get("datasource.url"); 10 | private static final String username = SettingsConfig.get("datasource.username"); 11 | private static final String password = SettingsConfig.get("datasource.password"); 12 | 13 | public static Connection getConnection() { 14 | try { 15 | if (connection == null || connection.isClosed()) { 16 | connection = DriverManager.getConnection(url, username, password); 17 | } 18 | } catch (SQLException e) { 19 | throw new IllegalStateException(e); 20 | } 21 | return connection; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/config/SettingsConfig.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.config; 2 | 3 | import java.util.ResourceBundle; 4 | 5 | public class SettingsConfig { 6 | private static final ResourceBundle settings = ResourceBundle.getBundle("application"); 7 | 8 | public static String get(String key) { 9 | return settings.getString(key); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/exception/DataAccessException.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.exception; 2 | 3 | public class DataAccessException extends RuntimeException { 4 | public DataAccessException(String message) { 5 | super(message); 6 | } 7 | 8 | public DataAccessException(Throwable cause) { 9 | super(cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/exception/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.exception; 2 | 3 | public class NotFoundException extends RuntimeException{ 4 | public NotFoundException(String message) { 5 | super(message); 6 | } 7 | public NotFoundException(String message, Throwable cause) { 8 | super(message, cause); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/todo/Priority.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.todo; 2 | 3 | public enum Priority { 4 | LOW, MEDIUM, HIGH 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/todo/Todo.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.todo; 2 | 3 | import dev.jlkeesh.httpserver.annotations.Domain; 4 | import lombok.*; 5 | import lombok.extern.java.Log; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.Date; 9 | 10 | @Getter 11 | @Setter 12 | @ToString 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @Domain 16 | public class Todo { 17 | private Long id; 18 | private String title; 19 | private String description; 20 | private Long userId; 21 | private boolean done; 22 | private Priority priority; 23 | private Date createdAt; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/todo/TodoController.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.todo; 2 | 3 | import com.google.gson.Gson; 4 | import com.sun.net.httpserver.HttpExchange; 5 | import com.sun.net.httpserver.HttpHandler; 6 | import dev.jlkeesh.httpserver.exception.NotFoundException; 7 | import dev.jlkeesh.httpserver.todo.dto.BaseResponse; 8 | import dev.jlkeesh.httpserver.todo.dto.TodoCreateDto; 9 | import dev.jlkeesh.httpserver.todo.dto.TodoUpdateDto; 10 | import dev.jlkeesh.httpserver.utils.GsonUtil; 11 | import lombok.extern.java.Log; 12 | import org.w3c.dom.stylesheets.LinkStyle; 13 | 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.io.OutputStream; 17 | import java.util.List; 18 | import java.util.logging.Level; 19 | 20 | @Log 21 | public class TodoController implements HttpHandler { 22 | private static final String CONTENT_TYPE_KEY = "Content-Type"; 23 | private static final String CONTENT_TYPE_VALUE = "text/html"; 24 | private final TodoService todoService; 25 | 26 | public TodoController(TodoService todoService) { 27 | this.todoService = todoService; 28 | } 29 | 30 | @Override 31 | public void handle(HttpExchange httpExchange) throws IOException { 32 | log.info("Request received. uri : , " + httpExchange.getRequestURI() + ". Method: " + httpExchange.getRequestMethod()); 33 | try { 34 | switch (httpExchange.getRequestMethod()) { 35 | case "GET" -> processGetRequest(httpExchange); 36 | case "POST" -> processPostRequest(httpExchange); 37 | case "DELETE" -> processDeleteRequest(httpExchange); 38 | case "PUT" -> processPutRequest(httpExchange); 39 | default -> processUnhandledRequest(httpExchange); 40 | } 41 | } catch (NotFoundException e) { 42 | log.log(Level.SEVERE, e.getMessage(), e); 43 | httpExchange.sendResponseHeaders(404, 0); 44 | OutputStream os = httpExchange.getResponseBody(); 45 | BaseResponse errorResponse = new BaseResponse<>(e.getMessage()); 46 | os.write(GsonUtil.objectToByteArray(errorResponse)); 47 | os.close(); 48 | } catch (Exception e) { 49 | log.log(Level.SEVERE, e.getMessage(), e); 50 | httpExchange.sendResponseHeaders(500, 0); 51 | OutputStream os = httpExchange.getResponseBody(); 52 | BaseResponse errorResponse = new BaseResponse<>("internal server error"); 53 | os.write(GsonUtil.objectToByteArray(errorResponse)); 54 | os.close(); 55 | } 56 | } 57 | 58 | private void processPutRequest(HttpExchange httpExchange) throws IOException { 59 | OutputStream os = httpExchange.getResponseBody(); 60 | InputStream is = httpExchange.getRequestBody(); 61 | TodoUpdateDto dto = GsonUtil.fromJson(is, TodoUpdateDto.class); 62 | Todo todo = todoService.update(dto); 63 | byte[] bytes = GsonUtil.objectToByteArray(new BaseResponse<>(todo)); 64 | httpExchange.sendResponseHeaders(200, 0); 65 | httpExchange.getResponseHeaders().add(CONTENT_TYPE_KEY, CONTENT_TYPE_VALUE); 66 | os.write(bytes); 67 | os.close(); 68 | } 69 | 70 | private void processDeleteRequest(HttpExchange httpExchange) throws IOException { 71 | OutputStream os = httpExchange.getResponseBody(); 72 | String uri = getPath(httpExchange); 73 | Long id = getPathVariable(uri); 74 | todoService.deleteById(id); 75 | BaseResponse baseResponse = new BaseResponse<>("todo successfully deleted"); 76 | byte[] bytes = GsonUtil.objectToByteArray(baseResponse); 77 | httpExchange.sendResponseHeaders(200, 0); 78 | httpExchange.getResponseHeaders().add(CONTENT_TYPE_KEY, CONTENT_TYPE_VALUE); 79 | os.write(bytes); 80 | os.close(); 81 | } 82 | 83 | private void processPostRequest(HttpExchange httpExchange) throws IOException { 84 | OutputStream os = httpExchange.getResponseBody(); 85 | InputStream is = httpExchange.getRequestBody(); 86 | TodoCreateDto dto = GsonUtil.fromJson(is, TodoCreateDto.class); 87 | Todo todo = todoService.create(dto); 88 | BaseResponse baseResponse = new BaseResponse<>(todo); 89 | byte[] bytes = GsonUtil.objectToByteArray(baseResponse); 90 | httpExchange.sendResponseHeaders(200, 0); 91 | httpExchange.getResponseHeaders().add(CONTENT_TYPE_KEY, CONTENT_TYPE_VALUE); 92 | os.write(bytes); 93 | os.close(); 94 | } 95 | 96 | private void processGetRequest(HttpExchange httpExchange) throws IOException { 97 | String uri = getPath(httpExchange); 98 | OutputStream os = httpExchange.getResponseBody(); 99 | String responseData = ""; 100 | if (uri.equals("/todo")) { 101 | List todos = todoService.getAll(); 102 | List rows = todos.stream().map(todo -> { 103 | StringBuilder stringBuilder = new StringBuilder(); 104 | return stringBuilder.append("") 105 | .append("" + todo.getId() + "") 106 | .append("" + todo.getTitle() + "") 107 | .append("" + todo.getDescription() + "") 108 | .append("" + todo.getUserId() + "") 109 | .append("" + todo.isDone() + "") 110 | .append("" + todo.getPriority() + "") 111 | .append("" + todo.getCreatedAt() + "") 112 | .append("").toString(); 113 | }).toList(); 114 | String tableBody = String.join("", rows); 115 | responseData = todo_list_html.formatted(tableBody); 116 | 117 | 118 | } else { 119 | Long id = getPathVariable(uri); 120 | Todo todo = todoService.getById(id); 121 | 122 | } 123 | httpExchange.getResponseHeaders().add(CONTENT_TYPE_KEY, CONTENT_TYPE_VALUE); 124 | httpExchange.sendResponseHeaders(200, 0); 125 | os.write(responseData.getBytes()); 126 | os.close(); 127 | } 128 | 129 | private void processUnhandledRequest(HttpExchange httpExchange) throws IOException { 130 | OutputStream os = httpExchange.getResponseBody(); 131 | httpExchange.sendResponseHeaders(404, 0); 132 | httpExchange.getResponseHeaders().add(CONTENT_TYPE_KEY, CONTENT_TYPE_VALUE); 133 | BaseResponse baseResponse = new BaseResponse<>("not found"); 134 | os.write(GsonUtil.objectToByteArray(baseResponse)); 135 | os.close(); 136 | } 137 | 138 | private static String getPath(HttpExchange httpExchange) { 139 | return httpExchange.getRequestURI().getPath(); 140 | } 141 | 142 | private static long getPathVariable(String uri) { 143 | return Long.parseLong(uri.split("/")[2]); 144 | } 145 | 146 | String todo_list_html = """ 147 | 148 | 149 | 150 | 151 | Todo List 152 | 161 | 162 | 163 |

Todo List

164 |
165 | ➕ add 166 |
167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | %s 181 | 182 |
idtitledescriptionuserIddoneprioritycreatedAt
183 | 184 | """; 185 | 186 | 187 | } 188 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/todo/TodoCreateController.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.todo; 2 | 3 | import com.sun.net.httpserver.HttpExchange; 4 | import com.sun.net.httpserver.HttpHandler; 5 | import dev.jlkeesh.httpserver.todo.dto.TodoCreateDto; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.net.URLDecoder; 11 | import java.nio.charset.StandardCharsets; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | public class TodoCreateController implements HttpHandler { 16 | private static final String CONTENT_TYPE_KEY = "Content-Type"; 17 | private static final String CONTENT_TYPE_VALUE = "text/html"; 18 | private final TodoService todoService; 19 | 20 | public TodoCreateController(TodoService todoService) { 21 | this.todoService = todoService; 22 | } 23 | 24 | @Override 25 | public void handle(HttpExchange httpExchange) throws IOException { 26 | switch (httpExchange.getRequestMethod()) { 27 | case "GET" -> processGetRequest(httpExchange); 28 | case "POST" -> processPostRequest(httpExchange); 29 | } 30 | } 31 | 32 | private void processPostRequest(HttpExchange httpExchange) throws IOException { 33 | InputStream is = httpExchange.getRequestBody(); 34 | String formDataAsString = new String(is.readAllBytes(), StandardCharsets.UTF_8); 35 | Map formData = parseFormData(formDataAsString); 36 | TodoCreateDto dto = new TodoCreateDto( 37 | formData.get("title"), 38 | formData.get("description"), 39 | Long.valueOf(formData.get("userId")), 40 | Priority.valueOf(formData.get("priority")) 41 | ); 42 | todoService.create(dto); // Redirect to /thankyou 43 | httpExchange.getResponseHeaders().set("Location", "/todo"); 44 | httpExchange.sendResponseHeaders(302, -1); // 302 Found 45 | } 46 | 47 | private Map parseFormData(String formDataAsString) { 48 | HashMap formData = new HashMap<>(); 49 | 50 | for (String keyValue : formDataAsString.split("&")) { 51 | String[] keyValueArray = keyValue.split("="); 52 | String key = URLDecoder.decode(keyValueArray[0], StandardCharsets.UTF_8); 53 | String value = URLDecoder.decode(keyValueArray[1], StandardCharsets.UTF_8); 54 | formData.put(key, value); 55 | } 56 | return formData; 57 | } 58 | 59 | private void processGetRequest(HttpExchange httpExchange) throws IOException { 60 | httpExchange.sendResponseHeaders(200, 0); 61 | httpExchange.getResponseHeaders().add(CONTENT_TYPE_KEY, CONTENT_TYPE_VALUE); 62 | OutputStream os = httpExchange.getResponseBody(); 63 | os.write(todo_add_html.getBytes()); 64 | os.close(); 65 | } 66 | 67 | String todo_add_html = """ 68 | 69 | 70 | 71 | 72 | Todo Add 73 | 74 | 75 |

Todo add

76 |
77 |
78 |
79 | 80 |
81 |
82 |
83 | 84 |
85 |
86 |
87 | 88 |
89 |
90 |
91 | 96 |
97 | 98 |
99 | 100 | """; 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/todo/TodoDAO.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.todo; 2 | 3 | import dev.jlkeesh.httpserver.config.DataSourceConfig; 4 | import dev.jlkeesh.httpserver.config.SettingsConfig; 5 | import dev.jlkeesh.httpserver.exception.DataAccessException; 6 | 7 | import java.sql.*; 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | public class TodoDAO { 12 | private final Connection connection; 13 | private final String selectQuery = SettingsConfig.get("query.todo.select"); 14 | private final String selectAllQuery = SettingsConfig.get("query.todo.select.all"); 15 | private final String deleteQuery = SettingsConfig.get("query.todo.delete"); 16 | private final String updateQuery = SettingsConfig.get("query.todo.update"); 17 | private final String insertQuery = SettingsConfig.get("query.todo.insert"); 18 | private final TodoRowMapper todoRowMapper = new TodoRowMapper(); 19 | 20 | public TodoDAO() { 21 | this.connection = DataSourceConfig.getConnection(); 22 | } 23 | 24 | public Optional findById(Long id) { 25 | try { 26 | PreparedStatement preparedStatement = connection.prepareStatement(selectQuery); 27 | preparedStatement.setLong(1, id); 28 | ResultSet resultSet = preparedStatement.executeQuery(); 29 | if (resultSet.next()) { 30 | return Optional.of(todoRowMapper.toDomain(resultSet)); 31 | } 32 | } catch (SQLException e) { 33 | throw new DataAccessException(e); 34 | } 35 | return Optional.empty(); 36 | } 37 | 38 | public Todo save(Todo todo) { 39 | try { 40 | if (todo.getId() == null) { 41 | return insert(todo); 42 | } else { 43 | return update(todo); 44 | } 45 | } catch (SQLException e) { 46 | throw new DataAccessException(e); 47 | } 48 | } 49 | 50 | private Todo update(Todo todo) throws SQLException { 51 | PreparedStatement psmt = connection.prepareStatement(updateQuery); 52 | psmt.setString(1, todo.getTitle()); 53 | psmt.setString(2, todo.getDescription()); 54 | psmt.setBoolean(3, todo.isDone()); 55 | psmt.setString(4, todo.getPriority().name()); 56 | psmt.setLong(5, todo.getId()); 57 | psmt.execute(); 58 | return todo; 59 | } 60 | 61 | private Todo insert(Todo todo) throws SQLException { 62 | PreparedStatement preparedStatement = connection.prepareStatement(insertQuery); 63 | preparedStatement.setString(1, todo.getTitle()); 64 | preparedStatement.setString(2, todo.getDescription()); 65 | preparedStatement.setLong(3, todo.getUserId()); 66 | preparedStatement.setBoolean(4, todo.isDone()); 67 | preparedStatement.setString(5, todo.getPriority().name()); 68 | ResultSet rs = preparedStatement.executeQuery(); 69 | if (rs.next()) { 70 | todo.setId(rs.getLong("id")); 71 | todo.setCreatedAt(rs.getDate("created_at")); 72 | } 73 | return todo; 74 | } 75 | 76 | public void deleteById(Long id) { 77 | try { 78 | PreparedStatement preparedStatement = connection.prepareStatement(deleteQuery); 79 | preparedStatement.setLong(1, id); 80 | preparedStatement.execute(); 81 | } catch (SQLException e) { 82 | throw new DataAccessException(e); 83 | } 84 | } 85 | 86 | public List findAll() { 87 | try { 88 | Statement statement = connection.createStatement(); 89 | ResultSet resultSet = statement.executeQuery(selectAllQuery); 90 | return todoRowMapper.toDomainList(resultSet); 91 | } catch (SQLException e) { 92 | throw new DataAccessException(e); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/todo/TodoRowMapper.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.todo; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class TodoRowMapper { 9 | public Todo toDomain(ResultSet resultSet) throws SQLException { 10 | return new Todo( 11 | resultSet.getLong("id"), 12 | resultSet.getString("title"), 13 | resultSet.getString("description"), 14 | resultSet.getLong("user_id"), 15 | resultSet.getBoolean("done"), 16 | Priority.valueOf(resultSet.getString("priority")), 17 | resultSet.getDate("created_at") 18 | ); 19 | } 20 | 21 | public List toDomainList(ResultSet resultSet) throws SQLException { 22 | List todos = new ArrayList<>(); 23 | while (resultSet.next()) { 24 | todos.add(toDomain(resultSet)); 25 | } 26 | return todos; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/todo/TodoService.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.todo; 2 | 3 | import dev.jlkeesh.httpserver.exception.NotFoundException; 4 | import dev.jlkeesh.httpserver.todo.dto.TodoCreateDto; 5 | import dev.jlkeesh.httpserver.todo.dto.TodoUpdateDto; 6 | 7 | import java.util.List; 8 | import java.util.Objects; 9 | 10 | public class TodoService { 11 | private final TodoDAO todoDAO; 12 | 13 | public TodoService(TodoDAO todoDAO) { 14 | this.todoDAO = todoDAO; 15 | } 16 | 17 | public Todo create(TodoCreateDto dto) { 18 | Todo todo = new Todo(); 19 | todo.setTitle(dto.title()); 20 | todo.setDescription(dto.description()); 21 | todo.setUserId(dto.userId()); 22 | todo.setPriority(dto.priority()); 23 | return todoDAO.save(todo); 24 | } 25 | 26 | public Todo update(TodoUpdateDto dto) { 27 | Todo todo = todoDAO.findById(dto.id()) 28 | .orElseThrow(() -> new NotFoundException("todo not found: " + dto.id())); 29 | if (Objects.nonNull(dto.title())) { 30 | todo.setTitle(dto.title()); 31 | } 32 | if (Objects.nonNull(dto.description())) { 33 | todo.setDescription(dto.description()); 34 | } 35 | if (Objects.nonNull(dto.priority())) { 36 | todo.setPriority(dto.priority()); 37 | } 38 | if (Objects.nonNull(dto.completed())) { 39 | todo.setDone(dto.completed()); 40 | } 41 | return todoDAO.save(todo); 42 | } 43 | 44 | public Todo getById(Long id) { 45 | return todoDAO.findById(id) 46 | .orElseThrow(() -> new NotFoundException("todo not found: " + id)); 47 | } 48 | 49 | public List getAll() { 50 | return todoDAO.findAll(); 51 | } 52 | 53 | public void deleteById(Long id) { 54 | todoDAO.deleteById(id); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/todo/dto/BaseResponse.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.todo.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | public class BaseResponse { 9 | private T data; 10 | private boolean success; 11 | private String error; 12 | public BaseResponse(T data) { 13 | this.data = data; 14 | this.success = true; 15 | } 16 | public BaseResponse(T data, boolean success) { 17 | this.data = data; 18 | this.success = success; 19 | } 20 | public BaseResponse(String error){ 21 | this.error = error; 22 | this.success = false; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/todo/dto/TodoCreateDto.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.todo.dto; 2 | 3 | import dev.jlkeesh.httpserver.todo.Priority; 4 | 5 | public record TodoCreateDto(String title, String description, Long userId, Priority priority) { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/todo/dto/TodoUpdateDto.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.todo.dto; 2 | 3 | import dev.jlkeesh.httpserver.todo.Priority; 4 | 5 | public record TodoUpdateDto(Long id, String title, String description, Priority priority, Boolean completed) { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/dev/jlkeesh/httpserver/utils/GsonUtil.java: -------------------------------------------------------------------------------- 1 | package dev.jlkeesh.httpserver.utils; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import dev.jlkeesh.httpserver.todo.dto.TodoCreateDto; 6 | import lombok.Getter; 7 | 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.nio.charset.StandardCharsets; 11 | 12 | public final class GsonUtil { 13 | @Getter 14 | private static final Gson gson = new GsonBuilder() 15 | .setPrettyPrinting() 16 | .create(); 17 | 18 | private GsonUtil() { 19 | throw new IllegalStateException("Utility class"); 20 | } 21 | 22 | public static String objectToJson(Object obj) { 23 | return gson.toJson(obj); 24 | } 25 | 26 | public static byte[] jsonStringToByteArray(String jsonString) { 27 | return jsonString.getBytes(); 28 | } 29 | 30 | public static byte[] objectToByteArray(Object obj) { 31 | return objectToJson(obj).getBytes(StandardCharsets.UTF_8); 32 | } 33 | 34 | public static T fromJson(InputStream is, Class clazz) { 35 | return gson.fromJson(new InputStreamReader(is), clazz); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Server config 2 | server.port=8080 3 | concurrent.request=10 4 | # Datasource config 5 | datasource.url=jdbc:postgresql://127.0.0.1:5432/httpserver 6 | datasource.username=postgres 7 | datasource.password=postgres 8 | # TODOS table related queries 9 | query.todo.insert=insert into todos(title, description,user_id,done,priority) values(?,?,?,?,?) returning id, created_at; 10 | query.todo.select=select id, title, description, user_id, done, priority, created_at from todos where id = ?; 11 | query.todo.select.all=select id, title, description, user_id, done, priority, created_at from todos; 12 | query.todo.delete=delete from todos where id = ?; 13 | query.todo.update=update todos set title = ?, description = ? , done = ?, priority = ? where id = ?; -------------------------------------------------------------------------------- /src/main/resources/templates/todo_add.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo Add 6 | 7 | 8 |

Todo add

9 |
10 |
11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 | 21 |
22 |
23 |
24 | 29 |
30 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/resources/templates/todo_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo List 6 | 15 | 16 | 17 | 18 |

Todo List

19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
idtitledescriptionuserIddoneprioritycreatedAt
idtitledescriptionuserIddoneprioritycreatedAt
43 | 44 | --------------------------------------------------------------------------------