Finished Prototype
This commit is contained in:
parent
ea54a27796
commit
6de476260d
30 changed files with 2189 additions and 0 deletions
13
entities/activity/activity.go
Normal file
13
entities/activity/activity.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package activity
|
||||
|
||||
import "git.nutfactory.org/hoernschen/ActivityPub/entities/object"
|
||||
|
||||
type Activity struct {
|
||||
Context string `json:"@context,omitempty"`
|
||||
Id string `json:"id,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Actor string `json:"actor,omitempty"`
|
||||
Object *object.Object `json:"object,omitempty"`
|
||||
Published int64 `json:"published,omitempty"`
|
||||
To string `json:"to,omitempty"`
|
||||
}
|
29
entities/activity/activityController.go
Normal file
29
entities/activity/activityController.go
Normal file
|
@ -0,0 +1,29 @@
|
|||
package activity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/object"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils"
|
||||
)
|
||||
|
||||
func New(id string, actorOfActivity string, objectOfActivity *object.Object, userId string) (newActivity *Activity) {
|
||||
published := objectOfActivity.Published
|
||||
to := objectOfActivity.To
|
||||
if published == 0 {
|
||||
published = time.Now().Unix()
|
||||
}
|
||||
if to == "" {
|
||||
to = utils.GenerateFollowersUrl(userId)
|
||||
}
|
||||
newActivity = &Activity{
|
||||
Context: utils.GetDefaultContext(),
|
||||
Id: id,
|
||||
Type: "Create",
|
||||
Actor: actorOfActivity,
|
||||
Object: objectOfActivity,
|
||||
Published: published,
|
||||
To: to,
|
||||
}
|
||||
return
|
||||
}
|
15
entities/actor/actor.go
Normal file
15
entities/actor/actor.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package actor
|
||||
|
||||
type Actor struct {
|
||||
Context string `json:"@context,omitempty"`
|
||||
Id string `json:"id,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
PreferredUsername string `json:"preferredUsername,omitempty"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Inbox string `json:"inbox,omitempty"`
|
||||
Outbox string `json:"outbox,omitempty"`
|
||||
Followers string `json:"followers,omitempty"`
|
||||
Following string `json:"following,omitempty"`
|
||||
Liked string `json:"liked,omitempty"`
|
||||
}
|
75
entities/actor/actorController.go
Normal file
75
entities/actor/actorController.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package actor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/config"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils"
|
||||
)
|
||||
|
||||
func New(userId string) (newActor *Actor) {
|
||||
id := utils.GenerateProfileUrl(userId)
|
||||
newActor = GenerateProfile(id, userId)
|
||||
return
|
||||
}
|
||||
|
||||
func GenerateProfile(id string, userId string) (profile *Actor) {
|
||||
profile = &Actor{
|
||||
Context: utils.GetDefaultContext(),
|
||||
Id: id,
|
||||
Name: userId,
|
||||
PreferredUsername: userId,
|
||||
Type: "Person",
|
||||
Inbox: utils.GenerateInboxUrl(userId),
|
||||
Outbox: utils.GenerateOutboxUrl(userId),
|
||||
Followers: utils.GenerateFollowersUrl(userId),
|
||||
Following: utils.GenerateFollowingUrl(userId),
|
||||
Liked: utils.GenerateLikedUrl(userId),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetProfileHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", utils.GetContentTypeString())
|
||||
foundActor, err := ReadActor(fmt.Sprintf("%s://%s%s", config.HttpString, config.Homeserver, r.URL.RequestURI()))
|
||||
if foundActor == nil || err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Actor not found: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
profile := GenerateProfile(foundActor.Id, foundActor.Name)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(profile); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetProfile(actorId string) (err error, profile *Actor) {
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
req, err := http.NewRequest(http.MethodGet, actorId, bytes.NewBuffer(nil))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header["Content-Type"] = []string{utils.GetContentTypeString()}
|
||||
r, err := client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if r.StatusCode != http.StatusOK {
|
||||
err = utils.HandleHTTPError(r)
|
||||
return
|
||||
} else {
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err = decoder.Decode(&profile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
76
entities/actor/actorDatabaseConnector.go
Normal file
76
entities/actor/actorDatabaseConnector.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package actor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils/database"
|
||||
)
|
||||
|
||||
func CreateActor(actor *Actor) (err error) {
|
||||
sqlStmt := fmt.Sprintf(`INSERT INTO actor
|
||||
(id, type, name, preferredUsername, summary, inbox, outbox, followers, following, liked)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||
|
||||
tx, err := database.DB.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
stmt, err := tx.Prepare(sqlStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
_, err = stmt.Exec(
|
||||
actor.Id,
|
||||
actor.Type,
|
||||
actor.Name,
|
||||
actor.PreferredUsername,
|
||||
actor.Summary,
|
||||
actor.Inbox,
|
||||
actor.Outbox,
|
||||
actor.Followers,
|
||||
actor.Following,
|
||||
actor.Liked,
|
||||
)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
func ReadActor(id string) (foundActor *Actor, err error) {
|
||||
queryStmt := fmt.Sprintf(`SELECT id, type, name, preferredUsername, summary, inbox, outbox, followers, following, liked
|
||||
FROM actor
|
||||
WHERE id = '%s'`, id)
|
||||
|
||||
rows, err := database.DB.Query(queryStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
foundActor = &Actor{}
|
||||
err = rows.Scan(
|
||||
&foundActor.Id,
|
||||
&foundActor.Type,
|
||||
&foundActor.Name,
|
||||
&foundActor.PreferredUsername,
|
||||
&foundActor.Summary,
|
||||
&foundActor.Inbox,
|
||||
&foundActor.Outbox,
|
||||
&foundActor.Followers,
|
||||
&foundActor.Following,
|
||||
&foundActor.Liked,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
5
entities/collection/collection.go
Normal file
5
entities/collection/collection.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package collection
|
||||
|
||||
type OutboxResponse struct {
|
||||
Location string `json:"Location,omitempty"`
|
||||
}
|
350
entities/collection/collectionController.go
Normal file
350
entities/collection/collectionController.go
Normal file
|
@ -0,0 +1,350 @@
|
|||
package collection
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/config"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/activity"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/actor"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/object"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/user"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func PostInboxHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", utils.GetContentTypeString())
|
||||
newActivity := &activity.Activity{}
|
||||
err := utils.CheckRequest(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(err); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
token, err := utils.GetAccessToken(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(err); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if token == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode("Missing Token"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
actorName := vars["actorName"]
|
||||
if actorName == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode("Missing Actor"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err = decoder.Decode(&newActivity)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Could not parse JSON: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if newActivity.Type == "Follow" {
|
||||
err = CreateCollectionObject(utils.GenerateFollowersUrl(actorName), newActivity.Actor)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Collection Object: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
log.Printf("%s follows %s", newActivity.Actor, newActivity.To)
|
||||
} else if newActivity.Type == "Create" {
|
||||
foundObject, err := object.ReadObject(newActivity.Object.Id)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Reading Object: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if foundObject == nil {
|
||||
_ = object.CreateObject(newActivity.Object)
|
||||
/*
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Object: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
*/
|
||||
}
|
||||
_ = CreateCollectionObject(utils.GenerateInboxUrl(actorName), newActivity.Object.Id)
|
||||
/*
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Collection Object: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
*/
|
||||
log.Printf("%s to %s: %s", newActivity.Actor, newActivity.To, newActivity.Object.Content)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode("Unsupported Activity Type"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func PostOutboxHandler(w http.ResponseWriter, r *http.Request) {
|
||||
packetLossNumber := rand.Intn(100)
|
||||
if packetLossNumber > config.Packetloss {
|
||||
w.Header().Set("Content-Type", utils.GetContentTypeString())
|
||||
newActivity := &activity.Activity{}
|
||||
err := utils.CheckRequest(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(err); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
token, err := utils.GetAccessToken(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(err); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
foundUser, err := user.ReadUserFromToken(token)
|
||||
if err != nil || foundUser == nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Unknown Token: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
buf.ReadFrom(r.Body)
|
||||
err = json.Unmarshal(buf.Bytes(), newActivity)
|
||||
if err != nil || newActivity.Type == "Note" {
|
||||
postedObject := &object.Object{}
|
||||
err = json.Unmarshal(buf.Bytes(), postedObject)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Could not parse JSON: %s")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
var newObject *object.Object
|
||||
if postedObject.Id == "" {
|
||||
err, newObject = object.New(
|
||||
postedObject.Type,
|
||||
postedObject.AttributedTo,
|
||||
postedObject.Content,
|
||||
postedObject.Published,
|
||||
postedObject.To,
|
||||
foundUser.Id,
|
||||
)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Error Creating Object: %s")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err = object.CreateObject(newObject)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Object: %s")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
newObject = postedObject
|
||||
}
|
||||
newActivity = activity.New(
|
||||
newObject.Id,
|
||||
newObject.AttributedTo,
|
||||
newObject,
|
||||
foundUser.Id,
|
||||
)
|
||||
}
|
||||
|
||||
var recipients []string
|
||||
if newActivity.Type == "Follow" {
|
||||
err = CreateCollectionObject(utils.GenerateFollowingUrl(foundUser.Id), newActivity.To)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Collection Object: %s")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
recipients = append(recipients, newActivity.To)
|
||||
} else if newActivity.Type == "Create" {
|
||||
err = CreateCollectionObject(utils.GenerateOutboxUrl(foundUser.Id), newActivity.Id)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Collection Object: %s")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err, recipients = GetReciepientsForActivity(newActivity)
|
||||
}
|
||||
|
||||
for _, recipient := range recipients {
|
||||
log.Printf("Send Activity to Recipient %s", recipient)
|
||||
|
||||
operation := func() error {
|
||||
return SendActivity(newActivity, recipient, token)
|
||||
}
|
||||
notify := func(err error, duration time.Duration) {
|
||||
log.Printf("Error Sending Activity, retrying in %ss: %s", duration/1000000000, err)
|
||||
}
|
||||
backoff.RetryNotify(operation, backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 16), notify)
|
||||
|
||||
//go retryActivity(newActivity, recipient, token)
|
||||
}
|
||||
|
||||
response := OutboxResponse{
|
||||
Location: newActivity.Id,
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func retryActivity(activityToSend *activity.Activity, recipient string, token string) (err error) {
|
||||
b, cancel := config.BackoffPolicy.Start(context.Background())
|
||||
defer cancel()
|
||||
|
||||
for backoff.Continue(b) {
|
||||
err := SendActivity(activityToSend, recipient, token)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
err = errors.New("Not able to send activity")
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
func GetReciepientsForActivity(activityToSend *activity.Activity) (err error, recipientsWithoutDuplicates []string) {
|
||||
recipients := []string{}
|
||||
if strings.Contains(activityToSend.To, config.Homeserver) {
|
||||
var foundActor *actor.Actor
|
||||
foundActor, err = actor.ReadActor(activityToSend.To)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if foundActor == nil {
|
||||
var foundCollectionObjects []string
|
||||
foundCollectionObjects, err = ReadCollectionObjects(activityToSend.To)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(foundCollectionObjects) <= 0 {
|
||||
err = errors.New("No Recipients")
|
||||
return
|
||||
}
|
||||
recipients = append(recipients, foundCollectionObjects...)
|
||||
} else {
|
||||
recipients = append(recipients, foundActor.Id)
|
||||
}
|
||||
} else {
|
||||
// Not Implemented
|
||||
}
|
||||
recipientsWithoutDuplicates = utils.RemoveDuplicates(recipients)
|
||||
return
|
||||
}
|
||||
|
||||
func SendActivity(activityToSend *activity.Activity, recipient string, token string) (err error) {
|
||||
if strings.Contains(recipient, config.Homeserver) {
|
||||
if activityToSend.Type == "Follow" {
|
||||
followers := fmt.Sprintf("%sfollowers/", activityToSend.To)
|
||||
err = CreateCollectionObject(followers, activityToSend.Actor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Printf("%s follows %s", activityToSend.Actor, activityToSend.To)
|
||||
} else if activityToSend.Type == "Create" {
|
||||
id := activityToSend.Id
|
||||
if activityToSend.Object != nil {
|
||||
id = activityToSend.Object.Id
|
||||
}
|
||||
inbox := fmt.Sprintf("%sinbox", recipient)
|
||||
err = CreateCollectionObject(inbox, id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Printf("%s to %s: %s", activityToSend.Actor, recipient, activityToSend.Object.Content)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
activityToSend.To = recipient
|
||||
var profile *actor.Actor
|
||||
err, profile = actor.GetProfile(recipient)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var reqBody []byte
|
||||
reqBody, err = json.Marshal(activityToSend)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
client := &http.Client{Timeout: 2 * time.Second}
|
||||
var req *http.Request
|
||||
log.Printf("Inbox: %s", profile.Inbox)
|
||||
req, err = http.NewRequest(http.MethodPost, profile.Inbox, bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header["Content-Type"] = []string{utils.GetContentTypeString()}
|
||||
req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s")}
|
||||
var res *http.Response
|
||||
res, err = client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
err = utils.HandleHTTPError(res)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
62
entities/collection/collectionDatabaseConnector.go
Normal file
62
entities/collection/collectionDatabaseConnector.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package collection
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils/database"
|
||||
)
|
||||
|
||||
func CreateCollectionObject(collectionId string, objectId string) (err error) {
|
||||
sqlStmt := fmt.Sprintf(`INSERT INTO collectionObject
|
||||
(collectionId, objectId)
|
||||
VALUES
|
||||
(?, ?)`)
|
||||
|
||||
tx, err := database.DB.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(sqlStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec(
|
||||
collectionId,
|
||||
objectId,
|
||||
)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
func ReadCollectionObjects(collectionId string) (collectionObjects []string, err error) {
|
||||
queryStmt := fmt.Sprintf(`SELECT objectId
|
||||
FROM collectionObject
|
||||
WHERE collectionId = '%s'`, collectionId)
|
||||
|
||||
rows, err := database.DB.Query(queryStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var collectionObject string
|
||||
err = rows.Scan(
|
||||
&collectionObject,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
collectionObjects = append(collectionObjects, collectionObject)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
11
entities/object/object.go
Normal file
11
entities/object/object.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package object
|
||||
|
||||
type Object struct {
|
||||
Context string `json:"@context,omitempty"`
|
||||
Id string `json:"id,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
AttributedTo string `json:"attributedTo,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Published int64 `json:"published,omitempty"`
|
||||
To string `json:"to,omitempty"`
|
||||
}
|
49
entities/object/objectController.go
Normal file
49
entities/object/objectController.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/config"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils"
|
||||
)
|
||||
|
||||
func New(objectType string, attributedTo string, content string, published int64, to string, userId string) (err error, newObject *Object) {
|
||||
err, id := utils.CreateUUID()
|
||||
url := fmt.Sprintf("%s://%s/%s/posts/%s", config.HttpString, config.Homeserver, userId, id)
|
||||
if published == 0 {
|
||||
published = time.Now().Unix()
|
||||
}
|
||||
if attributedTo == "" {
|
||||
attributedTo = utils.GenerateProfileUrl(userId)
|
||||
}
|
||||
newObject = &Object{
|
||||
Context: utils.GetDefaultContext(),
|
||||
Id: url,
|
||||
Type: objectType,
|
||||
AttributedTo: attributedTo,
|
||||
Content: content,
|
||||
Published: published,
|
||||
To: to,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", utils.GetContentTypeString())
|
||||
foundObject, err := ReadObject(r.URL.RequestURI())
|
||||
if foundObject == nil || err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Post not found: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
foundObject.Context = utils.GetDefaultContext()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(foundObject); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
70
entities/object/objectDatabaseConnector.go
Normal file
70
entities/object/objectDatabaseConnector.go
Normal file
|
@ -0,0 +1,70 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils/database"
|
||||
)
|
||||
|
||||
func CreateObject(object *Object) (err error) {
|
||||
sqlStmt := fmt.Sprintf(`INSERT INTO object
|
||||
(id, type, attributedTo, content, published, toActor)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?)`)
|
||||
|
||||
tx, err := database.DB.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(sqlStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec(
|
||||
object.Id,
|
||||
object.Type,
|
||||
object.AttributedTo,
|
||||
object.Content,
|
||||
object.Published,
|
||||
object.To,
|
||||
)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
func ReadObject(id string) (foundObject *Object, err error) {
|
||||
queryStmt := fmt.Sprintf(`SELECT id, type, attributedTo, content, published, toActor
|
||||
FROM object
|
||||
WHERE id = '%s'`, id)
|
||||
|
||||
rows, err := database.DB.Query(queryStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
foundObject = &Object{}
|
||||
err = rows.Scan(
|
||||
&foundObject.Id,
|
||||
&foundObject.Type,
|
||||
&foundObject.AttributedTo,
|
||||
&foundObject.Content,
|
||||
&foundObject.Published,
|
||||
&foundObject.To,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
19
entities/user/user.go
Normal file
19
entities/user/user.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package user
|
||||
|
||||
type User struct {
|
||||
Id string `json:"id,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Actor string `json:"actor,omitempty"`
|
||||
Sessions []string `json:"sessions,omitempty"`
|
||||
}
|
||||
|
||||
type RegisterRequest struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
type RegisterResponse struct {
|
||||
UserId string `json:"user_id,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
Actor string `json:"actor,omitempty"`
|
||||
}
|
103
entities/user/userController.go
Normal file
103
entities/user/userController.go
Normal file
|
@ -0,0 +1,103 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/entities/actor"
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils"
|
||||
)
|
||||
|
||||
func New(id string, password string) (err error, newUser *User) {
|
||||
err, hashedPassword := utils.Hash([]byte(password))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newUser = &User{
|
||||
Id: id,
|
||||
Password: hashedPassword,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func RegisterHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||
request := RegisterRequest{}
|
||||
err := utils.CheckRequest(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(err); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err = decoder.Decode(&request)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode("Could not parse JSON: %s"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err, newUser := New(request.Username, request.Password)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Error creating User Object: %s")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
foundUser, err := ReadUser(newUser.Id)
|
||||
if foundUser != nil || err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Username already in use: %s", err)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
newActor := actor.New(newUser.Id)
|
||||
err = actor.CreateActor(newActor)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Actor: %s")); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
newUser.Actor = newActor.Id
|
||||
err = CreateUser(newUser)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode("Database Error Creating User: %s"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err, token := utils.CreateToken()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode("Error Creating Token: %s"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err = CreateSession(newUser.Id, token)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
if err := json.NewEncoder(w).Encode("Error Creating Session: %s"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
response := RegisterResponse{
|
||||
UserId: newUser.Id,
|
||||
Token: token,
|
||||
Actor: newActor.Id,
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
132
entities/user/userDatabaseConnector.go
Normal file
132
entities/user/userDatabaseConnector.go
Normal file
|
@ -0,0 +1,132 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.nutfactory.org/hoernschen/ActivityPub/utils/database"
|
||||
)
|
||||
|
||||
func CreateUser(user *User) (err error) {
|
||||
sqlStmt := fmt.Sprintf(`INSERT INTO user
|
||||
(id, password, actor)
|
||||
VALUES
|
||||
(?, ?, ?)`)
|
||||
|
||||
tx, err := database.DB.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(sqlStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec(user.Id, user.Password, user.Actor)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
func CreateSession(userId string, token string) (err error) {
|
||||
sqlStmt := fmt.Sprintf(`INSERT INTO session
|
||||
(token, userId)
|
||||
VALUES
|
||||
(?, ?)`)
|
||||
|
||||
tx, err := database.DB.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(sqlStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
_, err = stmt.Exec(token, userId)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
func ReadUser(id string) (foundUser *User, err error) {
|
||||
queryStmt := fmt.Sprintf(`SELECT id, password, actor
|
||||
FROM user
|
||||
WHERE id = '%s'`, id)
|
||||
|
||||
rows, err := database.DB.Query(queryStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
foundUser = &User{}
|
||||
err = rows.Scan(&foundUser.Id, &foundUser.Password, &foundUser.Actor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
foundUser.Sessions, err = ReadSessionsForUser(foundUser.Id)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ReadUserFromToken(token string) (foundUser *User, err error) {
|
||||
queryStmt := fmt.Sprintf(`SELECT u.id, u.password, u.actor
|
||||
FROM user as u
|
||||
join session as s on u.id = s.userId
|
||||
WHERE s.token = '%s'`, token)
|
||||
|
||||
rows, err := database.DB.Query(queryStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
if rows.Next() {
|
||||
foundUser = &User{}
|
||||
err = rows.Scan(&foundUser.Id, &foundUser.Password, &foundUser.Actor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
foundUser.Sessions, err = ReadSessionsForUser(foundUser.Id)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ReadSessionsForUser(userId string) (sessions []string, err error) {
|
||||
queryStmt := fmt.Sprintf(`SELECT token
|
||||
FROM session
|
||||
WHERE userId = '%s'`, userId)
|
||||
|
||||
rows, err := database.DB.Query(queryStmt)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var session string
|
||||
err = rows.Scan(&session)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sessions = append(sessions, session)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue