models

package
v0.0.0-...-7f33b96 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 18, 2021 License: AGPL-3.0 Imports: 14 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var EventProject = bson.M{
	"id":                1,
	"title":             1,
	"description":       1,
	"comments":          1,
	"creator":           1,
	"groups":            1,
	"timestampCreation": 1,
	"timestampStart":    1,
	"timestampEnd":      1,
	"url":               1,
	"urlThumb":          1,
}

EventProject is a bson representation of the event object

View Source
var IPFSNodeProject = bson.M{
	"title":       1,
	"username":    1,
	"password":    1,
	"address":     1,
	"ipfsApiPort": 1,
	"ipfsApiUrl":  1,
	"ipfsGateway": 1,
}

IPFSNodeProject is a bson representation of the ipfs-node setting object

View Source
var MediaCollection = "media"

MediaCollection is the name of the mongo collection

View Source
var MediaListProject = bson.M{
	"_id":           1,
	"sha1":          1,
	"filename":      1,
	"filenameThumb": 1,
	"title":         1,
	"creator":       1,
	"url":           1,
	"urlThumb":      1,
	"type":          1,
	"extension":     1,
	"contentType":   1,
	"nodes":         NodeProject,
	"groups":        UserGroupProject,
}

MediaListProject is a bson representaion of the $project aggregation for mongodb

View Source
var MediaProject = bson.M{
	"_id":             1,
	"sha1":            1,
	"filename":        1,
	"filenameThumb":   1,
	"title":           1,
	"description":     1,
	"comments":        1,
	"creator":         1,
	"events":          1,
	"timestamp":       1,
	"timestampUpload": 1,
	"url":             1,
	"urlThumb":        1,
	"type":            1,
	"extension":       1,
	"contentType":     1,
	"tags":            1,

	"groups": UserGroupProject,
	"nodes":  NodeProject,
}

MediaProject is a bson representation of the $project aggregation for mongodb

View Source
var MediaProjectInternal = bson.M{
	"_id":             1,
	"sha1":            1,
	"filename":        1,
	"filenameThumb":   1,
	"title":           1,
	"description":     1,
	"comments":        1,
	"creator":         1,
	"events":          1,
	"groupIDs":        1,
	"timestamp":       1,
	"timestampUpload": 1,
	"url":             1,
	"urlThumb":        1,
	"type":            1,
	"extension":       1,
	"contentType":     1,
	"tags":            1,
	"nodes":           NodeProject,
}

MediaProjectInternal is a bson representation of the $project aggregation for mongodb

View Source
var NodeCollection = "node"

NodeCollection is the name of the mongo collection

View Source
var NodeProject = bson.M{
	"_id":          1,
	"title":        1,
	"creator":      1,
	"type":         1,
	"groups":       UserGroupProject,
	"APIEndpoint":  1,
	"dataEndpoint": 1,
}

NodeProject is a bson representation of the ipfs-node setting object

View Source
var NodeProjectAuthentication = bson.M{
	"_id":          1,
	"title":        1,
	"creator":      1,
	"groupIDs":     1,
	"secret":       1,
	"type":         1,
	"groups":       UserGroupProject,
	"APIEndpoint":  1,
	"dataEndpoint": 1,
	"usernames": bson.M{
		"$reduce": bson.M{
			"input":        "$usernames",
			"initialValue": bson.A{"$creator"},
			"in": bson.M{
				"$setUnion": bson.A{
					"$$value",
					"$$this",
				},
			},
		},
	},
}
View Source
var NodeProjectInternal = bson.M{
	"_id":          1,
	"title":        1,
	"creator":      1,
	"keycloakID":   1,
	"groupIDs":     1,
	"type":         1,
	"APIEndpoint":  1,
	"dataEndpoint": 1,
	"users":        1,
}

NodeProjectInternal is a bson representation of the ipfs-node setting object

View Source
var NodeProjectSecret = bson.M{
	"_id":    1,
	"secret": 1,
}

NodeProjectSecret is bson representation of the node to retrieve the secret

View Source
var NodeProjectUserReduction = bson.M{
	"users": bson.M{
		"$reduce": bson.M{
			"input":        "$users",
			"initialValue": bson.A{"$creator"},
			"in": bson.M{
				"$setUnion": bson.A{
					"$$value",
					"$$this",
				},
			},
		},
	},
}
View Source
var SettingsProject = bson.M{
	"ipfsNodes": IPFSNodeProject,
}

