mirror of
https://github.com/Unkn0wnCat/matrix-veles.git
synced 2025-07-25 04:17:59 +02:00
api: Update graphql schema
This commit is contained in:
parent
8256b70fcb
commit
39f721101e
9 changed files with 1955 additions and 89 deletions
File diff suppressed because it is too large
Load diff
|
@ -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)
|
||||
|
|
|
@ -19,11 +19,11 @@ type Entry struct {
|
|||
|
||||
func MakeEntry(dbEntry *model.DBEntry) *Entry {
|
||||
return &Entry{
|
||||
ID: dbEntry.ID.Hex(),
|
||||
Tags: dbEntry.Tags,
|
||||
PartOfIDs: dbEntry.PartOf,
|
||||
HashValue: dbEntry.HashValue,
|
||||
FileURL: &dbEntry.FileURL,
|
||||
ID: dbEntry.ID.Hex(),
|
||||
Tags: dbEntry.Tags,
|
||||
PartOfIDs: dbEntry.PartOf,
|
||||
HashValue: dbEntry.HashValue,
|
||||
//FileURL: &dbEntry.FileURL,
|
||||
Timestamp: dbEntry.Timestamp,
|
||||
AddedByID: *dbEntry.AddedBy,
|
||||
RawComments: dbEntry.Comments,
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@ import (
|
|||
|
||||
type DBEntry struct {
|
||||
ID primitive.ObjectID `bson:"_id" json:"id"`
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue