improvements to generating URIs and some template tweaks, minor tidying

This commit is contained in:
Yvan 2025-02-04 19:42:09 +00:00
parent 1682cbe81f
commit 516f90bf1f
6 changed files with 93 additions and 35 deletions

View file

@ -76,7 +76,15 @@ public class Bot {
Bot() { Bot() {
} }
Bot( String username, String name, String summary, Instant published, KeyPair keyPair, String type, boolean manuallyApproveFollowers, boolean indexable ) { Bot(
String username,
String name,
String summary,
Instant published,
KeyPair keyPair,
String type,
boolean manuallyApproveFollowers,
boolean indexable ) {
this.username = username; this.username = username;
this.name = name; this.name = name;
this.summary = summary; this.summary = summary;
@ -86,5 +94,6 @@ public class Bot {
this.manuallyApproveFollowers = manuallyApproveFollowers; this.manuallyApproveFollowers = manuallyApproveFollowers;
this.indexable = indexable; this.indexable = indexable;
} }
} }

View file

@ -4,6 +4,7 @@ import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.List; import java.util.List;
/** /**
@ -35,36 +36,73 @@ public class BotService {
} }
// whilst the URI structure of the below are up to the implementor we're using Mastodon as a reference // whilst the URI structure of the below are up to the implementer we're using Mastodon as a reference
// TODO: perhaps these could just be templated elsewhere... or part of a separat JSON generator // TODO: I've fiddled around loads with where to house/generate the values
// from the calls below, they have ended up here mainly as this is where it
// is cleanest to access the configuration properties.
//
// Opinion in material I've read seems very firm that knowledge of
// "configuration" should not exist at Entity level so I have not made them
// members of the Bot class. I've toyed with the idea of them simply all
// being kept as database values and generated on creation... and in some
// ways that is simply cleaner and thus still has an appeal to it. I just
// also feel uncomfortable about having a load of "derived data" in the
// database, even though there is also a efficiency argument. Perhaps the
// combination of both efficiency + design-readability should win it. (In
// which case this code could well just stay here to be used at creation.)
/** /**
* Generate the users URI * Generate the webfinger subject
*/ */
private String getUsersURI() { public String getWebfingerSubject(Bot bot) {
return props.getScheme() + "://" + props.getDomain() + "/users/"; return "acct:" + bot.getUsername() + "@" + props.getDomain();
}
/**
* Generate the webfinger subject
*/
public String getHandle(Bot bot) {
return "@" + bot.getUsername() + "@" + props.getDomain();
}
/**
* Generate the base users URI
*/
private UriComponentsBuilder getUsersURI() {
return UriComponentsBuilder.newInstance()
.scheme(props.getScheme())
.host(props.getDomain())
.pathSegment("users");
} }
/** /**
* The "id" of a bot (user/actor) is a URI combining domain and username: https://<domain>/users/<username> * The "id" of a bot (user/actor) is a URI combining domain and username: https://<domain>/users/<username>
*/ */
public String getId(Bot bot) { public String getId(Bot bot) {
return getUsersURI() + bot.getUsername(); return getUsersURI()
.pathSegment(bot.getUsername())
.toUriString();
} }
/** /**
* The "inbox" of a user is a URI of the form: https://<domain>/users/<username>/inbox * The "inbox" of a user is a URI of the form: https://<domain>/users/<username>/inbox
*/ */
public String getInbox(Bot bot) { public String getInbox(Bot bot) {
return getUsersURI() + bot.getUsername() + "/inbox"; return getUsersURI()
.pathSegment(bot.getUsername())
.pathSegment("inbox")
.toUriString();
} }
/** /**
* The "outbox" of a user is a URI of the form: https://<domain>/users/<username>/outbox * The "outbox" of a user is a URI of the form: https://<domain>/users/<username>/outbox
*/ */
public String getOutbox(Bot bot) { public String getOutbox(Bot bot) {
return getUsersURI() + bot.getUsername() + "/outbox"; return getUsersURI()
.pathSegment(bot.getUsername())
.pathSegment("outbox")
.toUriString();
} }
public Bot save(Bot bot) { public Bot save(Bot bot) {

View file

@ -65,14 +65,17 @@ public class RestHandler {
//return new ResponseEntity<>("These are not the droids you are looking for.", HttpStatus.NOT_FOUND); //return new ResponseEntity<>("These are not the droids you are looking for.", HttpStatus.NOT_FOUND);
} }
// TODO: So here's a whole other way of generating custom JSON as compared to the BotAPActorJSONSerializer approach // TODO: So here's a whole other way of generating custom JSON as
// compared to the BotAPActorJSONSerializer approach, should perhaps
// settle on one style of doing it (if we're going to do it either way
// in the end.)
Map<String, Object> response = Map.of( Map<String, Object> response = Map.of(
"subject", bot.getUsername() + "@springbot.seth.id.au", "subject", botServ.getWebfingerSubject( bot ),
"links", List.of( "links", List.of(
Map.of( Map.of(
"rel", "self", "rel", "self",
"type", "application/activity+json", "type", "application/activity+json",
"href", "https://springbot.seth.id.au/users/" + bot.getUsername() "href", botServ.getId( bot )
) )
) )
); );

View file

@ -6,6 +6,9 @@ logging.file.name=logs/springbot.log
logging.file.path=logs logging.file.path=logs
spring.output.ansi.enabled=ALWAYS spring.output.ansi.enabled=ALWAYS
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG
server.tomcat.basedir=logs
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a "%r" %s (%D ms)
spring.mvc.view.prefix: /WEB-INF/views/ spring.mvc.view.prefix: /WEB-INF/views/
spring.mvc.view.suffix: .jsp spring.mvc.view.suffix: .jsp

View file

@ -3,25 +3,21 @@
<head> <head>
<meta charset="ISO-8859-1"> <meta charset="ISO-8859-1">
<title>List Bot</title> <title>List Bot</title>
<style type="text/css"> .main {
span { text-align: center;
display: inline-block;
width: 200px;
text-align: left;
} }
</style> </style>
</head> </head>
<body> <body>
<div align="center"> <div class="main">
<h2>Pick-a-Bot!</h2> <h2>Pick-a-Bot!</h2>
<ul th:if="${bots.empty}"> <th:block th:if="${bots.empty}">
<li>No Bots!</li> <h3>No Bots!</h3>
</ul>
<th:block th:each="bot : ${bots}">
<span>Bot:</span>
<span><a th:href="@{/viewbot/} + ${bot.username}">[[${bot.username}]]</a></span>
<br>
</th:block> </th:block>
<th:block th:each="bot : ${bots}">
<a th:href="@{/viewbot/} + ${bot.username}">[[${@botService.getHandle(bot)}]]</a><br>
</th:block>
<br>
<br> <br>
<a href="/">HOME</a> <a href="/">HOME</a>
</div> </div>

View file

@ -4,24 +4,33 @@
<meta charset="ISO-8859-1"> <meta charset="ISO-8859-1">
<title>View Bot</title> <title>View Bot</title>
<style type="text/css"> <style type="text/css">
.main {
text-align: center;
}
span { span {
display: inline-block; display: inline-block;
width: 200px; width: 40em;
text-align: left; text-align: left;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
span.left {
text-align: right;
margin-right: 1em;
}
</style> </style>
</head> </head>
<body> <body>
<div align="center"> <div class="main">
<h2>Bot:</h2> <h2>Bot:</h2>
<span>Username:</span><span th:text="${bot.username}"></span><br/> <span class="left">Username:</span><span th:text="${bot.username}"></span><br/>
<span>Id:</span><span th:text="'@' + ${bot.username} + '@springbot.seth.id.au'"></span><br/> <span class="left">Id:</span><span th:text="${@botService.getHandle(bot)}"></span><br/>
<span>Display Name:</span><span th:text="${bot.name}"></span><br/> <span class="left">Display Name:</span><span th:text="${bot.name}"></span><br/>
<span>Description:</span><span th:text="${bot.summary}"></span><br/> <span class="left">Description:</span><span th:text="${bot.summary}"></span><br/>
<span>Published:</span><span th:text="${bot.published}"></span><br/> <span class="left">Published:</span><span th:text="${bot.published}"></span><br/>
<span>Type:</span><span th:text="${bot.type}"></span><br/> <span class="left">Type:</span><span th:text="${bot.type}"></span><br/>
<span>Public Key:</span><pre th:text="${bot.getPublicKeyPEMString()}"></pre><br/> <span class="left">Public Key:</span><span></span>
<pre th:text="${bot.getPublicKeyPEMString()}"></pre>
<br>
<br> <br>
<a href="/">HOME</a> | <a href="/">HOME</a> |
<a href="/viewbot">Bot List</a> <a href="/viewbot">Bot List</a>