Merge branch 'develop' into librarization
This commit is contained in:
commit
d5c2fe47da
|
@ -530,10 +530,14 @@ func deleteFederatedPost(app *App, p *PublicPost, collID int64) error {
|
||||||
|
|
||||||
inboxes := map[string][]string{}
|
inboxes := map[string][]string{}
|
||||||
for _, f := range *followers {
|
for _, f := range *followers {
|
||||||
if _, ok := inboxes[f.SharedInbox]; ok {
|
inbox := f.SharedInbox
|
||||||
inboxes[f.SharedInbox] = append(inboxes[f.SharedInbox], f.ActorID)
|
if inbox == "" {
|
||||||
|
inbox = f.Inbox
|
||||||
|
}
|
||||||
|
if _, ok := inboxes[inbox]; ok {
|
||||||
|
inboxes[inbox] = append(inboxes[inbox], f.ActorID)
|
||||||
} else {
|
} else {
|
||||||
inboxes[f.SharedInbox] = []string{f.ActorID}
|
inboxes[inbox] = []string{f.ActorID}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,10 +577,14 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error {
|
||||||
|
|
||||||
inboxes := map[string][]string{}
|
inboxes := map[string][]string{}
|
||||||
for _, f := range *followers {
|
for _, f := range *followers {
|
||||||
if _, ok := inboxes[f.SharedInbox]; ok {
|
inbox := f.SharedInbox
|
||||||
inboxes[f.SharedInbox] = append(inboxes[f.SharedInbox], f.ActorID)
|
if inbox == "" {
|
||||||
|
inbox = f.Inbox
|
||||||
|
}
|
||||||
|
if _, ok := inboxes[inbox]; ok {
|
||||||
|
inboxes[inbox] = append(inboxes[inbox], f.ActorID)
|
||||||
} else {
|
} else {
|
||||||
inboxes[f.SharedInbox] = []string{f.ActorID}
|
inboxes[inbox] = []string{f.ActorID}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -629,8 +637,7 @@ func getActor(app *App, actorIRI string) (*activitystreams.Person, *RemoteUser,
|
||||||
log.Error("Unable to get actor! %v", err)
|
log.Error("Unable to get actor! %v", err)
|
||||||
return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't fetch actor."}
|
return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't fetch actor."}
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(actorResp, &actor); err != nil {
|
if err := unmarshalActor(actorResp, actor); err != nil {
|
||||||
// FIXME: Hubzilla has an object for the Actor's url: cannot unmarshal object into Go struct field Person.url of type string
|
|
||||||
log.Error("Unable to unmarshal actor! %v", err)
|
log.Error("Unable to unmarshal actor! %v", err)
|
||||||
return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't parse actor."}
|
return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't parse actor."}
|
||||||
}
|
}
|
||||||
|
@ -645,3 +652,48 @@ func getActor(app *App, actorIRI string) (*activitystreams.Person, *RemoteUser,
|
||||||
}
|
}
|
||||||
return actor, remoteUser, nil
|
return actor, remoteUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unmarshal actor normalizes the actor response to conform to
|
||||||
|
// the type Person from github.com/writeas/web-core/activitysteams
|
||||||
|
//
|
||||||
|
// some implementations return different context field types
|
||||||
|
// this converts any non-slice contexts into a slice
|
||||||
|
func unmarshalActor(actorResp []byte, actor *activitystreams.Person) error {
|
||||||
|
// FIXME: Hubzilla has an object for the Actor's url: cannot unmarshal object into Go struct field Person.url of type string
|
||||||
|
|
||||||
|
// flexActor overrides the Context field to allow
|
||||||
|
// all valid representations during unmarshal
|
||||||
|
flexActor := struct {
|
||||||
|
activitystreams.Person
|
||||||
|
Context json.RawMessage `json:"@context,omitempty"`
|
||||||
|
}{}
|
||||||
|
if err := json.Unmarshal(actorResp, &flexActor); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
actor.Endpoints = flexActor.Endpoints
|
||||||
|
actor.Followers = flexActor.Followers
|
||||||
|
actor.Following = flexActor.Following
|
||||||
|
actor.ID = flexActor.ID
|
||||||
|
actor.Icon = flexActor.Icon
|
||||||
|
actor.Inbox = flexActor.Inbox
|
||||||
|
actor.Name = flexActor.Name
|
||||||
|
actor.Outbox = flexActor.Outbox
|
||||||
|
actor.PreferredUsername = flexActor.PreferredUsername
|
||||||
|
actor.PublicKey = flexActor.PublicKey
|
||||||
|
actor.Summary = flexActor.Summary
|
||||||
|
actor.Type = flexActor.Type
|
||||||
|
actor.URL = flexActor.URL
|
||||||
|
|
||||||
|
func(val interface{}) {
|
||||||
|
switch val.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
// already a slice, do nothing
|
||||||
|
actor.Context = val.([]interface{})
|
||||||
|
default:
|
||||||
|
actor.Context = []interface{}{val}
|
||||||
|
}
|
||||||
|
}(flexActor.Context)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package writefreely
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/writeas/web-core/activitystreams"
|
||||||
|
)
|
||||||
|
|
||||||
|
var actorTestTable = []struct {
|
||||||
|
Name string
|
||||||
|
Resp []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"Context as a string",
|
||||||
|
[]byte(`{"@context":"https://www.w3.org/ns/activitystreams"}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Context as a list",
|
||||||
|
[]byte(`{"@context":["one string", "two strings"]}`),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalActor(t *testing.T) {
|
||||||
|
for _, tc := range actorTestTable {
|
||||||
|
actor := activitystreams.Person{}
|
||||||
|
err := unmarshalActor(tc.Resp, &actor)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s failed with error %s", tc.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,9 +14,10 @@ import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"github.com/writeas/web-core/log"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/writeas/web-core/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func exportPostsCSV(u *User, posts *[]PublicPost) []byte {
|
func exportPostsCSV(u *User, posts *[]PublicPost) []byte {
|
||||||
|
@ -37,7 +38,7 @@ func exportPostsCSV(u *User, posts *[]PublicPost) []byte {
|
||||||
w := csv.NewWriter(&b)
|
w := csv.NewWriter(&b)
|
||||||
w.WriteAll(r) // calls Flush internally
|
w.WriteAll(r) // calls Flush internally
|
||||||
if err := w.Error(); err != nil {
|
if err := w.Error(); err != nil {
|
||||||
log.Info("error writing csv:", err)
|
log.Info("error writing csv: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.Bytes()
|
return b.Bytes()
|
||||||
|
|
|
@ -252,6 +252,8 @@ body {
|
||||||
margin-bottom: 0.25em;
|
margin-bottom: 0.25em;
|
||||||
&+time {
|
&+time {
|
||||||
display: block;
|
display: block;
|
||||||
|
margin-top: 0.25em;
|
||||||
|
margin-bottom: 0.25em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
time {
|
time {
|
||||||
|
@ -604,6 +606,9 @@ body#collection article, body#subpage article {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
.book {
|
.book {
|
||||||
|
h2 {
|
||||||
|
font-size: 1.4em;
|
||||||
|
}
|
||||||
a.hidden.action {
|
a.hidden.action {
|
||||||
color: #666;
|
color: #666;
|
||||||
float: right;
|
float: right;
|
||||||
|
|
54
posts.go
54
posts.go
|
@ -14,6 +14,12 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/guregu/null"
|
"github.com/guregu/null"
|
||||||
"github.com/guregu/null/zero"
|
"github.com/guregu/null/zero"
|
||||||
|
@ -31,11 +37,6 @@ import (
|
||||||
"github.com/writeas/web-core/tags"
|
"github.com/writeas/web-core/tags"
|
||||||
"github.com/writeas/writefreely/page"
|
"github.com/writeas/writefreely/page"
|
||||||
"github.com/writeas/writefreely/parse"
|
"github.com/writeas/writefreely/parse"
|
||||||
"html/template"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -68,6 +69,7 @@ type (
|
||||||
|
|
||||||
AuthenticatedPost struct {
|
AuthenticatedPost struct {
|
||||||
ID string `json:"id" schema:"id"`
|
ID string `json:"id" schema:"id"`
|
||||||
|
Web bool `json:"web" schema:"web"`
|
||||||
*SubmittedPost
|
*SubmittedPost
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -623,6 +625,10 @@ func existingPost(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.Web {
|
||||||
|
p.IsRTL.Valid = true
|
||||||
|
}
|
||||||
|
|
||||||
if p.SubmittedPost == nil {
|
if p.SubmittedPost == nil {
|
||||||
return ErrPostNoUpdatableVals
|
return ErrPostNoUpdatableVals
|
||||||
}
|
}
|
||||||
|
@ -732,7 +738,24 @@ func deletePost(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||||
var collID sql.NullInt64
|
var collID sql.NullInt64
|
||||||
var coll *Collection
|
var coll *Collection
|
||||||
var pp *PublicPost
|
var pp *PublicPost
|
||||||
if accessToken != "" || u != nil {
|
if editToken != "" {
|
||||||
|
// TODO: SELECT owner_id, as well, and return appropriate error if NULL instead of running two queries
|
||||||
|
var dummy int64
|
||||||
|
err = app.db.QueryRow("SELECT 1 FROM posts WHERE id = ?", friendlyID).Scan(&dummy)
|
||||||
|
switch {
|
||||||
|
case err == sql.ErrNoRows:
|
||||||
|
return impart.HTTPError{http.StatusNotFound, "Post not found."}
|
||||||
|
}
|
||||||
|
err = app.db.QueryRow("SELECT 1 FROM posts WHERE id = ? AND owner_id IS NULL", friendlyID).Scan(&dummy)
|
||||||
|
switch {
|
||||||
|
case err == sql.ErrNoRows:
|
||||||
|
// Post already has an owner. This could provide a bad experience
|
||||||
|
// for the user, but it's more important to ensure data isn't lost
|
||||||
|
// unexpectedly. So prevent deletion via token.
|
||||||
|
return impart.HTTPError{http.StatusConflict, "This post belongs to some user (hopefully yours). Please log in and delete it from that user's account."}
|
||||||
|
}
|
||||||
|
res, err = app.db.Exec("DELETE FROM posts WHERE id = ? AND modify_token = ? AND owner_id IS NULL", friendlyID, editToken)
|
||||||
|
} else if accessToken != "" || u != nil {
|
||||||
// Caller provided some way to authenticate; assume caller expects the
|
// Caller provided some way to authenticate; assume caller expects the
|
||||||
// post to be deleted based on a specific post owner, thus we should
|
// post to be deleted based on a specific post owner, thus we should
|
||||||
// return corresponding errors.
|
// return corresponding errors.
|
||||||
|
@ -780,27 +803,8 @@ func deletePost(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||||
res, err = t.Exec("DELETE FROM posts WHERE id = ? AND owner_id = ?", friendlyID, ownerID)
|
res, err = t.Exec("DELETE FROM posts WHERE id = ? AND owner_id = ?", friendlyID, ownerID)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if editToken == "" {
|
|
||||||
return impart.HTTPError{http.StatusBadRequest, "No authenticated user or post token given."}
|
return impart.HTTPError{http.StatusBadRequest, "No authenticated user or post token given."}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: SELECT owner_id, as well, and return appropriate error if NULL instead of running two queries
|
|
||||||
var dummy int64
|
|
||||||
err = app.db.QueryRow("SELECT 1 FROM posts WHERE id = ?", friendlyID).Scan(&dummy)
|
|
||||||
switch {
|
|
||||||
case err == sql.ErrNoRows:
|
|
||||||
return impart.HTTPError{http.StatusNotFound, "Post not found."}
|
|
||||||
}
|
|
||||||
err = app.db.QueryRow("SELECT 1 FROM posts WHERE id = ? AND owner_id IS NULL", friendlyID).Scan(&dummy)
|
|
||||||
switch {
|
|
||||||
case err == sql.ErrNoRows:
|
|
||||||
// Post already has an owner. This could provide a bad experience
|
|
||||||
// for the user, but it's more important to ensure data isn't lost
|
|
||||||
// unexpectedly. So prevent deletion via token.
|
|
||||||
return impart.HTTPError{http.StatusConflict, "This post belongs to some user (hopefully yours). Please log in and delete it from that user's account."}
|
|
||||||
}
|
|
||||||
res, err = app.db.Exec("DELETE FROM posts WHERE id = ? AND modify_token = ? AND owner_id IS NULL", friendlyID, editToken)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -263,6 +263,7 @@
|
||||||
</dd>
|
</dd>
|
||||||
<dt> </dt><dd><input type="submit" value="Save changes" /></dd>
|
<dt> </dt><dd><input type="submit" value="Save changes" /></dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
<input type="hidden" name="web" value="true" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue