api: Update graphql schema

This commit is contained in:
Kevin Kandlbinder 2022-03-24 15:30:05 +01:00
parent 8256b70fcb
commit 39f721101e
9 changed files with 1955 additions and 89 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,10 @@
package graph
import (
"context"
"errors"
"github.com/Unkn0wnCat/matrix-veles/graph/model"
"github.com/Unkn0wnCat/matrix-veles/internal/db"
model2 "github.com/Unkn0wnCat/matrix-veles/internal/db/model"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
@ -9,6 +12,54 @@ import (
"time"
)
func GetUserFromContext(ctx context.Context) (*model2.DBUser, error) {
userID, err := GetUserIDFromContext(ctx)
if err != nil {
return nil, err
}
user, err := db.GetUserByID(*userID)
if err != nil {
return nil, errors.New("invalid session")
}
return user, nil
}
func GetUserIDFromContext(ctx context.Context) (*primitive.ObjectID, error) {
claimsVal := ctx.Value("claims")
var claims model2.JwtClaims
if claimsVal != nil {
claims = claimsVal.(model2.JwtClaims)
if claims.Valid() == nil {
sub := claims.Subject
id, err := primitive.ObjectIDFromHex(sub)
if err != nil {
return nil, err
}
return &id, nil
}
}
return nil, errors.New("no user")
}
func CheckOwnerConstraint(ctx context.Context, realOwnerIDHex string) error {
constraint, ok := ctx.Value("ownerConstraint").(string)
if !ok {
return nil // This means no ownerConstraint is set
}
if constraint != realOwnerIDHex {
return errors.New("owner constraint violation")
}
return nil
}
func buildStringFilter(filter *model.StringFilter) bson.M {
compiledFilter := bson.M{}
@ -354,9 +405,9 @@ func buildDBEntryFilter(first *int, after *string, filter *model.EntryFilter, so
}
}
if filter.FileURL != nil {
/*if filter.FileURL != nil {
filterBsonW["file_url"] = buildStringFilter(filter.FileURL)
}
}*/
if filter.PartOf != nil {
filterBsonW["part_of"], err = buildIDArrayFilter(filter.PartOf)

View file

@ -23,7 +23,7 @@ func MakeEntry(dbEntry *model.DBEntry) *Entry {
Tags: dbEntry.Tags,
PartOfIDs: dbEntry.PartOf,
HashValue: dbEntry.HashValue,
FileURL: &dbEntry.FileURL,
//FileURL: &dbEntry.FileURL,
Timestamp: dbEntry.Timestamp,
AddedByID: *dbEntry.AddedBy,
RawComments: dbEntry.Comments,

View file

@ -9,6 +9,15 @@ import (
"time"
)
type AddMxid struct {
Mxid string `json:"mxid"`
}
type AddToList struct {
List string `json:"list"`
Entries []string `json:"entries"`
}
type CommentConnection struct {
PageInfo *PageInfo `json:"pageInfo"`
Edges []*CommentEdge `json:"edges"`
@ -19,6 +28,31 @@ type CommentEdge struct {
Cursor string `json:"cursor"`
}
type CommentEntry struct {
Entry string `json:"entry"`
Comment string `json:"comment"`
}
type CommentList struct {
List string `json:"list"`
Comment string `json:"comment"`
}
type CreateEntry struct {
Tags []string `json:"tags"`
PartOf []string `json:"partOf"`
HashValue string `json:"hashValue"`
Comment *string `json:"comment"`
}
type CreateList struct {
Name string `json:"name"`
Tags []string `json:"tags"`
Comment *string `json:"comment"`
Maintainers []string `json:"maintainers"`
Entries []string `json:"entries"`
}
type EntryArrayFilter struct {
ContainsAll []*EntryFilter `json:"containsAll"`
ContainsOne []*EntryFilter `json:"containsOne"`
@ -40,7 +74,6 @@ type EntryFilter struct {
HashValue *StringFilter `json:"hashValue"`
Tags *StringArrayFilter `json:"tags"`
AddedBy *string `json:"addedBy"`
FileURL *StringFilter `json:"fileUrl"`
Timestamp *TimestampFilter `json:"timestamp"`
PartOf *IDArrayFilter `json:"partOf"`
}
@ -104,6 +137,16 @@ type PageInfo struct {
EndCursor string `json:"endCursor"`
}
type Register struct {
Username string `json:"username"`
Password string `json:"password"`
MxID string `json:"mxID"`
}
type RemoveMxid struct {
Mxid string `json:"mxid"`
}
type SortRule struct {
Direction SortDirection `json:"direction"`
}
@ -195,3 +238,46 @@ func (e *SortDirection) UnmarshalGQL(v interface{}) error {
func (e SortDirection) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type UserRole string
const (
UserRoleAdmin UserRole = "ADMIN"
UserRoleUser UserRole = "USER"
UserRoleUnauthenticated UserRole = "UNAUTHENTICATED"
)
var AllUserRole = []UserRole{
UserRoleAdmin,
UserRoleUser,
UserRoleUnauthenticated,
}
func (e UserRole) IsValid() bool {
switch e {
case UserRoleAdmin, UserRoleUser, UserRoleUnauthenticated:
return true
}
return false
}
func (e UserRole) String() string {
return string(e)
}
func (e *UserRole) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = UserRole(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid UserRole", str)
}
return nil
}
func (e UserRole) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}

View file

@ -5,6 +5,15 @@
scalar Time
directive @loggedIn on FIELD_DEFINITION
directive @hasRole(role: UserRole!) on FIELD_DEFINITION
directive @owner on FIELD_DEFINITION
directive @maintainer on FIELD_DEFINITION
enum UserRole {
ADMIN
USER
UNAUTHENTICATED
}
enum SortDirection {
ASC
@ -48,7 +57,6 @@ type Entry {
tags: [String!]
partOf(first: Int, after: String): ListConnection
hashValue: String!
fileUrl: String
timestamp: Time!
addedBy: User!
comments(first: Int, after: String): CommentConnection
@ -68,6 +76,7 @@ type List {
id: ID!
name: String!
tags: [String!]
creator: User!
comments(first: Int, after: String): CommentConnection
maintainers(first: Int, after: String): UserConnection!
entries(first: Int, after: String): EntryConnection
@ -172,7 +181,6 @@ input EntryFilter {
hashValue: StringFilter
tags: StringArrayFilter
addedBy: ID
fileUrl: StringFilter
timestamp: TimestampFilter
partOf: IDArrayFilter
}
@ -198,6 +206,8 @@ type Query {
user(id: ID, username: String): User! @loggedIn
entry(id: ID, hashValue: String): Entry! @loggedIn
list(id: ID, name: String): List! @loggedIn
self: User! @loggedIn
}
input Login {
@ -205,6 +215,63 @@ input Login {
password: String!
}
input Register {
username: String!
password: String!
mxID: String!
}
input CreateEntry {
tags: [String!]
partOf: [ID!]
hashValue: String!
comment: String
}
input CommentEntry {
entry: ID!
comment: String!
}
input CreateList {
name: String!
tags: [String!]
comment: String
maintainers: [ID!]
entries: [ID!]
}
input CommentList {
list: ID!
comment: String!
}
input AddToList {
list: ID!
entries: [ID!]!
}
input AddMXID {
mxid: String!
}
input RemoveMXID {
mxid: String!
}
type Mutation {
login(input: Login!): String!
register(input: Register!): String! @hasRole(role: UNAUTHENTICATED)
addMXID(input: AddMXID!): User! @loggedIn
removeMXID(input: RemoveMXID!): User! @loggedIn
createEntry(input: CreateEntry!): Entry! @loggedIn
commentEntry(input: CommentEntry!): Entry! @loggedIn
deleteEntry(input: ID!): Boolean! @loggedIn @hasRole(role: ADMIN)
createList(input: CreateList!): List! @loggedIn
commentList(input: CommentList!): List! @loggedIn
addToList(input: AddToList!): List! @loggedIn @maintainer
deleteList(input: ID!): Boolean! @loggedIn @owner
}

View file

@ -129,6 +129,10 @@ func (r *entryResolver) Comments(ctx context.Context, obj *model.Entry, first *i
return ResolveComments(comments, first, after)
}
func (r *listResolver) Creator(ctx context.Context, obj *model.List) (*model.User, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *listResolver) Comments(ctx context.Context, obj *model.List, first *int, after *string) (*model.CommentConnection, error) {
comments := obj.RawComments
@ -325,6 +329,46 @@ func (r *mutationResolver) Login(ctx context.Context, input model.Login) (string
return ss, nil
}
func (r *mutationResolver) Register(ctx context.Context, input model.Register) (string, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *mutationResolver) AddMxid(ctx context.Context, input model.AddMxid) (*model.User, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *mutationResolver) RemoveMxid(ctx context.Context, input model.RemoveMxid) (*model.User, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *mutationResolver) CreateEntry(ctx context.Context, input model.CreateEntry) (*model.Entry, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *mutationResolver) CommentEntry(ctx context.Context, input model.CommentEntry) (*model.Entry, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *mutationResolver) DeleteEntry(ctx context.Context, input string) (bool, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *mutationResolver) CreateList(ctx context.Context, input model.CreateList) (*model.List, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *mutationResolver) CommentList(ctx context.Context, input model.CommentList) (*model.List, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *mutationResolver) AddToList(ctx context.Context, input model.AddToList) (*model.List, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *mutationResolver) DeleteList(ctx context.Context, input string) (bool, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *queryResolver) Users(ctx context.Context, first *int, after *string, filter *model.UserFilter, sort *model.UserSort) (*model.UserConnection, error) {
dbFilter, dbSort, dbLimit, err := buildDBUserFilter(first, after, filter, sort)
if err != nil {
@ -652,6 +696,15 @@ func (r *queryResolver) List(ctx context.Context, id *string, name *string) (*mo
return nil, errors.New("not found")
}
func (r *queryResolver) Self(ctx context.Context) (*model.User, error) {
user, err := GetUserFromContext(ctx)
if err != nil {
return nil, errors.New("invalid session")
}
return model.MakeUser(user), nil
}
// Comment returns generated.CommentResolver implementation.
func (r *Resolver) Comment() generated.CommentResolver { return &commentResolver{r} }
@ -672,16 +725,3 @@ type entryResolver struct{ *Resolver }
type listResolver struct{ *Resolver }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
// !!! WARNING !!!
// The code below was going to be deleted when updating resolvers. It has been copied here so you have
// one last chance to move it out of harms way if you want. There are two reasons this happens:
// - When renaming or deleting a resolver the old code will be put in here. You can safely delete
// it when you're done.
// - You have helper methods in this file. Move them out to keep these resolver files clean.
func (r *commentResolver) Timestamp(ctx context.Context, obj *model.Comment) (*time.Time, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *entryResolver) Timestamp(ctx context.Context, obj *model.Entry) (*time.Time, error) {
panic(fmt.Errorf("not implemented"))
}

View file

@ -10,7 +10,7 @@ type DBEntry struct {
Tags []string `bson:"tags" json:"tags"` // Tags used for searching entries and ordering
PartOf []*primitive.ObjectID `bson:"part_of" json:"part_of"` // PartOf specifies the lists this entry is part of
HashValue string `bson:"hash_value" json:"hash"` // HashValue is the SHA512-hash of the file
FileURL string `bson:"file_url" json:"file_url"` // FileURL may be set to a file link
//FileURL string `bson:"file_url" json:"file_url"` // FileURL may be set to a file link
Timestamp time.Time `bson:"timestamp" json:"timestamp"` // Timestamp of when this entry was added
AddedBy *primitive.ObjectID `bson:"added_by" json:"added_by"` // AddedBy is a reference to the user who added this
Comments []*DBComment `bson:"comments" json:"comments"` // Comments regarding this entry

View file

@ -10,6 +10,7 @@ import (
"github.com/99designs/gqlgen/graphql/playground"
"github.com/Unkn0wnCat/matrix-veles/graph"
"github.com/Unkn0wnCat/matrix-veles/graph/generated"
model2 "github.com/Unkn0wnCat/matrix-veles/graph/model"
"github.com/Unkn0wnCat/matrix-veles/internal/db/model"
"github.com/go-chi/chi/v5"
"github.com/go-chi/cors"
@ -49,7 +50,43 @@ func SetupAPI() chi.Router {
}
}
return nil, errors.New("authentication required")
return nil, errors.New("authorization required")
}
c.Directives.HasRole = func(ctx context.Context, obj interface{}, next graphql.Resolver, role model2.UserRole) (res interface{}, err error) {
user, err := graph.GetUserFromContext(ctx)
if err != nil {
if role == model2.UserRoleUnauthenticated {
return next(ctx)
}
return nil, errors.New("authorization required")
}
switch role {
case model2.UserRoleUser:
return next(ctx)
case model2.UserRoleAdmin:
if user.Admin != nil && *user.Admin {
return next(ctx)
}
case model2.UserRoleUnauthenticated:
break
default:
return nil, errors.New("server error")
}
return nil, errors.New("unauthorized")
}
c.Directives.Owner = func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
user, err := graph.GetUserFromContext(ctx)
if err != nil {
return nil, errors.New("authorization required")
}
ctx2 := context.WithValue(ctx, "ownerConstraint", user.ID.Hex())
return next(ctx2)
}
srv := handler.NewDefaultServer(generated.NewExecutableSchema(c))

View file

@ -86,7 +86,7 @@ func apiHandleBotEntriesPost(res http.ResponseWriter, req *http.Request) {
Tags: body.Tags,
PartOf: body.PartOf,
HashValue: body.Hash,
FileURL: body.FileURL,
//FileURL: body.FileURL,
Timestamp: time.Now(),
AddedBy: &userId,
Comments: nil,