SettingsProject is a bson representation of the settings object

View Source
var TagCollection = "tag"

TagCollection is the name of the mongo collection

View Source
var UserGroupCollection = "usergroup"

UserGroupCollection is the name of the mongo collection

View Source
var UserGroupProject = bson.M{
	"_id":     1,
	"title":   1,
	"creator": 1,
	"users":   1,
}

UserGroupProject is a bson representation of a user group

Functions

func BulkAddMediaEvent

func BulkAddMediaEvent(db *mongo.Database, mediaIDs []primitive.ObjectID, eventIDs []primitive.ObjectID, permission bson.M) (*mongo.BulkWriteResult, error)

BulkAddMediaEvent bulk operates an add events to many media ids

func BulkAddMediaGroup

func BulkAddMediaGroup(db *mongo.Database, mediaIDs []primitive.ObjectID, groupIDs []primitive.ObjectID, permission bson.M) (*mongo.BulkWriteResult, error)

BulkAddMediaGroup bulk operates an adds groups to many media ids

func BulkAddTagEvent

func BulkAddTagEvent(db *mongo.Database, tags []string, ids []primitive.ObjectID, permission bson.M) (*mongo.BulkWriteResult, error)

BulkAddTagEvent bulk operates a tag slice to many media ids

func BulkAddTagMedia

func BulkAddTagMedia(db *mongo.Database, tags []string, ids []primitive.ObjectID, permission bson.M) (*mongo.BulkWriteResult, error)

BulkAddTagMedia bulk operates an add Tags to many media ids

func BulkDeleteMedia

func BulkDeleteMedia(db *mongo.Database, mediaIDs []primitive.ObjectID, permission bson.M) (int, string)

BulkDeleteMedia deletes multiple media by ids

0 -> ok 1 -> no permission was specified 2 -> no IDs specified to delete 3 -> could not execute BulkDeleteMedia

func BulkRemoveMediaGroup

func BulkRemoveMediaGroup(db *mongo.Database, mediaIDs []primitive.ObjectID, groupIDs []primitive.ObjectID, permission bson.M) (*mongo.BulkWriteResult, error)

BulkRemoveMediaGroup bulk operates an pulls groups from many media ids

func VerifyTag

func VerifyTag(db *mongo.Database, tag string) (string, error)

VerifyTag creates the tag if not in the db already and returns the name in the db

func VerifyTags

func VerifyTags(db *mongo.Database, tags []string) ([]string, error)

VerifyTags iterates over an array of tags and creates the tags if not in db already. Uniquifies the slice and returns the clean slice.

Types

type Comment

type Comment struct {
	Timestamp int64  `json:"timestamp,omitempty"`
	Username  string `json:"username,omitempty"`
	Comment   string `json:"comment,omitempty"`
}

Comment type that is beeing referenced by multiple other types

func (*Comment) AddMetadata

func (c *Comment) AddMetadata(username string)

AddMetadata sets the passed username and the current timestamp for this comment

func (*Comment) IsValid

func (c *Comment) IsValid() error

IsValid verifies, that all values of that comment are valid and allowed

type Event

type Event struct {
	ID                primitive.ObjectID   `json:"id,omitempty" bson:"_id,omitempty"`
	Title             string               `json:"title,omitempty" bson:"title,omitempty"`
	Description       string               `json:"description,omitempty" bson:"description,omitempty"`
	Comments          []*Comment           `json:"comments,omitempty" bson:"comments,omitempty"`
	Creator           string               `json:"creator,omitempty" bson:"creator,omitempty"`
	Groups            []primitive.ObjectID `json:"groups,omitempty" bson:"groups,omitempty"`
	TimestampCreation int64                `json:"timestampCreation,omitempty" bson:"timestampCreation,omitempty"`
	TimestampStart    int64                `json:"timestampStart,omitempty" bson:"timestampStart,omitempty"`
	TimestampEnd      int64                `json:"timestampEnd,omitempty" bson:"timestampEnd,omitempty"`
	URL               string               `json:"url,omitempty" bson:"url,omitempty"`
	URLThumb          string               `json:"urlThumb,omitempty" bson:"urlThumb,omitempty"`
}

Event holds comments, media and the information about the the event

func GetAllEvents

func GetAllEvents(db *mongo.Database) ([]Event, error)

GetAllEvents selects all Events from the mongodb

func GetEventsByIDs

func GetEventsByIDs(db *mongo.Database, ids []primitive.ObjectID, permission bson.M) ([]Event, error)

GetEventsByIDs selects multiple Media Documents for the passed ids. verifies the reading permissions

func GetEventsByKeyword

func GetEventsByKeyword(db *mongo.Database, permission bson.M, keyword string, limit int) ([]Event, error)

GetEventsByKeyword returns the topmost events that are starting with the keyword

func (*Event) AddEvent

func (e *Event) AddEvent(db *mongo.Database) (*mongo.InsertOneResult, error)

AddEvent creates the model in the mongodb

func (*Event) DeleteEvent

func (e *Event) DeleteEvent(db *mongo.Database) (*mongo.DeleteResult, error)

DeleteEvent deletes the model from the mongodb

func (*Event) GetEvent

func (e *Event) GetEvent(db *mongo.Database, permission bson.M) error

GetEvent returns the specified entry from the mongodb

func (*Event) GetEventCreate

func (e *Event) GetEventCreate(db *mongo.Database, permission bson.M, creator string) error

GetEventCreate selects the passed event from database -> creates if not exist

func (*Event) UpdateEvent

func (e *Event) UpdateEvent(db *mongo.Database, ue Event, permission bson.M) (*mongo.UpdateResult, error)

UpdateEvent updates the record with the passed one

func (*Event) VerifyEvent

func (e *Event) VerifyEvent(db *mongo.Database, permission bson.M) error

VerifyEvent verifies all mandatory fields of the specified event does not verify ID

type IPFSNode

type IPFSNode struct {
	Title       string `json:"title" bson:"title"`
	Username    string `json:"username" bson:"username"`
	Password    string `json:"password,omitempty" bson:"password,omitempty"`
	Address     string `json:"address,omitempty" bson:"address,omitempty"`
	IPFSAPIPort int    `json:"ipfsApiPort,omitempty" bson:"ipfsApiPort,omitempty"`
	IPFSAPIURL  string `json:"ipfsApiUrl,omitempty" bson:"ipfsApiUrl,omitempty"`
	IPFSGateway string `json:"ipfsGateway,omitempty" bson:"ipfsGateway,omitempty"`
}

IPFSNode stores all relevant information about an additional node for uploads

func (*IPFSNode) Valid

func (s *IPFSNode) Valid() error

Valid checks whether the ipfs-node settings is valid or not

type Invite

type Invite struct {
	ID    primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
	Token string             `json:"token,omitempty" bson:"token,omitempty"`
	Until int64              `json:"until,omitempty" bson:"until,omitempty"`
	Used  bool               `json:"used,omitempty" bson:"used,omitempty"`
}

Invite represents the database entry for the tokens

func (*Invite) FindID

func (i *Invite) FindID(db *mongo.Database) error

FindID selects an Invite with the given ID

func (*Invite) FindToken

func (i *Invite) FindToken(db *mongo.Database) error

FindToken selects an Invite with the given token

func (*Invite) Init

func (i *Invite) Init(db *mongo.Database, validity int) (*mongo.InsertOneResult, error)

Init inits a token and saves it into database. Default validity is 3 days

func (*Invite) Invalidate

func (i *Invite) Invalidate(db *mongo.Database) error

Invalidate verifies that the token is in the database and is valid. If the token is valid, it sets it's used state to used.

func (*Invite) Revalidate

func (i *Invite) Revalidate(db *mongo.Database, validity int) error

Revalidate sets the given token usage to false and adjusts the until date if needed

type Media

type Media struct {
	ID              primitive.ObjectID   `json:"id,omitempty" bson:"_id,omitempty"`
	Sha1            string               `json:"sha1,omitempty" bson:"sha1,omitempty"`
	FileName        string               `json:"filename,omitempty" bson:"filename,omitempty"`
	FileNameThumb   string               `json:"filenameThumb,omitempty" bson:"filenameThumb,omitempty"`
	Title           string               `json:"title,omitempty" bson:"title,omitempty"`
	Description     string               `json:"description,omitempty" bson:"description,omitempty"`
	Comments        []*Comment           `json:"comments,omitempty" bson:"comments,omitempty"`
	Creator         string               `json:"creator,omitempty" bson:"creator,omitempty"`
	Events          []primitive.ObjectID `json:"events,omitempty" bson:"events,omitempty"`
	GroupIDs        []primitive.ObjectID `json:"groupIDs,omitempty" bson:"groupIDs,omitempty"`
	Timestamp       int64                `json:"timestamp,omitempty" bson:"timestamp,omitempty"`
	TimestampUpload int64                `json:"timestampUpload,omitempty" bson:"timestampUpload,omitempty"`
	URL             string               `json:"url,omitempty" bson:"url,omitempty"`
	URLThumb        string               `json:"urlThumb,omitempty" bson:"urlThumb,omitempty"`
	Type            string               `json:"type,omitempty" bson:"type,omitempty"`
	Extension       string               `json:"extension,omitempty" bson:"extension,omitempty"`
	ContentType     string               `json:"contentType,omitempty" bson:"contentType,omitempty"`
	Tags            []string             `json:"tags,omitempty" bson:"tags,omitempty"`
	NodeIDs         []primitive.ObjectID `json:"nodeIDs,omitempty" bson:"nodeIDs,omitempty"`
	// Users           []string             `json:"users,omitempty"`
	Groups []UserGroup `json:"groups,omitempty"`
	Nodes  []Node      `json:"nodes,omitempty"`
}

Media holds all information about the item

func GetAllMedia

func GetAllMedia(db *mongo.Database) ([]Media, error)

GetAllMedia selects all Media from the mongodb

func GetMediaByIDs

func GetMediaByIDs(db *mongo.Database, ids []primitive.ObjectID, permission bson.M) ([]Media, error)

GetMediaByIDs selects multiple Media Documents for the passed ids. verifies the reading permissions

func GetMediaPage

func GetMediaPage(db *mongo.Database, query MediaQuery, permission bson.M) ([]Media, error)

GetMediaPage returns the requested page after a specific id

func (*Media) AddMedia

func (m *Media) AddMedia(db *mongo.Database) (*mongo.InsertOneResult, error)

AddMedia creates the model in the mongodb

func (*Media) AddTag

func (m *Media) AddTag(db *mongo.Database, t string) error

AddTag adds a tag to the mapped tag set (ignores duplicates) Overrides the current model instance

func (*Media) AddTags

func (m *Media) AddTags(db *mongo.Database, tags []string) error

AddTags adds a tag array to the mapped tag set (ignores duplicates) Overrides the current model instance

func (*Media) AddUserGroups

func (m *Media) AddUserGroups(db *mongo.Database, ugIDs []primitive.ObjectID) error

AddUserGroups adds an array of primitive.ObjectID (of a usergroup) to the mapped usergroup set (ignores duplicates) Overrides the current model instance

func (*Media) CheckComments

func (m *Media) CheckComments(user string) error

CheckComments verifies that only one new comment has been passed and assignes the passed username and the current unix timestamp to the new comment this method is NOT called on create or update

func (*Media) DeleteMedia

func (m *Media) DeleteMedia(db *mongo.Database) (*mongo.DeleteResult, error)

DeleteMedia deletes the model from the mongodb

func (*Media) GetMedia

func (m *Media) GetMedia(db *mongo.Database, permission bson.M, project primitive.M) error

GetMedia returns the specified entry from the mongodb project represents the selection (defaults to MediaProject if nil)

func (*Media) ManageGroupIDs

func (m *Media) ManageGroupIDs(db *mongo.Database, operator string) int

ManageGroupIDs executes an operation on the groupIDs field of this document allowed operators: - update status: -1 -> unknown operator passed 0 -> ok 1 -> error during operation 2 -> nothing has been modified on the database

func (*Media) Save

func (m *Media) Save(db *mongo.Database) error

Save writes changes, made to the instance itself, to the database and overrides the instance with the return value from the database

func (*Media) UpdateMedia

func (m *Media) UpdateMedia(db *mongo.Database, um Media) error

UpdateMedia updates the record with the passed one Does NOT call the checkComments Method

type MediaEventMap

type MediaEventMap struct {
	Events   []Event  `json:"events,omitempty"`
	MediaIDs []string `json:"mediaIDs,omitempty"`
}

MediaEventMap is used to map an array of events to an array of media

type MediaGroupMap

type MediaGroupMap struct {
	Groups   []UserGroup `json:"groups,omitempty"`
	MediaIDs []string    `json:"mediaIDs,omitempty"`
}

MediaGroupMap is used to map an array of groups to an array of media

type MediaQuery

type MediaQuery struct {
	After  primitive.ObjectID
	Before primitive.ObjectID
	Event  primitive.ObjectID
	Filter string
	From   primitive.ObjectID
	Until  primitive.ObjectID
	Size   int
	ASC    int16
}

MediaQuery holds query options for the media api

func (*MediaQuery) IsValid

func (mq *MediaQuery) IsValid() error

IsValid validates that the passed query combination is allowed for filtering

type Node

type Node struct {
	ID           primitive.ObjectID   `json:"id,omitempty" bson:"_id,omitempty"`
	Title        string               `json:"title,omitempty" bson:"title,omitempty"`
	Creator      string               `json:"creator,omitempty" bson:"creator,omitempty"`
	GroupIDs     []primitive.ObjectID `json:"groupIDs,omitempty" bson:"groupIDs,omitempty"`
	KeycloakID   string               `json:"keycloakID,omitempty" bson:"keycloakID,omitempty"`
	Type         string               `json:"type,omitempty" bson:"type,omitempty"`
	Secret       string               `json:"secret,omitempty" bson:"secret,omitempty"`
	APIEndpoint  string               `json:"APIEndpoint,omitempty" bson:"APIEndpoint,omitempty"`
	DataEndpoint string               `json:"dataEndpoint,omitempty" bson:"dataEndpoint,omitempty"`
	// UserSession  string               `json:"userSession,omitempty" bson:"-"`
	Groups    []UserGroup `json:"groups,omitempty" bson:"-"`
	Users     []string    `json:"users,omitempty" bson:"-"`
	Usernames []string    `json:"usernames,omitempty" bson:"-"`
}

Node holts the users and the information about the group

func GetAllNodes

func GetAllNodes(db *mongo.Database, permission bson.M, mode string) ([]Node, error)

GetAllNodes selects all Nodes from the mongodb

func (*Node) AddNode

func (n *Node) AddNode(db *mongo.Database) primitive.ObjectID

AddNode creates the model in the mongodb

func (*Node) AddUserGroups

func (n *Node) AddUserGroups(db *mongo.Database, ugIDs []primitive.ObjectID, permission bson.M) error

AddUserGroups adds an array of primitive.ObjectID (of a usergroup) to the mapped usergroup set (ignores duplicates) Overrides the current model instance

func (*Node) DeleteNode

func (n *Node) DeleteNode(db *mongo.Database) (*mongo.DeleteResult, error)

DeleteNode deletes the model from the mongodb

func (*Node) GetNode

func (n *Node) GetNode(db *mongo.Database, permission bson.M, project primitive.M) error

GetNode returns the specified entry from the mongodb project -> estimated result representation

func (*Node) GetUser

func (n *Node) GetUser(db *mongo.Database) ([]string, int)

func (*Node) Replace

func (n *Node) Replace(db *mongo.Database) int

Replace replaces the corresponding document in the database with the current state 0 -> ok | 1 -> error during find and update

func (*Node) UpdateNode

func (n *Node) UpdateNode(db *mongo.Database, ue Node, permission bson.M) (*mongo.UpdateResult, error)

UpdateNode updates the record with the passed one

func (*Node) UpdateNodeSecret

func (n *Node) UpdateNodeSecret(db *mongo.Database, permission bson.M, secret string) int

UpdateNodeSecret updates the secret entry of a document 0 -> ok 1 -> nothing has been changed 2 -> update failed 3 -> id is zero 4 -> permission could not be verified

func (*Node) VerifyNode

func (n *Node) VerifyNode(db *mongo.Database, permission bson.M) error

VerifyNode verifies all mandatory fields of the specified node does not verify ID

type Settings

type Settings struct {
	IPFSNodes []*IPFSNode `json:"ipfsNodes,omitempty" bson:"ipfsNodes,omitempty"`
}

Settings stores all user related configurations

type Tag

type Tag struct {
	ID   primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
	Name string             `json:"name,omitempty" bson:"name,omitempty"`
}

Tag has a name and an ID for the reference

func GetTagsByKeyword

func GetTagsByKeyword(db *mongo.Database, keyword string, limit int) ([]Tag, error)

GetTagsByKeyword returns the topmost tags that are starting with the keyword

func (*Tag) AddTag

func (t *Tag) AddTag(db *mongo.Database) (*mongo.InsertOneResult, error)

AddTag creates the model in the mongodb

func (*Tag) DeleteTag

func (t *Tag) DeleteTag(db *mongo.Database) (*mongo.DeleteResult, error)

DeleteTag deletes the model from the mongodb

func (*Tag) GetIDCreate

func (t *Tag) GetIDCreate(db *mongo.Database) error

GetIDCreate searches the database for the passed tag and adds the id to the current tag. It creates a new tag document if the passed tag was not find in the database

func (*Tag) GetTag

func (t *Tag) GetTag(db *mongo.Database) error

GetTag returns the specified entry from the mongodb

func (*Tag) GetTagByName

func (t *Tag) GetTagByName(db *mongo.Database) error

GetTagByName returns the specified entry from the mongodb

func (*Tag) UpdateTag

func (t *Tag) UpdateTag(db *mongo.Database, ut Tag) (*mongo.UpdateResult, error)

UpdateTag updates the record with the passed one

type TagEventMap

type TagEventMap struct {
	Events []Event  `json:"events,omitempty"`
	Tags   []string `json:"tags,omitempty"`
}

TagEventMap ist used to map an array of tags to an array of media

type TagMediaMap

type TagMediaMap struct {
	IDs  []string `json:"ids,omitempty"`
	Tags []string `json:"tags,omitempty"`
}

TagMediaMap ist used to map an array of tags to an array of media

type UserGroup

type UserGroup struct {
	ID      primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
	Title   string             `json:"title,omitempty" bson:"title,omitempty"`
	Creator string             `json:"creator,omitempty" bson:"creator,omitempty"`
	Users   []string           `json:"users,omitempty" bson:"users,omitempty"`
}

UserGroup holts the users and the information about the group

func GetUserGroups

func GetUserGroups(db *mongo.Database, user string) ([]UserGroup, error)

GetUserGroups returns all groups the user is part of

func GetUserGroupsByIDs

func GetUserGroupsByIDs(db *mongo.Database, ids []primitive.ObjectID, permission bson.M) ([]UserGroup, error)

GetUserGroupsByIDs returns a slice of usergroups, that are matching the given id slice

func GetUserGroupsByKeyword

func GetUserGroupsByKeyword(db *mongo.Database, keyword string, limit int) ([]UserGroup, error)

GetUserGroupsByKeyword returns the topmost groups that are starting with the keyword

func (*UserGroup) AddUserGroup

func (ug *UserGroup) AddUserGroup(db *mongo.Database, skipVerify bool) (*mongo.InsertOneResult, error)

AddUserGroup creates the model in the mongodb

func (*UserGroup) DeleteUserGroup

func (ug *UserGroup) DeleteUserGroup(db *mongo.Database) (*mongo.DeleteResult, error)

DeleteUserGroup deletes the model from the mongodb

func (*UserGroup) GetUserGroup

func (ug *UserGroup) GetUserGroup(db *mongo.Database, permission bson.M) error

GetUserGroup returns the specified entry from the mongodb

func (*UserGroup) Save

func (ug *UserGroup) Save(db *mongo.Database, skipVerify bool) error

Save writes changes, made to the instance itself, to the database and overrides the instance with the return value from the database

func (*UserGroup) UpdateUserGroup

func (ug *UserGroup) UpdateUserGroup(db *mongo.Database, uug UserGroup, skipVerify bool) (*mongo.UpdateResult, error)

UpdateUserGroup updates the record with the passed one

func (*UserGroup) Verify

func (ug *UserGroup) Verify(db *mongo.Database) error

Verify tries to verify the usergroup object

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL