Skip to content
Open
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
17 changes: 12 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
</properties>

<dependencies>
<!-- Deepgram Java SDK -->
<dependency>
<groupId>com.deepgram</groupId>
<artifactId>deepgram-java-sdk</artifactId>
<version>0.2.0</version>
</dependency>

<!-- Javalin web framework -->
<dependency>
<groupId>io.javalin</groupId>
Expand All @@ -37,14 +44,14 @@
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-toml</artifactId>
<version>2.18.2</version>
<version>2.18.6</version>
</dependency>

<!-- Jackson JSON support -->
<!-- Jackson JDK8 module for Optional type serialization -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>2.18.6</version>
</dependency>

<!-- Environment variable loading from .env -->
Expand Down
159 changes: 87 additions & 72 deletions src/main/java/com/deepgram/starter/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,73 +2,93 @@
* Java Text Intelligence Starter - Backend Server
*
* Simple REST API server providing text intelligence analysis
* powered by Deepgram's Text Intelligence service.
* powered by the Deepgram Java SDK.
*
* Key Features:
* - Contract-compliant API endpoint: POST /api/text-intelligence
* - Accepts text or URL in JSON body
* - Supports multiple intelligence features: summarization, topics, sentiment, intents
* - CORS-enabled for frontend communication
* - JWT session auth with rate limiting (production only)
* - Uses Deepgram Java SDK for text analysis
*/
package com.deepgram.starter;

// ============================================================================
// SECTION 1: IMPORTS
// ============================================================================

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.toml.TomlMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import io.github.cdimascio.dotenv.Dotenv;
import io.javalin.Javalin;
import io.javalin.http.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.deepgram.DeepgramClient;
import com.deepgram.core.DeepgramHttpException;
import com.deepgram.resources.read.v1.text.requests.TextAnalyzeRequest;
import com.deepgram.resources.read.v1.text.types.TextAnalyzeRequestSummarize;
import com.deepgram.types.ReadV1Request;
import com.deepgram.types.ReadV1RequestText;
import com.deepgram.types.ReadV1RequestUrl;
import com.deepgram.types.ReadV1Response;

import java.io.File;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;

// ============================================================================
// MAIN APPLICATION
// SECTION 2: MAIN APPLICATION
// ============================================================================

