forked from appwrite/templates
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue # 78 | Feature: Add Email Contact Form Function for Java
- Loading branch information
1 parent
c9dc35a
commit b143f3e
Showing
7 changed files
with
469 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Compiled class file | ||
*.class | ||
|
||
# Log file | ||
*.log | ||
|
||
# BlueJ files | ||
*.ctxt | ||
|
||
# Mobile Tools for Java (J2ME) | ||
.mtj.tmp/ | ||
|
||
# Package Files # | ||
*.jar | ||
*.war | ||
*.nar | ||
*.ear | ||
*.zip | ||
*.tar.gz | ||
*.rar | ||
|
||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml | ||
hs_err_pid* | ||
replay_pid* | ||
# Ignore Gradle project-specific cache directory | ||
.gradle | ||
|
||
# Ignore Gradle build output directory | ||
build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# 📬 JAVA Email Contact Form Function | ||
|
||
Sends an email with the contents of a HTML form. | ||
|
||
## 🧰 Usage | ||
|
||
### GET / | ||
|
||
HTML forms for interacting with the function. | ||
|
||
### POST / | ||
|
||
Submit form data to send an email | ||
|
||
**Parameters** | ||
|
||
| Name | Description | Location | Type | Sample Value | | ||
| ------ | --------------------------------- | ---------- | ------ | -------------------------------- | | ||
| \_next | URL for redirect after submission | Form Param | String | `https://mywebapp.org/success` | | ||
| \* | Any form values to send in email | Form Param | String | `Hey, I'd like to get in touch!` | | ||
|
||
**Response** | ||
|
||
Sample `200` Response: | ||
|
||
```text | ||
Location: https://mywebapp.org/success | ||
``` | ||
|
||
Sample `400` Response: | ||
|
||
```text | ||
Location: https://mywebapp.org/referer?error=Error+Description | ||
``` | ||
|
||
## ⚙️ Configuration | ||
|
||
| Setting | Value | | ||
| ----------------- | --------------- | | ||
| Runtime | Java (17) | | ||
| Entrypoint | `src/Main.java` | | ||
| Permissions | `any` | | ||
| Timeout (Seconds) | 15 | | ||
|
||
## 🔒 Environment Variables | ||
|
||
### SMTP_HOST | ||
|
||
The address of your SMTP server. Many STMP providers will provide this information in their documentation. Some popular providers include: Mailgun, SendGrid, and Gmail. | ||
|
||
| Question | Answer | | ||
| ------------ | ------------------ | | ||
| Required | Yes | | ||
| Sample Value | `smtp.mailgun.org` | | ||
|
||
### SMTP_PORT | ||
|
||
The port of your STMP server. Commnly used ports include `25`, `465`, and `587`. | ||
|
||
| Question | Answer | | ||
| ------------ | ------ | | ||
| Required | Yes | | ||
| Sample Value | `25` | | ||
|
||
### SMTP_USERNAME | ||
|
||
The username for your SMTP server. This is commonly your email address. | ||
|
||
| Question | Answer | | ||
| ------------ | ----------------------- | | ||
| Required | Yes | | ||
| Sample Value | `no-reply@mywebapp.org` | | ||
|
||
### SMTP_PASSWORD | ||
|
||
The password for your SMTP server. | ||
|
||
| Question | Answer | | ||
| ------------ | --------------------- | | ||
| Required | Yes | | ||
| Sample Value | `5up3r5tr0ngP4ssw0rd` | | ||
|
||
### SUBMIT_EMAIL | ||
|
||
The email address to send form submissions to. | ||
|
||
| Question | Answer | | ||
| ------------ | ----------------- | | ||
| Required | Yes | | ||
| Sample Value | `me@mywebapp.org` | | ||
|
||
### ALLOWED_ORIGINS | ||
|
||
An optional comma-separated list of allowed origins for CORS (defaults to `*`). This is an important security measure to prevent malicious users from abusing your function. | ||
|
||
| Question | Answer | | ||
| ------------- | ------------------------------------------------------------------- | | ||
| Required | No | | ||
| Sample Value | `https://mywebapp.org,https://mywebapp.com` | | ||
| Documentation | [MDN: CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
dependencies { | ||
implementation 'io.appwrite:sdk-for-kotlin:4.0.0' | ||
implementation 'com.sun.mail:jakarta.mail:2.0.0' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package io.openruntimes.java.src; | ||
|
||
import io.openruntimes.java.RuntimeContext; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.HashMap; | ||
import java.lang.*; | ||
|
||
|
||
public class Cors { | ||
|
||
/* | ||
* Returns true if the origin is allowed to make requests to this endpoint | ||
* | ||
* Parameters: | ||
* req: Request object | ||
* | ||
* Returns: | ||
* (boolean): True if the origin is allowed, False otherwise | ||
*/ | ||
public static boolean isOriginPermitted(RuntimeContext context) { | ||
String allowedOrigins = System.getenv("ALLOWED_ORIGINS"); | ||
if (allowedOrigins == null || allowedOrigins.equals("*")) { | ||
return true; | ||
} | ||
|
||
List<String> allowedOriginsList = Arrays.asList(allowedOrigins.split(",")); | ||
String originHeader = context.getReq().getHeaders().get("Origin"); | ||
return originHeader != null && allowedOriginsList.contains(originHeader); | ||
} | ||
|
||
/* | ||
* Returns the CORS headers for the request | ||
* | ||
* Parameters: | ||
* req: Request object | ||
* | ||
* Returns: | ||
* (Map<String, String>): CORS headers | ||
*/ | ||
public static Map<String, String> getCorsHeaders(RuntimeContext context) { | ||
if (!context.getReq().getHeaders().containsKey("origin")) { | ||
return new HashMap<>(); | ||
} | ||
|
||
String allowedOrigins = System.getenv("ALLOWED_ORIGINS"); | ||
if (allowedOrigins == null || allowedOrigins.equals("*")) { | ||
return new HashMap<>(Map.of("Access-Control-Allow-Origin", "*")); | ||
} | ||
|
||
String originHeader = context.getReq().getHeaders().get("origin"); | ||
return new HashMap<>(Map.of("Access-Control-Allow-Origin", originHeader)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package io.openruntimes.java.src; | ||
|
||
public class ErrorCode { | ||
public static final String INVALID_REQUEST = "invalid-request"; | ||
public static final String MISSING_FORM_FIELDS = "missing-form-fields"; | ||
public static final String SERVER_ERROR = "server-error"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package io.openruntimes.java.src; | ||
|
||
import io.openruntimes.java.RuntimeContext; | ||
import io.openruntimes.java.RuntimeOutput; | ||
import java.util.Map; | ||
import java.util.List; | ||
import java.util.HashMap; | ||
import java.util.Arrays; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import java.lang.*; | ||
import io.appwrite.Client; | ||
import jakarta.mail.MessagingException; | ||
|
||
|
||
public class Main { | ||
|
||
public RuntimeOutput main(RuntimeContext context) throws Exception { | ||
|
||
List<String> requiredEnvVariables = Arrays.asList( | ||
"SUBMIT_EMAIL", | ||
"SMTP_HOST", | ||
"SMTP_USERNAME", | ||
"SMTP_PASSWORD" | ||
); | ||
|
||
Utils.throwIfMissing(System.getenv(), requiredEnvVariables); | ||
if (System.getenv("ALLOWED_ORIGINS").equals("*")) { | ||
context.log("WARNING: Allowing requests from any origin - this is a security risk!"); | ||
} | ||
|
||
if (context.getReq().getMethod().equals("GET")) { | ||
return context.getRes().send( | ||
Utils.getHtmlContent("index.html"), | ||
200, | ||
Map.of("content-type", "text/html") | ||
); | ||
} | ||
|
||
|
||
if(!context.getReq().getHeaders().get("content-type").equals("application/x-www-form-urlencoded")){ | ||
context.error("Incorrect content type"); | ||
String referer = context.getReq().getHeaders().get("referer"); | ||
String errorCode = ErrorCode.INVALID_REQUEST; | ||
return context.getRes().redirect( | ||
String.format("%s?code=%s", referer, errorCode) | ||
); | ||
} | ||
|
||
if(!Cors.isOriginPermitted(context)){ | ||
context.error("Origin not permitted"); | ||
String referer = context.getReq().getHeaders().get("referer"); | ||
String errorCode = ErrorCode.INVALID_REQUEST; | ||
return context.getRes().redirect( | ||
String.format("%s?code=%s", referer, errorCode) | ||
); | ||
} | ||
|
||
Map<String, List<String>> formData = new HashMap<>(); | ||
String body = (String) context.getReq().getBody(); | ||
String[] params = body.split("&"); | ||
for (String param : params) { | ||
String[] keyValue = param.split("="); | ||
String key = keyValue[0]; | ||
String value = keyValue[1]; | ||
if (!formData.containsKey(key)) { | ||
formData.put(key, new ArrayList<>()); | ||
} | ||
formData.get(key).add(value); | ||
} | ||
|
||
Map<String, String> form = new HashMap<>(); | ||
for (Map.Entry<String, List<String>> entry : formData.entrySet()) { | ||
form.put(entry.getKey(), entry.getValue().get(0)); | ||
} | ||
|
||
try { | ||
Utils.throwIfMissing(form, Collections.singletonList("email")); | ||
} catch (IllegalArgumentException ex) { | ||
String referer = context.getReq().getHeaders().get("referer"); | ||
String errorCode = ErrorCode.MISSING_FORM_FIELDS; | ||
return context.getRes().redirect( | ||
String.format("%s?code=%s", referer, errorCode), | ||
301, | ||
Cors.getCorsHeaders(context) | ||
); | ||
} | ||
|
||
|
||
try { | ||
Map<String, String> emailOptions = new HashMap<>(); | ||
emailOptions.put("from", System.getenv("SMTP_USERNAME")); | ||
emailOptions.put("to", System.getenv("SUBMIT_EMAIL")); | ||
emailOptions.put("subject", "New Contact Form Submission"); | ||
emailOptions.put("text", Utils.templateFormMessage(form)); | ||
Utils.sendEmail(emailOptions); | ||
} catch (MessagingException ex) { | ||
context.log("MessagingException: " + ex.getMessage()); | ||
String referer = context.getReq().getHeaders().get("referer"); | ||
String errorCode = ErrorCode.SERVER_ERROR; | ||
return context.getRes().redirect( | ||
String.format("%s?code=%s", referer, errorCode), | ||
301, | ||
Cors.getCorsHeaders(context) | ||
); | ||
} catch (Exception ex) { | ||
context.log("Exception: " + ex.getMessage()); | ||
String referer = context.getReq().getHeaders().get("referer"); | ||
String errorCode = ErrorCode.SERVER_ERROR; | ||
return context.getRes().redirect( | ||
String.format("%s?code=%s", referer, errorCode), | ||
301, | ||
Cors.getCorsHeaders(context) | ||
); | ||
} | ||
|
||
if (form.get("_next") == null || form.get("_next").isEmpty()) { | ||
return context.getRes().send( | ||
Utils.getHtmlContent("success.html"), | ||
200, | ||
Map.of("content-type", "text/html; charset=utf-8") | ||
); | ||
} | ||
|
||
return context.getRes().redirect( | ||
Utils.joinURL(context.getReq().getHeaders().get("referer"), form.get("_next").substring(0,1)), | ||
301, | ||
Cors.getCorsHeaders(context) | ||
); | ||
} | ||
} |
Oops, something went wrong.