Initial commit

This commit is contained in:
2025-03-23 12:12:53 +01:00
commit c39e022e41
102 changed files with 12503 additions and 0 deletions
@@ -0,0 +1,163 @@
package codes.thischwa.cf;
import codes.thischwa.cf.model.AbstractEntity;
import codes.thischwa.cf.model.AbstractResponse;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.nio.charset.StandardCharsets;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.classic.methods.HttpDelete;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPatch;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpPut;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
/**
* Abstract base class for creating HTTP clients to interact with the Cloudflare API. Provides
* methods for handling GET and POST requests and includes utilities for constructing HTTP clients,
* managing authentication, and handling JSON serialization.
*/
@Slf4j
abstract class CfBasicHttpClient {
private final String baseUrl;
private final String authEmail;
private final String authKey;
private final String authToken;
private final ObjectMapper objectMapper;
CfBasicHttpClient(String baseUrl, String authEmail, String authKey, String authToken) {
this.baseUrl = baseUrl;
this.authEmail = authEmail;
this.authKey = authKey;
this.authToken = authToken;
this.objectMapper = initObjectMapper();
}
private ObjectMapper initObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
return objectMapper;
}
private CloseableHttpClient createHttpClient() {
return HttpClients.custom()
.addRequestInterceptorFirst(
(request, context, execChain) -> {
request.addHeader(HttpHeaders.ACCEPT_CHARSET, "UTF-8");
request.addHeader(HttpHeaders.ACCEPT_ENCODING, "gzip");
request.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.getMimeType());
request.addHeader(
HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
request.addHeader("X-Auth-Email", authEmail);
request.addHeader("X-Auth-Key", authKey);
request.addHeader("X-Auth-Token", authToken);
})
.build();
}
private <T extends AbstractResponse> T executeRequest(
ClassicHttpRequest request, Class<T> responseType) throws CloudflareApiException {
String logUri = null;
try (CloseableHttpClient client = createHttpClient()) {
ResultWrapper result =
client.execute(
request,
(ClassicHttpResponse response) ->
new ResultWrapper(
response.getCode(),
EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8)));
logUri = request.getRequestUri();
if (result.statusCode >= 200 && result.statusCode < 300) {
return objectMapper.readValue(result.responseBody, responseType);
} else {
log.error(
"{} request failed for URL {}: Status {}",
request.getMethod(),
request.getUri(),
result.statusCode);
throw new CloudflareApiException(
request.getMethod() + " request failed with status code: " + result.statusCode);
}
} catch (JsonProcessingException e) {
log.error("JSON parsing error for request to {}", logUri, e);
throw new CloudflareApiException("Error processing JSON response", e);
} catch (Exception e) {
log.error("Error during request execution", e);
throw new CloudflareApiException("Request failed", e);
}
}
/** Sends a GET request to the given endpoint and maps the response. */
protected <T extends AbstractResponse> T getRequest(String endpoint, Class<T> responseType)
throws CloudflareApiException {
HttpGet request = new HttpGet(buildUrl(endpoint));
return executeRequest(request, responseType);
}
/** Sends a DELETE request to the given endpoint and maps the response. */
protected <T extends AbstractResponse> T deleteRequest(String endpoint, Class<T> responseType)
throws CloudflareApiException {
HttpDelete request = new HttpDelete(buildUrl(endpoint));
return executeRequest(request, responseType);
}
/** Sends a POST request with a payload to the given endpoint and maps the response. */
protected <T extends AbstractResponse, R extends AbstractEntity> T postRequest(
String endpoint, R requestPayload, Class<T> responseType) throws CloudflareApiException {
HttpPost request = new HttpPost(buildUrl(endpoint));
setRequestPayload(request, requestPayload);
return executeRequest(request, responseType);
}
/** Sends a PUT request with a payload to the given endpoint and maps the response. */
protected <T extends AbstractResponse, R extends AbstractEntity> T putRequest(
String endpoint, R requestPayload, Class<T> responseType) throws CloudflareApiException {
HttpPut request = new HttpPut(buildUrl(endpoint));
setRequestPayload(request, requestPayload);
return executeRequest(request, responseType);
}
/** Sends a PATCH request with a payload to the given endpoint and maps the response. */
protected <T extends AbstractResponse, R extends AbstractEntity> T patchRequest(
String endpoint, R requestPayload, Class<T> responseType) throws CloudflareApiException {
HttpPatch request = new HttpPatch(buildUrl(endpoint));
setRequestPayload(request, requestPayload);
return executeRequest(request, responseType);
}
/** Sets the JSON payload for a request. */
private <R extends AbstractEntity> void setRequestPayload(
BasicClassicHttpRequest request, R requestPayload) throws CloudflareApiException {
try {
request.setEntity(
new StringEntity(
objectMapper.writeValueAsString(requestPayload), ContentType.APPLICATION_JSON));
} catch (JsonProcessingException e) {
throw new CloudflareApiException("Error serializing JSON payload", e);
}
}
private String buildUrl(String endpoint) {
return baseUrl + endpoint;
}
private record ResultWrapper(int statusCode, String responseBody) {}
}
@@ -0,0 +1,307 @@
package codes.thischwa.cf;
import codes.thischwa.cf.model.AbstractResponse;
import codes.thischwa.cf.model.PagingRequest;
import codes.thischwa.cf.model.RecordEntity;
import codes.thischwa.cf.model.RecordMultipleResponse;
import codes.thischwa.cf.model.RecordSingleResponse;
import codes.thischwa.cf.model.RecordType;
import codes.thischwa.cf.model.ZoneEntity;
import codes.thischwa.cf.model.ZoneMultipleResponse;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
/**
* CfDnsClient is a client interface to interact with Cloudflare DNS service. It allows managing DNS
* records and zones within the Cloudflare system, including creating, updating, retrieving, and
* deleting DNS records.
*
* <p>Example:
* <pre><code>
* // Create a new CfDnsClient instance
* CfDnsClient client = new CfDnsClient(
* "email@example.com",
* "yourApiKey",
* "yourApiToken"
* );
*
* // Retrieve a zone
* ZoneEntity zone = cfDnsClient.zoneInfo("example.com");
* System.out.println("Zone ID: " + zone.getId());
*
* // Retrieve records of a zone
* List<RecordEntity> records = cfDnsClient.sldListAll(zone, "sld");
* records.forEach(record ->
* System.out.println("Record Type: " + record.getType() + ", Value: " + record.getContent())
* );
* </code></pre>
*/
@Setter
@Slf4j
public class CfDnsClient extends CfBasicHttpClient {
private static final String DEFAULT_BASEURL = "https://api.cloudflare.com/client/v4";
private boolean emptyResultThrowsException;
/**
* Constructs a CfDnsClient instance for interacting with the Cloudflare DNS API.
*
* @param authEmail The email address associated with the Cloudflare account, used for
* authentication.
* @param authKey The API key of the Cloudflare account, used as part of the authentication
* process.
* @param authToken The API token for accessing specific resources within the Cloudflare account.
*/
public CfDnsClient(String authEmail, String authKey, String authToken) {
this(DEFAULT_BASEURL, authEmail, authKey, authToken);
}
/**
* Constructs a CfDnsClient instance for interacting with the Cloudflare DNS API.
*
* @param baseUrl The base URL of the Cloudflare API to be used for requests.
* @param authEmail The email address associated with the Cloudflare account, used for
* authentication.
* @param authKey The API key of the Cloudflare account, used as part of the authentication
* process.
* @param authToken The API token for accessing specific resources within the Cloudflare account.
*/
public CfDnsClient(String baseUrl, String authEmail, String authKey, String authToken) {
this(true, baseUrl, authEmail, authKey, authToken);
}
/**
* Constructs a new instance of {@code CfDnsClient}, which facilitates interactions with the
* Cloudflare DNS API.
*
* @param emptyResultThrowsException Specifies if an exception should be thrown when the API
* response is empty. Default is true.
* @param baseUrl The base URL for the Cloudflare API endpoint.
* @param authEmail The email associated with the Cloudflare account for authentication.
* @param authKey The API key for authenticating the client with Cloudflare services.
* @param authToken The authentication token used for authorized access to Cloudflare API.
*/
public CfDnsClient(
boolean emptyResultThrowsException,
String baseUrl,
String authEmail,
String authKey,
String authToken) {
super(baseUrl, authEmail, authKey, authToken);
this.emptyResultThrowsException = emptyResultThrowsException;
}
/**
* Retrieves a list of all zones from the Cloudflare API. <br>
* This method sends a GET request to the Cloudflare API endpoint for listing zones, processes the
* response, and returns the resulting list of ZoneEntity objects.
*
* @return A list of ZoneEntity objects representing the zones retrieved from the Cloudflare API.
* @throws CloudflareApiException If an error occurs during the API request or response handling.
*/
public List<ZoneEntity> zoneListAll() throws CloudflareApiException {
return zoneListAll(PagingRequest.defaultPaging());
}
/**
* Retrieves a list of all zones from the Cloudflare API. <br>
* This method sends a GET request to the Cloudflare API endpoint for listing zones, processes the
* response, and returns the resulting list of ZoneEntity objects.
*
* @return A list of ZoneEntity objects representing the zones retrieved from the Cloudflare API.
* @throws CloudflareApiException If an error occurs during the API request or response handling.
*/
public List<ZoneEntity> zoneListAll(PagingRequest pagingRequest) throws CloudflareApiException {
String endpoint = pagingRequest.addQueryString(CfRequest.ZONE_LIST.buildPath());
ZoneMultipleResponse response = getRequest(endpoint, ZoneMultipleResponse.class);
checkResponse(response);
return response.getResult();
}
/**
* Retrieves detailed information about a specific zone by its name.
*
* @param name The name of the zone to retrieve information for.
* @return A {@link ZoneEntity} object that contains details of the specified zone.
* @throws CloudflareApiException If an error occurs while making the API request or processing
* the response.
*/
public ZoneEntity zoneInfo(String name) throws CloudflareApiException {
String endpoint = CfRequest.ZONE_INFO.buildPath(name);
ZoneMultipleResponse response = getRequest(endpoint, ZoneMultipleResponse.class);
checkResponse(response, true);
return response.getResult().get(0);
}
/**
* Retrieves all record entities for a specific second-level domain (SLD) within a given DNS zone.
*
* @param zone The DNS zone entity for which the SLD records are to be fetched.
* @param sld The second-level domain name for which the records are retrieved.
* @return A list of {@code RecordEntity} objects representing the DNS records associated with the
* provided SLD.
* @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API.
*/
public List<RecordEntity> sldListAll(ZoneEntity zone, String sld) throws CloudflareApiException {
return sldListAll(zone, sld, PagingRequest.defaultPaging());
}
/**
* Retrieves all record entities for a specific second-level domain (SLD) within a given DNS zone.
*
* @param zone The DNS zone entity for which the SLD records are to be fetched.
* @param sld The second-level domain name for which the records are retrieved.
* @param pagingRequest The paging request.
* @return A list of {@code RecordEntity} objects representing the DNS records associated with the
* provided SLD.
* @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API.
*/
public List<RecordEntity> sldListAll(ZoneEntity zone, String sld, PagingRequest pagingRequest)
throws CloudflareApiException {
String fqdn = sld + "." + zone.getName();
String endpoint =
pagingRequest.addQueryString(CfRequest.RECORD_INFO_NAME.buildPath(zone.getId(), fqdn));
RecordMultipleResponse resp = getRequest(endpoint, RecordMultipleResponse.class);
checkResponse(resp);
return resp.getResult();
}
/**
* Retrieves detailed information about a specific second-level domain (SLD) record for a given
* zone and record type from the Cloudflare API.
*
* @param zone the zone entity that contains information about the DNS zone
* @param sld the second-level domain (SLD) for which the record information is requested
* @param type the type of DNS record (e.g., A, AAAA, CNAME) being queried
* @return the record entity containing detailed information about the requested SLD and record
* type
* @throws CloudflareApiException if an error occurs during interaction with the Cloudflare API
*/
public RecordEntity sldInfo(ZoneEntity zone, String sld, RecordType type)
throws CloudflareApiException {
String fqdn = sld + "." + zone.getName();
String endpoint = CfRequest.RECORD_INFO_NAME_TYPE.buildPath(zone.getId(), fqdn, type);
RecordMultipleResponse resp = getRequest(endpoint, RecordMultipleResponse.class);
checkResponse(resp, true);
return resp.getResult().get(0);
}
/**
* Creates a new DNS record in the specified zone using the Cloudflare API.
*
* @param zone The zone entity where the record will be created. Contains details such as zone ID.
* @param rec The record entity representing the DNS record to be created, including its
* attributes.
* @return The created record entity as returned by the Cloudflare API.
* @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API.
*/
public RecordEntity recordCreate(ZoneEntity zone, RecordEntity rec)
throws CloudflareApiException {
String endpoint = CfRequest.RECORD_CREATE.buildPath(zone.getId());
RecordSingleResponse resp = postRequest(endpoint, rec, RecordSingleResponse.class);
checkResponse(resp);
return resp.getResult();
}
/**
* Deletes a DNS record of the specified type within a given zone on the Cloudflare API.
*
* @param zone The zone entity that specifies the zone in which the record exists.
* @param rec The record entity that represents the DNS record to be deleted.
* @return {@code true} if the DNS record was successfully deleted; {@code false} otherwise.
* @throws CloudflareApiException if there is an issue during the API communication or the request
* fails for any reason.
*/
public boolean recordDelete(ZoneEntity zone, RecordEntity rec) throws CloudflareApiException {
boolean changed = recordDelete(zone, rec.getId());
if (changed) {
log.info("Record {} of the type {} successful deleted.", rec.getName(), rec.getType());
} else {
log.warn("Record {} of the type {} was not deleted.", rec.getName(), rec.getType());
}
return changed;
}
/**
* Deletes a DNS record of the specified type within a given zone on the Cloudflare API.
*
* @param zone The zone entity that specifies the zone in which the record exists.
* @param id The record entity that represents the DNS record to be deleted.
* @return {@code true} if the DNS record was successfully deleted; {@code false} otherwise.
* @throws CloudflareApiException if there is an issue during the API communication or the request
* fails for any reason.
*/
public boolean recordDelete(ZoneEntity zone, String id) throws CloudflareApiException {
String endpoint = CfRequest.RECORD_DELETE.buildPath(zone.getId(), id);
RecordSingleResponse resp = deleteRequest(endpoint, RecordSingleResponse.class);
checkResponse(resp);
return resp.getResult().getId().equals(id);
}
/**
* Updates an existing DNS record in a specified Cloudflare zone.
*
* @param zone the zone entity containing the ID of the target zone
* @param rec the record entity containing the ID of the DNS record to be updated and its updated
* data
* @return the updated record entity as returned by the Cloudflare API
* @throws CloudflareApiException if an error occurs while interacting with the Cloudflare API
*/
public RecordEntity recordUpdate(ZoneEntity zone, RecordEntity rec)
throws CloudflareApiException {
// reset all dates, it causes an API issue
rec.setModifiedOn(null);
rec.setCreatedOn(null);
String endpoint = CfRequest.RECORD_UPDATE.buildPath(zone.getId(), rec.getId());
RecordSingleResponse resp = patchRequest(endpoint, rec, RecordSingleResponse.class);
checkResponse(resp);
return resp.getResult();
}
/**
* Attempts to delete a DNS record of a specific type for a given zone and second-level domain
* (SLD), if it exists.
*
* @param zone The zone in which the DNS record resides. It provides information about the domain.
* @param sld The second-level domain (SLD) of the fully qualified domain name (FQDN) for which
* the record is being deleted.
* @param type The type of the DNS record to be deleted (e.g., A, CNAME, TXT).
* @throws CloudflareApiException If an error occurs while interacting with the Cloudflare API.
*/
public void recordDeleteTypeIfExists(ZoneEntity zone, String sld, RecordType type)
throws CloudflareApiException {
String fqdn = sld + "." + zone.getName();
try {
RecordEntity rec = sldInfo(zone, sld, type);
recordDelete(zone, rec);
log.debug("Record {} of type {} successful deleted.", fqdn, type);
} catch (CloudflareNotFoundException e) {
log.debug("Record {} of type {} does not exist.", fqdn, type);
}
}
private void checkResponse(AbstractResponse resp) throws CloudflareApiException {
checkResponse(resp, false);
}
private void checkResponse(AbstractResponse resp, boolean singleResultExpected)
throws CloudflareApiException {
if (!resp.isSuccess()) {
String errors =
resp.getErrors().stream().map(Object::toString).collect(Collectors.joining(", "));
throw new CloudflareApiException("Error in response: " + errors);
}
if (resp instanceof RecordMultipleResponse respMulti) {
if (singleResultExpected && respMulti.getResultInfo().getTotalCount() > 1) {
throw new CloudflareApiException(
"Unexpected result count: " + respMulti.getResultInfo().getTotalCount());
}
if (emptyResultThrowsException && respMulti.getResultInfo().getTotalCount() == 0) {
throw new CloudflareNotFoundException("No result found");
}
}
}
}
@@ -0,0 +1,48 @@
package codes.thischwa.cf;
import lombok.Getter;
/**
* Enum CfRequest encapsulates various API endpoint paths for managing DNS zones and records in a
* cohesive and reusable manner. Each enum constant represents a specific API request path.
*/
@Getter
public enum CfRequest {
// for handling zones
ZONE_LIST("/zones"),
ZONE_INFO("/zones?name=%s"),
// for handling records
RECORD_CREATE("/zones/%s/dns_records"),
RECORD_INFO_NAME("/zones/%s/dns_records?name=%s"),
RECORD_INFO_NAME_TYPE("/zones/%s/dns_records?name=%s&type=%s"),
RECORD_UPDATE("/zones/%s/dns_records/%s"),
RECORD_DELETE("/zones/%s/dns_records/%s");
private static final char varIdentification = '%';
private final String path;
CfRequest(String path) {
this.path = path;
}
/**
* Constructs the complete API endpoint path by formatting the base path with the provided
* arguments.
*
* @param vars the arguments to format the path string with; these are typically specific
* identifiers or parameters required by the API endpoint.
* @return the fully constructed API endpoint path as a string.
*/
String buildPath(Object... vars) {
long varCount = path.chars().filter(c -> c == varIdentification).count();
if (varCount != vars.length) {
throw new IllegalArgumentException(
String.format(
"The number of variables (%d) does not match the number of parameters (%d) in the path string: %s",
vars.length, varCount, path));
}
return String.format(path, vars);
}
}
@@ -0,0 +1,40 @@
package codes.thischwa.cf;
import java.io.Serial;
/**
* Represents a custom exception for errors encountered while interacting with the Cloudflare API.
*/
public class CloudflareApiException extends Exception {
@Serial private static final long serialVersionUID = 1L;
/**
* Constructs a new CloudflareApiException with the specified detail message.
*
* @param message the detail message, which provides more information about the exception.
*/
public CloudflareApiException(String message) {
super(message);
}
/**
* Constructs a new CloudflareApiException with the specified detail message and cause.
*
* @param message the detail message, which provides additional context or information about the exception.
* @param cause the cause of this exception, which is the underlying throwable that triggered this exception.
*/
public CloudflareApiException(String message, Throwable cause) {
super(message, cause);
}
/**
* Constructs a new CloudflareApiException with the specified cause.
*
* @param cause the cause of this exception, which is the underlying throwable
* that triggered this exception.
*/
public CloudflareApiException(Throwable cause) {
super(cause);
}
}
@@ -0,0 +1,42 @@
package codes.thischwa.cf;
/**
* This exception is thrown to indicate that a requested resource was not found during interaction
* with the Cloudflare API.
*
* <p>It extends {@link CloudflareApiException} to provide specific errors related to situations
* where Cloudflare responds with a "not found" operation.
*/
public class CloudflareNotFoundException extends CloudflareApiException {
/**
* Constructs a new CloudflareNotFoundException with the specified detail message.
*
* @param message the detail message, which provides additional context about the "not found" error
* encountered during interaction with the Cloudflare API.
*/
public CloudflareNotFoundException(String message) {
super(message);
}
/**
* Constructs a new CloudflareNotFoundException with the specified detail message and cause.
*
* @param message the detail message, which provides additional context about the "not found" error
* encountered during interaction with the Cloudflare API.
* @param cause the cause of this exception, which is the underlying throwable that triggered this exception.
*/
public CloudflareNotFoundException(String message, Throwable cause) {
super(message, cause);
}
/**
* Constructs a new CloudflareNotFoundException with the specified cause.
*
* @param cause the cause of this exception, which is the underlying throwable
* that triggered this exception.
*/
public CloudflareNotFoundException(Throwable cause) {
super(cause);
}
}
@@ -0,0 +1,18 @@
package codes.thischwa.cf.model;
import lombok.Data;
/**
* Represents a base abstract entity class for modeling domain objects with a unique identifier.
*
* <p>This class provides a fundamental contract for entities by implementing the {@link
* ResponseEntity} interface. The primary attribute of this class is the `id` field, which serves as
* a unique identifier for all derived entities.
*
* <p>Commonly extended by other entity classes to maintain a consistent entity structure across the
* domain models. This encourages code reusability and consistency within the system.
*/
@Data
public class AbstractEntity implements ResponseEntity {
private String id;
}
@@ -0,0 +1,31 @@
package codes.thischwa.cf.model;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Abstract base class for response models that contain multiple result entries.
*
* <p>This class is designed to handle API responses where multiple entities are part of the result
* set, such as paginated or batched data. It extends {@link AbstractResponse} to include additional
* attributes specific to multi-entity responses.
*
* <p>Attributes:
*
* <ul>
* <li>`resultInfo`: Provides metadata about the result set, such as pagination details like page
* number, total count, number of results per page, etc.
* <li>`result`: A list of entities representing the main body of the response. The type of
* entities in the result list is determined by the generic parameter {@code T}, which must
* extend {@link ResponseEntity}.
* </ul>
*
* <p>Subclasses can be created by specifying the entity type that the response should handle.
*/
@EqualsAndHashCode(callSuper = true)
@Data
public abstract class AbstractMultipleResponse<T extends ResponseEntity> extends AbstractResponse {
private ResultInfo resultInfo;
private List<T> result;
}
@@ -0,0 +1,28 @@
package codes.thischwa.cf.model;
import java.util.List;
import lombok.Data;
/**
* Abstract base class for API response models.
*
* <p>This class encapsulates common attributes used to represent the result of an API request. It
* can be extended to define more specific response structures.
*
* <p>Attributes:
*
* <ol>
* <li><b>success</b>: Indicates whether the API request was successful.
* <li><b>errors</b>: A list of error messages, if any, returned by the API.
* <li><b>messages</b>: A list of informational or status messages accompanying the response.
* </ol>
*
* <p>This structure is designed for consistency and ease of extending response models in
* applications that require uniform response structures.
*/
@Data
public abstract class AbstractResponse {
private boolean success;
private List<String> errors;
private List<String> messages;
}
@@ -0,0 +1,24 @@
package codes.thischwa.cf.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Represents a base abstract response model for handling single response entities within an API
* response.
*
* <p>This class extends {@code AbstractResponse}, inheriting common response attributes such as
* success status, error messages, and informational messages. It introduces a single generic type
* parameter {@code <T>} which extends {@code ResponseEntity}, enforcing type consistency for the
* result attribute.
*
* <p>Primary Attribute: {@code result}: Represents the single entity result of the response. This
* is a generic type that ensures flexibility and adaptability for different entity models.
*
* @param <T> The type of the response entity that extends {@code ResponseEntity}.
*/
@EqualsAndHashCode(callSuper = true)
@Data
public abstract class AbstractSingleResponse<T extends ResponseEntity> extends AbstractResponse {
private T result;
}
@@ -0,0 +1,77 @@
package codes.thischwa.cf.model;
import java.util.Map;
import lombok.Data;
/**
* Represents a request model for paginated data.
*
* <p>This class encapsulates the page number and the number of items per page for a paginated
* request, along with utility methods for constructing and retrieving pagination parameters.
*
* <p>Key functionalities:
*
* <ul>
* <li>Creating a {@code PagingRequest} instance with specific pagination values using the {@code
* of} method.
* <li>Creating a default {@code PagingRequest} with predefined pagination values.
* <li>Retrieving pagination parameters as a key-value map.
* <li>Generating a query string representation for the pagination parameters.
* </ul>
*/
@Data
public class PagingRequest {
private int page;
private int perPage;
PagingRequest(int page, int perPage) {
this.page = page;
this.perPage = perPage;
}
/**
* Creates a new {@code PagingRequest} instance with the specified page number and items per page.
*
* @param page the page number to be requested
* @param perPage the number of items to be included per page
* @return a new {@code PagingRequest} instance with the provided parameters
*/
public static PagingRequest of(int page, int perPage) {
return new PagingRequest(page, perPage);
}
/**
* Creates a default {@code PagingRequest} instance with a page number set to 1 and a high number
* of items per page (5,000,000) to accommodate large dataset requests.
*
* @return a default {@code PagingRequest} instance with predefined pagination parameters
*/
public static PagingRequest defaultPaging() {
return new PagingRequest(1, 5000000);
}
/**
* Retrieves the pagination parameters in a key-value map format.
*
* @return a map containing the pagination parameters, where the key "page" indicates the current
* page number and the key "perPage" indicates the number of items per page.
*/
public Map<String, String> getPagingParams() {
return Map.of("page", String.valueOf(page), "perPage", String.valueOf(perPage));
}
/**
* Appends a query string with pagination parameters (page and perPage) to the provided endpoint.
*
* @param endpoint the base URL or API endpoint to which the query string will be appended
* @return the complete URL with the appended query string for pagination
*/
public String addQueryString(String endpoint) {
return endpoint + queryString(endpoint.contains("?"));
}
private String queryString(boolean add) {
String qs = "page=" + page + "&perPage=" + perPage;
return add ? "&" + qs : "?" + qs;
}
}
@@ -0,0 +1,59 @@
package codes.thischwa.cf.model;
import java.time.LocalDateTime;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.jetbrains.annotations.Nullable;
/**
* Represents a DNS record entity within a specific zone.
*
* <p>Attributes defined in this class include:
*
* <ul>
* <li>DNS record type such as "A" or "CNAME".
* <li>Name of the DNS record.
* <li>Content of the DNS record, such as an IP address.
* <li>Flags indicating whether the record is proxiable or proxied.
* <li>TTL (Time-To-Live) for the DNS record.
* <li>A locked status to indicate immutability of the record.
* <li>Zone-specific metadata including zone ID and name.
* <li>Timestamps for creation and modification.
* </ul>
*
* <p>Provides a static factory method {@code build} for creating a DNS record with specific
* attributes.
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class RecordEntity extends AbstractEntity {
private String type;
private String name;
private String content;
private Boolean proxiable;
private Boolean proxied;
private Integer ttl;
private Boolean locked;
@Nullable private String zoneId;
@Nullable private String zoneName;
@Nullable private LocalDateTime modifiedOn;
@Nullable private LocalDateTime createdOn;
/**
* Builds and returns a {@link RecordEntity} instance with the specified attributes.
*
* @param name the name of the DNS record
* @param type the {@link RecordType} of the DNS record
* @param ttl the time-to-live (TTL) value for the DNS record
* @param ip the content of the DNS record, typically an IP address
* @return a {@link RecordEntity} populated with the provided attributes
*/
public static RecordEntity build(String name, RecordType type, Integer ttl, String ip) {
RecordEntity rec = new RecordEntity();
rec.setName(name);
rec.setType(type.getType());
rec.setTtl(ttl);
rec.setContent(ip);
return rec;
}
}
@@ -0,0 +1,4 @@
package codes.thischwa.cf.model;
/** Represents the API response of the Cloudflare API containing multiple DNS record entities. */
public class RecordMultipleResponse extends AbstractMultipleResponse<RecordEntity> {}
@@ -0,0 +1,4 @@
package codes.thischwa.cf.model;
/** Represents the API response of the Cloudflare API containing a single DNS record entity. */
public class RecordSingleResponse extends AbstractSingleResponse<RecordEntity> {}
@@ -0,0 +1,46 @@
package codes.thischwa.cf.model;
import lombok.Getter;
/**
* Enum representing various DNS record types.
*
* <p>Each constant in this enum corresponds to a specific DNS record type, such as "A", "AAAA",
* "CNAME", or "TXT". This enum provides a means to standardize the representation of these record
* types throughout the application while allowing easy retrieval of their string representation.
*/
@Getter
public enum RecordType {
A("A"),
AAAA("AAAA"),
CAA("CAA"),
CERT("CERT"),
CNAME("CNAME"),
DNSKEY("DNSKEY"),
DS("DS"),
HTTPS("HTTPS"),
LOC("LOC"),
MX("MX"),
NAPTR("NAPTR"),
NS("NS"),
OPENPGPKEY("OPENPGPKEY"),
PTR("PTR"),
SMIMEA("SMIMEA"),
SRV("SRV"),
SSHFP("SSHFP"),
SVCB("SVCB"),
TLSA("TLSA"),
TXT("TXT"),
URI("URI");
private final String type;
RecordType(String type) {
this.type = type;
}
@Override
public String toString() {
return getType();
}
}
@@ -0,0 +1,17 @@
package codes.thischwa.cf.model;
/**
* Represents a contract for entities that have a unique identifier.
*
* <p>This interface is primarily used as a common abstraction for domain objects that require a
* unique identifier, enabling type consistency and code reusability.
*/
public interface ResponseEntity {
/**
* Retrieves the unique identifier of the entity.
*
* @return the unique identifier as a String
*/
String getId();
}
@@ -0,0 +1,26 @@
package codes.thischwa.cf.model;
import lombok.Data;
/**
* Represents metadata for paginated results.
*
* <p>This class contains information about the current page, page size, total pages, and result
* counts, which can be utilized in managing and navigating through paginated data.
*
* <ul>
* <li><b>page:</b> The current page number.
* <li><b>perPage:</b> The number of results per page.
* <li><b>totalPages:</b> The total number of pages available.
* <li><b>count:</b> The number of results on the current page.
* <li><b>totalCount:</b> The total number of results across all pages.
* </ul>
*/
@Data
public class ResultInfo {
private int page;
private int perPage;
private int totalPages;
private int count;
private int totalCount;
}
@@ -0,0 +1,40 @@
package codes.thischwa.cf.model;
import java.time.LocalDateTime;
import java.util.Set;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Represents a DNS zone entity in the Cloudflare DNS system. <br>
*
* <p>This class encapsulates all relevant data and metadata associated with a zone, including but
* not limited to the following attributes:
*
* <ul>
* <li>Zone name.
* <li>Development mode status.
* <li>Active and original name servers linked to the zone.
* <li>Timestamps indicating when the zone was created, modified, or activated.
* <li>Current operational status of the zone (e.g., active, inactive).
* <li>Boolean flag indicating whether the zone is paused.
* <li>Zone type, representing the nature of the DNS zone (e.g., full, partial).
* </ul>
*
* <p>This class extends {@link AbstractEntity} to inherit basic entity properties and to provide a
* consistent interface across domain models.
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ZoneEntity extends AbstractEntity {
private String name;
private Integer developmentMode;
private Set<String> nameServers;
private Set<String> originalNameServers;
private LocalDateTime createdOn;
private LocalDateTime modifiedOn;
private LocalDateTime activatedOn;
private String status;
private Boolean paused;
private String type;
}
@@ -0,0 +1,4 @@
package codes.thischwa.cf.model;
/** Represents a response model that contains multiple {@link ZoneEntity} instances. */
public class ZoneMultipleResponse extends AbstractMultipleResponse<ZoneEntity> {}
@@ -0,0 +1,2 @@
/** The model of CloudflareDNS-java. */
package codes.thischwa.cf.model;
@@ -0,0 +1,2 @@
/** The base package of CloudflareDNS-java. */
package codes.thischwa.cf;