mirror of
https://github.com/MarginaliaSearch/MarginaliaSearch.git
synced 2025-02-24 05:18:58 +00:00
(control) Add basic api key management
This commit is contained in:
parent
9979c9defe
commit
63e857f7cd
@ -12,6 +12,7 @@ import nu.marginalia.mq.MqMessageState;
|
|||||||
import nu.marginalia.mq.persistence.MqPersistence;
|
import nu.marginalia.mq.persistence.MqPersistence;
|
||||||
import nu.marginalia.renderer.RendererFactory;
|
import nu.marginalia.renderer.RendererFactory;
|
||||||
import nu.marginalia.service.server.*;
|
import nu.marginalia.service.server.*;
|
||||||
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import spark.Request;
|
import spark.Request;
|
||||||
@ -30,6 +31,7 @@ public class ControlService extends Service {
|
|||||||
private final ServiceMonitors monitors;
|
private final ServiceMonitors monitors;
|
||||||
private final HeartbeatService heartbeatService;
|
private final HeartbeatService heartbeatService;
|
||||||
private final EventLogService eventLogService;
|
private final EventLogService eventLogService;
|
||||||
|
private final ApiKeyService apiKeyService;
|
||||||
private final ControlActorService controlActorService;
|
private final ControlActorService controlActorService;
|
||||||
private final StaticResources staticResources;
|
private final StaticResources staticResources;
|
||||||
private final MessageQueueViewService messageQueueViewService;
|
private final MessageQueueViewService messageQueueViewService;
|
||||||
@ -46,6 +48,7 @@ public class ControlService extends Service {
|
|||||||
StaticResources staticResources,
|
StaticResources staticResources,
|
||||||
MessageQueueViewService messageQueueViewService,
|
MessageQueueViewService messageQueueViewService,
|
||||||
ControlFileStorageService controlFileStorageService,
|
ControlFileStorageService controlFileStorageService,
|
||||||
|
ApiKeyService apiKeyService,
|
||||||
MqPersistence persistence
|
MqPersistence persistence
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
|
|
||||||
@ -53,6 +56,7 @@ public class ControlService extends Service {
|
|||||||
this.monitors = monitors;
|
this.monitors = monitors;
|
||||||
this.heartbeatService = heartbeatService;
|
this.heartbeatService = heartbeatService;
|
||||||
this.eventLogService = eventLogService;
|
this.eventLogService = eventLogService;
|
||||||
|
this.apiKeyService = apiKeyService;
|
||||||
|
|
||||||
var indexRenderer = rendererFactory.renderer("control/index");
|
var indexRenderer = rendererFactory.renderer("control/index");
|
||||||
var servicesRenderer = rendererFactory.renderer("control/services");
|
var servicesRenderer = rendererFactory.renderer("control/services");
|
||||||
@ -64,6 +68,8 @@ public class ControlService extends Service {
|
|||||||
var storageCrawlsRenderer = rendererFactory.renderer("control/storage-crawls");
|
var storageCrawlsRenderer = rendererFactory.renderer("control/storage-crawls");
|
||||||
var storageProcessedRenderer = rendererFactory.renderer("control/storage-processed");
|
var storageProcessedRenderer = rendererFactory.renderer("control/storage-processed");
|
||||||
|
|
||||||
|
var apiKeysRenderer = rendererFactory.renderer("control/api-keys");
|
||||||
|
|
||||||
var storageDetailsRenderer = rendererFactory.renderer("control/storage-details");
|
var storageDetailsRenderer = rendererFactory.renderer("control/storage-details");
|
||||||
var updateMessageStateRenderer = rendererFactory.renderer("control/dialog-update-message-state");
|
var updateMessageStateRenderer = rendererFactory.renderer("control/dialog-update-message-state");
|
||||||
|
|
||||||
@ -95,6 +101,7 @@ public class ControlService extends Service {
|
|||||||
|
|
||||||
final HtmlRedirect redirectToServices = new HtmlRedirect("/services");
|
final HtmlRedirect redirectToServices = new HtmlRedirect("/services");
|
||||||
final HtmlRedirect redirectToProcesses = new HtmlRedirect("/actors");
|
final HtmlRedirect redirectToProcesses = new HtmlRedirect("/actors");
|
||||||
|
final HtmlRedirect redirectToApiKeys = new HtmlRedirect("/api-keys");
|
||||||
final HtmlRedirect redirectToStorage = new HtmlRedirect("/storage");
|
final HtmlRedirect redirectToStorage = new HtmlRedirect("/storage");
|
||||||
|
|
||||||
Spark.post("/public/fsms/:fsm/start", controlActorService::startFsm, redirectToProcesses);
|
Spark.post("/public/fsms/:fsm/start", controlActorService::startFsm, redirectToProcesses);
|
||||||
@ -107,6 +114,12 @@ public class ControlService extends Service {
|
|||||||
Spark.post("/public/storage/specs", controlActorService::createCrawlSpecification, redirectToStorage);
|
Spark.post("/public/storage/specs", controlActorService::createCrawlSpecification, redirectToStorage);
|
||||||
Spark.post("/public/storage/:fid/delete", controlFileStorageService::flagFileForDeletionRequest, redirectToStorage);
|
Spark.post("/public/storage/:fid/delete", controlFileStorageService::flagFileForDeletionRequest, redirectToStorage);
|
||||||
|
|
||||||
|
Spark.get("/public/api-keys", this::apiKeysModel, apiKeysRenderer::render);
|
||||||
|
Spark.post("/public/api-keys", this::createApiKey, redirectToApiKeys);
|
||||||
|
Spark.delete("/public/api-keys/:key", this::deleteApiKey, redirectToApiKeys);
|
||||||
|
// HTML forms don't support the DELETE verb :-(
|
||||||
|
Spark.post("/public/api-keys/:key/delete", this::deleteApiKey, redirectToApiKeys);
|
||||||
|
|
||||||
Spark.get("/public/message/:id/state", (rq, rsp) -> persistence.getMessage(Long.parseLong(rq.params("id"))), updateMessageStateRenderer::render);
|
Spark.get("/public/message/:id/state", (rq, rsp) -> persistence.getMessage(Long.parseLong(rq.params("id"))), updateMessageStateRenderer::render);
|
||||||
Spark.post("/public/message/:id/state", (rq, rsp) -> {
|
Spark.post("/public/message/:id/state", (rq, rsp) -> {
|
||||||
MqMessageState state = MqMessageState.valueOf(rq.queryParams("state"));
|
MqMessageState state = MqMessageState.valueOf(rq.queryParams("state"));
|
||||||
@ -120,6 +133,37 @@ public class ControlService extends Service {
|
|||||||
monitors.subscribe(this::logMonitorStateChange);
|
monitors.subscribe(this::logMonitorStateChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Object createApiKey(Request request, Response response) {
|
||||||
|
String license = request.queryParams("license");
|
||||||
|
String name = request.queryParams("name");
|
||||||
|
String email = request.queryParams("email");
|
||||||
|
int rate = Integer.parseInt(request.queryParams("rate"));
|
||||||
|
|
||||||
|
if (StringUtil.isBlank(license) ||
|
||||||
|
StringUtil.isBlank(name) ||
|
||||||
|
StringUtil.isBlank(email) ||
|
||||||
|
rate <= 0)
|
||||||
|
{
|
||||||
|
response.status(400);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
apiKeyService.addApiKey(license, name, email, rate);
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object deleteApiKey(Request request, Response response) {
|
||||||
|
String licenseKey = request.params("key");
|
||||||
|
apiKeyService.deleteApiKey(licenseKey);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object apiKeysModel(Request request, Response response) {
|
||||||
|
return Map.of("apikeys", apiKeyService.getApiKeys());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void logRequest(Request request) {
|
public void logRequest(Request request) {
|
||||||
if ("GET".equals(request.requestMethod()))
|
if ("GET".equals(request.requestMethod()))
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
package nu.marginalia.control.model;
|
||||||
|
public record ApiKeyModel(String licenseKey, String license, String name, String email, int rate) {}
|
@ -0,0 +1,93 @@
|
|||||||
|
package nu.marginalia.control.svc;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
import nu.marginalia.control.model.ApiKeyModel;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class ApiKeyService {
|
||||||
|
|
||||||
|
private final HikariDataSource dataSource;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ApiKeyService(HikariDataSource dataSource) {
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ApiKeyModel> getApiKeys() {
|
||||||
|
try (var conn = dataSource.getConnection()) {
|
||||||
|
try (var stmt = conn.prepareStatement("""
|
||||||
|
SELECT LICENSE_KEY, LICENSE, NAME, EMAIL, RATE FROM EC_API_KEY
|
||||||
|
""")) {
|
||||||
|
List<ApiKeyModel> ret = new ArrayList<>(100);
|
||||||
|
var rs = stmt.executeQuery();
|
||||||
|
while (rs.next()) {
|
||||||
|
ret.add(new ApiKeyModel(
|
||||||
|
rs.getString("LICENSE_KEY"),
|
||||||
|
rs.getString("LICENSE"),
|
||||||
|
rs.getString("NAME"),
|
||||||
|
rs.getString("EMAIL"),
|
||||||
|
rs.getInt("RATE")));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SQLException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiKeyModel addApiKey(String license, String name, String email, int rate) {
|
||||||
|
try (var conn = dataSource.getConnection()) {
|
||||||
|
try (var insertStmt = conn.prepareStatement("""
|
||||||
|
INSERT INTO EC_API_KEY (LICENSE_KEY, LICENSE, NAME, EMAIL, RATE) SELECT SHA(?), ?, ?, ?, ?
|
||||||
|
""");
|
||||||
|
// we could do SELECT SHA(?) here I guess if performance was a factor, but it's not
|
||||||
|
var queryStmt = conn.prepareStatement("SELECT LICENSE_KEY FROM EC_API_KEY WHERE LICENSE_KEY = SHA(?)")
|
||||||
|
) {
|
||||||
|
final String seedString = UUID.randomUUID() + "-" + name + "-" + email;
|
||||||
|
|
||||||
|
insertStmt.setString(1, seedString);
|
||||||
|
insertStmt.setString(2, license);
|
||||||
|
insertStmt.setString(3, name);
|
||||||
|
insertStmt.setString(4, email);
|
||||||
|
insertStmt.setInt(5, rate);
|
||||||
|
insertStmt.executeUpdate();
|
||||||
|
|
||||||
|
queryStmt.setString(1, seedString);
|
||||||
|
var rs = queryStmt.executeQuery();
|
||||||
|
if (rs.next()) {
|
||||||
|
return new ApiKeyModel(
|
||||||
|
rs.getString("LICENSE_KEY"),
|
||||||
|
license,
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("Failed to insert key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SQLException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteApiKey(String key) {
|
||||||
|
try (var conn = dataSource.getConnection()) {
|
||||||
|
try (var stmt = conn.prepareStatement("""
|
||||||
|
DELETE FROM EC_API_KEY WHERE LICENSE_KEY = ?
|
||||||
|
""")) {
|
||||||
|
stmt.setString(1, key);
|
||||||
|
stmt.executeUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SQLException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Control Service</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="stylesheet" href="/style.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{> control/partials/nav}}
|
||||||
|
<section>
|
||||||
|
|
||||||
|
<h1>API Keys</h1>
|
||||||
|
<table id="apikeys">
|
||||||
|
<tr>
|
||||||
|
<th colspan="3">Key</th>
|
||||||
|
<th> </th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>License</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Contact</th>
|
||||||
|
<th>Rate</th>
|
||||||
|
</tr>
|
||||||
|
{{#each apikeys}}
|
||||||
|
<tr>
|
||||||
|
<td colspan="3">{{licenseKey}}</td>
|
||||||
|
<td>
|
||||||
|
<form method="post" action="/api-keys/{{licenseKey}}/delete" onsubmit="return confirm('Confirm deletion of {{licenseKey}}')">
|
||||||
|
<input type="submit" value="Delete" />
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{license}}</td>
|
||||||
|
<td>{{name}}</td>
|
||||||
|
<td>{{email}}</td>
|
||||||
|
<td>{{rate}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</table>
|
||||||
|
<h2>Add New</h2>
|
||||||
|
<form action="/api-keys" method="post">
|
||||||
|
<label for="name">Name</label><br>
|
||||||
|
<input type="text" name="name" id="name" /><br>
|
||||||
|
<label for="email">Contact Email</label><br>
|
||||||
|
<input type="text" name="email" id="email" /><br>
|
||||||
|
<label for="license">License</label><br>
|
||||||
|
<input type="text" name="license" id="license" value="CC-BY-NC-SA 4.0" /><br>
|
||||||
|
<label for="rate">Rate</label><br>
|
||||||
|
<input type="text" name="rate" id="rate" value="15" /><br><br>
|
||||||
|
<input type="submit" value="Create" />
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
<script src="/refresh.js"></script>
|
||||||
|
<script>
|
||||||
|
window.setInterval(() => {
|
||||||
|
refresh(["apikeys"]);
|
||||||
|
}, 2000);
|
||||||
|
</script>
|
||||||
|
</html>
|
@ -12,16 +12,16 @@ erase information about its owner, and inboxes will consider the message new aga
|
|||||||
<form method="post" action="/message/{{msgId}}/state">
|
<form method="post" action="/message/{{msgId}}/state">
|
||||||
<label for="msgId">msgId</label><br>
|
<label for="msgId">msgId</label><br>
|
||||||
<input type="text" disabled id="msgId" name="msgId" value="{{msgId}}">
|
<input type="text" disabled id="msgId" name="msgId" value="{{msgId}}">
|
||||||
<br><br>
|
<br>
|
||||||
<label for="relatedId">relatedId</label><br>
|
<label for="relatedId">relatedId</label><br>
|
||||||
<input type="text" disabled id="relatedId" name="relatedId" value="{{relatedId}}">
|
<input type="text" disabled id="relatedId" name="relatedId" value="{{relatedId}}">
|
||||||
<br><br>
|
<br>
|
||||||
<label for="function">function</label><br>
|
<label for="function">function</label><br>
|
||||||
<input type="text" disabled id="function" name="function" value="{{function}}">
|
<input type="text" disabled id="function" name="function" value="{{function}}">
|
||||||
<br><br>
|
<br>
|
||||||
<label for="payload">payload</label><br>
|
<label for="payload">payload</label><br>
|
||||||
<input type="text" disabled id="payload" name="payload" value="{{payload}}">
|
<input type="text" disabled id="payload" name="payload" value="{{payload}}">
|
||||||
<br><br>
|
<br>
|
||||||
<label for="oldState">current state</label><br>
|
<label for="oldState">current state</label><br>
|
||||||
<input type="text" disabled id="oldState" name="oldState" value="{{state}}">
|
<input type="text" disabled id="oldState" name="oldState" value="{{state}}">
|
||||||
<br>
|
<br>
|
||||||
@ -37,5 +37,8 @@ erase information about its owner, and inboxes will consider the message new aga
|
|||||||
|
|
||||||
<input type="submit" value="Update">
|
<input type="submit" value="Update">
|
||||||
</form>
|
</form>
|
||||||
|
<p>Note that while setting a message to NEW or in some instances ACK typically causes an Actor
|
||||||
|
to act on the message, setting a message in ACK to ERR or DEAD will not stop action, but only
|
||||||
|
prevent resumption of action. To stop a running actor, use the Actors view and press the toggle.</p>
|
||||||
</section>
|
</section>
|
||||||
</html>
|
</html>
|
@ -15,7 +15,13 @@
|
|||||||
action="/fsms/{{name}}/stop"
|
action="/fsms/{{name}}/stop"
|
||||||
method="post"
|
method="post"
|
||||||
onsubmit="return toggleActorSwitch('{{name}}')">
|
onsubmit="return toggleActorSwitch('{{name}}')">
|
||||||
<input type="submit" value="On" class="toggle-switch-on" id="toggle-{{name}}-button">
|
<input
|
||||||
|
type="submit"
|
||||||
|
value="On"
|
||||||
|
class="toggle-switch-on"
|
||||||
|
id="toggle-{{name}}-button"
|
||||||
|
title="Terminate the actor"
|
||||||
|
>
|
||||||
</form>
|
</form>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{#if terminal}}
|
{{#if terminal}}
|
||||||
@ -32,6 +38,7 @@
|
|||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{#if canStart}}
|
{{#if canStart}}
|
||||||
value="Off"
|
value="Off"
|
||||||
|
title="Start the actor"
|
||||||
{{/if}}
|
{{/if}}
|
||||||
class="toggle-switch-off"
|
class="toggle-switch-off"
|
||||||
|
|
||||||
|
@ -4,5 +4,8 @@
|
|||||||
<li><a href="/services">Services</a></li>
|
<li><a href="/services">Services</a></li>
|
||||||
<li><a href="/actors">Actors</a></li>
|
<li><a href="/actors">Actors</a></li>
|
||||||
<li><a href="/storage">Storage</a></li>
|
<li><a href="/storage">Storage</a></li>
|
||||||
|
<li><a href="/api-keys">API Keys</a></li>
|
||||||
|
<li><a href="/blacklist">Blacklist</a></li>
|
||||||
|
<li><a href="/complaints">Complaints</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
@ -4,20 +4,18 @@
|
|||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Path</th>
|
<th>Path</th>
|
||||||
<th>Must Clean</th>
|
|
||||||
<th>Permit Temp</th>
|
<th>Permit Temp</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{base.type}}</td>
|
<td>{{base.type}}</td>
|
||||||
<td>{{base.name}}</td>
|
<td>{{base.name}}</td>
|
||||||
<td>{{base.path}}</td>
|
<td>{{base.path}}</td>
|
||||||
<td>{{base.mustClean}}</td>
|
|
||||||
<td>{{base.permitTemp}}</td>
|
<td>{{base.permitTemp}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th colspan="2">Path</th>
|
<th>Path</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
{{#each storage}}
|
{{#each storage}}
|
||||||
@ -26,7 +24,7 @@
|
|||||||
<a href="/storage/{{storage.id}}">Info</a>
|
<a href="/storage/{{storage.id}}">Info</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{storage.type}}</td>
|
<td>{{storage.type}}</td>
|
||||||
<td colspan="2">{{storage.path}}</td>
|
<td>{{storage.path}}</td>
|
||||||
<td>{{storage.description}}</td>
|
<td>{{storage.description}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
@ -47,22 +47,22 @@
|
|||||||
<h2>Actions</h2>
|
<h2>Actions</h2>
|
||||||
{{#with storage.self}}
|
{{#with storage.self}}
|
||||||
{{#if isCrawlable}}
|
{{#if isCrawlable}}
|
||||||
<form method="post" action="/storage/{{storage.id}}/crawl">
|
<form method="post" action="/storage/{{storage.id}}/crawl" onsubmit="return confirm('Confirm crawling of {{storage.path}}')">
|
||||||
Perform a full re-crawl of this data: <button type="submit">Crawl</button> <br>
|
Perform a full re-crawl of this data: <button type="submit">Crawl</button> <br>
|
||||||
</form>
|
</form>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if isLoadable}}
|
{{#if isLoadable}}
|
||||||
<form method="post" action="/storage/{{storage.id}}/load">
|
<form method="post" action="/storage/{{storage.id}}/load" onsubmit="return confirm('Confirm loading of {{storage.path}}')">
|
||||||
Load this data into index: <button type="submit">Load</button> <br>
|
Load this data into index: <button type="submit">Load</button> <br>
|
||||||
</form>
|
</form>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if isConvertible}}
|
{{#if isConvertible}}
|
||||||
<form method="post" action="/storage/{{storage.id}}/process">
|
<form method="post" action="/storage/{{storage.id}}/process" onsubmit="return confirm('Confirm processing of {{storage.path}}')">
|
||||||
Process and load this data into index: <button type="submit">Process</button> <br>
|
Process and load this data into index: <button type="submit">Process</button> <br>
|
||||||
</form>
|
</form>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if isRecrawlable}}
|
{{#if isRecrawlable}}
|
||||||
<form method="post" action="/storage/{{storage.id}}/recrawl">
|
<form method="post" action="/storage/{{storage.id}}/recrawl" onsubmit="return confirm('Confirm re-crawling of {{storage.path}}')">
|
||||||
Perform a re-crawl of this data: <button type="submit">Recrawl</button><br>
|
Perform a re-crawl of this data: <button type="submit">Recrawl</button><br>
|
||||||
</form>
|
</form>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -16,20 +16,18 @@
|
|||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Path</th>
|
<th>Path</th>
|
||||||
<th>Must Clean</th>
|
|
||||||
<th>Permit Temp</th>
|
<th>Permit Temp</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{base.type}}</td>
|
<td>{{base.type}}</td>
|
||||||
<td>{{base.name}}</td>
|
<td>{{base.name}}</td>
|
||||||
<td>{{base.path}}</td>
|
<td>{{base.path}}</td>
|
||||||
<td>{{base.mustClean}}</td>
|
|
||||||
<td>{{base.permitTemp}}</td>
|
<td>{{base.permitTemp}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th colspan="2">Path</th>
|
<th>Path</th>
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
{{#each storage}}
|
{{#each storage}}
|
||||||
@ -37,7 +35,7 @@
|
|||||||
<td>
|
<td>
|
||||||
</td>
|
</td>
|
||||||
<td>{{storage.type}}</td>
|
<td>{{storage.type}}</td>
|
||||||
<td colspan="2">{{storage.path}}</td>
|
<td>{{storage.path}}</td>
|
||||||
<td>{{storage.description}}</td>
|
<td>{{storage.description}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
package nu.marginalia.control.svc;
|
||||||
|
|
||||||
|
import com.zaxxer.hikari.HikariConfig;
|
||||||
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
import nu.marginalia.control.model.ApiKeyModel;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Tag;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.parallel.Execution;
|
||||||
|
import org.testcontainers.containers.MariaDBContainer;
|
||||||
|
import org.testcontainers.junit.jupiter.Container;
|
||||||
|
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD;
|
||||||
|
|
||||||
|
@Testcontainers
|
||||||
|
@Execution(SAME_THREAD)
|
||||||
|
@Tag("slow")
|
||||||
|
public class ApiKeyServiceTest {
|
||||||
|
@Container
|
||||||
|
static MariaDBContainer<?> mariaDBContainer = new MariaDBContainer<>("mariadb")
|
||||||
|
.withDatabaseName("WMSA_prod")
|
||||||
|
.withUsername("wmsa")
|
||||||
|
.withPassword("wmsa")
|
||||||
|
.withInitScript("db/migration/V23_06_0_006__api_key.sql")
|
||||||
|
.withNetworkAliases("mariadb");
|
||||||
|
|
||||||
|
static HikariDataSource dataSource;
|
||||||
|
@BeforeAll
|
||||||
|
public static void setup() {
|
||||||
|
HikariConfig config = new HikariConfig();
|
||||||
|
config.setJdbcUrl(mariaDBContainer.getJdbcUrl());
|
||||||
|
config.setUsername("wmsa");
|
||||||
|
config.setPassword("wmsa");
|
||||||
|
|
||||||
|
dataSource = new HikariDataSource(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void tearDown() {
|
||||||
|
dataSource.close();
|
||||||
|
mariaDBContainer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getKeys() {
|
||||||
|
var apiKeyService = new ApiKeyService(dataSource);
|
||||||
|
apiKeyService.addApiKey("public domain", "bob dobbs", "bob@dobbstown.com", 30);
|
||||||
|
apiKeyService.addApiKey("public domain", "connie dobbs", "cdobbs@dobbstown.com", 15);
|
||||||
|
|
||||||
|
var keys = apiKeyService.getApiKeys();
|
||||||
|
System.out.println(keys);
|
||||||
|
assertEquals(2, keys.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void addApiKey() {
|
||||||
|
var apiKeyService = new ApiKeyService(dataSource);
|
||||||
|
apiKeyService.addApiKey("public domain", "bob dobbs", "bob@dobbstown.com", 30);
|
||||||
|
|
||||||
|
var keys = apiKeyService.getApiKeys();
|
||||||
|
|
||||||
|
System.out.println(keys);
|
||||||
|
assertEquals(1, keys.size());
|
||||||
|
|
||||||
|
var key = keys.get(0);
|
||||||
|
|
||||||
|
assertEquals("public domain", key.license());
|
||||||
|
assertEquals("bob dobbs", key.name());
|
||||||
|
assertEquals("bob@dobbstown.com", key.email());
|
||||||
|
assertEquals(30, key.rate());
|
||||||
|
assertNotNull(key.licenseKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void deleteApiKey() {
|
||||||
|
var apiKeyService = new ApiKeyService(dataSource);
|
||||||
|
apiKeyService.addApiKey("public domain", "bob dobbs", "bob@dobbstown.com", 30);
|
||||||
|
|
||||||
|
List<ApiKeyModel> keys = apiKeyService.getApiKeys();
|
||||||
|
|
||||||
|
assertEquals(1, keys.size());
|
||||||
|
|
||||||
|
String licenseKey= keys.get(0).licenseKey();
|
||||||
|
apiKeyService.deleteApiKey(licenseKey);
|
||||||
|
|
||||||
|
keys = apiKeyService.getApiKeys();
|
||||||
|
assertEquals(0, keys.size());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user