Initial commit.

This commit is contained in:
Andrew Pietila 2025-01-11 09:42:29 -06:00
commit 7a2fe26ddf
8 changed files with 363 additions and 0 deletions

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf

33
.gitignore vendored Normal file
View file

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

95
pom.xml Normal file
View file

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>social.brainz</groupId>
<artifactId>relay-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Brainz Relay</name>
<description>Demo project for Spring Boot</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.parsson</groupId>
<artifactId>jakarta.json</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>com.apicatalog</groupId>
<artifactId>titanium-json-ld</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,13 @@
package social.brainz.relay_server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BrainzRelayApplication {
public static void main(String[] args) {
SpringApplication.run(BrainzRelayApplication.class, args);
}
}

View file

@ -0,0 +1,62 @@
package social.brainz.relay_server.controllers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
@SuppressWarnings({ "unchecked", "rawtypes", "unused" })
@RestController
public class ActorController {
private static Map actorMap = new HashMap();
static {
// TODO: Config.
Map publicKeyMap = new HashMap<>();
publicKeyMap.put("id", "https://relay.brainz.social/actor#main-key");
publicKeyMap.put("owner", "https://relay.brainz.social/actor");
// TODO: Real public key
publicKeyMap.put("publicKeyPem", "");
actorMap.put("publicKey", publicKeyMap);
actorMap.put("inbox", "https://relay.brainz.social/inbox");
actorMap.put("outbox", "https://relay.brainz.social/outbox");
actorMap.put("following", "https://relay.brainz.social/following");
actorMap.put("followers", "https://relay.brainz.social/followers");
actorMap.put("preferredUsername", "relay");
Map endpointsMap = new HashMap<>();
endpointsMap.put("sharedInbox", "https://relay.brainz.social/inbox");
actorMap.put("endpoints", endpointsMap);
actorMap.put("summary", "BrainzRelay bot");
actorMap.put("url", "https://relay.brainz.relay/actor");
List contextList = new ArrayList<String>();
contextList.add("https://www.w3.org/ns/activitystreams");
contextList.add("https://w3id.org/security/v1");
actorMap.put("id", "https://relay.brainz.social/actor");
actorMap.put("type", "Application");
actorMap.put("name", "BrainzRelay");
}
@GetMapping("/actor")
public ResponseEntity<Map> getActor(@RequestHeader(HttpHeaders.ACCEPT) String accept) {
HttpHeaders headers = new HttpHeaders();
if ( accept.matches("application/activity\\+json")) {
headers.add("Content-Type", "application/activity+json");
} else if ( accept.matches("application/ld\\+json")) {
headers.add("Content-Type", "application/ld+json");
} else {
headers.add("Content-Type", "application/activity+json");
}
return new ResponseEntity<>(actorMap, headers, HttpStatus.OK);
}
}

View file

@ -0,0 +1,141 @@
package social.brainz.relay_server.controllers;
import java.io.StringReader;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.util.UriComponentsBuilder;
import com.apicatalog.jsonld.JsonLd;
import com.apicatalog.jsonld.JsonLdError;
import com.apicatalog.jsonld.document.JsonDocument;
import com.apicatalog.jsonld.http.media.MediaType;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
@Controller
public class InboxController {
@SuppressWarnings("rawtypes")
@PostMapping("/inbox")
public ResponseEntity postInbox(@RequestBody String entity) throws JsonLdError {
System.out.println(entity);
JsonObject result = JsonLd.compact(JsonDocument.of(MediaType.JSON, new StringReader(entity)),
"https://www.w3.org/ns/activitystreams").compactToRelative(false).get();
String activityType = result.getString("type").intern();
switch (activityType) {
case "Accept": {
JsonValue resultObject = result.get("object");
if (resultObject instanceof JsonObject resultObjectAsObject) {
System.out.println(resultObjectAsObject.get("type").toString());
if (resultObjectAsObject.getString("type").equals("Follow")) {
// TODO: Config.
if (resultObjectAsObject.getString("object").equals("https://relay.brainz.social/actor")) {
// noop
} else {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
}
} else {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
break;
}
case "Reject": {
// TODO: Send undo follow, and remove follow.
break;
}
case "Announce", "Create": {
JsonValue resultObject = result.get("object");
Map<String, Object> objectToPush = new HashMap<>();
List<String> contextList = new ArrayList<>();
contextList.add("https://www.w3.org/ns/activitystreams");
objectToPush.put("@context", contextList);
// TODO: Config
// TODO: Make a controller for this.
UriComponentsBuilder idBuilder = UriComponentsBuilder
.fromUriString("https://relay.brainz.social/announce");
idBuilder.queryParam("object",
resultObject instanceof JsonObject resultObjectObject
? resultObjectObject.getJsonString("id").getChars().toString()
: resultObject instanceof JsonString resultObjectString
? resultObjectString.getChars().toString()
: resultObject.toString());
objectToPush.put("id", idBuilder.encode().toUriString());
// TODO: Config
objectToPush.put("actor", "https://relay.brainz.social/actor");
objectToPush.put("published", ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT));
List<String> toList = new ArrayList<>();
toList.add("https://www.w3.org/ns/activitystreams#Public");
objectToPush.put("to", toList);
List<String> ccList = new ArrayList<>();
// TODO: Config
ccList.add("https://relay.brainz.social/");
ccList.add("https://relay.brainz.social/followers");
objectToPush.put("cc", ccList);
// JsonObject outputObject = Json.createObjectBuilder(objectToPush).build();
return new ResponseEntity<>(objectToPush, HttpStatus.OK);
}
case "Follow":
break;
case "Undo": {
JsonValue resultObject = result.get("object");
if (resultObject instanceof JsonObject resultObjectObject) {
// TODO: Handle Public.
if (resultObjectObject.getJsonString("object").getChars().toString()
.equals("https://relay.brainz.social/actor")) {
Map<String, Object> objectToPush = new HashMap<>();
List<String> contextList = new ArrayList<>();
contextList.add("https://www.w3.org/ns/activitystreams");
objectToPush.put("@context", contextList);
// TODO: Config
UriComponentsBuilder idBuilder = UriComponentsBuilder
.fromUriString("https://relay.brainz.social/undo");
idBuilder.queryParam("object",
resultObjectObject.getJsonString("id").getChars().toString());
objectToPush.put("id", idBuilder.encode().toUriString());
objectToPush.put("type", "Undo");
// TODO: Config
objectToPush.put("actor", "https://relay.brainz.social/actor");
Map<String, Object> subObjectToPush = new HashMap<>();
// TODO: Config
UriComponentsBuilder subIdBuilder = UriComponentsBuilder
.fromUriString("https://relay.brainz.social/follow");
subIdBuilder.queryParam("target",
resultObjectObject.getJsonString("actor").getChars().toString());
subObjectToPush.put("id", subIdBuilder.build().toUriString());
subObjectToPush.put("type", "Follow");
// TODO: Config
subObjectToPush.put("actor", "https://relay.brainz.social/actor");
subObjectToPush.put("object", resultObjectObject.getJsonString("actor").getChars().toString());
objectToPush.put("object", subObjectToPush);
// TODO: Remove listener from relay.
// JsonObject outputObject = Json.createObjectBuilder(objectToPush).build();
return new ResponseEntity<>(objectToPush, HttpStatus.OK);
}
}
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
case "Delete":
case "Update":
case "Add":
case "Remove":
default:
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}

View file

@ -0,0 +1,16 @@
package social.brainz.relay_server.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class IndexController {
@GetMapping("/")
public String getIndex(@RequestParam(name = "name", required = false, defaultValue = "World") String name,
Model model) {
model.addAttribute("name", name);
return "index";
}
}

View file

@ -0,0 +1 @@
spring.application.name=Brainz Relay