Implement YAML config, start SQLite implementation
A config.yml file has been added, to allow configuring server settings. It will be expanded with new settings in the future. Also, SQLite support has been added, with a "database.sqlite" file. A basic table with user/pass/userid columns has been added for testing purposes.
This commit is contained in:
parent
4ab01c3787
commit
ce172c3dc4
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,4 @@
|
|||||||
.idea/
|
.idea/
|
||||||
target/
|
target/
|
||||||
|
run/
|
||||||
|
*.ignore
|
||||||
|
19
pom.xml
19
pom.xml
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>net.mindoverflow.webmarker</groupId>
|
<groupId>net.mindoverflow.webmarker</groupId>
|
||||||
<artifactId>WebMarker</artifactId>
|
<artifactId>WebMarker</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>0.0.1-alpha</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -16,12 +16,27 @@
|
|||||||
<version>1.13.1</version>
|
<version>1.13.1</version>
|
||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ro.pippo</groupId>
|
<groupId>ro.pippo</groupId>
|
||||||
<artifactId>pippo-controller</artifactId>
|
<artifactId>pippo-controller</artifactId>
|
||||||
<version>1.13.1</version>
|
<version>1.13.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>2.0.0-alpha1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xerial</groupId>
|
||||||
|
<artifactId>sqlite-jdbc</artifactId>
|
||||||
|
<version>3.32.3.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
<version>1.21</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -1,13 +1,39 @@
|
|||||||
package net.mindoverflow.webmarker;
|
package net.mindoverflow.webmarker;
|
||||||
|
|
||||||
import net.mindoverflow.webmarker.server.WebApplication;
|
import net.mindoverflow.webmarker.runnables.StatsRunnable;
|
||||||
|
import net.mindoverflow.webmarker.utils.Cached;
|
||||||
|
import net.mindoverflow.webmarker.utils.sql.SQLiteManager;
|
||||||
|
import net.mindoverflow.webmarker.webserver.WebApplication;
|
||||||
|
import net.mindoverflow.webmarker.utils.config.ConfigEntries;
|
||||||
|
import net.mindoverflow.webmarker.utils.config.ConfigManager;
|
||||||
|
import net.mindoverflow.webmarker.utils.messaging.Messenger;
|
||||||
import ro.pippo.core.Pippo;
|
import ro.pippo.core.Pippo;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class WebMarker {
|
public class WebMarker {
|
||||||
|
|
||||||
|
private static final Messenger msg = new Messenger();
|
||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
|
ConfigManager.checkFiles();
|
||||||
|
ConfigManager.loadFiles();
|
||||||
|
Cached.sqlManager = new SQLiteManager();
|
||||||
|
Cached.sqlManager.initialize();
|
||||||
|
|
||||||
|
msg.info("Loading Pippo framework...");
|
||||||
final Pippo pippo = new Pippo(new WebApplication());
|
final Pippo pippo = new Pippo(new WebApplication());
|
||||||
pippo.start();
|
msg.info("Loaded Pippo framework.");
|
||||||
|
|
||||||
|
msg.info("Starting webserver...");
|
||||||
|
int port = (int) ConfigEntries.WEBSERVER_PORT.getValue();
|
||||||
|
pippo.start(port);
|
||||||
|
msg.info("Started webserver.");
|
||||||
|
|
||||||
|
ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
|
||||||
|
exec.scheduleAtFixedRate(new StatsRunnable(), 0, 5, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package net.mindoverflow.webmarker.runnables;
|
||||||
|
|
||||||
|
import net.mindoverflow.webmarker.utils.messaging.Messenger;
|
||||||
|
|
||||||
|
public class StatsRunnable implements Runnable {
|
||||||
|
|
||||||
|
Messenger msg = new Messenger();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
double usedRam = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1000000.0;
|
||||||
|
msg.info("Used RAM: " + usedRam + "MB");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package net.mindoverflow.webmarker.utils;
|
||||||
|
|
||||||
|
import net.mindoverflow.webmarker.utils.sql.SQLiteManager;
|
||||||
|
|
||||||
|
public class Cached {
|
||||||
|
|
||||||
|
public static SQLiteManager sqlManager;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package net.mindoverflow.webmarker.utils.config;
|
||||||
|
|
||||||
|
public enum ConfigEntries
|
||||||
|
{
|
||||||
|
WEBSERVER_PORT("port", 7344);
|
||||||
|
|
||||||
|
private final String path;
|
||||||
|
private Object value;
|
||||||
|
|
||||||
|
ConfigEntries(String path, Object value)
|
||||||
|
{
|
||||||
|
this.path = path;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(Object value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package net.mindoverflow.webmarker.utils.config;
|
||||||
|
|
||||||
|
import net.mindoverflow.webmarker.utils.messaging.MessageLevel;
|
||||||
|
import net.mindoverflow.webmarker.utils.messaging.Messenger;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class ConfigManager
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final Messenger msg = new Messenger();
|
||||||
|
|
||||||
|
public static void checkFiles()
|
||||||
|
{
|
||||||
|
msg.send(MessageLevel.INFO, "Checking configuration files: ");
|
||||||
|
|
||||||
|
File configFile = FileType.CONFIG_YAML.file;
|
||||||
|
|
||||||
|
if(!configFile.exists())
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
InputStream stream = ConfigManager.class.getResourceAsStream("/" + configFile.getName());
|
||||||
|
Files.copy(stream, Paths.get(configFile.getAbsolutePath()));
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
msg.sendLine(MessageLevel.NONE, "FATAL");
|
||||||
|
e.printStackTrace();
|
||||||
|
msg.critical("Error creating missing file!");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.sendLine(MessageLevel.NONE, "OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadFiles()
|
||||||
|
{
|
||||||
|
msg.send(MessageLevel.INFO, "Loading configuration files: ");
|
||||||
|
|
||||||
|
try {
|
||||||
|
InputStream stream = new FileInputStream(FileType.CONFIG_YAML.file);
|
||||||
|
Yaml yaml = new Yaml();
|
||||||
|
FileType.CONFIG_YAML.yaml = yaml.load(stream);
|
||||||
|
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
msg.critical("Error loading config file!");
|
||||||
|
}
|
||||||
|
|
||||||
|
for(ConfigEntries entry : ConfigEntries.values())
|
||||||
|
{
|
||||||
|
String path = entry.getPath();
|
||||||
|
Object value = FileType.CONFIG_YAML.yaml.get(path);
|
||||||
|
entry.setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.sendLine(MessageLevel.NONE, "OK");
|
||||||
|
|
||||||
|
for(ConfigEntries entry : ConfigEntries.values())
|
||||||
|
{ msg.info(entry.name() + ": " + entry.getValue()); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getJarAbsolutePath()
|
||||||
|
{
|
||||||
|
|
||||||
|
Path currentPath = Paths.get("");
|
||||||
|
return currentPath.toAbsolutePath().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FileType
|
||||||
|
{
|
||||||
|
DATABASE_FILE(new File(getJarAbsolutePath() + File.separator + "database.sqlite"), null),
|
||||||
|
CONFIG_YAML(new File(getJarAbsolutePath() + File.separator + "config.yml"), new HashMap<>());
|
||||||
|
|
||||||
|
public File file;
|
||||||
|
public HashMap<String, Object> yaml;
|
||||||
|
FileType(File givenFile, HashMap<String, Object> yamlConfig)
|
||||||
|
{
|
||||||
|
file = givenFile;
|
||||||
|
yaml = yamlConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package net.mindoverflow.webmarker.utils.messaging;
|
||||||
|
|
||||||
|
public enum MessageLevel {
|
||||||
|
|
||||||
|
INFO("[INFO] "),
|
||||||
|
WARNING("[WARNING] "),
|
||||||
|
CRITICAL("[CRITICAL] "),
|
||||||
|
NONE("");
|
||||||
|
|
||||||
|
private String prefix;
|
||||||
|
|
||||||
|
MessageLevel(String prefix)
|
||||||
|
{
|
||||||
|
this.prefix = prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrefix()
|
||||||
|
{
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package net.mindoverflow.webmarker.utils.messaging;
|
||||||
|
|
||||||
|
public class Messenger {
|
||||||
|
|
||||||
|
public void info(String message)
|
||||||
|
{ sendLine(MessageLevel.INFO, message); }
|
||||||
|
|
||||||
|
public void warn(String message)
|
||||||
|
{ sendLine(MessageLevel.WARNING, message); }
|
||||||
|
|
||||||
|
public void critical(String message)
|
||||||
|
{ sendLine(MessageLevel.CRITICAL, message); }
|
||||||
|
|
||||||
|
public void sendLine(MessageLevel level, String message)
|
||||||
|
{ System.out.println(level.getPrefix() + message); }
|
||||||
|
|
||||||
|
public void send(MessageLevel level, String message)
|
||||||
|
{ System.out.print(level.getPrefix() + message); }
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package net.mindoverflow.webmarker.utils.sql;
|
||||||
|
|
||||||
|
import net.mindoverflow.webmarker.utils.sql.primitives.SQLColumn;
|
||||||
|
import net.mindoverflow.webmarker.utils.sql.primitives.SQLDataType;
|
||||||
|
|
||||||
|
public enum FDatabaseColumn
|
||||||
|
{
|
||||||
|
|
||||||
|
ALL(new SQLColumn("*"), null),
|
||||||
|
USERNAME(new SQLColumn("username"), SQLDataType.VARCHAR_128),
|
||||||
|
PASSWORD(new SQLColumn("password"), SQLDataType.VARCHAR_128),
|
||||||
|
USERID(new SQLColumn("userid"), SQLDataType.VARCHAR_128),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
private final SQLColumn column;
|
||||||
|
private final SQLDataType type;
|
||||||
|
|
||||||
|
FDatabaseColumn(SQLColumn column, SQLDataType type)
|
||||||
|
{
|
||||||
|
this.column = column;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQLColumn getColumn()
|
||||||
|
{ return column; }
|
||||||
|
|
||||||
|
public SQLDataType getDataType()
|
||||||
|
{ return type; }
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package net.mindoverflow.webmarker.utils.sql;
|
||||||
|
|
||||||
|
|
||||||
|
import net.mindoverflow.webmarker.utils.sql.primitives.SQLTable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public enum FDatabaseTable
|
||||||
|
{
|
||||||
|
USERS(new SQLTable("users", // table name
|
||||||
|
new ArrayList<>(){{ // columns
|
||||||
|
add(FDatabaseColumn.USERID);
|
||||||
|
add(FDatabaseColumn.USERNAME);
|
||||||
|
add(FDatabaseColumn.PASSWORD);
|
||||||
|
}})),
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
private final SQLTable table;
|
||||||
|
|
||||||
|
FDatabaseTable(SQLTable table)
|
||||||
|
{ this.table = table; }
|
||||||
|
|
||||||
|
public SQLTable getTable()
|
||||||
|
{ return table; }
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
package net.mindoverflow.webmarker.utils.sql;
|
||||||
|
|
||||||
|
import net.mindoverflow.webmarker.utils.config.ConfigManager;
|
||||||
|
import net.mindoverflow.webmarker.utils.messaging.MessageLevel;
|
||||||
|
import net.mindoverflow.webmarker.utils.messaging.Messenger;
|
||||||
|
import net.mindoverflow.webmarker.utils.sql.primitives.SQLDataType;
|
||||||
|
import net.mindoverflow.webmarker.utils.sql.primitives.SQLTable;
|
||||||
|
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SQLiteManager {
|
||||||
|
|
||||||
|
private final Messenger msg = new Messenger();
|
||||||
|
private Connection connection;
|
||||||
|
|
||||||
|
public void initialize()
|
||||||
|
{
|
||||||
|
msg.send(MessageLevel.INFO, "Connecting to SQLite database: ");
|
||||||
|
|
||||||
|
String url = "jdbc:sqlite:" + ConfigManager.FileType.DATABASE_FILE.file.getAbsolutePath();
|
||||||
|
try {
|
||||||
|
connection = DriverManager.getConnection(url);
|
||||||
|
if(connection != null && !connection.isClosed())
|
||||||
|
{
|
||||||
|
msg.sendLine(MessageLevel.NONE, "OK");
|
||||||
|
doInitialSetup();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (SQLException throwables) {
|
||||||
|
msg.sendLine(MessageLevel.NONE, "FATAL");
|
||||||
|
throwables.printStackTrace();
|
||||||
|
msg.critical("Error connecting to SQLite database!");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doInitialSetup()
|
||||||
|
{
|
||||||
|
for(FDatabaseTable currentTable : FDatabaseTable.values())
|
||||||
|
{
|
||||||
|
if(!tableExists(currentTable))
|
||||||
|
{
|
||||||
|
msg.info("Creating SQLite table `" + currentTable.getTable().getTableSQLName() + "`");
|
||||||
|
createTable(currentTable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tableExists(FDatabaseTable tableEnum)
|
||||||
|
{
|
||||||
|
String name = tableEnum.getTable().getTableSQLName();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DatabaseMetaData meta = connection.getMetaData();
|
||||||
|
ResultSet result = meta.getTables(null, null, name, null);
|
||||||
|
while(result.next())
|
||||||
|
{
|
||||||
|
String tableName = result.getString("TABLE_NAME");
|
||||||
|
if(tableName != null && tableName.equals(name.toLowerCase())) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (SQLException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
msg.critical("Error checking SQLite table " + name + "!");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTable(FDatabaseTable tableEnum)
|
||||||
|
{
|
||||||
|
SQLTable table = tableEnum.getTable();
|
||||||
|
List<FDatabaseColumn> columns = table.getColumns();
|
||||||
|
List<SQLDataType> dataTypes = table.getDataTypes();
|
||||||
|
|
||||||
|
StringBuilder query = new StringBuilder("CREATE TABLE IF NOT EXISTS ").append(table.getTableSQLName()).append(" (");
|
||||||
|
|
||||||
|
int pos = 0;
|
||||||
|
for(FDatabaseColumn column : columns)
|
||||||
|
{
|
||||||
|
query.append(column.getColumn().getColumnSQLName()).append(" ").append(dataTypes.get(pos).getSQLName());
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
if(pos < columns.size()) // we don't want to append a colon to the last entry
|
||||||
|
{ query.append(", "); }
|
||||||
|
}
|
||||||
|
query.append(");");
|
||||||
|
executeUpdate(query.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeUpdate(String query)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(connection == null || connection.isClosed())
|
||||||
|
{
|
||||||
|
msg.critical("Lost connection to SQLite database!");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Statement statement = connection.createStatement();
|
||||||
|
statement.executeUpdate(query);
|
||||||
|
} catch (SQLException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
msg.critical("Error executing SQLite update!");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package net.mindoverflow.webmarker.utils.sql.primitives;
|
||||||
|
|
||||||
|
public class SQLColumn {
|
||||||
|
|
||||||
|
private final String columnName;
|
||||||
|
|
||||||
|
public SQLColumn(String name)
|
||||||
|
{ columnName = name; }
|
||||||
|
|
||||||
|
public String getColumnSQLName()
|
||||||
|
{ return columnName; }
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package net.mindoverflow.webmarker.utils.sql.primitives;
|
||||||
|
|
||||||
|
public enum SQLDataType
|
||||||
|
{
|
||||||
|
INTEGER("INTEGER NOT NULL"),
|
||||||
|
VARCHAR_128("VARCHAR(128)"),
|
||||||
|
TEXT("TEXT"),
|
||||||
|
DATETIME("DATETIME"),
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
private String sqlName;
|
||||||
|
SQLDataType(String sqlName)
|
||||||
|
{ this.sqlName = sqlName; }
|
||||||
|
|
||||||
|
public String getSQLName()
|
||||||
|
{ return this.sqlName; }
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package net.mindoverflow.webmarker.utils.sql.primitives;
|
||||||
|
|
||||||
|
import net.mindoverflow.webmarker.utils.sql.FDatabaseColumn;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SQLTable
|
||||||
|
{
|
||||||
|
private final String tableName;
|
||||||
|
private final List<FDatabaseColumn> columns;
|
||||||
|
private final List<SQLDataType> columnTypes = new ArrayList<>();
|
||||||
|
|
||||||
|
public SQLTable(String tableName, List<FDatabaseColumn> columns)
|
||||||
|
{
|
||||||
|
this.tableName = tableName;
|
||||||
|
this.columns = columns;
|
||||||
|
|
||||||
|
for(FDatabaseColumn column : columns)
|
||||||
|
{ columnTypes.add(column.getDataType()); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTableSQLName()
|
||||||
|
{ return tableName; }
|
||||||
|
|
||||||
|
public List<FDatabaseColumn> getColumns()
|
||||||
|
{ return columns; }
|
||||||
|
|
||||||
|
public List<SQLDataType> getDataTypes()
|
||||||
|
{ return columnTypes; }
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package net.mindoverflow.webmarker.server;
|
package net.mindoverflow.webmarker.webserver;
|
||||||
|
|
||||||
import net.mindoverflow.webmarker.utils.URLMap;
|
import net.mindoverflow.webmarker.utils.URLMap;
|
||||||
import ro.pippo.controller.ControllerApplication;
|
import ro.pippo.controller.ControllerApplication;
|
||||||
@ -46,6 +46,13 @@ public class WebApplication extends ControllerApplication {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
POST("/post", routeContext -> {
|
||||||
|
int userId = routeContext.getParameter("id").toInt();
|
||||||
|
String url = routeContext.getParameter("url").toString();
|
||||||
|
|
||||||
|
routeContext.send("AAAAAAAAAAAAA " + url);
|
||||||
|
});
|
||||||
|
|
||||||
ANY("/.*", new TrailingSlashHandler(false)); // remove trailing slash
|
ANY("/.*", new TrailingSlashHandler(false)); // remove trailing slash
|
||||||
}
|
}
|
||||||
}
|
}
|
1
src/main/resources/config.yml
Normal file
1
src/main/resources/config.yml
Normal file
@ -0,0 +1 @@
|
|||||||
|
port: 7344
|
Loading…
Reference in New Issue
Block a user