The whole Bot model/repo setup has been redesigned to follow the more complete/correct DAO-like pattern. Mostly everything still works, just not handling the not found exception rising from the Optional I think.
This commit is contained in:
parent
4f73698e2f
commit
95fed42020
8 changed files with 211 additions and 78 deletions
|
|
@ -2,86 +2,53 @@ package dev.activitypub.activitypubbot;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import jakarta.persistence.Entity;
|
import lombok.AccessLevel;
|
||||||
import jakarta.persistence.EntityListeners;
|
import lombok.Data;
|
||||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.GeneratedValue;
|
|
||||||
import jakarta.persistence.GenerationType;
|
|
||||||
import org.springframework.data.annotation.CreatedDate;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Our core Bot (aka user) data as stored persistently in the database.
|
* POJO for our Bot...
|
||||||
* This is all the key non-derived data as required by the ActivityPub
|
|
||||||
* specification. NOTE: This is not a comprehensive implementation!
|
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Data
|
||||||
@EntityListeners(AuditingEntityListener.class)
|
|
||||||
public class Bot {
|
public class Bot {
|
||||||
|
|
||||||
@Id
|
@Getter(AccessLevel.NONE) @Setter(AccessLevel.NONE)
|
||||||
@GeneratedValue(strategy=GenerationType.AUTO)
|
Long id; // does this even need to be exposed here?
|
||||||
private Long id;
|
|
||||||
|
|
||||||
|
String username; // "preferredUsername: springbot",
|
||||||
|
String name; // "Spring Bot",
|
||||||
|
String summary; // "<p>A bot written using Java/Spring</p>",
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
@Setter(AccessLevel.NONE)
|
||||||
// The next values are user-supplied
|
Instant published; // "2025-01-24T00:00:00Z",
|
||||||
|
|
||||||
/**
|
@Setter(AccessLevel.NONE)
|
||||||
* Username of the bot, i.e. the value after the @: @<username>@<domain>
|
String publicKeyPem; // "-----BEGIN PUBLIC KEY-----\\nMI [...] AB\\n-----END PUBLIC KEY-----"
|
||||||
* Note that in the Activity Pub spec this is encoded as 'preferredUsername',
|
|
||||||
* we shorten just to username here for clarity and brevity in the code.
|
|
||||||
*
|
|
||||||
* @param username the username value
|
|
||||||
* @return the username value
|
|
||||||
*/
|
|
||||||
@Column(nullable=false,unique=true)
|
|
||||||
@Getter @Setter private String username; // "preferredUsername: springbot",
|
|
||||||
|
|
||||||
/**
|
@Setter(AccessLevel.NONE)
|
||||||
* The "friendly" formatted name of the bot, can have spaces, etc.
|
String type = "Person";
|
||||||
*
|
|
||||||
* @param name a presentational name for the bot
|
|
||||||
* @return the presentational name for the bot
|
|
||||||
*/
|
|
||||||
@Column(nullable=true,unique=false)
|
|
||||||
@Getter @Setter private String name; // "Spring Bot",
|
|
||||||
|
|
||||||
/**
|
@Setter(AccessLevel.NONE)
|
||||||
* A bit of text to describe the bot/account, an "about".
|
boolean manuallyApproveFollowers = false;
|
||||||
*
|
|
||||||
* @param summary Text describing the bot/account
|
|
||||||
* @return the summary string
|
|
||||||
*/
|
|
||||||
@Column(nullable=true,unique=false)
|
|
||||||
@Getter @Setter private String summary; // "<p>A bot written using Java/Spring</p>",
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
@Setter(AccessLevel.NONE)
|
||||||
// The following values will be auto-generated on bot-creation
|
boolean indexable = false;
|
||||||
|
|
||||||
@CreatedDate // TODO: is this really the right approach to auto-timestamp, not sure about all this "auditing" stuff, feels like a misuse
|
|
||||||
@Getter private Instant published; // "2025-01-24T00:00:00Z",
|
|
||||||
|
|
||||||
// TODO how and where do we generate this beastie?!?!
|
|
||||||
@Column(nullable=true,unique=false) // FIXME: this isn't true, just easy for now
|
|
||||||
@Getter private String publicKeyPem; // "-----BEGIN PUBLIC KEY-----\\nMI [...] AB\\n-----END PUBLIC KEY-----"
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// These values can just be constants for the timebeing
|
|
||||||
|
|
||||||
@Column(nullable=false,unique=false)
|
|
||||||
@Getter private static final String type = "Person";
|
|
||||||
|
|
||||||
@Column(nullable=false,unique=false)
|
|
||||||
@Getter private static final boolean manuallyApproveFollowers = false;
|
|
||||||
|
|
||||||
@Column(nullable=false,unique=false)
|
|
||||||
@Getter private static final boolean indexable = false;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Bot: " + this.username;
|
return "Bot: " + this.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bot() {
|
||||||
}
|
}
|
||||||
|
Bot( String username, String name, String summary, String type, boolean manuallyApproveFollowers, boolean indexable, String pubicKeyPem ) {
|
||||||
|
this.username = username;
|
||||||
|
this.name = name;
|
||||||
|
this.summary = summary;
|
||||||
|
this.type = type;
|
||||||
|
this.manuallyApproveFollowers = manuallyApproveFollowers;
|
||||||
|
this.indexable = indexable;
|
||||||
|
this.publicKeyPem = publicKeyPem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,13 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
public class BotController {
|
public class BotController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private BotRepo botRepo;
|
private BotService botServ;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private BotService botService;
|
|
||||||
|
|
||||||
@GetMapping("/viewbot")
|
@GetMapping("/viewbot")
|
||||||
public String listAll(Model model) {
|
public String listAll(Model model) {
|
||||||
log.info("WebHandler::viewbot");
|
log.info("WebHandler::viewbot");
|
||||||
|
|
||||||
List<Bot> botlist = botService.findAll();
|
List<Bot> botlist = botServ.findAll();
|
||||||
model.addAttribute("bots", botlist);
|
model.addAttribute("bots", botlist);
|
||||||
|
|
||||||
return "viewbot";
|
return "viewbot";
|
||||||
|
|
@ -47,7 +44,7 @@ public class BotController {
|
||||||
public String makebotpost(@ModelAttribute("bot") Bot bot) {
|
public String makebotpost(@ModelAttribute("bot") Bot bot) {
|
||||||
//log.info(bot);
|
//log.info(bot);
|
||||||
|
|
||||||
botRepo.save(bot);
|
botServ.save(bot);
|
||||||
|
|
||||||
return "makebot_submitted";
|
return "makebot_submitted";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package dev.activitypub.activitypubbot;
|
||||||
|
|
||||||
|
import org.springframework.data.repository.CrudRepository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface BotJdbcRepo extends CrudRepository<BotModel, Long> {
|
||||||
|
Optional<BotModel> findByUsername(String username);
|
||||||
|
List<BotModel> findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package dev.activitypub.activitypubbot;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRUD layer? TODO: Does "JPA" make sense in the name here?
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public class BotJpaRepo implements BotRepo {
|
||||||
|
|
||||||
|
// underlying BotModel storage
|
||||||
|
private final BotJdbcRepo botJdbcRepo;
|
||||||
|
|
||||||
|
public BotJpaRepo(BotJdbcRepo botJdbcRepo) {
|
||||||
|
this.botJdbcRepo = botJdbcRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bot save(Bot bot) {
|
||||||
|
BotModel botModel = BotModel.from(bot);
|
||||||
|
BotModel saved = botJdbcRepo.save(botModel);
|
||||||
|
return saved.asBot();
|
||||||
|
// this pattern comes from the 'ensembler' MemberRepositoryJdbcAdapter impl
|
||||||
|
// TODO: in our case I'm not sure we need to be returning a Bot, could be void?
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Bot> findByUsername(String username) {
|
||||||
|
// username is a unique key so technically should only be one of these...?
|
||||||
|
Optional<BotModel> botModels = botJdbcRepo.findByUsername(username);
|
||||||
|
return botModels.map(BotModel::asBot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Bot> findAll() {
|
||||||
|
List<BotModel> botModels = botJdbcRepo.findAll();
|
||||||
|
return botModels.stream().map(BotModel::asBot).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
package dev.activitypub.activitypubbot;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.EntityListeners;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import java.time.Instant;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Our core Bot (aka user) data as stored persistently in the database.
|
||||||
|
* This is all the key non-derived data as required by the ActivityPub
|
||||||
|
* specification. NOTE: This is not a comprehensive implementation!
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "bot")
|
||||||
|
@EntityListeners(AuditingEntityListener.class)
|
||||||
|
public class BotModel {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy=GenerationType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// The next values are user-supplied
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Username of the bot, i.e. the value after the @: @<username>@<domain>
|
||||||
|
* Note that in the Activity Pub spec this is encoded as 'preferredUsername',
|
||||||
|
* we shorten just to username here for clarity and brevity in the code.
|
||||||
|
*
|
||||||
|
* @param username the username value
|
||||||
|
* @return the username value
|
||||||
|
*/
|
||||||
|
@Column(nullable=false,unique=true)
|
||||||
|
@Getter @Setter private String username; // "preferredUsername: springbot",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "friendly" formatted name of the bot, can have spaces, etc.
|
||||||
|
*
|
||||||
|
* @param name a presentational name for the bot
|
||||||
|
* @return the presentational name for the bot
|
||||||
|
*/
|
||||||
|
@Column(nullable=true,unique=false)
|
||||||
|
@Getter @Setter private String name; // "Spring Bot",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bit of text to describe the bot/account, an "about".
|
||||||
|
*
|
||||||
|
* @param summary Text describing the bot/account
|
||||||
|
* @return the summary string
|
||||||
|
*/
|
||||||
|
@Column(nullable=true,unique=false)
|
||||||
|
@Getter @Setter private String summary; // "<p>A bot written using Java/Spring</p>",
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
// The following values will be auto-generated on bot-creation
|
||||||
|
|
||||||
|
@CreatedDate // TODO: is this really the right approach to auto-timestamp, not sure about all this "auditing" stuff, feels like a misuse
|
||||||
|
@Getter private Instant published; // "2025-01-24T00:00:00Z",
|
||||||
|
|
||||||
|
// TODO how and where do we generate this beastie?!?!
|
||||||
|
@Column(nullable=true,unique=false) // FIXME: this isn't true, just easy for now
|
||||||
|
@Getter private String publicKeyPem; // "-----BEGIN PUBLIC KEY-----\\nMI [...] AB\\n-----END PUBLIC KEY-----"
|
||||||
|
|
||||||
|
@Column(nullable=false,unique=false)
|
||||||
|
@Getter private String type;
|
||||||
|
|
||||||
|
@Column(nullable=false,unique=false)
|
||||||
|
@Getter private boolean manuallyApproveFollowers;
|
||||||
|
|
||||||
|
@Column(nullable=false,unique=false)
|
||||||
|
@Getter private boolean indexable;
|
||||||
|
|
||||||
|
static BotModel from(Bot bot) {
|
||||||
|
BotModel bm = new BotModel();
|
||||||
|
bm.username = bot.getUsername();
|
||||||
|
bm.name = bot.getName();
|
||||||
|
bm.summary = bot.getSummary();
|
||||||
|
bm.type = bot.getType();
|
||||||
|
bm.manuallyApproveFollowers = bot.isManuallyApproveFollowers();
|
||||||
|
bm.indexable = bot.isIndexable();
|
||||||
|
bm.publicKeyPem = bot.getPublicKeyPem();
|
||||||
|
return bm;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bot asBot() {
|
||||||
|
return new Bot( username, name, summary, type, manuallyApproveFollowers, indexable, publicKeyPem );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Bot: " + this.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,20 @@
|
||||||
package dev.activitypub.activitypubbot;
|
package dev.activitypub.activitypubbot;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import java.util.Optional;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database shenanigans...
|
* Interface for a Bot respository.
|
||||||
|
*
|
||||||
|
* Working to the @Repository documentation and some other examples
|
||||||
|
* for this, a pattern noted to be "close to the DAO pattern". But I
|
||||||
|
* am not yet clear on the need/justification for this approach.
|
||||||
*/
|
*/
|
||||||
public interface BotRepo extends JpaRepository<Bot, Long> {
|
public interface BotRepo {
|
||||||
Bot findByUsername(String username);
|
|
||||||
|
Bot save(Bot bot);
|
||||||
|
|
||||||
|
Optional<Bot> findByUsername(String username);
|
||||||
|
|
||||||
|
List<Bot> findAll();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public class BotService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bot getBotByUsername( String username ) {
|
public Bot getBotByUsername( String username ) {
|
||||||
return repo.findByUsername( username );
|
return repo.findByUsername( username ).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -66,5 +66,9 @@ public class BotService {
|
||||||
public String getOutbox(Bot bot) {
|
public String getOutbox(Bot bot) {
|
||||||
return getUsersURI() + bot.getUsername() + "/outbox";
|
return getUsersURI() + bot.getUsername() + "/outbox";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Bot save(Bot bot) {
|
||||||
|
return repo.save(bot);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import org.thymeleaf.spring5.SpringTemplateEngine;
|
||||||
* Here we handle any JSON/REST requests, which is how ActivityPub instances talk to each other.
|
* Here we handle any JSON/REST requests, which is how ActivityPub instances talk to each other.
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping( headers = "accept=application/json" )
|
|
||||||
public class RestHandler {
|
public class RestHandler {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
|
|
@ -32,7 +31,7 @@ public class RestHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Access the bot/user
|
* Get the bot/user 'actor' data response
|
||||||
*/
|
*/
|
||||||
@GetMapping(value = "/users/{username}", produces = "application/activity+json") // content type based on Masto request
|
@GetMapping(value = "/users/{username}", produces = "application/activity+json") // content type based on Masto request
|
||||||
public ResponseEntity<String> actor(@PathVariable String username) {
|
public ResponseEntity<String> actor(@PathVariable String username) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue