2023-09-23 20:28:12 +03:00
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package ap
import (
"fmt"
"net/url"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/paging"
)
2024-02-23 18:24:40 +03:00
// TODO: replace must of this logic with just
// using extractIRIs() on the iterator types.
// ToCollectionIterator attempts to resolve the given vocab type as a Collection
// like object and wrap in a standardised interface in order to iterate its contents.
func ToCollectionIterator ( t vocab . Type ) ( CollectionIterator , error ) {
switch name := t . GetTypeName ( ) ; name {
case ObjectCollection :
t := t . ( vocab . ActivityStreamsCollection )
return WrapCollection ( t ) , nil
case ObjectOrderedCollection :
t := t . ( vocab . ActivityStreamsOrderedCollection )
return WrapOrderedCollection ( t ) , nil
default :
return nil , fmt . Errorf ( "%T(%s) was not Collection-like" , t , name )
}
}
2023-09-23 20:28:12 +03:00
// ToCollectionPageIterator attempts to resolve the given vocab type as a CollectionPage
// like object and wrap in a standardised interface in order to iterate its contents.
func ToCollectionPageIterator ( t vocab . Type ) ( CollectionPageIterator , error ) {
switch name := t . GetTypeName ( ) ; name {
case ObjectCollectionPage :
2023-10-04 15:09:42 +03:00
t := t . ( vocab . ActivityStreamsCollectionPage )
2023-09-23 20:28:12 +03:00
return WrapCollectionPage ( t ) , nil
case ObjectOrderedCollectionPage :
2023-10-04 15:09:42 +03:00
t := t . ( vocab . ActivityStreamsOrderedCollectionPage )
2023-09-23 20:28:12 +03:00
return WrapOrderedCollectionPage ( t ) , nil
default :
return nil , fmt . Errorf ( "%T(%s) was not CollectionPage-like" , t , name )
}
}
2024-02-23 18:24:40 +03:00
// WrapCollection wraps an ActivityStreamsCollection in a standardised collection interface.
func WrapCollection ( collection vocab . ActivityStreamsCollection ) CollectionIterator {
return & regularCollectionIterator { ActivityStreamsCollection : collection }
}
// WrapOrderedCollection wraps an ActivityStreamsOrderedCollection in a standardised collection interface.
func WrapOrderedCollection ( collection vocab . ActivityStreamsOrderedCollection ) CollectionIterator {
return & orderedCollectionIterator { ActivityStreamsOrderedCollection : collection }
}
2023-09-23 20:28:12 +03:00
// WrapCollectionPage wraps an ActivityStreamsCollectionPage in a standardised collection page interface.
func WrapCollectionPage ( page vocab . ActivityStreamsCollectionPage ) CollectionPageIterator {
return & regularCollectionPageIterator { ActivityStreamsCollectionPage : page }
}
// WrapOrderedCollectionPage wraps an ActivityStreamsOrderedCollectionPage in a standardised collection page interface.
func WrapOrderedCollectionPage ( page vocab . ActivityStreamsOrderedCollectionPage ) CollectionPageIterator {
return & orderedCollectionPageIterator { ActivityStreamsOrderedCollectionPage : page }
}
2024-02-23 18:24:40 +03:00
// regularCollectionIterator implements CollectionIterator
// for the vocab.ActivitiyStreamsCollection type.
type regularCollectionIterator struct {
vocab . ActivityStreamsCollection
items vocab . ActivityStreamsItemsPropertyIterator
once bool // only init items once
}
func ( iter * regularCollectionIterator ) NextItem ( ) TypeOrIRI {
if ! iter . initItems ( ) {
return nil
}
cur := iter . items
iter . items = iter . items . Next ( )
return cur
}
func ( iter * regularCollectionIterator ) PrevItem ( ) TypeOrIRI {
if ! iter . initItems ( ) {
return nil
}
cur := iter . items
iter . items = iter . items . Prev ( )
return cur
}
2024-04-16 14:10:13 +03:00
func ( iter * regularCollectionIterator ) TotalItems ( ) int {
totalItems := iter . GetActivityStreamsTotalItems ( )
if totalItems == nil || ! totalItems . IsXMLSchemaNonNegativeInteger ( ) {
return - 1
}
return totalItems . Get ( )
}
2024-02-23 18:24:40 +03:00
func ( iter * regularCollectionIterator ) initItems ( ) bool {
if iter . once {
return ( iter . items != nil )
}
iter . once = true
if iter . ActivityStreamsCollection == nil {
return false // no page set
}
items := iter . GetActivityStreamsItems ( )
if items == nil {
return false // no items found
}
iter . items = items . Begin ( )
return ( iter . items != nil )
}
// orderedCollectionIterator implements CollectionIterator
// for the vocab.ActivitiyStreamsOrderedCollection type.
type orderedCollectionIterator struct {
vocab . ActivityStreamsOrderedCollection
items vocab . ActivityStreamsOrderedItemsPropertyIterator
once bool // only init items once
}
func ( iter * orderedCollectionIterator ) NextItem ( ) TypeOrIRI {
if ! iter . initItems ( ) {
return nil
}
cur := iter . items
iter . items = iter . items . Next ( )
return cur
}
func ( iter * orderedCollectionIterator ) PrevItem ( ) TypeOrIRI {
if ! iter . initItems ( ) {
return nil
}
cur := iter . items
iter . items = iter . items . Prev ( )
return cur
}
2024-04-16 14:10:13 +03:00
func ( iter * orderedCollectionIterator ) TotalItems ( ) int {
totalItems := iter . GetActivityStreamsTotalItems ( )
if totalItems == nil || ! totalItems . IsXMLSchemaNonNegativeInteger ( ) {
return - 1
}
return totalItems . Get ( )
}
2024-02-23 18:24:40 +03:00
func ( iter * orderedCollectionIterator ) initItems ( ) bool {
if iter . once {
return ( iter . items != nil )
}
iter . once = true
if iter . ActivityStreamsOrderedCollection == nil {
return false // no page set
}
items := iter . GetActivityStreamsOrderedItems ( )
if items == nil {
return false // no items found
}
iter . items = items . Begin ( )
return ( iter . items != nil )
}
2023-09-23 20:28:12 +03:00
// regularCollectionPageIterator implements CollectionPageIterator
// for the vocab.ActivitiyStreamsCollectionPage type.
type regularCollectionPageIterator struct {
vocab . ActivityStreamsCollectionPage
items vocab . ActivityStreamsItemsPropertyIterator
once bool // only init items once
}
func ( iter * regularCollectionPageIterator ) NextPage ( ) WithIRI {
if iter . ActivityStreamsCollectionPage == nil {
return nil
}
return iter . GetActivityStreamsNext ( )
}
func ( iter * regularCollectionPageIterator ) PrevPage ( ) WithIRI {
if iter . ActivityStreamsCollectionPage == nil {
return nil
}
return iter . GetActivityStreamsPrev ( )
}
2023-10-04 15:09:42 +03:00
func ( iter * regularCollectionPageIterator ) NextItem ( ) TypeOrIRI {
2023-09-23 20:28:12 +03:00
if ! iter . initItems ( ) {
return nil
}
cur := iter . items
iter . items = iter . items . Next ( )
return cur
}
2023-10-04 15:09:42 +03:00
func ( iter * regularCollectionPageIterator ) PrevItem ( ) TypeOrIRI {
2023-09-23 20:28:12 +03:00
if ! iter . initItems ( ) {
return nil
}
cur := iter . items
iter . items = iter . items . Prev ( )
return cur
}
2024-04-16 14:10:13 +03:00
func ( iter * regularCollectionPageIterator ) TotalItems ( ) int {
totalItems := iter . GetActivityStreamsTotalItems ( )
if totalItems == nil || ! totalItems . IsXMLSchemaNonNegativeInteger ( ) {
return - 1
}
return totalItems . Get ( )
}
2023-09-23 20:28:12 +03:00
func ( iter * regularCollectionPageIterator ) initItems ( ) bool {
if iter . once {
return ( iter . items != nil )
}
iter . once = true
if iter . ActivityStreamsCollectionPage == nil {
return false // no page set
}
items := iter . GetActivityStreamsItems ( )
if items == nil {
return false // no items found
}
iter . items = items . Begin ( )
return ( iter . items != nil )
}
// orderedCollectionPageIterator implements CollectionPageIterator
// for the vocab.ActivitiyStreamsOrderedCollectionPage type.
type orderedCollectionPageIterator struct {
vocab . ActivityStreamsOrderedCollectionPage
items vocab . ActivityStreamsOrderedItemsPropertyIterator
once bool // only init items once
}
func ( iter * orderedCollectionPageIterator ) NextPage ( ) WithIRI {
if iter . ActivityStreamsOrderedCollectionPage == nil {
return nil
}
return iter . GetActivityStreamsNext ( )
}
func ( iter * orderedCollectionPageIterator ) PrevPage ( ) WithIRI {
if iter . ActivityStreamsOrderedCollectionPage == nil {
return nil
}
return iter . GetActivityStreamsPrev ( )
}
2023-10-04 15:09:42 +03:00
func ( iter * orderedCollectionPageIterator ) NextItem ( ) TypeOrIRI {
2023-09-23 20:28:12 +03:00
if ! iter . initItems ( ) {
return nil
}
cur := iter . items
iter . items = iter . items . Next ( )
return cur
}
2023-10-04 15:09:42 +03:00
func ( iter * orderedCollectionPageIterator ) PrevItem ( ) TypeOrIRI {
2023-09-23 20:28:12 +03:00
if ! iter . initItems ( ) {
return nil
}
cur := iter . items
iter . items = iter . items . Prev ( )
return cur
}
2024-04-16 14:10:13 +03:00
func ( iter * orderedCollectionPageIterator ) TotalItems ( ) int {
totalItems := iter . GetActivityStreamsTotalItems ( )
if totalItems == nil || ! totalItems . IsXMLSchemaNonNegativeInteger ( ) {
return - 1
}
return totalItems . Get ( )
}
2023-09-23 20:28:12 +03:00
func ( iter * orderedCollectionPageIterator ) initItems ( ) bool {
if iter . once {
return ( iter . items != nil )
}
iter . once = true
if iter . ActivityStreamsOrderedCollectionPage == nil {
return false // no page set
}
items := iter . GetActivityStreamsOrderedItems ( )
if items == nil {
return false // no items found
}
iter . items = items . Begin ( )
return ( iter . items != nil )
}
type CollectionParams struct {
// Containing collection
// ID (i.e. NOT the page).
ID * url . URL
2023-11-20 15:22:28 +03:00
// First page details.
2024-04-02 12:42:24 +03:00
First * paging . Page
2023-11-20 15:22:28 +03:00
Query url . Values
2023-09-23 20:28:12 +03:00
// Total no. items.
2024-06-11 12:54:59 +03:00
// Omitted if nil.
Total * int
2023-09-23 20:28:12 +03:00
}
type CollectionPageParams struct {
// containing collection.
CollectionParams
// Paging details.
Current * paging . Page
Next * paging . Page
Prev * paging . Page
Query url . Values
// Item appender for each item at index.
Append func ( int , ItemsPropertyBuilder )
Count int
}
// CollectionPage is a simplified interface type
// that can be fulfilled by either of (where required):
// vocab.ActivityStreamsCollection
// vocab.ActivityStreamsOrderedCollection
type CollectionBuilder interface {
SetJSONLDId ( vocab . JSONLDIdProperty )
SetActivityStreamsFirst ( vocab . ActivityStreamsFirstProperty )
SetActivityStreamsTotalItems ( i vocab . ActivityStreamsTotalItemsProperty )
}
// CollectionPageBuilder is a simplified interface type
// that can be fulfilled by either of (where required):
// vocab.ActivityStreamsCollectionPage
// vocab.ActivityStreamsOrderedCollectionPage
type CollectionPageBuilder interface {
SetJSONLDId ( vocab . JSONLDIdProperty )
SetActivityStreamsPartOf ( vocab . ActivityStreamsPartOfProperty )
SetActivityStreamsNext ( vocab . ActivityStreamsNextProperty )
SetActivityStreamsPrev ( vocab . ActivityStreamsPrevProperty )
SetActivityStreamsTotalItems ( i vocab . ActivityStreamsTotalItemsProperty )
}
// ItemsPropertyBuilder is a simplified interface type
// that can be fulfilled by either of (where required):
// vocab.ActivityStreamsItemsProperty
// vocab.ActivityStreamsOrderedItemsProperty
type ItemsPropertyBuilder interface {
AppendIRI ( * url . URL )
2024-06-11 12:54:59 +03:00
AppendActivityStreamsCreate ( vocab . ActivityStreamsCreate )
2023-09-23 20:28:12 +03:00
// NOTE: add more of the items-property-like interface
// functions here as you require them for building pages.
}
// NewASCollection builds and returns a new ActivityStreams Collection from given parameters.
func NewASCollection ( params CollectionParams ) vocab . ActivityStreamsCollection {
collection := streams . NewActivityStreamsCollection ( )
2023-11-20 15:22:28 +03:00
buildCollection ( collection , params )
2023-09-23 20:28:12 +03:00
return collection
}
// NewASCollectionPage builds and returns a new ActivityStreams CollectionPage from given parameters (including item property appending function).
func NewASCollectionPage ( params CollectionPageParams ) vocab . ActivityStreamsCollectionPage {
collectionPage := streams . NewActivityStreamsCollectionPage ( )
itemsProp := streams . NewActivityStreamsItemsProperty ( )
buildCollectionPage ( collectionPage , itemsProp , collectionPage . SetActivityStreamsItems , params )
return collectionPage
}
// NewASOrderedCollection builds and returns a new ActivityStreams OrderedCollection from given parameters.
func NewASOrderedCollection ( params CollectionParams ) vocab . ActivityStreamsOrderedCollection {
collection := streams . NewActivityStreamsOrderedCollection ( )
2023-11-20 15:22:28 +03:00
buildCollection ( collection , params )
2023-09-23 20:28:12 +03:00
return collection
}
// NewASOrderedCollectionPage builds and returns a new ActivityStreams OrderedCollectionPage from given parameters (including item property appending function).
func NewASOrderedCollectionPage ( params CollectionPageParams ) vocab . ActivityStreamsOrderedCollectionPage {
collectionPage := streams . NewActivityStreamsOrderedCollectionPage ( )
itemsProp := streams . NewActivityStreamsOrderedItemsProperty ( )
buildCollectionPage ( collectionPage , itemsProp , collectionPage . SetActivityStreamsOrderedItems , params )
return collectionPage
}
2023-11-20 15:22:28 +03:00
func buildCollection [ C CollectionBuilder ] ( collection C , params CollectionParams ) {
2023-09-23 20:28:12 +03:00
// Add the collection ID property.
idProp := streams . NewJSONLDIdProperty ( )
idProp . SetIRI ( params . ID )
collection . SetJSONLDId ( idProp )
// Add the collection totalItems count property.
2024-06-11 12:54:59 +03:00
if params . Total != nil {
totalItems := streams . NewActivityStreamsTotalItemsProperty ( )
totalItems . Set ( * params . Total )
collection . SetActivityStreamsTotalItems ( totalItems )
}
2023-09-23 20:28:12 +03:00
2024-04-02 12:42:24 +03:00
// No First page means we're done.
if params . First == nil {
return
}
2023-11-20 15:22:28 +03:00
// Append paging query params
// to those already in ID prop.
pageQueryParams := appendQuery (
params . Query ,
params . ID . Query ( ) ,
)
2023-09-23 20:28:12 +03:00
2023-11-20 15:22:28 +03:00
// Build the first page link IRI.
firstIRI := params . First . ToLinkURL (
params . ID . Scheme ,
params . ID . Host ,
params . ID . Path ,
pageQueryParams ,
)
2023-09-23 20:28:12 +03:00
// Add the collection first IRI property.
first := streams . NewActivityStreamsFirstProperty ( )
first . SetIRI ( firstIRI )
collection . SetActivityStreamsFirst ( first )
}
func buildCollectionPage [ C CollectionPageBuilder , I ItemsPropertyBuilder ] ( collectionPage C , itemsProp I , setItems func ( I ) , params CollectionPageParams ) {
// Add the partOf property for its containing collection ID.
partOfProp := streams . NewActivityStreamsPartOfProperty ( )
partOfProp . SetIRI ( params . ID )
collectionPage . SetActivityStreamsPartOf ( partOfProp )
2023-11-20 15:22:28 +03:00
// Append paging query params
// to those already in ID prop.
pageQueryParams := appendQuery (
params . Query ,
params . ID . Query ( ) ,
)
2023-09-23 20:28:12 +03:00
// Build the current page link IRI.
currentIRI := params . Current . ToLinkURL (
params . ID . Scheme ,
params . ID . Host ,
params . ID . Path ,
2023-11-20 15:22:28 +03:00
pageQueryParams ,
2023-09-23 20:28:12 +03:00
)
// Add the collection ID property for
// the *current* collection page params.
idProp := streams . NewJSONLDIdProperty ( )
idProp . SetIRI ( currentIRI )
collectionPage . SetJSONLDId ( idProp )
// Build the next page link IRI.
nextIRI := params . Next . ToLinkURL (
params . ID . Scheme ,
params . ID . Host ,
params . ID . Path ,
2023-11-20 15:22:28 +03:00
pageQueryParams ,
2023-09-23 20:28:12 +03:00
)
if nextIRI != nil {
// Add the collection next property for the next page.
nextProp := streams . NewActivityStreamsNextProperty ( )
nextProp . SetIRI ( nextIRI )
collectionPage . SetActivityStreamsNext ( nextProp )
}
// Build the prev page link IRI.
prevIRI := params . Prev . ToLinkURL (
params . ID . Scheme ,
params . ID . Host ,
params . ID . Path ,
2023-11-20 15:22:28 +03:00
pageQueryParams ,
2023-09-23 20:28:12 +03:00
)
if prevIRI != nil {
// Add the collection prev property for the prev page.
prevProp := streams . NewActivityStreamsPrevProperty ( )
prevProp . SetIRI ( prevIRI )
collectionPage . SetActivityStreamsPrev ( prevProp )
}
// Add the collection totalItems count property.
2024-06-11 12:54:59 +03:00
if params . Total != nil {
totalItems := streams . NewActivityStreamsTotalItemsProperty ( )
totalItems . Set ( * params . Total )
collectionPage . SetActivityStreamsTotalItems ( totalItems )
}
2023-09-23 20:28:12 +03:00
if params . Append == nil {
// nil check outside the for loop.
panic ( "nil params.Append function" )
}
// Append each of the items to the provided
// pre-allocated items property builder type.
for i := 0 ; i < params . Count ; i ++ {
params . Append ( i , itemsProp )
}
// Set the collection
// page items property.
setItems ( itemsProp )
}
2023-11-20 15:22:28 +03:00
// appendQuery appends query values in 'src' to 'dst', returning 'dst'.
func appendQuery ( dst , src url . Values ) url . Values {
if dst == nil {
return src
}
for k , vs := range src {
dst [ k ] = append ( dst [ k ] , vs ... )
2023-09-23 20:28:12 +03:00
}
2023-11-20 15:22:28 +03:00
return dst
2023-09-23 20:28:12 +03:00
}