diff --git a/java-springboot/.dockerignore b/java-springboot/.dockerignore
new file mode 100644
index 0000000..77d9707
--- /dev/null
+++ b/java-springboot/.dockerignore
@@ -0,0 +1,55 @@
+# Compiled class files
+target/
+*.class
+
+# Log files
+*.log
+
+# Package files
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# Virtual machine crash logs
+hs_err_pid*
+
+# IDE files
+.idea/
+.vscode/
+*.iml
+*.ipr
+*.iws
+
+# OS generated files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# Maven
+.mvn/
+mvnw
+mvnw.cmd
+
+# Git
+.git/
+.gitignore
+
+# Documentation
+README.md
+*.md
+
+# Suga files
+.suga/
+suga.yaml
+suga_gen/
+
+# Test files
+user_names.txt
\ No newline at end of file
diff --git a/java-springboot/Dockerfile b/java-springboot/Dockerfile
new file mode 100644
index 0000000..327cb02
--- /dev/null
+++ b/java-springboot/Dockerfile
@@ -0,0 +1,48 @@
+# Multi-stage Docker build for Java Spring Boot application
+
+# Build stage
+FROM maven:3.8.4-openjdk-8-slim AS build
+
+# Set working directory
+WORKDIR /app
+
+# Copy Maven configuration files
+COPY pom.xml .
+
+# Download dependencies (this layer will be cached if pom.xml doesn't change)
+RUN mvn dependency:go-offline -B
+
+# Copy source code
+COPY src ./src
+
+# Build the application
+RUN mvn clean package -DskipTests
+
+# Runtime stage
+FROM openjdk:8-jre-slim
+
+# Set working directory
+WORKDIR /app
+
+# Create a non-root user for security
+RUN groupadd -r appuser && useradd -r -g appuser appuser
+
+# Copy the built JAR from build stage
+COPY --from=build /app/target/hello-world-api-1.0.0.jar app.jar
+
+# Change ownership to appuser
+RUN chown -R appuser:appuser /app
+USER appuser
+
+# Expose port 8080
+EXPOSE 8080
+
+# Set JVM options for containerized environment
+ENV JAVA_OPTS="-Xmx512m -Xms256m"
+
+# Health check - using a simple endpoint since actuator is not included
+HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:8080/ || exit 1
+
+# Run the application
+ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
\ No newline at end of file
diff --git a/java-springboot/README.md b/java-springboot/README.md
new file mode 100644
index 0000000..8f0b392
--- /dev/null
+++ b/java-springboot/README.md
@@ -0,0 +1,108 @@
+# Hello World API Server (Java)
+
+This project contains a Hello World API server built with Java and Spring Boot framework.
+
+## Features
+
+- RESTful API endpoints for Hello World functionality
+- Integration with Suga to manage infrastructure
+- Docker containerization
+- Logging configuration with Logback
+
+## Files Structure
+
+- `pom.xml` - Maven build configuration with Spring Boot dependencies
+- `src/main/java/com/example/helloworldapi/HelloWorldApiApplication.java` - Main Spring Boot application
+- `src/main/java/com/example/helloworldapi/controller/HelloWorldController.java` - REST controller
+- `src/main/resources/application.properties` - Application configuration
+- `src/main/resources/logback.xml` - Logging configuration
+- `Dockerfile` - Multi-stage Docker build configuration
+- `.dockerignore` - Docker ignore patterns
+
+## Prerequisites
+
+- Java 8 or later
+- Maven 3.6 or later (or use Maven wrapper)
+- Internet connection for downloading dependencies
+
+## To run locally
+
+To run the application locally using the Suga development environment:
+
+```bash
+cd java-springboot
+suga dev
+```
+
+This command will start the Spring Boot server on port 4000.
+
+Alternatively, you can run it directly using Maven:
+
+```bash
+cd java-springboot
+./mvnw spring-boot:run
+```
+
+Or using Maven directly:
+```bash
+cd java-springboot
+mvn spring-boot:run
+```
+
+## To build
+
+```bash
+cd java-springboot
+./mvnw clean install
+```
+
+## API Endpoints
+
+- `GET /api/hello` - Returns "Hello, World!"
+- `GET /api/hello/name?name=YourName` - Returns "Hello, YourName!" and logs the name to S3 bucket
+- `GET /api/` - Returns welcome message with available endpoints
+- `GET /api/logs` - Returns logged user names from S3 bucket
+
+## Testing the API
+
+Once the server is running (either on port 4000 directly or via the Suga Load Balancer on port 3000), you can test the endpoints using:
+
+### curl
+
+```bash
+# Direct access (if not using Suga dev)
+curl http://localhost:4000/api/hello
+curl "http://localhost:4000/api/hello/name?name=John"
+curl http://localhost:4000/api/logs
+
+# Via Suga Load Balancer
+curl http://localhost:3000/api/hello
+curl "http://localhost:3000/api/hello/name?name=John"
+curl http://localhost:3000/api/logs
+```
+
+## S3 Bucket Logging Feature
+
+The server includes S3 bucket logging functionality using the Suga client:
+
+- When users access `/api/hello/name?name=YourName` with a custom name (not "World"), the server logs the name to an S3 bucket
+- Each entry includes a timestamp and the user's name
+- The log file is stored as `user_names.txt` in the configured S3 bucket
+- Example log entry: `[2025-10-20 17:52:30] User name: John`
+- Access logged entries via the `/api/logs` endpoint
+
+## Technology Stack
+
+- **Java** - Popular, high-performance programming language
+- **Spring Boot** - Framework for building production-ready Spring applications
+- **Maven** - Build automation tool
+- **Logback** - Logging framework
+- **Suga Client** - Infrastructure Orchestrator
+- **Docker** - Containerization
+
+## Notes
+
+- The server runs on port 4000 by default (configurable via PORT environment variable)
+- The loadbalancer locally uses port 3000 and forwards to the server as needed.
+- Spring Boot provides extensive features for rapid application development
+- User names are only logged when they differ from the default "World" value
\ No newline at end of file
diff --git a/java-springboot/pom.xml b/java-springboot/pom.xml
new file mode 100644
index 0000000..b457460
--- /dev/null
+++ b/java-springboot/pom.xml
@@ -0,0 +1,54 @@
+
+
+ 4.0.0
+
+ com.example
+ hello-world-api
+ 1.0.0
+ jar
+
+ Hello World API
+ A simple Hello World API server built with Spring Boot
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.18
+
+
+
+
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ com.addsuga
+ suga-client
+ 0.0.1
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/java-springboot/src/main/java/com/example/helloworldapi/HelloWorldApiApplication.java b/java-springboot/src/main/java/com/example/helloworldapi/HelloWorldApiApplication.java
new file mode 100644
index 0000000..6a31cc6
--- /dev/null
+++ b/java-springboot/src/main/java/com/example/helloworldapi/HelloWorldApiApplication.java
@@ -0,0 +1,12 @@
+package com.example.helloworldapi;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class HelloWorldApiApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(HelloWorldApiApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/java-springboot/src/main/java/com/example/helloworldapi/SimpleHttpServer.java b/java-springboot/src/main/java/com/example/helloworldapi/SimpleHttpServer.java
new file mode 100644
index 0000000..8827f1a
--- /dev/null
+++ b/java-springboot/src/main/java/com/example/helloworldapi/SimpleHttpServer.java
@@ -0,0 +1,138 @@
+package com.example.helloworldapi;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.HashMap;
+import java.util.Map;
+
+public class SimpleHttpServer {
+
+ private static final String LOG_FILE = "user_names.txt";
+
+ public static void main(String[] args) throws IOException {
+ HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
+
+ // Create API endpoints
+ server.createContext("/api/hello", new HelloHandler());
+ server.createContext("/api/hello/name", new HelloNameHandler());
+ server.createContext("/api/", new RootHandler());
+ server.createContext("/", new WelcomeHandler());
+
+ server.setExecutor(null); // creates a default executor
+ server.start();
+
+ System.out.println("Hello World API Server started on port 8080");
+ System.out.println("Available endpoints:");
+ System.out.println(" GET http://localhost:8080/api/hello");
+ System.out.println(" GET http://localhost:8080/api/hello/name?name=YourName");
+ System.out.println(" GET http://localhost:8080/api/");
+ System.out.println(" GET http://localhost:8080/");
+ }
+
+ static class HelloHandler implements HttpHandler {
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ if ("GET".equals(exchange.getRequestMethod())) {
+ String response = "Hello, World!";
+ sendResponse(exchange, response);
+ } else {
+ sendResponse(exchange, "Method not allowed", 405);
+ }
+ }
+ }
+
+ static class HelloNameHandler implements HttpHandler {
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ if ("GET".equals(exchange.getRequestMethod())) {
+ URI requestURI = exchange.getRequestURI();
+ String query = requestURI.getQuery();
+ String name = "World";
+
+ if (query != null) {
+ Map queryParams = parseQuery(query);
+ name = queryParams.getOrDefault("name", "World");
+ }
+
+ // Log the name to file if it's not the default "World"
+ if (!"World".equals(name)) {
+ logNameToFile(name);
+ }
+
+ String response = "Hello, " + name + "!";
+ sendResponse(exchange, response);
+ } else {
+ sendResponse(exchange, "Method not allowed", 405);
+ }
+ }
+ }
+
+ static class RootHandler implements HttpHandler {
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ if ("GET".equals(exchange.getRequestMethod())) {
+ String response = "Welcome to the Hello World API! Try /api/hello or /api/hello/name?name=YourName";
+ sendResponse(exchange, response);
+ } else {
+ sendResponse(exchange, "Method not allowed", 405);
+ }
+ }
+ }
+
+ static class WelcomeHandler implements HttpHandler {
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ if ("GET".equals(exchange.getRequestMethod())) {
+ String response = "Hello World API Server is running! Visit /api/ for API endpoints.";
+ sendResponse(exchange, response);
+ } else {
+ sendResponse(exchange, "Method not allowed", 405);
+ }
+ }
+ }
+
+ private static void sendResponse(HttpExchange exchange, String response) throws IOException {
+ sendResponse(exchange, response, 200);
+ }
+
+ private static void sendResponse(HttpExchange exchange, String response, int statusCode) throws IOException {
+ exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=UTF-8");
+ exchange.sendResponseHeaders(statusCode, response.getBytes().length);
+ OutputStream os = exchange.getResponseBody();
+ os.write(response.getBytes());
+ os.close();
+ }
+
+ private static void logNameToFile(String name) {
+ try (FileWriter writer = new FileWriter(LOG_FILE, true)) {
+ String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+ writer.write(String.format("[%s] User name: %s%n", timestamp, name));
+ System.out.println("Logged name to file: " + name);
+ } catch (IOException e) {
+ System.err.println("Error writing to log file: " + e.getMessage());
+ }
+ }
+
+ private static Map parseQuery(String query) {
+ Map result = new HashMap<>();
+ if (query != null) {
+ String[] pairs = query.split("&");
+ for (String pair : pairs) {
+ String[] keyValue = pair.split("=");
+ if (keyValue.length == 2) {
+ result.put(keyValue[0], keyValue[1]);
+ }
+ }
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/java-springboot/src/main/java/com/example/helloworldapi/controller/HelloWorldController.java b/java-springboot/src/main/java/com/example/helloworldapi/controller/HelloWorldController.java
new file mode 100644
index 0000000..ad547f7
--- /dev/null
+++ b/java-springboot/src/main/java/com/example/helloworldapi/controller/HelloWorldController.java
@@ -0,0 +1,80 @@
+package com.example.helloworldapi.controller;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import com.addsuga.SugaClient;
+import com.addsuga.Bucket;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+@RestController
+@RequestMapping("/api")
+public class HelloWorldController {
+
+ private final SugaClient sugaClient;
+ private final Bucket imageBucket;
+ private static final String LOG_KEY = "user_names.txt";
+
+ public HelloWorldController() {
+ this.sugaClient = new SugaClient();
+ this.imageBucket = this.sugaClient.createBucket("image");
+ }
+
+ @GetMapping("/hello")
+ public String hello() {
+ return "Hello, World!";
+ }
+
+ @GetMapping("/hello/name")
+ public String helloName(@RequestParam(value = "name", defaultValue = "World") String name) {
+ // Log the name to S3 bucket if it's not the default "World"
+ if (!"World".equals(name)) {
+ logNameToBucket(name);
+ }
+ return String.format("Hello, %s!", name);
+ }
+
+ @GetMapping("/")
+ public String root() {
+ return "Welcome to the Hello World API! Try /api/hello or /api/hello/name?name=YourName";
+ }
+
+ @GetMapping("/logs")
+ public String getLogs() {
+ try {
+ byte[] logsData = imageBucket.read(LOG_KEY);
+ String logs = new String(logsData);
+ return "User logs from S3 bucket:\n" + logs;
+ } catch (Exception e) {
+ System.err.println("Error reading from S3 bucket: " + e.getMessage());
+ return "Error reading logs from S3 bucket: " + e.getMessage();
+ }
+ }
+
+ private void logNameToBucket(String name) {
+ try {
+ String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+ String logEntry = String.format("[%s] User name: %s%n", timestamp, name);
+
+ // Read existing logs and append new entry
+ String existingLogs = "";
+ try {
+ byte[] existingData = imageBucket.read(LOG_KEY);
+ existingLogs = new String(existingData);
+ } catch (Exception e) {
+ // If file doesn't exist or can't be read, start with empty string
+ System.out.println("Starting new log file in S3 bucket");
+ }
+
+ String updatedLogs = existingLogs + logEntry;
+ imageBucket.write(LOG_KEY, updatedLogs.getBytes());
+
+ System.out.println("Logged name to S3 bucket: " + name);
+ } catch (Exception e) {
+ System.err.println("Error writing to S3 bucket: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/java-springboot/src/main/resources/application.properties b/java-springboot/src/main/resources/application.properties
new file mode 100644
index 0000000..ef8a740
--- /dev/null
+++ b/java-springboot/src/main/resources/application.properties
@@ -0,0 +1,13 @@
+# Server Configuration
+server.port=${PORT:4000}
+server.servlet.context-path=/
+
+# Application Configuration
+spring.application.name=hello-world-api
+
+# Logging Configuration
+logging.level.com.example.helloworldapi=INFO
+logging.level.org.springframework.web=INFO
+
+# Banner Configuration
+spring.main.banner-mode=console
\ No newline at end of file
diff --git a/java-springboot/suga.yaml b/java-springboot/suga.yaml
new file mode 100644
index 0000000..bd5ee0a
--- /dev/null
+++ b/java-springboot/suga.yaml
@@ -0,0 +1,25 @@
+target: suga/aws@1
+name: java-springboot
+description: Test project in java
+services:
+ backend_api:
+ subtype: lambda
+ container:
+ docker:
+ dockerfile: Dockerfile
+ context: .
+ dev:
+ script: mvn spring-boot:run
+buckets:
+ image:
+ subtype: s3
+ access:
+ backend_api:
+ - read
+ - write
+entrypoints:
+ entrypoint:
+ subtype: cloudfront
+ routes:
+ /:
+ name: backend_api
diff --git a/java-springboot/suga_gen/GeneratedSugaClient.java b/java-springboot/suga_gen/GeneratedSugaClient.java
new file mode 100644
index 0000000..07b9f04
--- /dev/null
+++ b/java-springboot/suga_gen/GeneratedSugaClient.java
@@ -0,0 +1,37 @@
+// Code generated by Suga SDK generator. DO NOT EDIT.
+
+package com.example.helloworldapi;
+
+import com.addsuga.client.SugaClient;
+
+import com.addsuga.client.Bucket;
+
+
+/**
+ * Generated client provides access to suga application resources
+ */
+public class GeneratedSugaClient extends SugaClient {
+
+ private final Bucket image;
+
+
+ public GeneratedSugaClient() {
+ super();
+
+ this.image = createBucket("image");
+
+ }
+
+ public GeneratedSugaClient(String address) {
+ super(address);
+
+ this.image = createBucket("image");
+
+ }
+
+
+ public Bucket getImage() {
+ return this.image;
+ }
+
+}
\ No newline at end of file
diff --git a/kotlin-ktor/.dockerignore b/kotlin-ktor/.dockerignore
new file mode 100644
index 0000000..1de8372
--- /dev/null
+++ b/kotlin-ktor/.dockerignore
@@ -0,0 +1,54 @@
+# Compiled class files
+build/
+*.class
+
+# Log files
+*.log
+
+# Package files
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# Virtual machine crash logs
+hs_err_pid*
+
+# IDE files
+.idea/
+.vscode/
+*.iml
+*.ipr
+*.iws
+
+# OS generated files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# Gradle
+.gradle/
+gradle/wrapper/
+
+# Git
+.git/
+.gitignore
+
+# Documentation
+README.md
+*.md
+
+# Suga files
+.suga/
+suga.yaml
+suga_gen/
+
+# Test files
+user_names.txt
\ No newline at end of file
diff --git a/kotlin-ktor/.gradle/8.5/checksums/checksums.lock b/kotlin-ktor/.gradle/8.5/checksums/checksums.lock
new file mode 100644
index 0000000..0163667
Binary files /dev/null and b/kotlin-ktor/.gradle/8.5/checksums/checksums.lock differ
diff --git a/kotlin-ktor/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock b/kotlin-ktor/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock
new file mode 100644
index 0000000..653a3d1
Binary files /dev/null and b/kotlin-ktor/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock differ
diff --git a/kotlin-ktor/.gradle/8.5/dependencies-accessors/gc.properties b/kotlin-ktor/.gradle/8.5/dependencies-accessors/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/kotlin-ktor/.gradle/8.5/executionHistory/executionHistory.lock b/kotlin-ktor/.gradle/8.5/executionHistory/executionHistory.lock
new file mode 100644
index 0000000..f97d052
Binary files /dev/null and b/kotlin-ktor/.gradle/8.5/executionHistory/executionHistory.lock differ
diff --git a/kotlin-ktor/.gradle/8.5/fileChanges/last-build.bin b/kotlin-ktor/.gradle/8.5/fileChanges/last-build.bin
new file mode 100644
index 0000000..f76dd23
Binary files /dev/null and b/kotlin-ktor/.gradle/8.5/fileChanges/last-build.bin differ
diff --git a/kotlin-ktor/.gradle/8.5/fileHashes/fileHashes.lock b/kotlin-ktor/.gradle/8.5/fileHashes/fileHashes.lock
new file mode 100644
index 0000000..e9b41c6
Binary files /dev/null and b/kotlin-ktor/.gradle/8.5/fileHashes/fileHashes.lock differ
diff --git a/kotlin-ktor/.gradle/8.5/gc.properties b/kotlin-ktor/.gradle/8.5/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/kotlin-ktor/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/kotlin-ktor/.gradle/buildOutputCleanup/buildOutputCleanup.lock
new file mode 100644
index 0000000..79096af
Binary files /dev/null and b/kotlin-ktor/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ
diff --git a/kotlin-ktor/.gradle/buildOutputCleanup/cache.properties b/kotlin-ktor/.gradle/buildOutputCleanup/cache.properties
new file mode 100644
index 0000000..40308cb
--- /dev/null
+++ b/kotlin-ktor/.gradle/buildOutputCleanup/cache.properties
@@ -0,0 +1,2 @@
+#Tue Oct 21 12:26:59 AEDT 2025
+gradle.version=8.5
diff --git a/kotlin-ktor/.gradle/buildOutputCleanup/outputFiles.bin b/kotlin-ktor/.gradle/buildOutputCleanup/outputFiles.bin
new file mode 100644
index 0000000..3569130
Binary files /dev/null and b/kotlin-ktor/.gradle/buildOutputCleanup/outputFiles.bin differ
diff --git a/kotlin-ktor/.gradle/vcs-1/gc.properties b/kotlin-ktor/.gradle/vcs-1/gc.properties
new file mode 100644
index 0000000..e69de29
diff --git a/kotlin-ktor/Dockerfile b/kotlin-ktor/Dockerfile
new file mode 100644
index 0000000..5c7f3f8
--- /dev/null
+++ b/kotlin-ktor/Dockerfile
@@ -0,0 +1,48 @@
+# Multi-stage Docker build for Kotlin Ktor application
+
+# Build stage
+FROM gradle:8.5-jdk8 AS build
+
+# Set working directory
+WORKDIR /app
+
+# Copy Gradle configuration files
+COPY build.gradle.kts gradle.properties ./
+
+# Download dependencies (this layer will be cached if build files don't change)
+RUN gradle dependencies --no-daemon
+
+# Copy source code
+COPY src ./src
+
+# Build the application
+RUN gradle build --no-daemon
+
+# Runtime stage
+FROM openjdk:8-jre-slim
+
+# Set working directory
+WORKDIR /app
+
+# Create a non-root user for security
+RUN groupadd -r appuser && useradd -r -g appuser appuser
+
+# Copy the built JAR from build stage
+COPY --from=build /app/build/libs/kotlin-ktor-1.0.0-all.jar app.jar
+
+# Change ownership to appuser
+RUN chown -R appuser:appuser /app
+USER appuser
+
+# Expose port 8080
+EXPOSE 8080
+
+# Set JVM options for containerized environment
+ENV JAVA_OPTS="-Xmx512m -Xms256m"
+
+# Health check - using a simple endpoint
+HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:8080/api/ || exit 1
+
+# Run the application
+ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
\ No newline at end of file
diff --git a/kotlin-ktor/README.md b/kotlin-ktor/README.md
new file mode 100644
index 0000000..40cad0e
--- /dev/null
+++ b/kotlin-ktor/README.md
@@ -0,0 +1,108 @@
+# Hello World API Server (Kotlin)
+
+This project contains a Hello World API server built with Kotlin and Ktor framework.
+
+## Features
+
+- RESTful API endpoints for Hello World functionality
+- Integration with Suga to manage infrastructure
+- Docker containerization
+- Logging configuration with Logback
+
+## Files Structure
+
+- `build.gradle.kts` - Gradle build configuration with Kotlin DSL
+- `gradle.properties` - Gradle properties with version definitions
+- `src/main/kotlin/com/example/helloworldapi/Application.kt` - Main Ktor application
+- `src/main/resources/application.conf` - Application configuration
+- `src/main/resources/logback.xml` - Logging configuration
+- `Dockerfile` - Multi-stage Docker build configuration
+- `.dockerignore` - Docker ignore patterns
+
+## Prerequisites
+
+- Java 8 or later
+- Gradle 8.4 or later (or use Gradle wrapper)
+- Internet connection for downloading dependencies
+
+## To run locally
+
+To run the application locally using the Suga development environment:
+
+```bash
+cd kotlin-ktor
+suga dev
+```
+
+This command will start the Ktor server on port 4000.
+
+Alternatively, you can run it directly using Gradle:
+
+```bash
+cd kotlin-ktor
+./gradlew run
+```
+
+Or using Gradle directly:
+```bash
+cd kotlin-ktor
+gradle run
+```
+
+## To build
+
+```bash
+cd kotlin-ktor
+./gradlew build
+```
+
+## API Endpoints
+
+- `GET /api/hello` - Returns "Hello, World!"
+- `GET /api/hello/name?name=YourName` - Returns "Hello, YourName!" and logs the name to S3 bucket
+- `GET /api/` - Returns welcome message with available endpoints
+- `GET /api/logs` - Returns logged user names from S3 bucket
+
+## Testing the API
+
+Once the server is running (either on port 4000 directly or via the Suga Load Balancer on port 3000), you can test the endpoints using:
+
+### curl
+
+```bash
+# Direct access (if not using Suga dev)
+curl http://localhost:4000/api/hello
+curl "http://localhost:4000/api/hello/name?name=John"
+curl http://localhost:4000/api/logs
+
+# Via Suga Load Balancer
+curl http://localhost:3000/api/hello
+curl "http://localhost:3000/api/hello/name?name=John"
+curl http://localhost:3000/api/logs
+```
+
+## S3 Bucket Logging Feature
+
+The server includes S3 bucket logging functionality using the Suga client:
+
+- When users access `/api/hello/name?name=YourName` with a custom name (not "World"), the server logs the name to an S3 bucket
+- Each entry includes a timestamp and the user's name
+- The log file is stored as `user_names.txt` in the configured S3 bucket
+- Example log entry: `[2025-10-20 17:52:30] User name: John`
+- Access logged entries via the `/api/logs` endpoint
+
+## Technology Stack
+
+- **Kotlin** - Modern JVM language with concise syntax
+- **Ktor** - Asynchronous framework for building connected applications
+- **Gradle** - Build automation tool with Kotlin DSL
+- **Logback** - Logging framework
+- **Suga Client** - Infrastructure Orchestrator
+- **Docker** - Containerization
+
+## Notes
+
+- The server runs on port 4000 by default (configurable via PORT environment variable)
+- The loadbalancer locally uses port 3000 and forwards to the server as needed.
+- Ktor provides built-in support for asynchronous request handling
+- User names are only logged when they differ from the default "World" value
diff --git a/kotlin-ktor/bin/main/application.conf b/kotlin-ktor/bin/main/application.conf
new file mode 100644
index 0000000..108e0f9
--- /dev/null
+++ b/kotlin-ktor/bin/main/application.conf
@@ -0,0 +1,10 @@
+ktor {
+ application {
+ modules = [ com.example.helloworldapi.ApplicationKt.module ]
+ }
+ deployment {
+ port = 4000
+ port = ${?PORT}
+ host = 0.0.0.0
+ }
+}
\ No newline at end of file
diff --git a/kotlin-ktor/bin/main/com/example/helloworldapi/Application.kt b/kotlin-ktor/bin/main/com/example/helloworldapi/Application.kt
new file mode 100644
index 0000000..b1eb99a
--- /dev/null
+++ b/kotlin-ktor/bin/main/com/example/helloworldapi/Application.kt
@@ -0,0 +1,84 @@
+package com.example.helloworldapi
+
+import com.addsuga.SugaClient
+import com.addsuga.Bucket
+import io.ktor.http.*
+import io.ktor.server.application.*
+import io.ktor.server.engine.*
+import io.ktor.server.netty.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+import io.ktor.server.netty.EngineMain
+
+fun main(args: Array): Unit = EngineMain.main(args)
+
+fun Application.module() {
+ configureRouting()
+}
+
+fun Application.configureRouting() {
+ val sugaClient = SugaClient()
+ val imageBucket = sugaClient.createBucket("image")
+ val logKey = "user_names.txt"
+
+ routing {
+ route("/api") {
+ get("/hello") {
+ call.respondText("Hello, World!")
+ }
+
+ get("/hello/name") {
+ val name = call.request.queryParameters["name"] ?: "World"
+
+ // Log the name to S3 bucket if it's not the default "World"
+ if (name != "World") {
+ logNameToBucket(imageBucket, logKey, name)
+ }
+
+ call.respondText("Hello, $name!")
+ }
+
+ get("/") {
+ call.respondText("Welcome to the Hello World API! Try /api/hello or /api/hello/name?name=YourName")
+ }
+
+ get("/logs") {
+ try {
+ val logsData = imageBucket.read(logKey)
+ val logs = String(logsData)
+ call.respondText("User logs from S3 bucket:\n$logs")
+ } catch (e: Exception) {
+ println("Error reading from S3 bucket: ${e.message}")
+ call.respondText("Error reading logs from S3 bucket: ${e.message}", status = HttpStatusCode.InternalServerError)
+ }
+ }
+ }
+ }
+}
+
+private fun logNameToBucket(imageBucket: Bucket, logKey: String, name: String) {
+ try {
+ val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
+ val logEntry = "[$timestamp] User name: $name\n"
+
+ // Read existing logs and append new entry
+ val existingLogs = try {
+ val existingData = imageBucket.read(logKey)
+ String(existingData)
+ } catch (e: Exception) {
+ // If file doesn't exist or can't be read, start with empty string
+ println("Starting new log file in S3 bucket")
+ ""
+ }
+
+ val updatedLogs = existingLogs + logEntry
+ imageBucket.write(logKey, updatedLogs.toByteArray())
+
+ println("Logged name to S3 bucket: $name")
+ } catch (e: Exception) {
+ println("Error writing to S3 bucket: ${e.message}")
+ }
+}
\ No newline at end of file
diff --git a/kotlin-ktor/bin/main/logback.xml b/kotlin-ktor/bin/main/logback.xml
new file mode 100644
index 0000000..925052b
--- /dev/null
+++ b/kotlin-ktor/bin/main/logback.xml
@@ -0,0 +1,14 @@
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/kotlin-ktor/build.gradle.kts b/kotlin-ktor/build.gradle.kts
new file mode 100644
index 0000000..afab8b5
--- /dev/null
+++ b/kotlin-ktor/build.gradle.kts
@@ -0,0 +1,39 @@
+val ktor_version: String by project
+val kotlin_version: String by project
+val logback_version: String by project
+
+plugins {
+ kotlin("jvm") version "1.8.22"
+ id("io.ktor.plugin") version "2.3.5"
+ id("org.jetbrains.kotlin.plugin.serialization") version "1.8.22"
+ application
+}
+
+group = "com.example"
+version = "1.0.0"
+
+application {
+ mainClass.set("com.example.helloworldapi.ApplicationKt")
+
+ val isDevelopment: Boolean = project.ext.has("development")
+ applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation("io.ktor:ktor-server-core-jvm")
+ implementation("io.ktor:ktor-server-netty-jvm")
+ implementation("io.ktor:ktor-server-config-yaml")
+ implementation("io.ktor:ktor-server-content-negotiation-jvm")
+ implementation("io.ktor:ktor-serialization-kotlinx-json-jvm")
+ implementation("ch.qos.logback:logback-classic:$logback_version")
+
+ // Suga client dependency (equivalent to the Java version)
+ implementation("com.addsuga:suga-client:0.0.1")
+
+ testImplementation("io.ktor:ktor-server-tests-jvm")
+ testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
+}
\ No newline at end of file
diff --git a/kotlin-ktor/gradle.properties b/kotlin-ktor/gradle.properties
new file mode 100644
index 0000000..8cde68c
--- /dev/null
+++ b/kotlin-ktor/gradle.properties
@@ -0,0 +1,4 @@
+ktor_version=2.3.5
+kotlin_version=1.8.22
+logback_version=1.4.11
+kotlin.code.style=official
\ No newline at end of file
diff --git a/kotlin-ktor/gradle/wrapper/gradle-wrapper.jar b/kotlin-ktor/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..d64cd49
Binary files /dev/null and b/kotlin-ktor/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/kotlin-ktor/gradle/wrapper/gradle-wrapper.properties b/kotlin-ktor/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..7cf0814
--- /dev/null
+++ b/kotlin-ktor/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
\ No newline at end of file
diff --git a/kotlin-ktor/gradlew b/kotlin-ktor/gradlew
new file mode 100644
index 0000000..cc44973
--- /dev/null
+++ b/kotlin-ktor/gradlew
@@ -0,0 +1,247 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments).
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
\ No newline at end of file
diff --git a/kotlin-ktor/gradlew.bat b/kotlin-ktor/gradlew.bat
new file mode 100644
index 0000000..c4e5997
--- /dev/null
+++ b/kotlin-ktor/gradlew.bat
@@ -0,0 +1,90 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd_ return code. Not all Java distributions support $GRADLE_EXIT_CONSOLE.
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit %ERRORLEVEL%
+exit /b %ERRORLEVEL%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
\ No newline at end of file
diff --git a/kotlin-ktor/src/main/kotlin/com/example/helloworldapi/Application.kt b/kotlin-ktor/src/main/kotlin/com/example/helloworldapi/Application.kt
new file mode 100644
index 0000000..b1eb99a
--- /dev/null
+++ b/kotlin-ktor/src/main/kotlin/com/example/helloworldapi/Application.kt
@@ -0,0 +1,84 @@
+package com.example.helloworldapi
+
+import com.addsuga.SugaClient
+import com.addsuga.Bucket
+import io.ktor.http.*
+import io.ktor.server.application.*
+import io.ktor.server.engine.*
+import io.ktor.server.netty.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+import io.ktor.server.netty.EngineMain
+
+fun main(args: Array): Unit = EngineMain.main(args)
+
+fun Application.module() {
+ configureRouting()
+}
+
+fun Application.configureRouting() {
+ val sugaClient = SugaClient()
+ val imageBucket = sugaClient.createBucket("image")
+ val logKey = "user_names.txt"
+
+ routing {
+ route("/api") {
+ get("/hello") {
+ call.respondText("Hello, World!")
+ }
+
+ get("/hello/name") {
+ val name = call.request.queryParameters["name"] ?: "World"
+
+ // Log the name to S3 bucket if it's not the default "World"
+ if (name != "World") {
+ logNameToBucket(imageBucket, logKey, name)
+ }
+
+ call.respondText("Hello, $name!")
+ }
+
+ get("/") {
+ call.respondText("Welcome to the Hello World API! Try /api/hello or /api/hello/name?name=YourName")
+ }
+
+ get("/logs") {
+ try {
+ val logsData = imageBucket.read(logKey)
+ val logs = String(logsData)
+ call.respondText("User logs from S3 bucket:\n$logs")
+ } catch (e: Exception) {
+ println("Error reading from S3 bucket: ${e.message}")
+ call.respondText("Error reading logs from S3 bucket: ${e.message}", status = HttpStatusCode.InternalServerError)
+ }
+ }
+ }
+ }
+}
+
+private fun logNameToBucket(imageBucket: Bucket, logKey: String, name: String) {
+ try {
+ val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
+ val logEntry = "[$timestamp] User name: $name\n"
+
+ // Read existing logs and append new entry
+ val existingLogs = try {
+ val existingData = imageBucket.read(logKey)
+ String(existingData)
+ } catch (e: Exception) {
+ // If file doesn't exist or can't be read, start with empty string
+ println("Starting new log file in S3 bucket")
+ ""
+ }
+
+ val updatedLogs = existingLogs + logEntry
+ imageBucket.write(logKey, updatedLogs.toByteArray())
+
+ println("Logged name to S3 bucket: $name")
+ } catch (e: Exception) {
+ println("Error writing to S3 bucket: ${e.message}")
+ }
+}
\ No newline at end of file
diff --git a/kotlin-ktor/src/main/resources/application.conf b/kotlin-ktor/src/main/resources/application.conf
new file mode 100644
index 0000000..108e0f9
--- /dev/null
+++ b/kotlin-ktor/src/main/resources/application.conf
@@ -0,0 +1,10 @@
+ktor {
+ application {
+ modules = [ com.example.helloworldapi.ApplicationKt.module ]
+ }
+ deployment {
+ port = 4000
+ port = ${?PORT}
+ host = 0.0.0.0
+ }
+}
\ No newline at end of file
diff --git a/kotlin-ktor/src/main/resources/logback.xml b/kotlin-ktor/src/main/resources/logback.xml
new file mode 100644
index 0000000..925052b
--- /dev/null
+++ b/kotlin-ktor/src/main/resources/logback.xml
@@ -0,0 +1,14 @@
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/kotlin-ktor/suga.yaml b/kotlin-ktor/suga.yaml
new file mode 100644
index 0000000..c4a7604
--- /dev/null
+++ b/kotlin-ktor/suga.yaml
@@ -0,0 +1,25 @@
+target: suga/aws@1
+name: kotlin-ktor
+description: Test project in kotlin
+services:
+ backend_api:
+ subtype: lambda
+ container:
+ docker:
+ dockerfile: Dockerfile
+ context: .
+ dev:
+ script: cmd /c gradlew.bat run
+buckets:
+ image:
+ subtype: s3
+ access:
+ backend_api:
+ - read
+ - write
+entrypoints:
+ entrypoint:
+ subtype: cloudfront
+ routes:
+ /:
+ name: backend_api
\ No newline at end of file
diff --git a/kotlin-ktor/suga_gen/GeneratedSugaClient.kt b/kotlin-ktor/suga_gen/GeneratedSugaClient.kt
new file mode 100644
index 0000000..490ca67
--- /dev/null
+++ b/kotlin-ktor/suga_gen/GeneratedSugaClient.kt
@@ -0,0 +1,29 @@
+// Code generated by Suga SDK generator. DO NOT EDIT.
+
+package com.example.helloworldapi
+
+import com.addsuga.client.SugaClient
+
+import com.addsuga.client.Bucket
+
+
+/**
+ * Generated client provides access to suga application resources
+ */
+class GeneratedSugaClient : SugaClient {
+
+ val image: Bucket
+
+
+ constructor() : super() {
+
+ this.image = createBucket("image")
+
+ }
+
+ constructor(address: String) : super(address) {
+
+ this.image = createBucket("image")
+
+ }
+}
\ No newline at end of file