public class App {

private static final Logger log = LoggerFactory.getLogger(App.class);
private static final ObjectMapper jsonMapper = new ObjectMapper();

/**
* Shared Jackson ObjectMapper for JSON serialization.
* The Jdk8Module is registered to support serialization of Java 8
* Optional types used throughout the Deepgram SDK response objects.
*/
private static final ObjectMapper jsonMapper = new ObjectMapper()
.registerModule(new Jdk8Module());
private static final TomlMapper tomlMapper = new TomlMapper();
private static final HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();

// ========================================================================
// CONFIGURATION
// SECTION 3: CONFIGURATION
// ========================================================================

private static int port;
private static String host;
private static String apiKey;
private static DeepgramClient dgClient;
private static Algorithm jwtAlgorithm;

/** JWT expiry time (1 hour) */
private static final long JWT_EXPIRY_SECONDS = 3600;

// ========================================================================
// STARTUP
// SECTION 4: STARTUP
// ========================================================================

/**
* Application entry point. Loads configuration, validates the API key,
* initializes the Deepgram SDK client, and starts the Javalin HTTP server.
*
* @param args Command-line arguments (unused)
*/
public static void main(String[] args) {
// Load .env file (ignore if not present)
Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load();
Expand All @@ -80,8 +100,11 @@ public static void main(String[] args) {
// Initialize session secret
initSessionSecret(dotenv);

// Load Deepgram API key
// Load Deepgram API key and initialize SDK client
apiKey = loadApiKey(dotenv);
dgClient = DeepgramClient.builder()
.apiKey(apiKey)
.build();

// Create and configure Javalin app
Javalin app = Javalin.create(config -> {
Expand Down Expand Up @@ -137,18 +160,17 @@ public static void main(String[] args) {

System.out.println();
System.out.println("=".repeat(70));
System.out.printf("Backend API running at http://localhost:%d%n", port);
System.out.println();
System.out.println("GET /api/session");
System.out.println("POST /api/text-intelligence (auth required)");
System.out.println("GET /api/metadata");
System.out.println("GET /health");
System.out.printf(" Backend API running at http://localhost:%d%n", port);
System.out.println(" GET /api/session");
System.out.println(" POST /api/text-intelligence (auth required)");
System.out.println(" GET /api/metadata");
System.out.println(" GET /health");
System.out.println("=".repeat(70));
System.out.println();
}

// ========================================================================
// SESSION AUTH — JWT tokens for production security
// SECTION 5: SESSION AUTH — JWT tokens for production security
// ========================================================================

/**
Expand Down Expand Up @@ -203,7 +225,7 @@ private static void requireSession(Context ctx) {
}

// ========================================================================
// API KEY LOADING
// SECTION 6: API KEY LOADING
// ========================================================================

/**
Expand All @@ -226,7 +248,7 @@ private static String loadApiKey(Dotenv dotenv) {
}

// ========================================================================
// ROUTE HANDLERS
// SECTION 7: ROUTE HANDLERS
// ========================================================================

/**
Expand Down Expand Up @@ -277,6 +299,8 @@ private static void handleMetadata(Context ctx) {
* POST /api/text-intelligence
*
* Contract-compliant text intelligence endpoint per starter-contracts specification.
* Uses the Deepgram Java SDK for text analysis.
*
* Accepts:
* - Query parameters: summarize, topics, sentiment, intents, language (all optional)
* - Body: JSON with either text or url field (required, not both)
Expand Down Expand Up @@ -355,61 +379,52 @@ private static void handleTextIntelligence(Context ctx) {
return;
}

// Build Deepgram API URL with query parameters
StringBuilder dgUrl = new StringBuilder("https://api.deepgram.com/v1/read?language=");
dgUrl.append(URLEncoder.encode(language, StandardCharsets.UTF_8));
// Build the request body — either text or URL
ReadV1Request requestBody;
if (url != null && !url.isEmpty()) {
requestBody = ReadV1Request.of(
ReadV1RequestUrl.builder().url(url).build());
} else {
requestBody = ReadV1Request.of(
ReadV1RequestText.builder().text(text).build());
}

if ("true".equals(summarize) || "v2".equals(summarize)) {
dgUrl.append("&summarize=v2");
// Build the TextAnalyzeRequest with query parameters via SDK
TextAnalyzeRequest.Builder builder = (TextAnalyzeRequest.Builder)
TextAnalyzeRequest.builder().body(requestBody);

builder.language(language);

if ("true".equalsIgnoreCase(summarize) || "v2".equalsIgnoreCase(summarize)) {
builder.summarize(TextAnalyzeRequestSummarize.V2);
}
if ("true".equals(topics)) {
dgUrl.append("&topics=true");
if ("true".equalsIgnoreCase(topics)) {
builder.topics(true);
}
if ("true".equals(sentiment)) {
dgUrl.append("&sentiment=true");
if ("true".equalsIgnoreCase(sentiment)) {
builder.sentiment(true);
}
if ("true".equals(intents)) {
dgUrl.append("&intents=true");
if ("true".equalsIgnoreCase(intents)) {
builder.intents(true);
}

// Build request body for Deepgram — send text or url as JSON
Map<String, String> dgBody = new LinkedHashMap<>();
if (url != null && !url.isEmpty()) {
dgBody.put("url", url);
} else {
dgBody.put("text", text);
}
String dgBodyJson = jsonMapper.writeValueAsString(dgBody);

// Call Deepgram Read API
HttpRequest dgReq = HttpRequest.newBuilder()
.uri(URI.create(dgUrl.toString()))
.timeout(Duration.ofSeconds(30))
.header("Authorization", "Token " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(dgBodyJson))
.build();

HttpResponse<String> dgResp = httpClient.send(dgReq, HttpResponse.BodyHandlers.ofString());

// Handle non-2xx from Deepgram
if (dgResp.statusCode() < 200 || dgResp.statusCode() >= 300) {
log.error("Deepgram API Error (status {}): {}", dgResp.statusCode(), dgResp.body());
String errCode = (url != null && !url.isEmpty()) ? "INVALID_URL" : "INVALID_TEXT";
String errMsg = (url != null && !url.isEmpty()) ? "Failed to process URL" : "Failed to process text";
ctx.status(400);
ctx.json(processingError(errCode, errMsg));
return;
}
TextAnalyzeRequest request = builder.build();

// Call the Deepgram API via SDK
ReadV1Response response = dgClient.read().v1().text().analyze(request);

// Parse Deepgram response to extract results
JsonNode dgResult = jsonMapper.readTree(dgResp.body());
JsonNode results = dgResult.has("results") ? dgResult.get("results") : jsonMapper.createObjectNode();
// Return results — serialize the SDK response to match contract format
Map<String, Object> result = new LinkedHashMap<>();
result.put("results", jsonMapper.convertValue(response.getResults(), Map.class));
ctx.json(result);

// Return results
Map<String, Object> response = new LinkedHashMap<>();
response.put("results", jsonMapper.treeToValue(results, Object.class));
ctx.json(response);
} catch (DeepgramHttpException e) {
// Handle Deepgram API errors with their original status code
log.error("Deepgram API Error (status {}): {}", e.statusCode(), e.getMessage());
String errCode = (url != null && !url.isEmpty()) ? "INVALID_URL" : "INVALID_TEXT";
String errMsg = (url != null && !url.isEmpty()) ? "Failed to process URL" : "Failed to process text";
ctx.status(400);
ctx.json(processingError(errCode, errMsg));

} catch (Exception e) {
log.error("Text Intelligence Error", e);
Expand All @@ -436,7 +451,7 @@ private static void handleTextIntelligence(Context ctx) {
}

// ========================================================================
// HELPER FUNCTIONS
// SECTION 8: HELPER FUNCTIONS
// ========================================================================

/**
Expand Down