mirror of
https://github.com/Sheldan/abstracto.git
synced 2026-01-21 07:10:26 +00:00
added a first version of java doc for the templating module
This commit is contained in:
@@ -12,6 +12,9 @@ import org.springframework.ui.freemarker.FreeMarkerConfigurationFactory;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Configuration bean used to provide the {@link Configuration} bean to the spring context.
|
||||
*/
|
||||
@org.springframework.context.annotation.Configuration
|
||||
public class FreemarkerConfiguration {
|
||||
|
||||
@@ -24,6 +27,12 @@ public class FreemarkerConfiguration {
|
||||
@Autowired
|
||||
private InstantMethod instantMethod;
|
||||
|
||||
/**
|
||||
* Creates a {@link Configuration} bean with the appropriate configuration which includes:
|
||||
* The correct compatibility version and the provided formatter methods to be used in the templates.
|
||||
* The encoding of the templates is set to UTF-8.
|
||||
* @return A configured {@link Configuration} bean according to the configuration
|
||||
*/
|
||||
@Bean
|
||||
public Configuration freeMarkerConfiguration() throws IOException, TemplateException {
|
||||
FreeMarkerConfigurationFactory factory = new FreeMarkerConfigurationFactory();
|
||||
|
||||
@@ -16,6 +16,10 @@ import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Loads the available templates from the class path and uploads them to the database, overriding existing templates in the process.
|
||||
* This will load all *.ftl files at any level within a folder named 'templates' in the resources folder.
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class TemplateSeedDataLoader {
|
||||
@@ -26,6 +30,10 @@ public class TemplateSeedDataLoader {
|
||||
@Autowired
|
||||
private TemplateManagementService service;
|
||||
|
||||
/**
|
||||
* Is executed when the spring context is started, this will load all templates from the class path and
|
||||
* store them in the database overriding the existing ones in the process.
|
||||
*/
|
||||
@EventListener
|
||||
public void handleContextRefreshEvent(ContextRefreshedEvent ctxStartEvt) {
|
||||
log.info("Updating templates.");
|
||||
|
||||
@@ -11,6 +11,10 @@ import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
/**
|
||||
* Loads the the template from the database to be used by Freemarker. This bean is also used when the templates within
|
||||
* templates are used.
|
||||
*/
|
||||
@Component
|
||||
public class DatabaseTemplateLoader implements TemplateLoader {
|
||||
|
||||
@@ -20,6 +24,11 @@ public class DatabaseTemplateLoader implements TemplateLoader {
|
||||
@Autowired
|
||||
private TemplateManagementService templateManagementService;
|
||||
|
||||
/**
|
||||
* Loads the content of the template object
|
||||
* @param s The key of the template to load
|
||||
* @return The template loaded from the database
|
||||
*/
|
||||
@Override
|
||||
public Object findTemplateSource(String s) throws IOException {
|
||||
return templateManagementService.getTemplateByKey(s);
|
||||
@@ -36,6 +45,12 @@ public class DatabaseTemplateLoader implements TemplateLoader {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the content of the template from the retrieved {@link Template} object
|
||||
* @param o The retrieved {@link Template} object from the database
|
||||
* @param s The encoding of the object
|
||||
* @return The content of the template as a String reader
|
||||
*/
|
||||
@Override
|
||||
public Reader getReader(Object o, String s) throws IOException {
|
||||
return new StringReader(((Template) o).getContent());
|
||||
|
||||
@@ -12,12 +12,23 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Method used to format the {@link Duration} object, as its not natively supported by Freemarker.
|
||||
* This method only accepts a single {@link Duration} object as the first parameter.
|
||||
*/
|
||||
@Component
|
||||
public class DurationMethod implements TemplateMethodModelEx {
|
||||
|
||||
@Autowired
|
||||
private TemplateService service;
|
||||
|
||||
/**
|
||||
* This method expects a single Duration object in the list of arguments. It will throw a {@link TemplateModelException}
|
||||
* otherwise
|
||||
* @param arguments The parameters passed to this method, should be only a single duration.
|
||||
* @return The string representation of the {@link Duration} object
|
||||
* @throws TemplateModelException
|
||||
*/
|
||||
@Override
|
||||
public Object exec(List arguments) throws TemplateModelException {
|
||||
if (arguments.size() != 1) {
|
||||
@@ -30,6 +41,7 @@ public class DurationMethod implements TemplateMethodModelEx {
|
||||
Duration duration = (Duration) wrappedObject;
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
// upgrading to java 9 makes this nicer
|
||||
// todo refactor to use a single template, instead of multiple ones
|
||||
long days = duration.toDays();
|
||||
if(days > 0) {
|
||||
stringBuilder.append(service.renderTemplate("day", getParam(days)));
|
||||
@@ -51,6 +63,11 @@ public class DurationMethod implements TemplateMethodModelEx {
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the parameter passed to the template for rendering the single time unit template.
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
private HashMap<String, Object> getParam(Long value) {
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
params.put("amount", value);
|
||||
|
||||
@@ -11,15 +11,23 @@ import org.springframework.stereotype.Component;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Formats the passed {@link Instant} object with the given Formatter. The format will be directly passed to {@link DateTimeFormatter}.
|
||||
*/
|
||||
@Component
|
||||
public class InstantMethod implements TemplateMethodModelEx {
|
||||
|
||||
@Autowired
|
||||
private TemplateService service;
|
||||
|
||||
/**
|
||||
* Renders the given {@link Instant} object with the given String. Internally {@link DateTimeFormatter} will be used.
|
||||
* @param arguments The list of arguments, first element must be an {@link Instant} and the second one must be a {@link String}.
|
||||
* @return The formatted {@link Instant} as a string.
|
||||
* @throws TemplateModelException If there are less or more arguments in the list and if the first element is not a {@link Instant}.
|
||||
*/
|
||||
@Override
|
||||
public Object exec(List arguments) throws TemplateModelException {
|
||||
if (arguments.size() != 2) {
|
||||
|
||||
@@ -4,9 +4,22 @@ package dev.sheldan.abstracto.templating.model;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter @Setter
|
||||
/**
|
||||
* The container class used to deserialize the embed configuration for the author in {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class EmbedAuthor {
|
||||
/**
|
||||
* The name used in the {@link net.dv8tion.jda.api.entities.MessageEmbed} author
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* The URL used in the {@link net.dv8tion.jda.api.entities.MessageEmbed} author
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* The picture used as the avatar of the author in {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
private String avatar;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,22 @@ package dev.sheldan.abstracto.templating.model;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Setter @Getter
|
||||
/**
|
||||
* The container class used to deserialize the embed configuration for the color in {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
public class EmbedColor {
|
||||
/**
|
||||
* The red part of RGB
|
||||
*/
|
||||
private Integer r;
|
||||
/**
|
||||
* The green part of RGB
|
||||
*/
|
||||
private Integer g;
|
||||
/**
|
||||
* The blue part of RGB
|
||||
*/
|
||||
private Integer b;
|
||||
}
|
||||
|
||||
@@ -6,16 +6,51 @@ import lombok.Setter;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Getter @Setter
|
||||
/**
|
||||
* The whole container object used to deserialize the whole embed configuration
|
||||
* https://raw.githubusercontent.com/DV8FromTheWorld/JDA/assets/assets/docs/embeds/07-addField.png
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class EmbedConfiguration {
|
||||
/**
|
||||
* The {@link EmbedAuthor} object holding the configuration for the author of the {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
private EmbedAuthor author;
|
||||
/**
|
||||
* The {@link EmbedTitle} object holding the configuration for the title in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
private EmbedTitle title;
|
||||
/**
|
||||
* The {@link EmbedColor} object holding the configuration for the color in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
private EmbedColor color;
|
||||
/**
|
||||
* The description which is going to be used in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* The link to the image used as a thumbnail in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
private String thumbnail;
|
||||
/**
|
||||
* The link to the image used as the image in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
private String imageUrl;
|
||||
/**
|
||||
* The collection containing all the objects containing the configuration for the fields in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
private List<EmbedField> fields;
|
||||
/**
|
||||
* The {@link EmbedFooter} object holding the configuration for the footer in {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
private EmbedFooter footer;
|
||||
/**
|
||||
* Thhe {@link OffsetDateTime} object used as the time stamp in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
private OffsetDateTime timeStamp;
|
||||
/**
|
||||
* The message which is posted along the {@link net.dv8tion.jda.api.entities.MessageEmbed} as a normal message.
|
||||
*/
|
||||
private String additionalMessage;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,23 @@ package dev.sheldan.abstracto.templating.model;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter @Setter
|
||||
/**
|
||||
* The container class used to deserialize the embed configuration for a field in {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class EmbedField {
|
||||
/**
|
||||
* The name of the field to be set, must not be null or empty
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* The value of the field to be set, must not be null or empty
|
||||
*/
|
||||
private String value;
|
||||
/**
|
||||
* Whether or not the field should be rendered inline within the {@link net.dv8tion.jda.api.entities.MessageEmbed}.
|
||||
* This means, if multiple fields can be put on the same height in the {@link net.dv8tion.jda.api.entities.MessageEmbed} this will be done by discord.
|
||||
*/
|
||||
private Boolean inline;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,18 @@ package dev.sheldan.abstracto.templating.model;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter @Setter
|
||||
/**
|
||||
* The container object used to deserialize the embed configuration for the footer in the {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class EmbedFooter {
|
||||
/**
|
||||
* The text which is going to be used as the footer text
|
||||
*/
|
||||
private String text;
|
||||
/**
|
||||
* The link to the image which is going to be used as the icon of the footer
|
||||
*/
|
||||
private String icon;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,18 @@ package dev.sheldan.abstracto.templating.model;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter @Setter
|
||||
/**
|
||||
* The container class used to deserialize the embed configuration used in the title in {@link net.dv8tion.jda.api.entities.MessageEmbed}
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class EmbedTitle {
|
||||
/**
|
||||
* The text which is going to be used as the title of the embed
|
||||
*/
|
||||
private String title;
|
||||
/**
|
||||
* The link which is used when clicking on the title of the embed
|
||||
*/
|
||||
private String url;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package dev.sheldan.abstracto.templating.loading;
|
||||
package dev.sheldan.abstracto.templating.repository;
|
||||
|
||||
import dev.sheldan.abstracto.templating.model.database.Template;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* Repository used to load the templates from the database.
|
||||
*/
|
||||
@Repository
|
||||
public interface TemplateRepository extends JpaRepository<Template, String> {
|
||||
}
|
||||
@@ -19,6 +19,9 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Bean used to render a template, identified by a key, with the passed model.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class TemplateServiceBean implements TemplateService {
|
||||
@@ -30,12 +33,29 @@ public class TemplateServiceBean implements TemplateService {
|
||||
private Gson gson;
|
||||
|
||||
|
||||
/**
|
||||
* Formats the passed passed count with the embed used for formatting pages.
|
||||
* @param count The index of the page you want formated.
|
||||
* @return The rendered template as a string object
|
||||
*/
|
||||
private String getPageString(Integer count) {
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
params.put("count", count);
|
||||
return renderTemplateWithMap("embed_page_count", params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the key which gets suffixed with '_embed' and this retrives the embed configuration. This configuration is then rendered
|
||||
* and deserialized with GSON into a {@link EmbedConfiguration} object. This object is then rendered into a {@link MessageToSend} and returned.
|
||||
* If the individual element do not fit in an embed, for example, if the field count is to high, another embed will be created in the {@link MessageToSend} object.
|
||||
* If multiple embeds are necessary to provide what the {@link EmbedConfiguration} wanted, this method will automatically set the footer of the additional {@link MessageEmbed}
|
||||
* with a formatted page count.
|
||||
* This method will to try its best to provided a message which can be handled by discord without rejecting it. Besides that, the content from the rendered template, will be passed
|
||||
* into the {@link EmbedBuilder} directly.
|
||||
* @param key The key of the embed template to be used for rendering.
|
||||
* @param model The model providing the properties to be used for rendering
|
||||
* @return The {@link MessageToSend} object which is properly split up in order to be send to discord.
|
||||
*/
|
||||
@Override
|
||||
public MessageToSend renderEmbedTemplate(String key, Object model) {
|
||||
String embedConfig = this.renderTemplate(key + "_embed", model);
|
||||
@@ -100,6 +120,12 @@ public class TemplateServiceBean implements TemplateService {
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enlarges the passed list of builders, if the passed index is not yet available within the list.
|
||||
* When a new builder is needed, this will automatically set the footer with a page indicator.
|
||||
* @param builders The current list of {@link EmbedBuilder} builders used.
|
||||
* @param neededIndex The desired index in the list which should be available for using.
|
||||
*/
|
||||
private void extendIfNecessary(List<EmbedBuilder> builders, double neededIndex) {
|
||||
if(neededIndex > builders.size() - 1) {
|
||||
for (int i = builders.size(); i < neededIndex + 1; i++) {
|
||||
@@ -110,6 +136,12 @@ public class TemplateServiceBean implements TemplateService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the template identified by the key with the passed {@link HashMap}
|
||||
* @param key The key of the template to be rendered.
|
||||
* @param parameters The {@link HashMap} to be used as the parameters for the template
|
||||
* @return The rendered template as a string
|
||||
*/
|
||||
@Override
|
||||
public String renderTemplateWithMap(String key, HashMap<String, Object> parameters) {
|
||||
try {
|
||||
@@ -120,6 +152,12 @@ public class TemplateServiceBean implements TemplateService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the template identified by the key with the passed object as model
|
||||
* @param key The key of the template to be rendered
|
||||
* @param model The object containing the model to be used in the template
|
||||
* @return The rendered template as a string
|
||||
*/
|
||||
@Override
|
||||
public String renderTemplate(String key, Object model) {
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package dev.sheldan.abstracto.templating.service.management;
|
||||
|
||||
import dev.sheldan.abstracto.templating.loading.TemplateRepository;
|
||||
import dev.sheldan.abstracto.templating.repository.TemplateRepository;
|
||||
import dev.sheldan.abstracto.templating.model.database.Template;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -8,6 +8,10 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* ManagementService bean used to retrieve the templates by key from the database.
|
||||
* This class uses the {@link TemplateRepository} bean to laod the {@link Template} objects.
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class TemplateManagementServiceBean implements TemplateManagementService {
|
||||
@@ -22,7 +26,7 @@ public class TemplateManagementServiceBean implements TemplateManagementService
|
||||
|
||||
@Override
|
||||
public boolean templateExists(String key) {
|
||||
return getTemplateByKey(key) != null;
|
||||
return repository.existsById(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
package dev.sheldan.abstracto.templating;
|
||||
|
||||
/**
|
||||
* An interface to be used on objects, which should be able to be processable by the template engine.
|
||||
* This contains a template key and the model which is used when rendering this template.
|
||||
*/
|
||||
public interface Templatable {
|
||||
/**
|
||||
* The template key to be used to render this object.
|
||||
* @return The template key as string
|
||||
*/
|
||||
String getTemplateName();
|
||||
|
||||
/**
|
||||
* The model to be used to render this template.
|
||||
* @return The model containing the attributes to be used for rendering.
|
||||
*/
|
||||
Object getTemplateModel();
|
||||
}
|
||||
|
||||
@@ -7,10 +7,19 @@ import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A full message which is ready to be send. This message can contain an arbitrary amount of embeds and a string message.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class MessageToSend {
|
||||
/**
|
||||
* The collections of embeds to be send. The first embed is in the same message as the string message.
|
||||
*/
|
||||
private List<MessageEmbed> embeds;
|
||||
/**
|
||||
* The string content to be used in the first message.
|
||||
*/
|
||||
private String message;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ import javax.persistence.*;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents the template stored in the database.
|
||||
*/
|
||||
@Builder
|
||||
@Entity
|
||||
@NoArgsConstructor
|
||||
@@ -19,10 +22,16 @@ import java.util.Objects;
|
||||
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
|
||||
public class Template {
|
||||
|
||||
/**
|
||||
* The globally unique key of the template
|
||||
*/
|
||||
@Id
|
||||
@Getter
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* The content of the template
|
||||
*/
|
||||
@Getter
|
||||
@Column(length = 4000)
|
||||
private String content;
|
||||
|
||||
@@ -4,8 +4,32 @@ import dev.sheldan.abstracto.templating.model.MessageToSend;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Provides methods to render templates with the appropriate model.
|
||||
*/
|
||||
public interface TemplateService {
|
||||
/**
|
||||
* The template containing a embed definition which should be rendered. The key must refer to an existing template and the supplied model will be used when rendering.
|
||||
* This creates a {@link MessageToSend} object containing the rendered template and might result in multiple embeds.
|
||||
* @param key The key of the embed template to be used for rendering.
|
||||
* @param model The model providing the properties to be used for rendering
|
||||
* @return A fully rendered message containing the content of the template and might contain multiple embeds.
|
||||
*/
|
||||
MessageToSend renderEmbedTemplate(String key, Object model);
|
||||
|
||||
/**
|
||||
* Renders the template identified by the key with the given {@link HashMap} used as model and returns the value as a string
|
||||
* @param key The key of the template to be rendered.
|
||||
* @param parameters The {@link HashMap} to be used as the parameters for the template
|
||||
* @return The template rendered as string.
|
||||
*/
|
||||
String renderTemplateWithMap(String key, HashMap<String, Object> parameters);
|
||||
|
||||
/**
|
||||
* Renders the template identified by the key with the given model and returns the value as a string
|
||||
* @param key The key of the template to be rendered
|
||||
* @param model The object containing the model to be used in the template
|
||||
* @return The template rendered as string.
|
||||
*/
|
||||
String renderTemplate(String key, Object model);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,29 @@ package dev.sheldan.abstracto.templating.service.management;
|
||||
|
||||
import dev.sheldan.abstracto.templating.model.database.Template;
|
||||
|
||||
/**
|
||||
* Provides methods to access the stored templates.
|
||||
*/
|
||||
public interface TemplateManagementService {
|
||||
/**
|
||||
* Retrieves the template identified by the key.
|
||||
* @param key They template key to search for
|
||||
* @return The {@link Template} identified by the key, if it exists.
|
||||
*/
|
||||
Template getTemplateByKey(String key);
|
||||
|
||||
/**
|
||||
* Checks whether or not the template exists in the database.
|
||||
* @param key They key of the template to search for
|
||||
* @return true, if the template exists and false otherwise
|
||||
*/
|
||||
boolean templateExists(String key);
|
||||
|
||||
/**
|
||||
* Creates a template identified by the key and with the provided content.
|
||||
* @param key They key of the template to create.
|
||||
* @param content The content the template should have
|
||||
* @return The created template in the database.
|
||||
*/
|
||||
Template createTemplate(String key, String content);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user