diff --git a/Java/Spring/activitypubbot/build.gradle b/Java/Spring/activitypubbot/build.gradle index 3d5e023..1d68338 100644 --- a/Java/Spring/activitypubbot/build.gradle +++ b/Java/Spring/activitypubbot/build.gradle @@ -27,13 +27,22 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-rest' implementation 'org.springframework.boot:spring-boot-starter-groovy-templates' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-webflux' - implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.shell:spring-shell-starter' + implementation 'org.postgresql:postgresql' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.projectreactor:reactor-test' testImplementation 'org.springframework.shell:spring-shell-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' + } dependencyManagement { diff --git a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/APProperties.java b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/APProperties.java index 4951179..4e8dd41 100644 --- a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/APProperties.java +++ b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/APProperties.java @@ -2,36 +2,29 @@ package dev.activitypub.activitypubbot; import org.springframework.context.annotation.Configuration; import org.springframework.boot.context.properties.ConfigurationProperties; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; /** - * Object bound to "ap" prefix in {@link org.springframework.core.env.Environment}. - * - * @author Yvan Seth + * Object bound to "springbot" prefix in {@link org.springframework.core.env.Environment}. */ @Configuration -@ConfigurationProperties(prefix = "ap") +@ConfigurationProperties(prefix = "springbot") public class APProperties { /** - * Key File Path + * Scheme (i.e. https) */ - private String keyFilePath = "keyfile.pem"; + @Getter @Setter private String scheme; // = "https"; /** - * Server domain + * Server domain - i.e. https://<domain>/@username */ - private String serverDomain = "activitypub.bot"; + @Getter @Setter private String domain; // = "activitypub.bot"; - public String getKeyFilePath() { - return keyFilePath; - } - public void setKeyFilePath(String keyFilePath) { - this.keyFilePath = keyFilePath; - } - public String getServerDomain() { - return serverDomain; - } - public void setServerDomain(String serverDomain) { - this.serverDomain = serverDomain; - } + /*String getScheme() { return this.scheme; } + String getDomain() { return this.domain; } + void setScheme( String scheme ) { this.scheme = scheme; } + void setDomain( String domain ) { this.domain = domain; }*/ } diff --git a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/ActivityPubBotApplication.java b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/ActivityPubBotApplication.java index 7a9c1e0..f2ed07f 100644 --- a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/ActivityPubBotApplication.java +++ b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/ActivityPubBotApplication.java @@ -9,18 +9,21 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; @SpringBootApplication +@EnableJpaAuditing public class ActivityPubBotApplication extends SpringBootServletInitializer { //comment below if deploying outside web container --> - @Override + /*@Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(ActivityPubBotApplication.class); - } + }*/ public static void main(String[] args) { SpringApplication.run(ActivityPubBotApplication.class); diff --git a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/Bot.java b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/Bot.java new file mode 100644 index 0000000..a65ee18 --- /dev/null +++ b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/Bot.java @@ -0,0 +1,114 @@ +package dev.activitypub.activitypubbot; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import java.sql.Date; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.Id; +import jakarta.persistence.Column; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import java.time.Instant; + +/** + * 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 +@EntityListeners(AuditingEntityListener.class) +public class Bot { + + // TODO: probably the wrong way to do this, and it isn't really compatible with 'Entity' anyway + //private APProperties props; + /*public Bot(APProperties props) { + this.props = props; + }*/ + // non-data auto-generated primary key, this should never be exposed + @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; // "

A bot written using Java/Spring

", + + + /////////////////////////////////////////////////////////////////////////// + // 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-----" + + /////////////////////////////////////////////////////////////////////////// + // 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; + + // TODO: should this class have the functions for generating derived values based on config vals? How does it get the config... + + /* these are all just derived from (preferred)Username - the scheme and domain-name should come from config + private String id; // "https://springbot.seth.id.au/users/springbot", + private String url; // "https://springbot.seth.id.au/@springbot", + private String inbox; // "https://springbot.seth.id.au/users/springbot/inbox", + private String publicKeyId; // "https://springbot.seth.id.au/users/springbot#main-key", + */ + public String getId() { + // TODO: is there some sort of 'uribuilder' - probably need our own, lots of uris to build + //return props.getScheme() + "://" + props.getDomain() + "/users/" + this.getUsername(); + return this.getUsername(); + } + + /* this is just a copy of "id" + private String publicKeyOwner; // "https://springap.seth.id.au/users/springbot", + */ + + @Override + public String toString() { + return "Bot: " + username; + } +} diff --git a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/BotController.java b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/BotController.java new file mode 100644 index 0000000..3b906d0 --- /dev/null +++ b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/BotController.java @@ -0,0 +1,45 @@ +package dev.activitypub.activitypubbot; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.ui.Model; +import java.util.List; + +@Controller +public class BotController { + + @Autowired + private BotRepo botRepo; + + @GetMapping("/viewbot") + public String listAll(Model model) { + System.out.println("WebHandler::viewbot"); + + List botlist = botRepo.findAll(); + model.addAttribute("bots", botlist); + + return "viewbot"; + } + + @GetMapping("/makebot") + public String makebotget(Model model) { + System.out.println("WebHandler::makebot"); + + Bot bot = new Bot(); + model.addAttribute("bot", bot); + + return "makebot"; + } + + @PostMapping("/makebot") + public String makebotpost(@ModelAttribute("bot") Bot bot) { + System.out.println(bot); + + botRepo.save(bot); + + return "makebot_submitted"; + } +} diff --git a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/BotRepo.java b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/BotRepo.java new file mode 100644 index 0000000..07d6f41 --- /dev/null +++ b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/BotRepo.java @@ -0,0 +1,7 @@ +package dev.activitypub.activitypubbot; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BotRepo extends JpaRepository { + +} diff --git a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/RestHandler.java b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/RestHandler.java index 8e6662c..1303f38 100644 --- a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/RestHandler.java +++ b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/RestHandler.java @@ -3,7 +3,6 @@ package dev.activitypub.activitypubbot; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.beans.factory.annotation.Autowired; /** * Here we handle any JSON/REST requests. @@ -12,15 +11,6 @@ import org.springframework.beans.factory.annotation.Autowired; @RequestMapping( headers = "accept=application/json" ) public class RestHandler { - @Autowired - public APProperties apProps; - - // FIXME: just playing with working out app properties access here - @GetMapping("/key") - public String key() { - return apProps.getKeyFilePath(); - } - /** * Really just an alias to /user/springbot * TODO: how to auto-map @ to /users/ diff --git a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/WebHandler.java b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/WebHandler.java index ce24321..56a0629 100644 --- a/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/WebHandler.java +++ b/Java/Spring/activitypubbot/src/main/java/dev/activitypub/activitypubbot/WebHandler.java @@ -2,6 +2,7 @@ package dev.activitypub.activitypubbot; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.beans.factory.annotation.Autowired; /** * Here we handle all non-JSON/REST requests - i.e. the normal "web" view @@ -9,6 +10,9 @@ import org.springframework.web.bind.annotation.RequestMapping; @Controller public class WebHandler { + @Autowired + private APProperties apProps; + /** * handle requests for our "actor" - this presents the web/html view of * the bot @@ -32,7 +36,7 @@ public class WebHandler { System.out.println("WebHandler::root"); return "index"; } - +/* // TODO: presumably there is some way to map things like / to capture string and attempt to resolve template @RequestMapping("/viewbot") public String viewbot() { @@ -44,4 +48,5 @@ public class WebHandler { System.out.println("WebHandler::makebot"); return "makebot"; } +*/ } diff --git a/Java/Spring/activitypubbot/src/main/resources/application.properties b/Java/Spring/activitypubbot/src/main/resources/application.properties index cbcf4e3..1006740 100644 --- a/Java/Spring/activitypubbot/src/main/resources/application.properties +++ b/Java/Spring/activitypubbot/src/main/resources/application.properties @@ -1,8 +1,22 @@ spring.application.name=activitypubbot +springbot.domain=springbot.seth.id.au +springbot.scheme=https + spring.mvc.view.prefix: /WEB-INF/views/ spring.mvc.view.suffix: .jsp + server.ssl.key-store-type=PKCS12 server.ssl.key-store=classpath:keys/activitypubbot.p12 server.ssl.key-store-password=password server.ssl.key-alias=activitypubbot server.ssl.enabled=true + +spring.datasource.url=jdbc:postgresql://localhost:5432/springbot +spring.datasource.username=springbot +spring.datasource.password=spr1ngb0t +# do not use the following property value in production +spring.jpa.hibernate.ddl-auto=update +spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect +spring.jpa.properties.jakarta.persistence.schema-generation.scripts.action=create +spring.jpa.properties.jakarta.persistence.schema-generation.scripts.create-target=create.sql +spring.jpa.properties.jakarta.persistence.schema-generation.scripts.create-source=metadata diff --git a/Java/Spring/activitypubbot/src/main/webapp/WEB-INF/views/index.jsp b/Java/Spring/activitypubbot/src/main/resources/templates/index.html similarity index 100% rename from Java/Spring/activitypubbot/src/main/webapp/WEB-INF/views/index.jsp rename to Java/Spring/activitypubbot/src/main/resources/templates/index.html diff --git a/Java/Spring/activitypubbot/src/main/resources/templates/makebot.html b/Java/Spring/activitypubbot/src/main/resources/templates/makebot.html new file mode 100644 index 0000000..47dab56 --- /dev/null +++ b/Java/Spring/activitypubbot/src/main/resources/templates/makebot.html @@ -0,0 +1,47 @@ + + + + +Make Bot + + + +
+

Make A Bot!

+
+ +
+ + +
+ + + +
+ + +
+
+ + diff --git a/Java/Spring/activitypubbot/src/main/resources/templates/makebot_submitted.html b/Java/Spring/activitypubbot/src/main/resources/templates/makebot_submitted.html new file mode 100644 index 0000000..bf1f207 --- /dev/null +++ b/Java/Spring/activitypubbot/src/main/resources/templates/makebot_submitted.html @@ -0,0 +1,24 @@ + + + + +Made Bot + + + +
+

Made Bot!

+ Username:
+ Display Name:
+ Description:
+ Published:
+ Type:
+
+ + diff --git a/Java/Spring/activitypubbot/src/main/resources/templates/viewbot.html b/Java/Spring/activitypubbot/src/main/resources/templates/viewbot.html new file mode 100644 index 0000000..4c6cda7 --- /dev/null +++ b/Java/Spring/activitypubbot/src/main/resources/templates/viewbot.html @@ -0,0 +1,25 @@ + + + + +View Bot + + + +
+

Pick-a-Bot!

+
    +
  • No Bots!
  • +
+
    +
  • Bot: Bot
  • +
+
+ + diff --git a/Java/Spring/activitypubbot/src/main/webapp/WEB-INF/views/makebot.jsp b/Java/Spring/activitypubbot/src/main/webapp/WEB-INF/views/makebot.jsp deleted file mode 100644 index 758de3c..0000000 --- a/Java/Spring/activitypubbot/src/main/webapp/WEB-INF/views/makebot.jsp +++ /dev/null @@ -1,10 +0,0 @@ - - - - -Make Bot - Spring Bot - an Activity Pub bot using the Spring Boot framework - - -

Make Yourself a Bot!

- - diff --git a/Java/Spring/activitypubbot/src/main/webapp/WEB-INF/views/viewbot.jsp b/Java/Spring/activitypubbot/src/main/webapp/WEB-INF/views/viewbot.jsp deleted file mode 100644 index c78b7e5..0000000 --- a/Java/Spring/activitypubbot/src/main/webapp/WEB-INF/views/viewbot.jsp +++ /dev/null @@ -1,10 +0,0 @@ - - - - -View Bot - Spring Bot - an Activity Pub bot using the Spring Boot framework - - -

Pick a Bot!

- -