Finished Prototype

This commit is contained in:
Hoernschen 2020-10-17 12:13:15 +02:00
parent ea54a27796
commit 6de476260d
30 changed files with 2189 additions and 0 deletions

View 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"`
}

View 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
View 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"`
}

View 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
}

View 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
}

View file

@ -0,0 +1,5 @@
package collection
type OutboxResponse struct {
Location string `json:"Location,omitempty"`
}

View 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
}

View 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
View 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"`
}

View 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)
}
}

View 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
View 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"`
}

View 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)
}
}

View 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
}