mirror of https://gitlab.com/ngerakines/tavern.git
Implemented group actor inbox.
This commit is contained in:
parent
0691aa7577
commit
a2c2b11a08
|
@ -1 +0,0 @@
|
|||
package common
|
|
@ -44,6 +44,19 @@ func (client ActivityClient) GetSigned(location string, localActor storage.Local
|
|||
return ldJsonGetSigned(client.HTTPClient, location, signer, localActor.Actor.GetKeyID(), privateKey)
|
||||
}
|
||||
|
||||
func (client ActivityClient) GetSignedWithKey(location, keyID string, privateKey *rsa.PrivateKey) (string, storage.Payload, error) {
|
||||
client.Logger.Debug("Sending signed activity request", zap.String("url", location))
|
||||
|
||||
sigConfig := []httpsig.Algorithm{httpsig.RSA_SHA256}
|
||||
headersToSign := []string{httpsig.RequestTarget, "date"}
|
||||
signer, _, err := httpsig.NewSigner(sigConfig, headersToSign, httpsig.Signature)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return ldJsonGetSigned(client.HTTPClient, location, signer, keyID, privateKey)
|
||||
}
|
||||
|
||||
func ldJsonGet(client common.HTTPClient, location string) (string, storage.Payload, error) {
|
||||
request, err := http.NewRequest("GET", location, nil)
|
||||
if err != nil {
|
||||
|
|
|
@ -16,17 +16,19 @@ create table if not exists public.groups
|
|||
unique (name)
|
||||
);
|
||||
|
||||
--- used to track actor to group follow requests
|
||||
create table if not exists public.group_members
|
||||
(
|
||||
id uuid not null
|
||||
constraint group_members_pk
|
||||
constraint group_followers_pk
|
||||
primary key,
|
||||
created_at timestamp with time zone not null,
|
||||
updated_at timestamp with time zone not null,
|
||||
group_actor_id uuid not null,
|
||||
member_actor_id uuid not null,
|
||||
activity jsonb not null,
|
||||
membership_type integer default 0 not null,
|
||||
constraint group_members_actors_uindex
|
||||
status integer default 0 not null,
|
||||
role integer default 0 not null,
|
||||
constraint group_followers_uindex
|
||||
unique (group_actor_id, member_actor_id)
|
||||
);
|
||||
|
|
|
@ -2,6 +2,9 @@ package storage
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -14,39 +17,54 @@ import (
|
|||
type GroupStorage interface {
|
||||
RecordGroup(ctx context.Context, userRowID, actorRowID uuid.UUID, name, publicKey, privateKey, displayName, about string) (uuid.UUID, error)
|
||||
RecordGroupAll(ctx context.Context, rowID uuid.UUID, createdAt, updatedAt time.Time, userRowID, actorRowID uuid.UUID, name, publicKey, privateKey, displayName, about string) (uuid.UUID, error)
|
||||
RecordGroupMember(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, membershipType int) (uuid.UUID, error)
|
||||
RecordGroupMemberAll(ctx context.Context, rowID uuid.UUID, createdAt, updatedAt time.Time, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, membershipType int) (uuid.UUID, error)
|
||||
GetGroupByName(ctx context.Context, name string) (Group, error)
|
||||
GetGroupByID(ctx context.Context, groupRowID uuid.UUID) (Group, error)
|
||||
GetGroupsByOwner(ctx context.Context, userRowID uuid.UUID) ([]Group, error)
|
||||
|
||||
ListGroups(ctx context.Context) ([]Group, error)
|
||||
GroupNameAvailable(context.Context, string) (bool, error)
|
||||
|
||||
CountGroupMembers(ctx context.Context, groupActorRowID uuid.UUID) (int, error)
|
||||
GroupMemberActorIDs(ctx context.Context, groupActorRowID uuid.UUID) ([]string, error)
|
||||
|
||||
RecordGroupMember(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, status RelationshipStatus, role GroupRole) (uuid.UUID, error)
|
||||
RecordGroupMemberAll(ctx context.Context, rowID uuid.UUID, createdAt, updatedAt time.Time, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, status RelationshipStatus, role GroupRole) (uuid.UUID, error)
|
||||
|
||||
UpdateGroupMemberStatus(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, status RelationshipStatus) error
|
||||
UpdateGroupMemberRole(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, role GroupRole) error
|
||||
|
||||
GroupActorsForMemberActorRowID(ctx context.Context, groupActorRowID uuid.UUID) ([]*Actor, error)
|
||||
|
||||
GroupMemberActorsForGroupActorID(ctx context.Context, groupActorRowID uuid.UUID) ([]*Actor, error)
|
||||
|
||||
RemoveGroupMember(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID) error
|
||||
GroupMemberCanSubmit(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID) (bool, error)
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
ID uuid.UUID
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string
|
||||
PublicKey string
|
||||
PrivateKey string
|
||||
DisplayName string
|
||||
About string
|
||||
AcceptFollowers bool
|
||||
OwnerID uuid.UUID
|
||||
ActorID uuid.UUID
|
||||
ID uuid.UUID
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string
|
||||
PublicKey string
|
||||
PrivateKey string
|
||||
DisplayName string
|
||||
About string
|
||||
AcceptFollowers bool
|
||||
DefaultMemberRole GroupRole
|
||||
OwnerID uuid.UUID
|
||||
ActorID uuid.UUID
|
||||
}
|
||||
|
||||
type GroupRole int16
|
||||
|
||||
const (
|
||||
// GroupOwner can manage the group.
|
||||
GroupOwner = 1
|
||||
GroupOwner GroupRole = 3
|
||||
// GroupMember can post to the group.
|
||||
GroupMember = 2
|
||||
GroupMember GroupRole = 1
|
||||
// GroupViewer can receive group activities, but cannot create group activities.
|
||||
GroupViewer = 3
|
||||
GroupViewer GroupRole = 0
|
||||
)
|
||||
|
||||
var groupFields = []string{
|
||||
|
@ -59,10 +77,29 @@ var groupFields = []string{
|
|||
"display_name",
|
||||
"about",
|
||||
"accept_followers",
|
||||
"default_member_role",
|
||||
"owner",
|
||||
"actor_id",
|
||||
}
|
||||
|
||||
func (g Group) GetPrivateKey() (*rsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode([]byte(g.PrivateKey))
|
||||
if block == nil {
|
||||
return nil, errors.New("invalid RSA PEM")
|
||||
}
|
||||
|
||||
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (g Group) GetDecodedPublicKey() (*rsa.PublicKey, error) {
|
||||
return DecodePublicKey(g.PublicKey)
|
||||
}
|
||||
|
||||
func (s pgStorage) RecordGroup(ctx context.Context, userRowID, actorRowID uuid.UUID, name, publicKey, privateKey, displayName, about string) (uuid.UUID, error) {
|
||||
rowID := NewV4()
|
||||
now := s.now()
|
||||
|
@ -76,16 +113,16 @@ func (s pgStorage) RecordGroupAll(ctx context.Context, rowID uuid.UUID, createdA
|
|||
return id, errors.WrapGroupUpsertFailedError(err)
|
||||
}
|
||||
|
||||
func (s pgStorage) RecordGroupMember(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, membershipType int) (uuid.UUID, error) {
|
||||
func (s pgStorage) RecordGroupMember(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, status RelationshipStatus, role GroupRole) (uuid.UUID, error) {
|
||||
rowID := NewV4()
|
||||
now := s.now()
|
||||
return s.RecordGroupMemberAll(ctx, rowID, now, now, groupActorRowID, memberActorRowID, activity, membershipType)
|
||||
return s.RecordGroupMemberAll(ctx, rowID, now, now, groupActorRowID, memberActorRowID, activity, status, role)
|
||||
}
|
||||
|
||||
func (s pgStorage) RecordGroupMemberAll(ctx context.Context, rowID uuid.UUID, createdAt, updatedAt time.Time, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, membershipType int) (uuid.UUID, error) {
|
||||
query := `INSERT INTO group_members (id, created_at, updated_at, group_actor_id, member_actor_id, activity, membership_type) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT ON CONSTRAINT group_members_actors_uindex DO UPDATE set updated_at = $3 RETURNING id`
|
||||
func (s pgStorage) RecordGroupMemberAll(ctx context.Context, rowID uuid.UUID, createdAt, updatedAt time.Time, groupActorRowID, memberActorRowID uuid.UUID, activity Payload, status RelationshipStatus, role GroupRole) (uuid.UUID, error) {
|
||||
query := `INSERT INTO group_members (id, created_at, updated_at, group_actor_id, member_actor_id, activity, relationship_status, member_role) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT ON CONSTRAINT group_members_actors_uindex DO UPDATE set updated_at = $3 RETURNING id`
|
||||
var id uuid.UUID
|
||||
err := s.db.QueryRowContext(ctx, query, rowID, createdAt, updatedAt, groupActorRowID, memberActorRowID, activity, membershipType).Scan(&id)
|
||||
err := s.db.QueryRowContext(ctx, query, rowID, createdAt, updatedAt, groupActorRowID, memberActorRowID, activity, status, role).Scan(&id)
|
||||
return id, errors.WrapGroupMemberUpsertFailedError(err)
|
||||
}
|
||||
|
||||
|
@ -138,6 +175,7 @@ func (s pgStorage) getGroups(ctx context.Context, query string, args ...interfac
|
|||
&group.DisplayName,
|
||||
&group.About,
|
||||
&group.AcceptFollowers,
|
||||
&group.DefaultMemberRole,
|
||||
&group.OwnerID,
|
||||
&group.ActorID)
|
||||
if err != nil {
|
||||
|
@ -192,7 +230,34 @@ func (s pgStorage) GroupMemberActorIDs(ctx context.Context, groupActorRowID uuid
|
|||
return actors, nil
|
||||
}
|
||||
|
||||
func (s pgStorage) GroupActorsForMemberActorRowID(ctx context.Context, groupActorRowID uuid.UUID) ([]*Actor, error) {
|
||||
func (s pgStorage) GroupActorsForMemberActorRowID(ctx context.Context, memberActorRowID uuid.UUID) ([]*Actor, error) {
|
||||
query := actorsSelectQuery("group_members gm ON a.id = gm.group_actor_id", []string{"gm.member_actor_id = $1"})
|
||||
return s.getActors(ctx, query, memberActorRowID)
|
||||
}
|
||||
|
||||
func (s pgStorage) GroupMemberActorsForGroupActorID(ctx context.Context, groupActorRowID uuid.UUID) ([]*Actor, error) {
|
||||
query := actorsSelectQuery("group_members gm ON a.id = gm.member_actor_id", []string{"gm.group_actor_id = $1"})
|
||||
return s.getActors(ctx, query, groupActorRowID)
|
||||
}
|
||||
|
||||
func (s pgStorage) UpdateGroupMemberStatus(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, status RelationshipStatus) error {
|
||||
now := s.now()
|
||||
_, err := s.db.ExecContext(ctx, "UPDATE group_members SET relationship_status = $4, updated_at = $3 WHERE group_actor_id = $1 AND member_actor_id = $2", groupActorRowID, memberActorRowID, now, status)
|
||||
return errors.WrapUserUpdateFailedError(err)
|
||||
}
|
||||
|
||||
func (s pgStorage) UpdateGroupMemberRole(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID, role GroupRole) error {
|
||||
now := s.now()
|
||||
_, err := s.db.ExecContext(ctx, "UPDATE group_members SET member_role = $4, updated_at = $3 WHERE group_actor_id = $1 AND member_actor_id = $2", groupActorRowID, memberActorRowID, now, role)
|
||||
return errors.WrapUserUpdateFailedError(err)
|
||||
}
|
||||
|
||||
func (s pgStorage) RemoveGroupMember(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID) error {
|
||||
_, err := s.db.ExecContext(ctx, `DELETE FROM group_members WHERE group_actor_id = $1 AND member_actor_id = $2`, groupActorRowID, memberActorRowID)
|
||||
return errors.WrapNetworkRelationshipUpdateFailedError(err)
|
||||
}
|
||||
|
||||
func (s pgStorage) GroupMemberCanSubmit(ctx context.Context, groupActorRowID, memberActorRowID uuid.UUID) (bool, error) {
|
||||
query := `SELECT COUNT(*) FROM group_members WHERE group_actor_id = $1 AND member_actor_id = $2 AND member_role IN ($3, $4)`
|
||||
return s.wrappedExists(errors.WrapGroupMemberQueryFailedError, ctx, query, groupActorRowID, memberActorRowID, GroupMember, GroupOwner)
|
||||
}
|
||||
|
|
|
@ -49,35 +49,15 @@
|
|||
{{ .ActorID }}
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-outline-primary" href="{{ url "group" .Name }}">
|
||||
<a href="{{ url "group" .Name }}">
|
||||
View
|
||||
</a>
|
||||
<a class="btn btn-sm btn-outline-primary" href="{{ url "group_manage" .Name }}">
|
||||
<a href="{{ url "group_manage" .Name }}">
|
||||
Manage
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ range .pending_following }}
|
||||
<tr>
|
||||
<td><span class="badge badge-danger">Pending</span> {{ .ActorID }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ range .invitations }}
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">Pending</span> {{ .ActorID }}</td>
|
||||
<td>
|
||||
<form method="post" action="{{ url "groups_accept" }}">
|
||||
<input type="hidden" name="actor" value="{{ . }}"/>
|
||||
<input class="btn btn-sm btn-success" type="submit" name="submit" value="Accept"/>
|
||||
</form>
|
||||
<form method="post" action="{{ url "groups_reject" }}">
|
||||
<input type="hidden" name="actor" value="{{ . }}"/>
|
||||
<input class="btn btn-sm btn-danger" type="submit" name="submit" value="Reject"/>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -120,15 +120,6 @@ func (h handler) dashboardGroupsCreate(c *gin.Context) {
|
|||
return err
|
||||
}
|
||||
|
||||
joinGroup := storage.EmptyPayload()
|
||||
joinGroup["@context"] = "https://www.w3.org/ns/activitystreams"
|
||||
joinGroup["id"] = common.ActivityURL(h.domain, storage.NewV4())
|
||||
joinGroup["summary"] = ""
|
||||
joinGroup["type"] = "Join"
|
||||
joinGroup["actor"] = localUserActor.ActorID
|
||||
joinGroup["object"] = groupActorID
|
||||
joinGroup["published"] = publishedAt
|
||||
|
||||
err = tx.RecordActorKey(ctx, actorRowID, groupKeyID, publicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -141,7 +132,17 @@ func (h handler) dashboardGroupsCreate(c *gin.Context) {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = tx.RecordGroupMember(ctx, actorRowID, localUser.ActorID, joinGroup, storage.GroupOwner)
|
||||
followGroup := storage.EmptyPayload()
|
||||
followGroup["@context"] = "https://www.w3.org/ns/activitystreams"
|
||||
followGroup["id"] = common.ActivityURL(h.domain, storage.NewV4())
|
||||
followGroup["summary"] = ""
|
||||
followGroup["type"] = "Follow"
|
||||
followGroup["actor"] = localUserActor.ActorID
|
||||
followGroup["to"] = []string{localUserActor.ActorID}
|
||||
followGroup["object"] = groupActorID
|
||||
followGroup["published"] = publishedAt
|
||||
|
||||
_, err = tx.RecordGroupMember(ctx, actorRowID, localUser.ActorID, followGroup, storage.AcceptRelationshipStatus, storage.GroupOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue