Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba1c1e0a4b | ||
|
|
18e77b8205 | ||
|
|
d8871259a1 | ||
|
|
26d7e36631 | ||
|
|
51489ed901 | ||
|
|
ed3e384d55 | ||
|
|
9455fd960f | ||
|
|
36939ffc59 | ||
|
|
5fedce5899 | ||
|
|
dced5282b1 | ||
|
|
d05f3a8ca8 | ||
|
|
53e2dfc6b6 | ||
|
|
fba499024b | ||
|
|
cc765e220a | ||
|
|
ba49b1fd30 | ||
|
|
8c290c8dcd | ||
|
|
576e1df001 | ||
|
|
7adcdfe1e0 | ||
|
|
6f04da49b8 | ||
|
|
9b705e6514 | ||
|
|
155309693a | ||
|
|
5895e09f37 | ||
|
|
223d9922e9 | ||
|
|
429387c9ac | ||
|
|
a7113d5efe | ||
|
|
a0cb8d7fa8 | ||
|
|
5189d486a5 | ||
|
|
011cb00001 | ||
|
|
7a52504381 | ||
|
|
866a5056be | ||
|
|
e0409a6635 | ||
|
|
bae15de3e8 | ||
|
|
3f5af44dd6 | ||
|
|
4bbb8bb9ae | ||
|
|
87cd5e8e3a | ||
|
|
136302084d | ||
|
|
0122f64fbb | ||
|
|
6bfb9cbe20 | ||
|
|
fca5176579 | ||
|
|
c4f7846269 |
+2
-1
@@ -1,6 +1,7 @@
|
||||
vendor
|
||||
schema/bindata.go
|
||||
graphql/schema/bindata.go
|
||||
config.json
|
||||
api
|
||||
debug
|
||||
build
|
||||
.vscode
|
||||
+27
-4
@@ -1,5 +1,6 @@
|
||||
## 1. Server
|
||||
# Use Go 1.10
|
||||
FROM golang:1.10
|
||||
FROM golang:1.10 AS build-server
|
||||
|
||||
# Load repository into docker
|
||||
WORKDIR /go/src/git.aiterp.net/rpdata/api/
|
||||
@@ -20,8 +21,30 @@ RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-wikifile
|
||||
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-ensurechannels ./cmd/rpdata-ensurechannels
|
||||
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-as2storyimport ./cmd/rpdata-as2storyimport
|
||||
|
||||
# Move artifacts into final container
|
||||
FROM alpine:latest
|
||||
## 2. React UI
|
||||
# Use Node.JS 18
|
||||
FROM node:10.5-alpine AS build-id
|
||||
|
||||
# The content is already there
|
||||
WORKDIR /rpdata-ui
|
||||
COPY ./rpdata-ui /rpdata-ui
|
||||
|
||||
# Prepare dependencies
|
||||
RUN npm install
|
||||
|
||||
# Run build
|
||||
RUN npm run build
|
||||
|
||||
## 3. Regroup
|
||||
# Use alpine linux
|
||||
FROM alpine:3.7
|
||||
|
||||
# Install certificates to enable HTTPS GET requests
|
||||
RUN apk add --no-cache ca-certificates
|
||||
COPY --from=0 /binaries/* /usr/local/bin/
|
||||
|
||||
# Copy build files into final container
|
||||
COPY --from=build-server /binaries/* /usr/local/bin/
|
||||
COPY --from=build-id /rpdata-ui/build /var/www/
|
||||
|
||||
# TODO: Use proper server instead of grapihql server
|
||||
CMD ["/usr/local/bin/rpdata-graphiql"]
|
||||
|
||||
@@ -78,23 +78,37 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
story, err := story.New(result.Name, result.Author, result.Category, false, false, tags, publishDate, fictionalDate)
|
||||
story, err := story.New(result.Name, result.Author[5:], result.Category, false, false, tags, publishDate, fictionalDate)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Change the story title
|
||||
title := result.Name
|
||||
if strings.HasPrefix(result.Source, "#") {
|
||||
firstNewline := strings.Index(result.Source, "\n")
|
||||
title = result.Source[1:firstNewline]
|
||||
result.Name = strings.Replace(result.Source[1:firstNewline], "\r", "", -1)
|
||||
result.Source = result.Source[firstNewline+1:]
|
||||
|
||||
result.Source = strings.Replace(result.Source, "\r\n", "\n", -1)
|
||||
if strings.HasPrefix(result.Source, "\r") || strings.HasPrefix(result.Source, "\n") {
|
||||
result.Source = result.Source[1:]
|
||||
}
|
||||
}
|
||||
|
||||
chapter, err := story.AddChapter(title, result.Author, result.Source, publishDate, fictionalDate)
|
||||
chapter, err := story.AddChapter(title, result.Author[5:], result.Source, publishDate, fictionalDate)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if !result.Unlisted {
|
||||
listed := true
|
||||
err := story.Edit(nil, nil, &listed, nil, nil)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(result.ID, "->", story.ID, chapter.ID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/loader"
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver"
|
||||
"git.aiterp.net/rpdata/api/graphql/schema"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/loader"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
logModel "git.aiterp.net/rpdata/api/model/log"
|
||||
"git.aiterp.net/rpdata/api/resolver"
|
||||
"git.aiterp.net/rpdata/api/schema"
|
||||
graphql "github.com/graph-gophers/graphql-go"
|
||||
"github.com/graph-gophers/graphql-go/relay"
|
||||
)
|
||||
@@ -28,7 +30,7 @@ func main() {
|
||||
}
|
||||
log.Println("Updated characters on", n, "logs")
|
||||
|
||||
schema, err := graphql.ParseSchema(schema.String(), &resolver.RootResolver{}, graphql.MaxParallelism(48))
|
||||
schema, err := graphql.ParseSchema(schema.String(), &resolver.Resolver{}, graphql.MaxParallelism(48))
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to parse schema:", err)
|
||||
}
|
||||
@@ -89,6 +91,24 @@ func main() {
|
||||
w.Write(json)
|
||||
})
|
||||
|
||||
change.InitializeTimeline()
|
||||
go func() {
|
||||
index := int64(0)
|
||||
|
||||
for {
|
||||
index++
|
||||
ch, err := change.Timeline.Get(context.Background(), index)
|
||||
if err == change.ErrExpired {
|
||||
continue
|
||||
} else if err != nil {
|
||||
log.Println("Timeline:", err)
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Change: time=%s model=%s op=%s id=%s author=%s\n", ch.Time.Local().Format("[Mon Jan 02 15:04 MST]"), ch.Model, ch.Op, ch.ObjectID, ch.Author)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Fatal(http.ListenAndServe(":17000", nil))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/channel"
|
||||
)
|
||||
|
||||
// ChannelAddArgs is input for the addChannel mutation
|
||||
type ChannelAddArgs struct {
|
||||
Input *struct {
|
||||
Name string
|
||||
Logged *bool
|
||||
Hub *bool
|
||||
EventName *string
|
||||
LocationName *string
|
||||
}
|
||||
}
|
||||
|
||||
// AddChannel resolves the addChannel mutation
|
||||
func (r *MutationResolver) AddChannel(ctx context.Context, args *ChannelAddArgs) (*types.ChannelResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("channel.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
logged := input.Logged != nil && *input.Logged
|
||||
|
||||
hub := input.Hub != nil && *input.Hub
|
||||
|
||||
eventName := ""
|
||||
if input.EventName != nil {
|
||||
eventName = *input.EventName
|
||||
}
|
||||
|
||||
locationName := ""
|
||||
if input.LocationName != nil {
|
||||
locationName = *input.LocationName
|
||||
}
|
||||
|
||||
channel, err := channel.New(input.Name, logged, hub, eventName, locationName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Channel", "add", user.ID, channel.Name, map[string]interface{}{
|
||||
"logged": channel.Logged,
|
||||
"hub": channel.Hub,
|
||||
"location": input.LocationName,
|
||||
"event": input.EventName,
|
||||
})
|
||||
|
||||
return &types.ChannelResolver{C: channel}, nil
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// AddChapterArgs is args for the addChapter mutation
|
||||
type AddChapterArgs struct {
|
||||
Input *struct {
|
||||
StoryID string
|
||||
Title string
|
||||
Author *string
|
||||
Source string
|
||||
FictionalDate *string
|
||||
}
|
||||
}
|
||||
|
||||
// AddChapter resolves the addChapter mutation
|
||||
func (r *MutationResolver) AddChapter(ctx context.Context, args *AddChapterArgs) (*types.ChapterResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member", "chapter.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
story, err := story.FindID(input.StoryID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
author := user.ID
|
||||
if input.Author != nil {
|
||||
author = *input.Author
|
||||
|
||||
if user.ID != author && !user.Permitted("chapter.add") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
|
||||
fictionalDate := time.Time{}
|
||||
if input.FictionalDate != nil {
|
||||
fictionalDate, err = time.Parse(time.RFC3339Nano, *input.FictionalDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
chapter, err := story.AddChapter(input.Title, author, input.Source, time.Now(), fictionalDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Chapter", "add", user.ID, chapter.ID, map[string]interface{}{
|
||||
"title": input.Title,
|
||||
"author": author,
|
||||
"fictionalDate": fictionalDate,
|
||||
})
|
||||
|
||||
return &types.ChapterResolver{C: chapter}, nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// AddCharacterInput is args for the addCharacter mutation
|
||||
type AddCharacterInput struct {
|
||||
Nick string
|
||||
Name string
|
||||
ShortName *string
|
||||
Author *string
|
||||
Description *string
|
||||
}
|
||||
|
||||
// AddCharacter resolves the addCharacter mutation
|
||||
func (r *MutationResolver) AddCharacter(ctx context.Context, args struct{ Input *AddCharacterInput }) (*types.CharacterResolver, error) {
|
||||
input := args.Input
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member", "character.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
nick := input.Nick
|
||||
|
||||
name := input.Name
|
||||
|
||||
shortName := ""
|
||||
if input.ShortName != nil {
|
||||
shortName = *input.ShortName
|
||||
} else {
|
||||
shortName = strings.SplitN(input.Name, " ", 2)[0]
|
||||
}
|
||||
|
||||
author := user.ID
|
||||
if input.Author != nil {
|
||||
author = *input.Author
|
||||
|
||||
if author != user.ID && !user.Permitted("character.add") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
|
||||
description := ""
|
||||
if input.Description != nil {
|
||||
description = *input.Description
|
||||
}
|
||||
|
||||
character, err := character.New(nick, name, shortName, author, description)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "add", user.ID, character.ID, map[string]interface{}{
|
||||
"name": character.Name,
|
||||
"nick": character.Nicks[0],
|
||||
"author": character.Author,
|
||||
})
|
||||
|
||||
log.ScheduleCharacterUpdate()
|
||||
|
||||
return &types.CharacterResolver{C: character}, nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// AddCharacterNickInput is args for the addCharacterNick mutation
|
||||
type AddCharacterNickInput struct {
|
||||
ID string
|
||||
Nick string
|
||||
}
|
||||
|
||||
// AddCharacterNick resolves the addCharacterNick mutation
|
||||
func (r *MutationResolver) AddCharacterNick(ctx context.Context, args struct{ Input *AddCharacterNickInput }) (*types.CharacterResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
character, err := character.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if character.Author != user.ID && !user.Permitted("character.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = character.AddNick(input.Nick)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "add.nick", user.ID, character.ID, map[string]interface{}{
|
||||
"nick": input.Nick,
|
||||
})
|
||||
|
||||
log.ScheduleCharacterUpdate()
|
||||
|
||||
return &types.CharacterResolver{C: character}, nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// LogAddArgs is args for the addLog mutation
|
||||
type LogAddArgs struct {
|
||||
Input *struct {
|
||||
Date string
|
||||
Channel string
|
||||
Title *string
|
||||
Open *bool
|
||||
Event *string
|
||||
Description *string
|
||||
}
|
||||
}
|
||||
|
||||
// AddLog resolves the addLog mutation
|
||||
func (r *MutationResolver) AddLog(ctx context.Context, args *LogAddArgs) (*types.LogResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("log.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
date, err := time.Parse(time.RFC3339Nano, args.Input.Date)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
title := ""
|
||||
if input.Title != nil {
|
||||
title = *input.Title
|
||||
}
|
||||
|
||||
event := ""
|
||||
if input.Event != nil {
|
||||
event = *input.Event
|
||||
}
|
||||
|
||||
description := ""
|
||||
if input.Description != nil {
|
||||
description = *input.Description
|
||||
}
|
||||
|
||||
open := input.Open != nil && *input.Open == true
|
||||
|
||||
log, err := log.New(date, input.Channel, title, event, description, open)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Log", "add", user.ID, log.ID, map[string]interface{}{
|
||||
"channel": log.Channel,
|
||||
"title": log.Title,
|
||||
"event": log.Event,
|
||||
"description": log.Description,
|
||||
"open": log.Open,
|
||||
})
|
||||
|
||||
return &types.LogResolver{L: log}, nil
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostAddArgs is args for addPost mutation
|
||||
type PostAddArgs struct {
|
||||
Input *struct {
|
||||
LogID string
|
||||
Time string
|
||||
Kind string
|
||||
Nick string
|
||||
Text string
|
||||
}
|
||||
}
|
||||
|
||||
// AddPost resolves the addPost mutation
|
||||
func (r *MutationResolver) AddPost(ctx context.Context, args *PostAddArgs) (*types.PostResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("post.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
postTime, err := time.Parse(time.RFC3339Nano, input.Time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log, err := log.FindID(input.LogID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
post, err := log.NewPost(postTime, input.Kind, input.Nick, input.Text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Post", "add", user.ID, post.ID, map[string]interface{}{
|
||||
"logId": post.LogID,
|
||||
"time": post.Time,
|
||||
"kind": post.Kind,
|
||||
"nick": post.Nick,
|
||||
"text": post.Text,
|
||||
"position": post.Position,
|
||||
})
|
||||
|
||||
go log.UpdateCharacters()
|
||||
|
||||
return &types.PostResolver{P: post}, nil
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoryAddArgs is args for the addStory mutation
|
||||
type StoryAddArgs struct {
|
||||
Input *struct {
|
||||
Name string
|
||||
Category string
|
||||
Author *string
|
||||
Open *bool
|
||||
Listed *bool
|
||||
FictionalDate *string
|
||||
Tags *[]struct {
|
||||
Kind string
|
||||
Name string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddStory resolves the addStory mutation
|
||||
func (r *MutationResolver) AddStory(ctx context.Context, args *StoryAddArgs) (*types.StoryResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member", "story.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
author := user.ID
|
||||
if input.Author != nil {
|
||||
author = *input.Author
|
||||
|
||||
if user.ID != author && !user.Permitted("story.add") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
|
||||
listed := (input.Listed != nil && *input.Listed == true)
|
||||
|
||||
open := (input.Open != nil && *input.Open == true)
|
||||
|
||||
tags := make([]story.Tag, 0, 8)
|
||||
if input.Tags != nil {
|
||||
for _, tagInput := range *input.Tags {
|
||||
tags = append(tags, story.Tag{
|
||||
Kind: tagInput.Kind,
|
||||
Name: tagInput.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fictionalDate := time.Time{}
|
||||
if input.FictionalDate != nil {
|
||||
date, err := time.Parse(time.RFC3339Nano, *input.FictionalDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fictionalDate = date
|
||||
}
|
||||
|
||||
story, err := story.New(input.Name, author, input.Category, listed, open, tags, time.Now(), fictionalDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Story", "add", user.ID, story.ID, map[string]interface{}{
|
||||
"name": input.Name,
|
||||
"category": input.Category,
|
||||
"author": input.Author,
|
||||
})
|
||||
|
||||
return &types.StoryResolver{S: story}, nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoryTagAddArgs is args for the addStoryTag mutation
|
||||
type StoryTagAddArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
Tag struct {
|
||||
Kind string
|
||||
Name string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddStoryTag resolves the addStoryTag mutation
|
||||
func (r *MutationResolver) AddStoryTag(ctx context.Context, args *StoryTagAddArgs) (*types.StoryResolver, error) {
|
||||
input := args.Input
|
||||
tag := story.Tag{Kind: input.Tag.Kind, Name: input.Tag.Name}
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member", "story.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
story, err := story.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if story.Author != user.ID && !user.Permitted("story.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = story.AddTag(tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Story", "add.tag", user.ID, story.ID, map[string]interface{}{
|
||||
"kind": tag.Kind,
|
||||
"name": tag.Name,
|
||||
})
|
||||
|
||||
return &types.StoryResolver{S: story}, nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/channel"
|
||||
)
|
||||
|
||||
// ChannelEditArgs is input for the editChannel mutation
|
||||
type ChannelEditArgs struct {
|
||||
Input *struct {
|
||||
Name string
|
||||
Logged *bool
|
||||
Hub *bool
|
||||
EventName *string
|
||||
LocationName *string
|
||||
}
|
||||
}
|
||||
|
||||
// EditChannel resolves the editChannel mutation
|
||||
func (r *MutationResolver) EditChannel(ctx context.Context, args *ChannelEditArgs) (*types.ChannelResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("channel.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
channel, err := channel.FindName(input.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = channel.Edit(input.Logged, input.Hub, input.EventName, input.LocationName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Channel", "edit", user.ID, channel.Name, map[string]interface{}{
|
||||
"logged": input.Logged,
|
||||
"hub": input.Hub,
|
||||
"location": input.LocationName,
|
||||
"event": input.EventName,
|
||||
})
|
||||
|
||||
return &types.ChannelResolver{C: channel}, nil
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// EditChapterArgs is args for the editChapter mutation
|
||||
type EditChapterArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
Title *string
|
||||
Source *string
|
||||
FictionalDate *string
|
||||
}
|
||||
}
|
||||
|
||||
// EditChapter resolves the editChapter mutation
|
||||
func (r *MutationResolver) EditChapter(ctx context.Context, args *EditChapterArgs) (*types.ChapterResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member", "chapter.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
chapter, err := story.FindChapterID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if chapter.Author != user.ID && !user.Permitted("chapter.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
var fictionalDate *time.Time
|
||||
if input.FictionalDate != nil {
|
||||
date, err := time.Parse(time.RFC3339Nano, *input.FictionalDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fictionalDate = &date
|
||||
}
|
||||
|
||||
err = chapter.Edit(input.Title, input.Source, fictionalDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Chapter", "edit", user.ID, chapter.ID, map[string]interface{}{
|
||||
"title": input.Title,
|
||||
"source": input.Source,
|
||||
"fictionalDate": fictionalDate,
|
||||
})
|
||||
|
||||
return &types.ChapterResolver{C: chapter}, nil
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
)
|
||||
|
||||
// CharacterEditInput is args for mutation addCharacterNick/removeCharacterNick
|
||||
type CharacterEditInput struct {
|
||||
ID string
|
||||
Name *string
|
||||
ShortName *string
|
||||
Description *string
|
||||
}
|
||||
|
||||
// EditCharacter resolves the editCharacter mutation
|
||||
func (r *MutationResolver) EditCharacter(ctx context.Context, args struct{ Input *CharacterEditInput }) (*types.CharacterResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
character, err := character.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if character.Author != user.ID && !user.Permitted("character.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
name := ""
|
||||
if input.Name != nil {
|
||||
name = *input.Name
|
||||
}
|
||||
|
||||
shortName := ""
|
||||
if input.ShortName != nil {
|
||||
shortName = *input.ShortName
|
||||
}
|
||||
|
||||
description := ""
|
||||
if input.Description != nil {
|
||||
description = *input.Description
|
||||
}
|
||||
|
||||
err = character.Edit(name, shortName, description)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "edit", user.ID, character.ID, map[string]interface{}{
|
||||
"name": character.Name,
|
||||
"shortName": character.ShortName,
|
||||
"description": character.Description,
|
||||
})
|
||||
|
||||
return &types.CharacterResolver{C: character}, nil
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
)
|
||||
|
||||
// FileEditArgs is args for the editFile mutation
|
||||
type FileEditArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
Name *string
|
||||
Public *bool
|
||||
}
|
||||
}
|
||||
|
||||
// EditFile resolves the editFile mutation
|
||||
func (r *MutationResolver) EditFile(ctx context.Context, args *FileEditArgs) (*types.FileResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
file, err := file.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file.Author != user.ID && !user.Permitted("file.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
err = file.Edit(input.Name, input.Public)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.FileResolver{F: file}, nil
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// LogEditArgs is args for the editLog mutation
|
||||
type LogEditArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
Title *string
|
||||
Event *string
|
||||
Description *string
|
||||
Open *bool
|
||||
}
|
||||
}
|
||||
|
||||
// EditLog resolves the editLog mutation
|
||||
func (r *MutationResolver) EditLog(ctx context.Context, args *LogEditArgs) (*types.LogResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("log.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
log, err := log.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = log.Edit(input.Title, input.Event, input.Description, input.Open)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Log", "edit", user.ID, log.ID, map[string]interface{}{
|
||||
"channel": log.Channel,
|
||||
"title": input.Title,
|
||||
"event": input.Event,
|
||||
"description": input.Description,
|
||||
"open": input.Open,
|
||||
})
|
||||
|
||||
return &types.LogResolver{L: log}, nil
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostEditArgs is args for the editPost mutation
|
||||
type PostEditArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
Time *string
|
||||
Kind *string
|
||||
Nick *string
|
||||
Text *string
|
||||
}
|
||||
}
|
||||
|
||||
// EditPost resolves the editPost mutation
|
||||
func (r *MutationResolver) EditPost(ctx context.Context, args *PostEditArgs) (*types.PostResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("post.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
postTime := (*time.Time)(nil)
|
||||
if args.Input.Time != nil {
|
||||
t, err := time.Parse(time.RFC3339Nano, *input.Time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
postTime = &t
|
||||
}
|
||||
|
||||
post, err := log.FindPostID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = post.Edit(postTime, input.Kind, input.Nick, input.Text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Post", "edit", user.ID, post.ID, map[string]interface{}{
|
||||
"time": postTime,
|
||||
"kind": input.Kind,
|
||||
"nick": input.Nick,
|
||||
"text": input.Text,
|
||||
})
|
||||
|
||||
go log.UpdateCharacters(post.LogID)
|
||||
|
||||
return &types.PostResolver{P: post}, nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoryEditArgs is args for the addStory mutation
|
||||
type StoryEditArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
Name *string
|
||||
Category *string
|
||||
Author *string
|
||||
Open *bool
|
||||
Listed *bool
|
||||
FictionalDate *string
|
||||
}
|
||||
}
|
||||
|
||||
// EditStory resolves the editStory mutation
|
||||
func (r *MutationResolver) EditStory(ctx context.Context, args *StoryEditArgs) (*types.StoryResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member", "story.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
story, err := story.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if story.Author != user.ID && !user.Permitted("story.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
var fictionalDate *time.Time
|
||||
if input.FictionalDate != nil {
|
||||
date, err := time.Parse(time.RFC3339Nano, *input.FictionalDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fictionalDate = &date
|
||||
}
|
||||
|
||||
err = story.Edit(input.Name, input.Category, input.Listed, input.Open, fictionalDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Story", "edit", user.ID, story.ID, map[string]interface{}{
|
||||
"name": input.Name,
|
||||
"category": input.Category,
|
||||
"author": input.Author,
|
||||
"open": input.Open,
|
||||
"listed": input.Listed,
|
||||
"fictionalDate": input.FictionalDate,
|
||||
})
|
||||
|
||||
return &types.StoryResolver{S: story}, nil
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
)
|
||||
|
||||
// LoginArgs is args for the login mutation
|
||||
type LoginArgs struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// Login resolves the login mutation
|
||||
func (r *MutationResolver) Login(ctx context.Context, args *LoginArgs) (*types.SessionResolver, error) {
|
||||
session := session.FromContext(ctx)
|
||||
|
||||
err := session.Login(args.Username, args.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.SessionResolver{S: session}, nil
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
)
|
||||
|
||||
// Logout resolves mutation.logout
|
||||
func (r *MutationResolver) Logout(ctx context.Context) (*types.SessionResolver, error) {
|
||||
session := session.FromContext(ctx)
|
||||
session.Logout()
|
||||
|
||||
return &types.SessionResolver{S: session}, nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostMoveArgs is args for movePost mutation
|
||||
type PostMoveArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
ToPosition int32
|
||||
}
|
||||
}
|
||||
|
||||
// MovePost resolves the movePost mutation
|
||||
func (r *MutationResolver) MovePost(ctx context.Context, args *PostMoveArgs) (*types.PostResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("post.move") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
post, err := log.FindPostID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = post.Move(int(input.ToPosition))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Post", "move", user.ID, post.ID, map[string]interface{}{
|
||||
"logId": post.LogID,
|
||||
"targetIndex": input.ToPosition,
|
||||
})
|
||||
|
||||
return &types.PostResolver{P: post}, nil
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/channel"
|
||||
)
|
||||
|
||||
// RemoveChannelArgs is args for removeChannels mutation
|
||||
type RemoveChannelArgs struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// RemoveChannel resolves the editChannel mutation
|
||||
func (r *MutationResolver) RemoveChannel(ctx context.Context, args RemoveChannelArgs) (*types.ChannelResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("channel.remove") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
channel, err := channel.FindName(args.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = channel.Remove()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Channel", "remove", user.ID, channel.Name, nil)
|
||||
|
||||
return &types.ChannelResolver{C: channel}, nil
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// RemoveChapterArgs is args for the addChapter mutation
|
||||
type RemoveChapterArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// RemoveChapter resolves the removeChapter mutation
|
||||
func (r *MutationResolver) RemoveChapter(ctx context.Context, args *RemoveChapterArgs) (*types.ChapterResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member", "chapter.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
chapter, err := story.FindChapterID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = chapter.Remove()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Chapter", "remove", user.ID, chapter.ID, nil)
|
||||
|
||||
return &types.ChapterResolver{C: chapter}, nil
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
)
|
||||
|
||||
// RemoveCharacterArgs is args for the removeCharacter mutation
|
||||
type RemoveCharacterArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// RemoveCharacter resolves the removeCharacter mutation
|
||||
func (r *MutationResolver) RemoveCharacter(ctx context.Context, args RemoveCharacterArgs) (*types.CharacterResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
character, err := character.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if character.Author != user.ID && !user.Permitted("character.remove") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = character.Remove()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "remove", user.ID, character.ID, map[string]interface{}{
|
||||
"name": character.Name,
|
||||
"author": character.Author,
|
||||
"nicks": character.Nicks,
|
||||
})
|
||||
|
||||
return &types.CharacterResolver{C: character}, nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// RemoveCharacterNick is args for the addCharacterNick mutation
|
||||
type RemoveCharacterNick struct {
|
||||
ID string
|
||||
Nick string
|
||||
}
|
||||
|
||||
// RemoveCharacterNick resolves the removeCharacterNick mutation
|
||||
func (r *MutationResolver) RemoveCharacterNick(ctx context.Context, args struct{ Input *RemoveCharacterNick }) (*types.CharacterResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
character, err := character.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if character.Author != user.ID && !user.Permitted("character.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = character.RemoveNick(input.Nick)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "remove.nick", user.ID, character.ID, map[string]interface{}{
|
||||
"nick": input.Nick,
|
||||
})
|
||||
|
||||
log.ScheduleCharacterUpdate()
|
||||
|
||||
return &types.CharacterResolver{C: character}, nil
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
)
|
||||
|
||||
// RemoveFileArgs is an arg
|
||||
type RemoveFileArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// RemoveFile resolves the removeFIle mutation
|
||||
func (r *MutationResolver) RemoveFile(ctx context.Context, args *RemoveFileArgs) (*types.FileResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
file, err := file.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file.Author != user.ID && !user.Permitted("file.remove") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
err = file.Delete()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.FileResolver{F: file}, nil
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// RemoveLogArgs is args for the removeLog mutation
|
||||
type RemoveLogArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// RemoveLog resolves the removeLog mutation
|
||||
func (r *MutationResolver) RemoveLog(ctx context.Context, args *RemoveLogArgs) (*types.LogResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("log.remove") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
log, err := log.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = log.Remove()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Log", "remove", user.ID, log.ID, nil)
|
||||
|
||||
return &types.LogResolver{L: log}, nil
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostRemoveArgs is args for the removePost mutation
|
||||
type PostRemoveArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// RemovePost resolves the removePost mutation
|
||||
func (r *MutationResolver) RemovePost(ctx context.Context, args PostRemoveArgs) (*types.PostResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("post.remove") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
post, err := log.RemovePost(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Post", "remove", user.ID, post.ID, map[string]interface{}{
|
||||
"logId": post.LogID,
|
||||
})
|
||||
|
||||
go log.UpdateCharacters(post.LogID)
|
||||
|
||||
return &types.PostResolver{P: post}, nil
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoryRemoveArgs is args for the removeStory mutation
|
||||
type StoryRemoveArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// RemoveStory resolves the removeStory mutation
|
||||
func (r *MutationResolver) RemoveStory(ctx context.Context, args *StoryRemoveArgs) (*types.StoryResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member", "story.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
story, err := story.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if story.Author != user.ID && !user.Permitted("story.remove") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = story.Remove()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Story", "remove", user.ID, story.ID, nil)
|
||||
|
||||
return &types.StoryResolver{S: story}, nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoryTagRemoveArgs is args for the removeStoryTag mutation
|
||||
type StoryTagRemoveArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
Tag struct {
|
||||
Kind string
|
||||
Name string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveStoryTag resolves the removeStoryTag mutation
|
||||
func (r *MutationResolver) RemoveStoryTag(ctx context.Context, args *StoryTagRemoveArgs) (*types.StoryResolver, error) {
|
||||
input := args.Input
|
||||
tag := story.Tag{Kind: input.Tag.Kind, Name: input.Tag.Name}
|
||||
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member", "story.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
story, err := story.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if story.Author != user.ID && !user.Permitted("story.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = story.RemoveTag(tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Story", "remove.tag", user.ID, story.ID, map[string]interface{}{
|
||||
"kind": tag.Kind,
|
||||
"name": tag.Name,
|
||||
})
|
||||
|
||||
return &types.StoryResolver{S: story}, nil
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Package mutations contains resolvers for individual GrahpQL mutations. They were previously mixed in with one another, but that caused
|
||||
// difficulty seeking out specific mutations.
|
||||
package mutations
|
||||
|
||||
import "errors"
|
||||
|
||||
// MutationResolver is a resolver for all mutations. The stuttery name is due to being embedded in the root resolver.
|
||||
type MutationResolver struct{}
|
||||
|
||||
// ErrUnauthorized is when a guest acts like they own the place
|
||||
var ErrUnauthorized = errors.New("Unauthorized")
|
||||
|
||||
// ErrPermissionDenied is returned when users act above their station
|
||||
var ErrPermissionDenied = errors.New("Permission denied")
|
||||
@@ -0,0 +1,23 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/channel"
|
||||
)
|
||||
|
||||
// ChannelArgs is args for channel query
|
||||
type ChannelArgs struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// Channel resolves the channel query
|
||||
func (r *QueryResolver) Channel(ctx context.Context, args *ChannelArgs) (*types.ChannelResolver, error) {
|
||||
channel, err := channel.FindName(args.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.ChannelResolver{C: channel}, nil
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/channel"
|
||||
)
|
||||
|
||||
// ChannelsArgs is args for channel query
|
||||
type ChannelsArgs struct {
|
||||
Filter *struct {
|
||||
Logged *bool
|
||||
EventName *string
|
||||
LocationName *string
|
||||
}
|
||||
}
|
||||
|
||||
// Channels resolves the channels query
|
||||
func (r *QueryResolver) Channels(ctx context.Context, args *ChannelsArgs) ([]*types.ChannelResolver, error) {
|
||||
var channels []channel.Channel
|
||||
var err error
|
||||
|
||||
filter := args.Filter
|
||||
if filter != nil {
|
||||
channels, err = channel.List(filter.Logged, filter.EventName, filter.LocationName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
channels, err = channel.List(nil, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resolvers := make([]*types.ChannelResolver, len(channels))
|
||||
for i := range channels {
|
||||
resolvers[i] = &types.ChannelResolver{C: channels[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// ChapterArgs is args for chapter query
|
||||
type ChapterArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// Chapter resolves the chapter query
|
||||
func (r *QueryResolver) Chapter(ctx context.Context, args *ChapterArgs) (*types.ChapterResolver, error) {
|
||||
chapter, err := story.FindChapterID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.ChapterResolver{C: chapter}, nil
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
)
|
||||
|
||||
// ErrIncorrectArguments is returned by character query.
|
||||
var ErrIncorrectArguments = errors.New("You can only query character by ID or Nick")
|
||||
|
||||
// CharacterArgs is args for character query
|
||||
type CharacterArgs struct {
|
||||
ID *string
|
||||
Nick *string
|
||||
}
|
||||
|
||||
// Character resolves the character query
|
||||
func (r *QueryResolver) Character(ctx context.Context, args *CharacterArgs) (*types.CharacterResolver, error) {
|
||||
var char character.Character
|
||||
var err error
|
||||
|
||||
if args.Nick != nil && args.ID != nil {
|
||||
return nil, ErrIncorrectArguments
|
||||
}
|
||||
|
||||
switch {
|
||||
case args.ID != nil:
|
||||
char, err = character.FindID(*args.ID)
|
||||
case args.Nick != nil:
|
||||
char, err = character.FindNick(*args.Nick)
|
||||
default:
|
||||
err = ErrIncorrectArguments
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.CharacterResolver{C: char}, nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
)
|
||||
|
||||
// CharactersArgs is args for characters query
|
||||
type CharactersArgs struct {
|
||||
Filter *struct {
|
||||
IDs *[]string
|
||||
Nicks *[]string
|
||||
Names *[]string
|
||||
Author *string
|
||||
Search *string
|
||||
}
|
||||
}
|
||||
|
||||
// Characters resolves the characters query
|
||||
func (r *QueryResolver) Characters(ctx context.Context, args *CharactersArgs) ([]*types.CharacterResolver, error) {
|
||||
var characters []character.Character
|
||||
var err error
|
||||
|
||||
filter := args.Filter
|
||||
if filter != nil {
|
||||
var ids []string
|
||||
var nicks []string
|
||||
var names []string
|
||||
|
||||
if filter.IDs != nil {
|
||||
ids = *filter.IDs
|
||||
}
|
||||
if filter.Nicks != nil {
|
||||
nicks = *filter.Nicks
|
||||
}
|
||||
if filter.Names != nil {
|
||||
names = *filter.Names
|
||||
}
|
||||
|
||||
characters, err = character.ListFilter(ids, nicks, names, filter.Author, filter.Search)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
characters, err = character.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resolvers := make([]*types.CharacterResolver, 0, len(characters))
|
||||
for _, character := range characters {
|
||||
resolvers = append(resolvers, &types.CharacterResolver{C: character})
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
)
|
||||
|
||||
// FileArgs is args for file query
|
||||
type FileArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// File resolves the file query
|
||||
func (r *QueryResolver) File(ctx context.Context, args *FileArgs) (*types.FileResolver, error) {
|
||||
file, err := file.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.FileResolver{F: file}, nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
)
|
||||
|
||||
// FilesArgs is args for files query
|
||||
type FilesArgs struct {
|
||||
Filter *struct {
|
||||
Public *bool
|
||||
MimeTypes *[]string
|
||||
}
|
||||
}
|
||||
|
||||
// Files resolves the file query
|
||||
func (r *QueryResolver) Files(ctx context.Context, args *FilesArgs) ([]*types.FileResolver, error) {
|
||||
filter := args.Filter
|
||||
user := session.FromContext(ctx).User()
|
||||
|
||||
author := ""
|
||||
if user != nil {
|
||||
author = user.ID
|
||||
}
|
||||
|
||||
mimeTypes := []string(nil)
|
||||
if filter.MimeTypes != nil {
|
||||
mimeTypes = *filter.MimeTypes
|
||||
}
|
||||
|
||||
files, err := file.List(author, filter.Public, mimeTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*types.FileResolver, len(files))
|
||||
for i := range files {
|
||||
resolvers[i] = &types.FileResolver{F: files[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// LogArgs is args for log query
|
||||
type LogArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// Log resolves the log query
|
||||
func (r *QueryResolver) Log(ctx context.Context, args *LogArgs) (*types.LogResolver, error) {
|
||||
log, err := log.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.LogResolver{L: log}, nil
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// LogsArgs is args for the logs query
|
||||
type LogsArgs struct {
|
||||
Filter *struct {
|
||||
Search *string
|
||||
Characters *[]string
|
||||
Channels *[]string
|
||||
Events *[]string
|
||||
Open *bool
|
||||
Limit *int32
|
||||
}
|
||||
}
|
||||
|
||||
// Logs resolves the logs query
|
||||
func (r *QueryResolver) Logs(ctx context.Context, args *LogsArgs) ([]*types.LogResolver, error) {
|
||||
var logs []log.Log
|
||||
var err error
|
||||
|
||||
filter := args.Filter
|
||||
|
||||
if filter != nil {
|
||||
limit := 100
|
||||
search := ""
|
||||
|
||||
if filter.Search != nil {
|
||||
search = *filter.Search
|
||||
limit = 0
|
||||
}
|
||||
|
||||
channels := []string(nil)
|
||||
if filter.Channels != nil {
|
||||
channels = *filter.Channels
|
||||
limit = 0
|
||||
}
|
||||
|
||||
characters := []string(nil)
|
||||
if filter.Characters != nil {
|
||||
characters = *filter.Characters
|
||||
limit = 0
|
||||
}
|
||||
|
||||
events := []string(nil)
|
||||
if filter.Events != nil {
|
||||
events = *filter.Events
|
||||
limit = 0
|
||||
}
|
||||
|
||||
if filter.Limit != nil {
|
||||
limit = int(*filter.Limit)
|
||||
}
|
||||
|
||||
open := filter.Open != nil && *filter.Open == true
|
||||
|
||||
logs, err = log.ListSearch(search, channels, characters, events, open, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
logs, err = log.List(100)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resolvers := make([]*types.LogResolver, len(logs))
|
||||
for i := range logs {
|
||||
resolvers[i] = &types.LogResolver{L: logs[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostArgs is args for the post query
|
||||
type PostArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// Post resolves the post query
|
||||
func (r *QueryResolver) Post(ctx context.Context, args *PostArgs) (*types.PostResolver, error) {
|
||||
post, err := log.FindPostID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.PostResolver{P: post}, nil
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostsArgs is args for the posts query
|
||||
type PostsArgs struct {
|
||||
IDs []string
|
||||
}
|
||||
|
||||
// Posts resolves the posts query
|
||||
func (r *QueryResolver) Posts(ctx context.Context, args *PostsArgs) ([]*types.PostResolver, error) {
|
||||
posts, err := log.ListPostIDs(args.IDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*types.PostResolver, len(posts))
|
||||
for i := range resolvers {
|
||||
resolvers[i] = &types.PostResolver{P: posts[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Package queries contains resolvers for individual GrahpQL queries. They were previously mixed in with one another, but that caused
|
||||
// difficulty seeking out specific queries.
|
||||
package queries
|
||||
|
||||
import "errors"
|
||||
|
||||
// QueryResolver is a resolver for all queries. The stuttery name is due to being embedded in the root resolver.
|
||||
type QueryResolver struct{}
|
||||
|
||||
// ErrCannotResolve is returned when a resolver constructor is at its wit's end
|
||||
var ErrCannotResolve = errors.New("Cannot resolve due to invalid arguments")
|
||||
@@ -0,0 +1,13 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
)
|
||||
|
||||
// Session resolves query.session
|
||||
func (r *QueryResolver) Session(ctx context.Context) (*types.SessionResolver, error) {
|
||||
return &types.SessionResolver{S: session.FromContext(ctx)}, nil
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoriesArg is args for stories query
|
||||
type StoriesArg struct {
|
||||
Filter *struct {
|
||||
Author *string
|
||||
Tags *[]struct {
|
||||
Kind string
|
||||
Name string
|
||||
}
|
||||
EarliestFictionalDate *string
|
||||
LatestFictionalDate *string
|
||||
Limit *int32
|
||||
}
|
||||
}
|
||||
|
||||
// Stories resolves the stories query
|
||||
func (r *QueryResolver) Stories(ctx context.Context, args *StoriesArg) ([]*types.StoryResolver, error) {
|
||||
filter := args.Filter
|
||||
|
||||
author := ""
|
||||
if filter != nil && filter.Author != nil {
|
||||
author = *filter.Author
|
||||
}
|
||||
|
||||
tags := make([]story.Tag, 0, 8)
|
||||
if filter != nil && filter.Tags != nil {
|
||||
for _, tagInput := range *filter.Tags {
|
||||
tags = append(tags, story.Tag{
|
||||
Kind: tagInput.Kind,
|
||||
Name: tagInput.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
earliest := time.Time{}
|
||||
err := error(nil)
|
||||
if filter != nil && filter.EarliestFictionalDate != nil {
|
||||
earliest, err = time.Parse(time.RFC3339Nano, *filter.EarliestFictionalDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
latest := time.Time{}
|
||||
if filter != nil && filter.LatestFictionalDate != nil {
|
||||
latest, err = time.Parse(time.RFC3339Nano, *filter.LatestFictionalDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
limit := 30
|
||||
if filter != nil && filter.Limit != nil {
|
||||
limit = int(*filter.Limit)
|
||||
}
|
||||
|
||||
stories, err := story.List(author, tags, earliest, latest, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*types.StoryResolver, len(stories))
|
||||
for i, story := range stories {
|
||||
resolvers[i] = &types.StoryResolver{S: story}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoryArgs is args for story query
|
||||
type StoryArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// Story resolves the story query
|
||||
func (r *QueryResolver) Story(ctx context.Context, args *StoryArgs) (*types.StoryResolver, error) {
|
||||
story, err := story.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.StoryResolver{S: story}, nil
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// Tags resolves the tags query
|
||||
func (r *QueryResolver) Tags() ([]*types.TagResolver, error) {
|
||||
tags, err := story.ListTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*types.TagResolver, len(tags))
|
||||
for i, tag := range tags {
|
||||
resolvers[i] = &types.TagResolver{T: tag}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/mutations"
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/queries"
|
||||
)
|
||||
|
||||
// The Resolver combines the query and mutation resolvers from the subpackages.
|
||||
// This is the one to pass along with the schema.
|
||||
type Resolver struct {
|
||||
queries.QueryResolver
|
||||
mutations.MutationResolver
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package types
|
||||
|
||||
import "git.aiterp.net/rpdata/api/model/channel"
|
||||
|
||||
// ChannelResolver for the Channel graphql type
|
||||
type ChannelResolver struct{ C channel.Channel }
|
||||
|
||||
// Name resolves channel.name
|
||||
func (r *ChannelResolver) Name() string {
|
||||
return r.C.Name
|
||||
}
|
||||
|
||||
// Logged resolves channel.logged
|
||||
func (r *ChannelResolver) Logged() bool {
|
||||
return r.C.Logged
|
||||
}
|
||||
|
||||
// Hub resolves channel.hub
|
||||
func (r *ChannelResolver) Hub() bool {
|
||||
return r.C.Hub
|
||||
}
|
||||
|
||||
// EventName resolves channel.eventName
|
||||
func (r *ChannelResolver) EventName() *string {
|
||||
if r.C.Event == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &r.C.Event
|
||||
}
|
||||
|
||||
// LocationName resolves channel.locationName
|
||||
func (r *ChannelResolver) LocationName() *string {
|
||||
if r.C.Location == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &r.C.Location
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// ChapterResolver for the Chapter graphql type
|
||||
type ChapterResolver struct{ C story.Chapter }
|
||||
|
||||
// ID resolves Chapter.id
|
||||
func (r *ChapterResolver) ID() string {
|
||||
return r.C.ID
|
||||
}
|
||||
|
||||
// StoryID resolves Chapter.storyId
|
||||
func (r *ChapterResolver) StoryID() string {
|
||||
return r.C.StoryID
|
||||
}
|
||||
|
||||
// Title resolves Chapter.title
|
||||
func (r *ChapterResolver) Title() string {
|
||||
return r.C.Title
|
||||
}
|
||||
|
||||
// Author resolves Chapter.author
|
||||
func (r *ChapterResolver) Author() string {
|
||||
return r.C.Author
|
||||
}
|
||||
|
||||
// Source resolves Chapter.source
|
||||
func (r *ChapterResolver) Source() string {
|
||||
return r.C.Source
|
||||
}
|
||||
|
||||
// CreatedDate resolves Chapter.createdDate
|
||||
func (r *ChapterResolver) CreatedDate() string {
|
||||
return r.C.CreatedDate.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// FictionalDate resolves Chapter.fictionalDate
|
||||
func (r *ChapterResolver) FictionalDate() string {
|
||||
return r.C.FictionalDate.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// EditedDate resolves Chapter.editedDate
|
||||
func (r *ChapterResolver) EditedDate() string {
|
||||
return r.C.EditedDate.Format(time.RFC3339Nano)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package types
|
||||
|
||||
import "git.aiterp.net/rpdata/api/model/character"
|
||||
|
||||
// CharacterResolver for the Character graphql type
|
||||
type CharacterResolver struct{ C character.Character }
|
||||
|
||||
// ID is a property resolver
|
||||
func (r *CharacterResolver) ID() string {
|
||||
return r.C.ID
|
||||
}
|
||||
|
||||
// Nick is a property resolver
|
||||
func (r *CharacterResolver) Nick() *string {
|
||||
if len(r.C.Nicks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &r.C.Nicks[0]
|
||||
}
|
||||
|
||||
// Nicks is a property resolver
|
||||
func (r *CharacterResolver) Nicks() []string {
|
||||
return r.C.Nicks
|
||||
}
|
||||
|
||||
// Name is a property resolver
|
||||
func (r *CharacterResolver) Name() string {
|
||||
return r.C.Name
|
||||
}
|
||||
|
||||
// ShortName is a property resolver
|
||||
func (r *CharacterResolver) ShortName() string {
|
||||
return r.C.ShortName
|
||||
}
|
||||
|
||||
// Author is a property resolver
|
||||
func (r *CharacterResolver) Author() string {
|
||||
return r.C.Author
|
||||
}
|
||||
|
||||
// Description is a property resolver
|
||||
func (r *CharacterResolver) Description() string {
|
||||
return r.C.Description
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
)
|
||||
|
||||
// FileResolver for the File graphql type
|
||||
type FileResolver struct{ F file.File }
|
||||
|
||||
// ID resolves File.id
|
||||
func (f *FileResolver) ID() string {
|
||||
return f.F.ID
|
||||
}
|
||||
|
||||
// Author resolves File.author
|
||||
func (f *FileResolver) Author() string {
|
||||
return f.F.Author
|
||||
}
|
||||
|
||||
// Kind resolves File.kind
|
||||
func (f *FileResolver) Kind() string {
|
||||
return f.F.Kind
|
||||
}
|
||||
|
||||
// Time resolves File.time
|
||||
func (f *FileResolver) Time() string {
|
||||
return f.F.Time.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// Public resolves File.public
|
||||
func (f *FileResolver) Public() bool {
|
||||
return f.F.Public
|
||||
}
|
||||
|
||||
// Name resolves File.name
|
||||
func (f *FileResolver) Name() string {
|
||||
return f.F.Name
|
||||
}
|
||||
|
||||
// MimeType resolves File.mimeType
|
||||
func (f *FileResolver) MimeType() string {
|
||||
return f.F.MimeType
|
||||
}
|
||||
|
||||
// Size resolves File.size
|
||||
func (f *FileResolver) Size() int32 {
|
||||
return int32(f.F.Size)
|
||||
}
|
||||
|
||||
// URL resolves File.url
|
||||
func (f *FileResolver) URL() *string {
|
||||
if f.F.URL == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &f.F.URL
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/loader"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// LogResolver for the Log graphql type
|
||||
type LogResolver struct{ L log.Log }
|
||||
|
||||
// ID resolves Log.id
|
||||
func (r *LogResolver) ID() string {
|
||||
return r.L.ID
|
||||
}
|
||||
|
||||
// ShortID resolves Log.shortId
|
||||
func (r *LogResolver) ShortID() string {
|
||||
return r.L.ShortID
|
||||
}
|
||||
|
||||
// Date resolves Log.date
|
||||
func (r *LogResolver) Date() string {
|
||||
return r.L.Date.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// ChannelName resolves Log.channelName
|
||||
func (r *LogResolver) ChannelName() string {
|
||||
return r.L.Channel
|
||||
}
|
||||
|
||||
// Channel resolves Log.channe
|
||||
func (r *LogResolver) Channel(ctx context.Context) (*ChannelResolver, error) {
|
||||
loader := loader.FromContext(ctx)
|
||||
if loader == nil {
|
||||
return nil, errors.New("no loader")
|
||||
}
|
||||
|
||||
channel, err := loader.Channel("name", r.L.Channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ChannelResolver{C: channel}, nil
|
||||
}
|
||||
|
||||
// Title resolves Log.title
|
||||
func (r *LogResolver) Title() string {
|
||||
return r.L.Title
|
||||
}
|
||||
|
||||
// Event resolves Log.event
|
||||
func (r *LogResolver) Event() string {
|
||||
return r.L.Event
|
||||
}
|
||||
|
||||
// Description resolves Log.description
|
||||
func (r *LogResolver) Description() string {
|
||||
return r.L.Description
|
||||
}
|
||||
|
||||
// Open resolves Log.open
|
||||
func (r *LogResolver) Open() bool {
|
||||
return r.L.Open
|
||||
}
|
||||
|
||||
// Characters resolves Log.characters
|
||||
func (r *LogResolver) Characters(ctx context.Context) ([]*CharacterResolver, error) {
|
||||
loader := loader.FromContext(ctx)
|
||||
if loader == nil {
|
||||
return nil, errors.New("no loader")
|
||||
}
|
||||
|
||||
chars, err := loader.Characters("id", r.L.CharacterIDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*CharacterResolver, 0, len(chars))
|
||||
for i := range chars {
|
||||
if chars[i].ID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
resolvers = append(resolvers, &CharacterResolver{C: chars[i]})
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
|
||||
// Posts resolves Log.posts
|
||||
func (r *LogResolver) Posts(ctx context.Context, args *LogPostArgs) ([]*PostResolver, error) {
|
||||
var kinds []string
|
||||
if args.Kinds != nil {
|
||||
kinds = *args.Kinds
|
||||
}
|
||||
|
||||
posts, err := r.L.Posts(kinds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*PostResolver, len(posts))
|
||||
for i := range posts {
|
||||
resolvers[i] = &PostResolver{posts[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
|
||||
// LogPostArgs is an arg
|
||||
type LogPostArgs struct {
|
||||
Kinds *[]string
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostResolver for the Post graphql type
|
||||
type PostResolver struct{ P log.Post }
|
||||
|
||||
// ID resolves Post.id
|
||||
func (r *PostResolver) ID() string {
|
||||
return r.P.ID
|
||||
}
|
||||
|
||||
// LogID resolves Post.logId
|
||||
func (r *PostResolver) LogID() string {
|
||||
return r.P.LogID
|
||||
}
|
||||
|
||||
// Time resolves Post.time
|
||||
func (r *PostResolver) Time() string {
|
||||
return r.P.Time.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// Kind resolves Post.logId
|
||||
func (r *PostResolver) Kind() string {
|
||||
return r.P.Kind
|
||||
}
|
||||
|
||||
// Nick resolves Post.nick
|
||||
func (r *PostResolver) Nick() string {
|
||||
return r.P.Nick
|
||||
}
|
||||
|
||||
// Text resolves Post.text
|
||||
func (r *PostResolver) Text() string {
|
||||
return r.P.Text
|
||||
}
|
||||
|
||||
// Position resolves Post.text
|
||||
func (r *PostResolver) Position() int32 {
|
||||
return int32(r.P.Position)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package types
|
||||
|
||||
import "git.aiterp.net/rpdata/api/internal/session"
|
||||
|
||||
// SessionResolver resolves Session
|
||||
type SessionResolver struct{ S *session.Session }
|
||||
|
||||
// User resolves Session.user
|
||||
func (r *SessionResolver) User() *UserResolver {
|
||||
user := r.S.User()
|
||||
if user == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &UserResolver{U: user}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoryResolver for the Story graphql type
|
||||
type StoryResolver struct{ S story.Story }
|
||||
|
||||
// ID resolves Story.id
|
||||
func (r *StoryResolver) ID() string {
|
||||
return r.S.ID
|
||||
}
|
||||
|
||||
// Author resolves Story.author
|
||||
func (r *StoryResolver) Author() string {
|
||||
return r.S.Author
|
||||
}
|
||||
|
||||
// Name resolves Story.name
|
||||
func (r *StoryResolver) Name() string {
|
||||
return r.S.Name
|
||||
}
|
||||
|
||||
// Category resolves Story.category
|
||||
func (r *StoryResolver) Category() string {
|
||||
return r.S.Category
|
||||
}
|
||||
|
||||
// Open resolves Story.open
|
||||
func (r *StoryResolver) Open() bool {
|
||||
return r.S.Open
|
||||
}
|
||||
|
||||
// Listed resolves Story.listed
|
||||
func (r *StoryResolver) Listed() bool {
|
||||
return r.S.Listed
|
||||
}
|
||||
|
||||
// Tags resolves Story.tags
|
||||
func (r *StoryResolver) Tags() []*TagResolver {
|
||||
resolvers := make([]*TagResolver, len(r.S.Tags))
|
||||
for i, tag := range r.S.Tags {
|
||||
resolvers[i] = &TagResolver{T: tag}
|
||||
}
|
||||
|
||||
return resolvers
|
||||
}
|
||||
|
||||
// Chapters resolves Story.chapters
|
||||
func (r *StoryResolver) Chapters() ([]*ChapterResolver, error) {
|
||||
chapters, err := r.S.Chapters()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*ChapterResolver, len(chapters))
|
||||
for i, chapter := range chapters {
|
||||
resolvers[i] = &ChapterResolver{C: chapter}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
|
||||
// CreatedDate resolves Story.createdDate
|
||||
func (r *StoryResolver) CreatedDate() string {
|
||||
return r.S.CreatedDate.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// FictionalDate resolves Story.fictionalDate
|
||||
func (r *StoryResolver) FictionalDate() string {
|
||||
return r.S.FictionalDate.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// UpdatedDate resolves Story.updatedDate
|
||||
func (r *StoryResolver) UpdatedDate() string {
|
||||
return r.S.UpdatedDate.Format(time.RFC3339Nano)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package types
|
||||
|
||||
import "git.aiterp.net/rpdata/api/model/story"
|
||||
|
||||
// TagResolver for the Tag graphql type
|
||||
type TagResolver struct{ T story.Tag }
|
||||
|
||||
// Kind resolves Tag.kind
|
||||
func (r *TagResolver) Kind() string {
|
||||
return r.T.Kind
|
||||
}
|
||||
|
||||
// Name resolves Tag.name
|
||||
func (r *TagResolver) Name() string {
|
||||
return r.T.Name
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package resolver
|
||||
package types
|
||||
|
||||
import "git.aiterp.net/rpdata/api/internal/session"
|
||||
|
||||
@@ -3,37 +3,52 @@ type Query {
|
||||
# Find character by either an ID or a nick.
|
||||
character(id: String, nick: String): Character
|
||||
|
||||
# Find characters by either a list of ids, nicks or an author. Only one parameter at a time
|
||||
characters(ids: [String!], nicks: [String!], author: String): [Character!]!
|
||||
# Find characters
|
||||
characters(filter: CharactersFilter): [Character!]!
|
||||
|
||||
|
||||
# Find channel by name
|
||||
channel(name: String!): Channel
|
||||
|
||||
# Find all channels. Specifying `logged: true` restricts the search to only logged.
|
||||
channels(logged: Boolean): [Channel!]!
|
||||
# Find channels
|
||||
channels(filter: ChannelsFilter): [Channel!]!
|
||||
|
||||
|
||||
# Find log by ID
|
||||
log(id: String): Log
|
||||
log(id: String!): Log
|
||||
|
||||
# Find logs by a list of IDs
|
||||
logs(input: LogQueryInput): [LogHeader!]!
|
||||
# Find logs
|
||||
logs(filter: LogsFilter): [LogHeader!]!
|
||||
|
||||
|
||||
# Find post by ID
|
||||
# Find post by ID.
|
||||
post(id: String!): Post
|
||||
|
||||
# Find posts by IDs. It's meant to allow other parts of the UI to link to a cluster of posts, e.g. for a room description for the
|
||||
# Mapp should it ever become a thing.
|
||||
# Mapp should it ever become a thing. This does not have a filter, since it's meant to be queried in the logs' response's selection set.
|
||||
posts(ids: [String!]!): [Post!]!
|
||||
|
||||
|
||||
# Find file by ID
|
||||
file(id: String!): File
|
||||
|
||||
# Files get all available files. If includePublic is set, it will return public files as well
|
||||
files(includePublic: Boolean, mimeTypes: [String!]): [File!]!
|
||||
# Find files
|
||||
files(filter: FilesFilter): [File!]!
|
||||
|
||||
|
||||
# Find story by ID
|
||||
story(id: String!): Story
|
||||
|
||||
# Find stories
|
||||
stories(filter: StoriesFilter): [Story!]!
|
||||
|
||||
|
||||
# Find story chapter by ID
|
||||
chapter(id: String!): Chapter
|
||||
|
||||
|
||||
# Find all distinct tags used in stories
|
||||
tags: [Tag!]!
|
||||
|
||||
|
||||
# Find current session
|
||||
@@ -95,7 +110,33 @@ type Mutation {
|
||||
editFile(input: EditFileInput!): File!
|
||||
|
||||
# Remove a file
|
||||
removeFile(id: String!): File
|
||||
removeFile(id: String!): File!
|
||||
|
||||
|
||||
# Add a story
|
||||
addStory(input: StoryAddInput!): Story!
|
||||
|
||||
# Add a story tag
|
||||
addStoryTag(input: StoryTagAddInput!): Story!
|
||||
|
||||
# Remove a story tag
|
||||
removeStoryTag(input: StoryTagRemoveInput!): Story!
|
||||
|
||||
# Edit a story
|
||||
editStory(input: StoryEditInput!): Story!
|
||||
|
||||
# Remove a story
|
||||
removeStory(id: String!): Story!
|
||||
|
||||
|
||||
# Add a chapter to a story
|
||||
addChapter(input: ChapterAddInput!): Chapter!
|
||||
|
||||
# Edit a chapter
|
||||
editChapter(input: ChapterEditInput!): Chapter!
|
||||
|
||||
# Remove a chapter
|
||||
removeChapter(id: String!): Chapter!
|
||||
|
||||
|
||||
# Log in
|
||||
@@ -16,6 +16,18 @@ type Channel {
|
||||
locationName: String
|
||||
}
|
||||
|
||||
# Filters for the channels query
|
||||
input ChannelsFilter {
|
||||
# Filter to either logged or unlogged channels
|
||||
logged: Boolean
|
||||
|
||||
# Filter by event name
|
||||
eventName: String
|
||||
|
||||
# Filter by location name
|
||||
locationName: String
|
||||
}
|
||||
|
||||
input ChannelAddInput {
|
||||
# The channel's name
|
||||
name: String!
|
||||
@@ -0,0 +1,59 @@
|
||||
# A Chapter is the main content body of a story.
|
||||
type Chapter {
|
||||
# The chapter's ID
|
||||
id: String!
|
||||
|
||||
# The chapter's title
|
||||
title: String!
|
||||
|
||||
# The chapter's author
|
||||
author: String!
|
||||
|
||||
# The chapter's source
|
||||
source: String!
|
||||
|
||||
# When the chapter was posted initialy
|
||||
createdDate: String!
|
||||
|
||||
# The in-universe date and time of the chapter
|
||||
fictionalDate: String!
|
||||
|
||||
# The date of edit.
|
||||
editedDate: String!
|
||||
}
|
||||
|
||||
# Input for addChapter mutation
|
||||
input ChapterAddInput {
|
||||
# The story to add a chapter to
|
||||
storyId: String!
|
||||
|
||||
# The title to give the chapter. It can not be left empty
|
||||
title: String!
|
||||
|
||||
# The author to set for the chapter. It will use the logged in user ID, and only
|
||||
# users with appropriate permissions can post as another author. Even then, it's only
|
||||
# for importing/moving content. This author name will not be used when logging the
|
||||
# submission
|
||||
author: String
|
||||
|
||||
# The markdown source for the content
|
||||
source: String!
|
||||
|
||||
# Optionally, assign a fictional date for the chapter
|
||||
fictionalDate: String
|
||||
}
|
||||
|
||||
# Input for editChapter mutation
|
||||
input ChapterEditInput {
|
||||
# The chapter to edit
|
||||
id: String!
|
||||
|
||||
# Change the chapter's title
|
||||
title: String
|
||||
|
||||
# Change the source
|
||||
source: String
|
||||
|
||||
# Set the fictional date for a chapter
|
||||
fictionalDate: String
|
||||
}
|
||||
@@ -22,6 +22,24 @@ type Character {
|
||||
description: String!
|
||||
}
|
||||
|
||||
# Filter for characters query
|
||||
input CharactersFilter {
|
||||
# Filter by character IDs
|
||||
ids: [String!]
|
||||
|
||||
# Filter by nicks
|
||||
nicks: [String!]
|
||||
|
||||
# Filter by names
|
||||
names: [String!]
|
||||
|
||||
# Filter by author
|
||||
author: String
|
||||
|
||||
# Filter by text search matching against the character's description
|
||||
search: String
|
||||
}
|
||||
|
||||
# Input for adding characters
|
||||
input CharacterAddInput {
|
||||
# The primary IRC nick name to recognize this character by
|
||||
@@ -29,6 +29,15 @@ type File {
|
||||
url: String
|
||||
}
|
||||
|
||||
# Filter for the files quiery.
|
||||
input FilesFilter {
|
||||
# If set, this will limit the results to either public or private files.
|
||||
public: Boolean
|
||||
|
||||
# Limit the MIME types of the files.
|
||||
mimeTypes: [String!]
|
||||
}
|
||||
|
||||
# Input for editFile mutation
|
||||
input EditFileInput {
|
||||
# The file's unique ID
|
||||
@@ -36,8 +36,8 @@ type Log {
|
||||
posts(kinds:[String!]): [Post!]!
|
||||
}
|
||||
|
||||
# Input for logs query
|
||||
input LogQueryInput {
|
||||
# Filter for logs query
|
||||
input LogsFilter {
|
||||
# Channels to limit results to (inclusive)
|
||||
channels: [String!]
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
# A Story is a piece of content that, unlike wiki articles, are not something that should be ordered by time instead of title. It can
|
||||
# contain multiple chapters by one or more autohrs
|
||||
type Story {
|
||||
# The story's ID
|
||||
id: String!
|
||||
|
||||
# The story's name, which will show up in the lists.
|
||||
name: String!
|
||||
|
||||
# The story's author
|
||||
author: String!
|
||||
|
||||
# Whether other users may add/edit chapters to the story.
|
||||
open: Boolean!
|
||||
|
||||
# Whether users without a direct link can find this story.
|
||||
listed: Boolean!
|
||||
|
||||
# The category of the story
|
||||
category: StoryCategory!
|
||||
|
||||
# The tags for this tory
|
||||
tags: [Tag!]!
|
||||
|
||||
# The chapters of this story
|
||||
chapters: [Chapter!]!
|
||||
|
||||
# The date the story was created (RFC3339 format).
|
||||
createdDate: String!
|
||||
|
||||
# The date the story is set in (RFC3339 format).
|
||||
fictionalDate: String!
|
||||
|
||||
# The date of the last major update to the story (RFC3339 format). This being bumped means a new chapter has been added.
|
||||
updatedDate: String!
|
||||
}
|
||||
|
||||
# Filter for stories query.
|
||||
input StoriesFilter {
|
||||
# What author to query for
|
||||
author: String
|
||||
|
||||
# What tags to query for
|
||||
tags: [TagInput!]
|
||||
|
||||
# The earliest fictionalDate
|
||||
earliestFictionalDate: String
|
||||
|
||||
# The latest fictionalDate
|
||||
latestFictionalDate: String
|
||||
|
||||
# The max amount of stories to get (default: 30)
|
||||
limit: Int
|
||||
}
|
||||
|
||||
# Input for the addStory mutation
|
||||
input StoryAddInput {
|
||||
# Set the name of the story, which will show up as the title, and as a suggestion for
|
||||
# the first chapter being added.
|
||||
name: String!
|
||||
|
||||
# Set the category for the new story.
|
||||
category: StoryCategory!
|
||||
|
||||
# Add the story under another name. This requires the story.add permission, and should not be
|
||||
# abused. The action will be logged with the actual creator's user ID.
|
||||
author: String
|
||||
|
||||
# Allow other users to post chapters to the story.
|
||||
open: Boolean
|
||||
|
||||
# Allow other users to see this page in any lists or search results.
|
||||
listed: Boolean
|
||||
|
||||
# Set which tags the story should have.
|
||||
tags: [TagInput!]
|
||||
|
||||
# Set the fictional date of the story (RFC3339 format).
|
||||
fictionalDate: String
|
||||
}
|
||||
|
||||
# Input for the editStory mutation
|
||||
input StoryEditInput {
|
||||
# What story to edit
|
||||
id: String!
|
||||
|
||||
# Set the name of the story, which will show up as the title, and as a suggestion for
|
||||
# the first chapter being added.
|
||||
name: String
|
||||
|
||||
# Set the category for the new story.
|
||||
category: StoryCategory
|
||||
|
||||
# Add the story under another name. This requires the story.add permission, and should not be
|
||||
# abused. The action will be logged with the actual creator's user ID.
|
||||
author: String
|
||||
|
||||
# Change whether to allow others to add chapters
|
||||
open: Boolean
|
||||
|
||||
# Change whether to show this story in the list and search results
|
||||
listed: Boolean
|
||||
|
||||
# Set the fictional date of the story (RFC3339 format).
|
||||
fictionalDate: String
|
||||
}
|
||||
|
||||
# Input for the addStoryTag mutation
|
||||
input StoryTagAddInput {
|
||||
# What story to add the tag to.
|
||||
id: String!
|
||||
|
||||
# The tag to add.
|
||||
tag: TagInput!
|
||||
}
|
||||
|
||||
# Input for the removeStoryTag mutation
|
||||
input StoryTagRemoveInput {
|
||||
# What story to remove the tag from.
|
||||
id: String!
|
||||
|
||||
# The tag to remove.
|
||||
tag: TagInput!
|
||||
}
|
||||
|
||||
# Possible values for Story.category
|
||||
enum StoryCategory {
|
||||
# Description and content of a document or item
|
||||
Document
|
||||
|
||||
# News stories
|
||||
News
|
||||
|
||||
# Information about something going on in the background that may or may not inform RP
|
||||
Background
|
||||
|
||||
# General information
|
||||
Info
|
||||
|
||||
# A short story
|
||||
Story
|
||||
}
|
||||
@@ -7,6 +7,15 @@ type Tag {
|
||||
name: String!
|
||||
}
|
||||
|
||||
# A Tag is a means of associating stories that have details in common with one another.
|
||||
input TagInput {
|
||||
# The tag's kind
|
||||
kind: TagKind!
|
||||
|
||||
# The tag's name
|
||||
name: String!
|
||||
}
|
||||
|
||||
# Allowed values for Tag.kind
|
||||
enum TagKind {
|
||||
# An organization is a catch all term for in-universe corporations, teams, groups, cults, forces, etc...
|
||||
@@ -2,6 +2,7 @@ package change
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/model/counter"
|
||||
@@ -30,8 +31,13 @@ var PublicModels = []string{
|
||||
"Post",
|
||||
}
|
||||
|
||||
var submitMutex sync.Mutex
|
||||
|
||||
// Submit submits a change to the history.
|
||||
func Submit(model, op, author, objectID string, data interface{}) (Change, error) {
|
||||
submitMutex.Lock()
|
||||
defer submitMutex.Unlock()
|
||||
|
||||
index, err := counter.Next("auto_increment", "Change")
|
||||
if err != nil {
|
||||
return Change{}, err
|
||||
@@ -47,6 +53,8 @@ func Submit(model, op, author, objectID string, data interface{}) (Change, error
|
||||
Data: data,
|
||||
}
|
||||
|
||||
Timeline.push(change)
|
||||
|
||||
err = collection.Insert(&change)
|
||||
if err != nil {
|
||||
return Change{}, err
|
||||
@@ -55,6 +63,28 @@ func Submit(model, op, author, objectID string, data interface{}) (Change, error
|
||||
return change, err
|
||||
}
|
||||
|
||||
// FindID gets a change by ID
|
||||
func FindID(id int) (Change, error) {
|
||||
change := Change{}
|
||||
err := collection.FindId(id).One(&change)
|
||||
if err != nil {
|
||||
return Change{}, err
|
||||
}
|
||||
|
||||
return change, nil
|
||||
}
|
||||
|
||||
// List gets all changes in ascending order
|
||||
func List() ([]Change, error) {
|
||||
changes := make([]Change, 0, 64)
|
||||
err := collection.Find(nil).Sort("_id").All(&changes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
store.HandleInit(func(db *mgo.Database) {
|
||||
collection = db.C("common.history")
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package change
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/globalsign/mgo"
|
||||
)
|
||||
|
||||
// ErrInvalidIndex is returned when a timeline subscriber tries to get a value
|
||||
// with a negative id
|
||||
var ErrInvalidIndex = errors.New("change.timeline: Invalid ID")
|
||||
|
||||
// ErrWaitRequired is returned when a timeline subscriber requests an entry that
|
||||
// has not yet been pushed
|
||||
var ErrWaitRequired = errors.New("change.timeline: Waiting required")
|
||||
|
||||
// ErrExpired is returned when a timeline subscriber requests a change that happened
|
||||
// too long ago for it to be in the database.
|
||||
var ErrExpired = errors.New("change.timeline: Change has expired")
|
||||
|
||||
type timeline struct {
|
||||
mutex sync.Mutex
|
||||
firstIndex int64
|
||||
currentID int64
|
||||
cache []Change
|
||||
notifyCh chan struct{}
|
||||
}
|
||||
|
||||
func (timeline *timeline) Get(ctx context.Context, id int64) (Change, error) {
|
||||
change, err := timeline.get(id, false)
|
||||
if err == ErrWaitRequired {
|
||||
if err := timeline.wait(ctx); err != nil {
|
||||
return Change{}, err
|
||||
}
|
||||
|
||||
return timeline.get(id, true)
|
||||
} else if err == mgo.ErrNotFound {
|
||||
return Change{}, ErrExpired
|
||||
}
|
||||
|
||||
return change, nil
|
||||
}
|
||||
|
||||
func (timeline *timeline) push(change Change) {
|
||||
timeline.mutex.Lock()
|
||||
timeline.cache = append(timeline.cache, change)
|
||||
if len(timeline.cache) > 128 {
|
||||
copy(timeline.cache, timeline.cache[32:])
|
||||
timeline.cache = timeline.cache[:len(timeline.cache)-32]
|
||||
}
|
||||
|
||||
// Only update the currentID if this is more recent than the existing ones
|
||||
if int64(change.ID) > timeline.currentID {
|
||||
timeline.currentID = int64(change.ID)
|
||||
}
|
||||
|
||||
if timeline.notifyCh != nil {
|
||||
close(timeline.notifyCh)
|
||||
timeline.notifyCh = nil
|
||||
}
|
||||
timeline.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (timeline *timeline) get(id int64, hasWaited bool) (Change, error) {
|
||||
if id < 0 {
|
||||
return Change{}, ErrInvalidIndex
|
||||
}
|
||||
|
||||
timeline.mutex.Lock()
|
||||
if !hasWaited && id > timeline.currentID {
|
||||
timeline.mutex.Unlock()
|
||||
return Change{}, ErrWaitRequired
|
||||
}
|
||||
|
||||
for _, change := range timeline.cache {
|
||||
if change.ID == int(id) {
|
||||
timeline.mutex.Unlock()
|
||||
return change, nil
|
||||
}
|
||||
}
|
||||
timeline.mutex.Unlock()
|
||||
|
||||
return FindID(int(id))
|
||||
}
|
||||
|
||||
func (timeline *timeline) wait(ctx context.Context) error {
|
||||
timeline.mutex.Lock()
|
||||
if timeline.notifyCh == nil {
|
||||
timeline.notifyCh = make(chan struct{})
|
||||
}
|
||||
|
||||
ch := timeline.notifyCh
|
||||
timeline.mutex.Unlock()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ch:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Timeline has operations for subscribing to changes in real time
|
||||
var Timeline = &timeline{}
|
||||
|
||||
// InitializeTimeline fills in the timeline with existing entries
|
||||
func InitializeTimeline() {
|
||||
changes, err := List()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, change := range changes {
|
||||
Timeline.push(change)
|
||||
}
|
||||
}
|
||||
@@ -109,13 +109,20 @@ func FindName(name string) (Channel, error) {
|
||||
|
||||
// List finds channels, if logged is true it will be limited to logged
|
||||
// channels
|
||||
func List(logged bool) ([]Channel, error) {
|
||||
func List(logged *bool, eventName *string, locationName *string) ([]Channel, error) {
|
||||
query := bson.M{}
|
||||
if logged {
|
||||
query["logged"] = true
|
||||
|
||||
if logged != nil {
|
||||
query["logged"] = *logged
|
||||
}
|
||||
if eventName != nil {
|
||||
query["eventName"] = *eventName
|
||||
}
|
||||
if locationName != nil {
|
||||
query["locationName"] = *locationName
|
||||
}
|
||||
|
||||
channels := make([]Channel, 0, 32)
|
||||
channels := make([]Channel, 0, 128)
|
||||
err := collection.Find(query).All(&channels)
|
||||
|
||||
return channels, err
|
||||
|
||||
@@ -154,6 +154,29 @@ func ListIDs(ids ...string) ([]Character, error) {
|
||||
return list(bson.M{"_id": bson.M{"$in": ids}})
|
||||
}
|
||||
|
||||
// ListFilter lists all logs matching the filters.
|
||||
func ListFilter(ids []string, nicks []string, names []string, author *string, search *string) ([]Character, error) {
|
||||
query := bson.M{}
|
||||
|
||||
if len(ids) > 0 {
|
||||
query["_id"] = bson.M{"$in": ids}
|
||||
}
|
||||
if len(nicks) > 0 {
|
||||
query["nicks"] = bson.M{"$in": nicks}
|
||||
}
|
||||
if len(names) > 0 {
|
||||
query["name"] = bson.M{"$in": names}
|
||||
}
|
||||
if author != nil {
|
||||
query["author"] = *author
|
||||
}
|
||||
if search != nil {
|
||||
query["$text"] = bson.M{"$search": *search}
|
||||
}
|
||||
|
||||
return list(query)
|
||||
}
|
||||
|
||||
// New creates a Character and pushes it to the database. It does some validation
|
||||
// on nick, name, shortName and author. Leave the shortname blank to have it be the
|
||||
// first name.
|
||||
@@ -227,5 +250,11 @@ func init() {
|
||||
if err != nil {
|
||||
log.Fatalln("init common.characters:", err)
|
||||
}
|
||||
err = collection.EnsureIndex(mgo.Index{
|
||||
Key: []string{"$text:description"},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln("init common.characters:", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+8
-7
@@ -168,21 +168,22 @@ func FindName(kind string, name string, author string) (File, error) {
|
||||
// List lists files according to the standard lookup. By default it's just the author's own files,
|
||||
// but if `public` is true it will alos include files made public by other authors. If `mimeTypes` contains
|
||||
// any, it will limit the results to that. If `author` is empty, it will only list public files
|
||||
func List(author string, public bool, mimeTypes []string) ([]File, error) {
|
||||
func List(author string, public *bool, mimeTypes []string) ([]File, error) {
|
||||
query := bson.M{}
|
||||
|
||||
if author != "" {
|
||||
if public {
|
||||
query["$or"] = []bson.M{
|
||||
bson.M{"author": author},
|
||||
bson.M{"public": true},
|
||||
if public != nil {
|
||||
if *public == false {
|
||||
query["author"] = author
|
||||
}
|
||||
|
||||
query["public"] = *public
|
||||
} else {
|
||||
query["author"] = author
|
||||
}
|
||||
} else {
|
||||
if !public {
|
||||
return nil, errors.New("No author specified, and public is unset")
|
||||
if public != nil && *public == false {
|
||||
return nil, errors.New("You cannot")
|
||||
}
|
||||
|
||||
query["public"] = true
|
||||
|
||||
+18
-10
@@ -86,16 +86,6 @@ func List(limit int) ([]Log, error) {
|
||||
return listLog(bson.M{}, limit)
|
||||
}
|
||||
|
||||
// Remove removes the log post with this ID. Both the long and short ID is accepted
|
||||
func Remove(id string) error {
|
||||
return logsCollection.Remove(bson.M{
|
||||
"$or": []bson.M{
|
||||
bson.M{"_id": id},
|
||||
bson.M{"shortId": id},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ListSearch lists the logs matching the parameters. Empty/zero values means the parameter is ingored when
|
||||
// building the query. This is the old aitelogs2 way, but with the addition of a text search.
|
||||
//
|
||||
@@ -328,6 +318,24 @@ func (log *Log) UpdateCharacters() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes the log and all associated posts from the database
|
||||
func (log *Log) Remove() error {
|
||||
err := logsCollection.Remove(bson.M{"_id": log.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = postCollection.RemoveAll(bson.M{"$or": []bson.M{
|
||||
bson.M{"logId": log.ID},
|
||||
bson.M{"logId": log.ShortID},
|
||||
}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findLog(query interface{}) (Log, error) {
|
||||
log := Log{}
|
||||
err := logsCollection.Find(query).One(&log)
|
||||
|
||||
+20
-1
@@ -41,7 +41,7 @@ func (chapter *Chapter) Edit(title, source *string, fictionalDate *time.Time) er
|
||||
changes["source"] = *source
|
||||
changed.Source = *source
|
||||
}
|
||||
if fictionalDate != nil && *fictionalDate != chapter.FictionalDate {
|
||||
if fictionalDate != nil && !fictionalDate.Equal(chapter.FictionalDate) {
|
||||
changes["fictionalDate"] = *fictionalDate
|
||||
changed.FictionalDate = *fictionalDate
|
||||
}
|
||||
@@ -61,6 +61,25 @@ func (chapter *Chapter) Remove() error {
|
||||
return chapterCollection.RemoveId(chapter.ID)
|
||||
}
|
||||
|
||||
// FindChapterID finds a chapter by its own ID
|
||||
func FindChapterID(id string) (Chapter, error) {
|
||||
chapter := Chapter{}
|
||||
err := chapterCollection.FindId(id).One(&chapter)
|
||||
|
||||
return chapter, err
|
||||
}
|
||||
|
||||
// ListChapterStoryID lists all chapters for the story ID
|
||||
func ListChapterStoryID(storyID string) ([]Chapter, error) {
|
||||
chapters := make([]Chapter, 0, 8)
|
||||
err := chapterCollection.Find(bson.M{"storyId": storyID}).Sort("createdDate").All(&chapters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chapters, nil
|
||||
}
|
||||
|
||||
// makeChapterID makes a random chapter ID that's 24 characters long
|
||||
func makeChapterID() string {
|
||||
result := "SC"
|
||||
|
||||
+11
-6
@@ -84,23 +84,23 @@ func (story *Story) Edit(name, category *string, listed, open *bool, fictionalDa
|
||||
changes := bson.M{}
|
||||
changed := *story
|
||||
|
||||
if name != nil && *name == story.Name {
|
||||
if name != nil && *name != story.Name {
|
||||
changes["name"] = *name
|
||||
changed.Name = *name
|
||||
}
|
||||
if category != nil && *category == story.Category {
|
||||
if category != nil && *category != story.Category {
|
||||
changes["category"] = *category
|
||||
changed.Name = *category
|
||||
}
|
||||
if listed != nil && *listed == story.Listed {
|
||||
if listed != nil && *listed != story.Listed {
|
||||
changes["listed"] = *listed
|
||||
changed.Listed = *listed
|
||||
}
|
||||
if open != nil && *open == story.Open {
|
||||
if open != nil && *open != story.Open {
|
||||
changes["open"] = *open
|
||||
changed.Open = *open
|
||||
}
|
||||
if fictionalDate != nil && *fictionalDate == story.FictionalDate {
|
||||
if fictionalDate != nil && !fictionalDate.Equal(story.FictionalDate) {
|
||||
changes["fictionalDate"] = *fictionalDate
|
||||
changed.FictionalDate = *fictionalDate
|
||||
}
|
||||
@@ -124,6 +124,11 @@ func (story *Story) Remove() error {
|
||||
return storyCollection.RemoveId(story.ID)
|
||||
}
|
||||
|
||||
// Chapters calls ListChapterStoryID with the story's ID:
|
||||
func (story *Story) Chapters() ([]Chapter, error) {
|
||||
return ListChapterStoryID(story.ID)
|
||||
}
|
||||
|
||||
// AddChapter adds a chapter to the story. This does not enforce the `Open` setting, but it will log a warning if it
|
||||
// occurs
|
||||
func (story *Story) AddChapter(title, author, source string, createdDate, finctionalDate time.Time) (Chapter, error) {
|
||||
@@ -215,7 +220,7 @@ func List(author string, tags []Tag, earliest, latest time.Time, limit int) ([]S
|
||||
}
|
||||
|
||||
stories := make([]Story, 0, 128)
|
||||
err := storyCollection.Find(query).Limit(limit).One(&stories)
|
||||
err := storyCollection.Find(query).Limit(limit).All(&stories)
|
||||
|
||||
return stories, err
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package story
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
// A Tag associates a story with other content, like other stories, logs and more.
|
||||
type Tag struct {
|
||||
Kind string `bson:"kind"`
|
||||
@@ -10,3 +17,20 @@ type Tag struct {
|
||||
func (tag *Tag) Equal(other Tag) bool {
|
||||
return tag.Kind == other.Kind && tag.Name == other.Name
|
||||
}
|
||||
|
||||
// ListTags lists all tags
|
||||
func ListTags() ([]Tag, error) {
|
||||
tags := make([]Tag, 0, 64)
|
||||
err := storyCollection.Find(bson.M{"listed": true, "tags": bson.M{"$ne": nil}}).Distinct("tags", &tags)
|
||||
|
||||
sort.Slice(tags, func(i, j int) bool {
|
||||
kindCmp := strings.Compare(tags[i].Kind, tags[j].Kind)
|
||||
if kindCmp != 0 {
|
||||
return kindCmp < 0
|
||||
}
|
||||
|
||||
return strings.Compare(tags[i].Name, tags[j].Name) < 0
|
||||
})
|
||||
|
||||
return tags, err
|
||||
}
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/channel"
|
||||
)
|
||||
|
||||
// ChannelResolver for the Channel graphql type
|
||||
type ChannelResolver struct{ C channel.Channel }
|
||||
|
||||
// ChannelArgs is args for channel query
|
||||
type ChannelArgs struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// Channel implements the channel query
|
||||
func (r *QueryResolver) Channel(ctx context.Context, args *ChannelArgs) (*ChannelResolver, error) {
|
||||
channel, err := channel.FindName(args.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ChannelResolver{C: channel}, nil
|
||||
}
|
||||
|
||||
// ChannelsArgs is args for channel query
|
||||
type ChannelsArgs struct {
|
||||
Logged *bool
|
||||
}
|
||||
|
||||
// Channels implements the channels query
|
||||
func (r *QueryResolver) Channels(ctx context.Context, args *ChannelsArgs) ([]*ChannelResolver, error) {
|
||||
channels, err := channel.List(args.Logged != nil && *args.Logged)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*ChannelResolver, len(channels))
|
||||
for i := range channels {
|
||||
resolvers[i] = &ChannelResolver{C: channels[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
|
||||
// ChannelAddEditInput is input for the addChannel mutation
|
||||
type ChannelAddEditInput struct {
|
||||
Name string
|
||||
Logged *bool
|
||||
Hub *bool
|
||||
EventName *string
|
||||
LocationName *string
|
||||
}
|
||||
|
||||
// AddChannel resolves the addChannel mutation
|
||||
func (r *MutationResolver) AddChannel(ctx context.Context, args struct{ Input *ChannelAddEditInput }) (*ChannelResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("channel.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
logged := args.Input.Logged != nil && *args.Input.Logged
|
||||
hub := args.Input.Hub != nil && *args.Input.Hub
|
||||
eventName := ""
|
||||
if args.Input.EventName != nil {
|
||||
eventName = *args.Input.EventName
|
||||
}
|
||||
locationName := ""
|
||||
if args.Input.LocationName != nil {
|
||||
locationName = *args.Input.LocationName
|
||||
}
|
||||
|
||||
channel, err := channel.New(args.Input.Name, logged, hub, eventName, locationName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ChannelResolver{C: channel}, nil
|
||||
}
|
||||
|
||||
// EditChannel resolves the editChannel mutation
|
||||
func (r *MutationResolver) EditChannel(ctx context.Context, args struct{ Input *ChannelAddEditInput }) (*ChannelResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("channel.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
channel, err := channel.FindName(args.Input.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = channel.Edit(args.Input.Logged, args.Input.Hub, args.Input.EventName, args.Input.LocationName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ChannelResolver{C: channel}, nil
|
||||
}
|
||||
|
||||
// RemoveChannel resolves the editChannel mutation
|
||||
func (r *MutationResolver) RemoveChannel(ctx context.Context, args ChannelArgs) (*ChannelResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("channel.remove") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
channel, err := channel.FindName(args.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = channel.Remove()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ChannelResolver{C: channel}, nil
|
||||
}
|
||||
|
||||
// Name resolves channel.name
|
||||
func (r *ChannelResolver) Name() string {
|
||||
return r.C.Name
|
||||
}
|
||||
|
||||
// Logged resolves channel.logged
|
||||
func (r *ChannelResolver) Logged() bool {
|
||||
return r.C.Logged
|
||||
}
|
||||
|
||||
// Hub resolves channel.hub
|
||||
func (r *ChannelResolver) Hub() bool {
|
||||
return r.C.Hub
|
||||
}
|
||||
|
||||
// EventName resolves channel.eventName
|
||||
func (r *ChannelResolver) EventName() *string {
|
||||
if r.C.Event == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &r.C.Event
|
||||
}
|
||||
|
||||
// LocationName resolves channel.locationName
|
||||
func (r *ChannelResolver) LocationName() *string {
|
||||
if r.C.Location == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &r.C.Location
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/loader"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// CharacterResolver for the Character graphql type
|
||||
type CharacterResolver struct{ C character.Character }
|
||||
|
||||
// CharacterArgs is an arg
|
||||
type CharacterArgs struct {
|
||||
ID *string
|
||||
Nick *string
|
||||
}
|
||||
|
||||
// Character resolver
|
||||
func (r *QueryResolver) Character(ctx context.Context, args *CharacterArgs) (*CharacterResolver, error) {
|
||||
var char character.Character
|
||||
var err error
|
||||
|
||||
loader := loader.FromContext(ctx)
|
||||
if loader == nil {
|
||||
return nil, errors.New("no loader")
|
||||
}
|
||||
|
||||
switch {
|
||||
case args.ID != nil:
|
||||
char, err = character.FindID(*args.ID)
|
||||
case args.Nick != nil:
|
||||
char, err = character.FindNick(*args.Nick)
|
||||
default:
|
||||
err = ErrCannotResolve
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &CharacterResolver{C: char}, nil
|
||||
}
|
||||
|
||||
// CharactersArgs is an arg
|
||||
type CharactersArgs struct {
|
||||
IDs *[]string
|
||||
Nicks *[]string
|
||||
Author *string
|
||||
}
|
||||
|
||||
// Characters resolves
|
||||
func (r *QueryResolver) Characters(ctx context.Context, args *CharactersArgs) ([]*CharacterResolver, error) {
|
||||
var chars []character.Character
|
||||
var err error
|
||||
|
||||
loader := loader.FromContext(ctx)
|
||||
if loader == nil {
|
||||
return nil, errors.New("no loader")
|
||||
}
|
||||
|
||||
switch {
|
||||
case args.IDs != nil:
|
||||
chars, err = character.ListIDs(*args.IDs...)
|
||||
case args.Nicks != nil:
|
||||
chars, err = character.ListNicks(*args.Nicks...)
|
||||
case args.Author != nil:
|
||||
chars, err = character.ListAuthor(*args.Author)
|
||||
default:
|
||||
chars, err = character.List()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*CharacterResolver, 0, len(chars))
|
||||
for i := range chars {
|
||||
if chars[i].ID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
resolvers = append(resolvers, &CharacterResolver{C: chars[i]})
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
|
||||
// AddCharacterInput is args for mutation addCharacter
|
||||
type AddCharacterInput struct {
|
||||
Nick string
|
||||
Name string
|
||||
ShortName *string
|
||||
Author *string
|
||||
Description *string
|
||||
}
|
||||
|
||||
// AddCharacter resolves the addCharacter mutation
|
||||
func (r *MutationResolver) AddCharacter(ctx context.Context, args struct{ Input *AddCharacterInput }) (*CharacterResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member", "character.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
nick := args.Input.Nick
|
||||
name := args.Input.Name
|
||||
|
||||
shortName := ""
|
||||
if args.Input.ShortName != nil {
|
||||
shortName = *args.Input.ShortName
|
||||
} else {
|
||||
shortName = strings.SplitN(args.Input.Name, " ", 2)[0]
|
||||
}
|
||||
|
||||
author := user.ID
|
||||
if args.Input.Author != nil {
|
||||
author = *args.Input.Author
|
||||
|
||||
if author != user.ID && !user.Permitted("character.add") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
|
||||
description := ""
|
||||
if args.Input.Description != nil {
|
||||
description = *args.Input.Description
|
||||
}
|
||||
|
||||
character, err := character.New(nick, name, shortName, author, description)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "add", user.ID, character.ID, map[string]interface{}{
|
||||
"name": character.Name,
|
||||
"nick": character.Nicks[0],
|
||||
"author": character.Author,
|
||||
})
|
||||
|
||||
log.ScheduleCharacterUpdate()
|
||||
|
||||
return &CharacterResolver{C: character}, nil
|
||||
}
|
||||
|
||||
// CharacterNickInput is args for mutation addCharacterNick/removeCharacterNick
|
||||
type CharacterNickInput struct {
|
||||
ID string
|
||||
Nick string
|
||||
}
|
||||
|
||||
// AddCharacterNick resolves the addCharacterNick mutation
|
||||
func (r *MutationResolver) AddCharacterNick(ctx context.Context, args struct{ Input *CharacterNickInput }) (*CharacterResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
character, err := character.FindID(args.Input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if character.Author != user.ID && !user.Permitted("character.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = character.AddNick(args.Input.Nick)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "add.nick", user.ID, character.ID, map[string]interface{}{
|
||||
"nick": args.Input.Nick,
|
||||
})
|
||||
|
||||
log.ScheduleCharacterUpdate()
|
||||
|
||||
return &CharacterResolver{C: character}, nil
|
||||
}
|
||||
|
||||
// RemoveCharacterNick resolves the removeCharacterNick mutation
|
||||
func (r *MutationResolver) RemoveCharacterNick(ctx context.Context, args struct{ Input *CharacterNickInput }) (*CharacterResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
character, err := character.FindID(args.Input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if character.Author != user.ID && !user.Permitted("character.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = character.RemoveNick(args.Input.Nick)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "remove.nick", user.ID, character.ID, map[string]interface{}{
|
||||
"nick": args.Input.Nick,
|
||||
})
|
||||
|
||||
log.ScheduleCharacterUpdate()
|
||||
|
||||
return &CharacterResolver{C: character}, nil
|
||||
}
|
||||
|
||||
// CharacterEditInput is args for mutation addCharacterNick/removeCharacterNick
|
||||
type CharacterEditInput struct {
|
||||
ID string
|
||||
Name *string
|
||||
ShortName *string
|
||||
Description *string
|
||||
}
|
||||
|
||||
// EditCharacter resolves the editCharacter mutation
|
||||
func (r *MutationResolver) EditCharacter(ctx context.Context, args struct{ Input *CharacterEditInput }) (*CharacterResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
character, err := character.FindID(args.Input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if character.Author != user.ID && !user.Permitted("character.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
name := ""
|
||||
if args.Input.Name != nil {
|
||||
name = *args.Input.Name
|
||||
}
|
||||
shortName := ""
|
||||
if args.Input.ShortName != nil {
|
||||
shortName = *args.Input.ShortName
|
||||
}
|
||||
description := ""
|
||||
if args.Input.Description != nil {
|
||||
description = *args.Input.Description
|
||||
}
|
||||
|
||||
err = character.Edit(name, shortName, description)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "edit", user.ID, character.ID, map[string]interface{}{
|
||||
"name": character.Name,
|
||||
"shortName": character.ShortName,
|
||||
"description": character.Description,
|
||||
})
|
||||
|
||||
return &CharacterResolver{C: character}, nil
|
||||
}
|
||||
|
||||
// RemoveCharacter resolves the removeCharacter mutation
|
||||
func (r *MutationResolver) RemoveCharacter(ctx context.Context, args struct{ ID string }) (*CharacterResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
character, err := character.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if character.Author != user.ID && !user.Permitted("character.remove") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = character.Remove()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "remove", user.ID, character.ID, map[string]interface{}{
|
||||
"name": character.Name,
|
||||
"author": character.Author,
|
||||
"nicks": character.Nicks,
|
||||
})
|
||||
|
||||
return &CharacterResolver{C: character}, nil
|
||||
}
|
||||
|
||||
// ID is a property resolver
|
||||
func (r *CharacterResolver) ID() string {
|
||||
return r.C.ID
|
||||
}
|
||||
|
||||
// Nick is a property resolver
|
||||
func (r *CharacterResolver) Nick() *string {
|
||||
if len(r.C.Nicks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &r.C.Nicks[0]
|
||||
}
|
||||
|
||||
// Nicks is a property resolver
|
||||
func (r *CharacterResolver) Nicks() []string {
|
||||
return r.C.Nicks
|
||||
}
|
||||
|
||||
// Name is a property resolver
|
||||
func (r *CharacterResolver) Name() string {
|
||||
return r.C.Name
|
||||
}
|
||||
|
||||
// ShortName is a property resolver
|
||||
func (r *CharacterResolver) ShortName() string {
|
||||
return r.C.ShortName
|
||||
}
|
||||
|
||||
// Author is a property resolver
|
||||
func (r *CharacterResolver) Author() string {
|
||||
return r.C.Author
|
||||
}
|
||||
|
||||
// Description is a property resolver
|
||||
func (r *CharacterResolver) Description() string {
|
||||
return r.C.Description
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package resolver
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrCannotResolve is returned when a resolver constructor is at its wit's end
|
||||
var ErrCannotResolve = errors.New("Cannot resolve due to invalid arguments")
|
||||
|
||||
// ErrNotImplemented is for TODOs
|
||||
var ErrNotImplemented = errors.New("Resolver not implemented")
|
||||
|
||||
// ErrUnauthorized is when a guest acts like they own the place
|
||||
var ErrUnauthorized = errors.New("Unauthorized")
|
||||
|
||||
// ErrPermissionDenied is returned when users act above their station
|
||||
var ErrPermissionDenied = errors.New("Permission denied")
|
||||
@@ -1,163 +0,0 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
)
|
||||
|
||||
// FileResolver for the File graphql type
|
||||
type FileResolver struct{ F file.File }
|
||||
|
||||
// FileArgs is an arg
|
||||
type FileArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// File implements the file query
|
||||
func (r *QueryResolver) File(ctx context.Context, args *FileArgs) (*FileResolver, error) {
|
||||
file, err := file.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FileResolver{F: file}, nil
|
||||
}
|
||||
|
||||
// FilesArgs is an arg
|
||||
type FilesArgs struct {
|
||||
IncludePublic *bool
|
||||
MimeTypes *[]string
|
||||
}
|
||||
|
||||
// Files implements the file query
|
||||
func (r *QueryResolver) Files(ctx context.Context, args *FilesArgs) ([]*FileResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
author := ""
|
||||
if user != nil {
|
||||
author = user.ID
|
||||
}
|
||||
|
||||
public := args.IncludePublic != nil && *args.IncludePublic == true
|
||||
|
||||
mimeTypes := []string(nil)
|
||||
if args.MimeTypes != nil {
|
||||
mimeTypes = *args.MimeTypes
|
||||
}
|
||||
|
||||
files, err := file.List(author, public, mimeTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*FileResolver, len(files))
|
||||
for i := range files {
|
||||
resolvers[i] = &FileResolver{F: files[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
|
||||
// FileEditInput is an input for the editFile mutation
|
||||
type FileEditInput struct {
|
||||
ID string
|
||||
Name *string
|
||||
Public *bool
|
||||
}
|
||||
|
||||
// EditFile resolves the editFile mutation
|
||||
func (r *MutationResolver) EditFile(ctx context.Context, args *struct{ Input FileEditInput }) (*FileResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
file, err := file.FindID(args.Input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file.Author != user.ID && !user.Permitted("file.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
err = file.Edit(args.Input.Name, args.Input.Public)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FileResolver{F: file}, nil
|
||||
}
|
||||
|
||||
// RemoveFile resolves the removeFIle mutation
|
||||
func (r *MutationResolver) RemoveFile(ctx context.Context, args *FileArgs) (*FileResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
file, err := file.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file.Author != user.ID && !user.Permitted("file.remove") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
err = file.Delete()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FileResolver{F: file}, nil
|
||||
}
|
||||
|
||||
// ID resolves File.id
|
||||
func (f *FileResolver) ID() string {
|
||||
return f.F.ID
|
||||
}
|
||||
|
||||
// Author resolves File.author
|
||||
func (f *FileResolver) Author() string {
|
||||
return f.F.Author
|
||||
}
|
||||
|
||||
// Kind resolves File.kind
|
||||
func (f *FileResolver) Kind() string {
|
||||
return f.F.Kind
|
||||
}
|
||||
|
||||
// Time resolves File.time
|
||||
func (f *FileResolver) Time() string {
|
||||
return f.F.Time.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// Public resolves File.public
|
||||
func (f *FileResolver) Public() bool {
|
||||
return f.F.Public
|
||||
}
|
||||
|
||||
// Name resolves File.name
|
||||
func (f *FileResolver) Name() string {
|
||||
return f.F.Name
|
||||
}
|
||||
|
||||
// MimeType resolves File.mimeType
|
||||
func (f *FileResolver) MimeType() string {
|
||||
return f.F.MimeType
|
||||
}
|
||||
|
||||
// Size resolves File.size
|
||||
func (f *FileResolver) Size() int32 {
|
||||
return int32(f.F.Size)
|
||||
}
|
||||
|
||||
// URL resolves File.url
|
||||
func (f *FileResolver) URL() *string {
|
||||
if f.F.URL == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &f.F.URL
|
||||
}
|
||||
-325
@@ -1,325 +0,0 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/loader"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// LogResolver for the Log graphql type
|
||||
type LogResolver struct{ L log.Log }
|
||||
|
||||
// LogArgs is an arg
|
||||
type LogArgs struct {
|
||||
ID *string
|
||||
}
|
||||
|
||||
// LogPostArgs is an arg
|
||||
type LogPostArgs struct {
|
||||
Kinds *[]string
|
||||
}
|
||||
|
||||
// Log finds log
|
||||
func (r *QueryResolver) Log(ctx context.Context, args *LogArgs) (*LogResolver, error) {
|
||||
var l log.Log
|
||||
var err error
|
||||
|
||||
switch {
|
||||
case args.ID != nil:
|
||||
l, err = log.FindID(*args.ID)
|
||||
default:
|
||||
err = ErrCannotResolve
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &LogResolver{L: l}, nil
|
||||
}
|
||||
|
||||
// LogQueryInput is an input
|
||||
type LogQueryInput struct {
|
||||
Search *string
|
||||
Characters *[]string
|
||||
Channels *[]string
|
||||
Events *[]string
|
||||
Open *bool
|
||||
Limit *int32
|
||||
}
|
||||
|
||||
// Logs lists logs
|
||||
func (r *QueryResolver) Logs(ctx context.Context, args *struct{ Input *LogQueryInput }) ([]*LogResolver, error) {
|
||||
var logs []log.Log
|
||||
var err error
|
||||
|
||||
input := args.Input
|
||||
|
||||
if input != nil {
|
||||
// Parse input
|
||||
limit := 100
|
||||
search := ""
|
||||
if input.Search != nil {
|
||||
search = *input.Search
|
||||
limit = 0
|
||||
}
|
||||
channels := []string(nil)
|
||||
if input.Channels != nil {
|
||||
channels = *input.Channels
|
||||
limit = 0
|
||||
}
|
||||
characters := []string(nil)
|
||||
if input.Characters != nil {
|
||||
characters = *input.Characters
|
||||
limit = 0
|
||||
}
|
||||
events := []string(nil)
|
||||
if input.Events != nil {
|
||||
events = *input.Events
|
||||
limit = 0
|
||||
}
|
||||
if input.Limit != nil {
|
||||
limit = int(*input.Limit)
|
||||
}
|
||||
open := input.Open != nil && *input.Open == true
|
||||
|
||||
logs, err = log.ListSearch(search, channels, characters, events, open, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
logs, err = log.List(100)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resolvers := make([]*LogResolver, len(logs))
|
||||
for i := range logs {
|
||||
resolvers[i] = &LogResolver{L: logs[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
|
||||
// LogAddInput is an input
|
||||
type LogAddInput struct {
|
||||
Date string
|
||||
Channel string
|
||||
Title *string
|
||||
Open *bool
|
||||
Event *string
|
||||
Description *string
|
||||
}
|
||||
|
||||
// AddLog resolves the addLog mutation
|
||||
func (r *MutationResolver) AddLog(ctx context.Context, args *struct{ Input LogAddInput }) (*LogResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("log.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
date, err := time.Parse(time.RFC3339Nano, args.Input.Date)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
title := ""
|
||||
if args.Input.Title != nil {
|
||||
title = *args.Input.Title
|
||||
}
|
||||
event := ""
|
||||
if args.Input.Event != nil {
|
||||
event = *args.Input.Event
|
||||
}
|
||||
description := ""
|
||||
if args.Input.Description != nil {
|
||||
description = *args.Input.Description
|
||||
}
|
||||
open := args.Input.Open != nil && *args.Input.Open == true
|
||||
|
||||
log, err := log.New(date, args.Input.Channel, title, event, description, open)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
change.Submit("Log", "add", user.ID, log.ID, map[string]interface{}{
|
||||
"channel": log.Channel,
|
||||
"title": log.Title,
|
||||
"event": log.Event,
|
||||
"description": log.Description,
|
||||
"open": log.Open,
|
||||
})
|
||||
|
||||
return &LogResolver{L: log}, nil
|
||||
}
|
||||
|
||||
// LogEditInput is an input
|
||||
type LogEditInput struct {
|
||||
ID string
|
||||
Title *string
|
||||
Event *string
|
||||
Description *string
|
||||
Open *bool
|
||||
}
|
||||
|
||||
// EditLog resolves the addLog mutation
|
||||
func (r *MutationResolver) EditLog(ctx context.Context, args *struct{ Input LogEditInput }) (*LogResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("log.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
l, err := log.FindID(args.Input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = l.Edit(args.Input.Title, args.Input.Event, args.Input.Description, args.Input.Open)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
change.Submit("Log", "edit", user.ID, l.ID, map[string]interface{}{
|
||||
"channel": l.Channel,
|
||||
"title": args.Input.Title,
|
||||
"event": args.Input.Event,
|
||||
"description": args.Input.Description,
|
||||
"open": args.Input.Open,
|
||||
})
|
||||
|
||||
return &LogResolver{L: l}, nil
|
||||
}
|
||||
|
||||
// RemoveLog resolves the removeLog mutation
|
||||
func (r *MutationResolver) RemoveLog(ctx context.Context, args *struct{ ID string }) (*LogResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("log.remove") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
l, err := log.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = log.Remove(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
change.Submit("Log", "add", user.ID, l.ID, map[string]interface{}{
|
||||
"channel": l.Channel,
|
||||
"title": l.Title,
|
||||
"event": l.Event,
|
||||
"description": l.Description,
|
||||
"open": l.Open,
|
||||
})
|
||||
|
||||
return &LogResolver{L: l}, nil
|
||||
}
|
||||
|
||||
// ID resolves Log.id
|
||||
func (r *LogResolver) ID() string {
|
||||
return r.L.ID
|
||||
}
|
||||
|
||||
// ShortID resolves Log.shortId
|
||||
func (r *LogResolver) ShortID() string {
|
||||
return r.L.ShortID
|
||||
}
|
||||
|
||||
// Date resolves Log.date
|
||||
func (r *LogResolver) Date() string {
|
||||
return r.L.Date.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// ChannelName resolves Log.channelName
|
||||
func (r *LogResolver) ChannelName() string {
|
||||
return r.L.Channel
|
||||
}
|
||||
|
||||
// Channel resolves Log.channe
|
||||
func (r *LogResolver) Channel(ctx context.Context) (*ChannelResolver, error) {
|
||||
loader := loader.FromContext(ctx)
|
||||
if loader == nil {
|
||||
return nil, errors.New("no loader")
|
||||
}
|
||||
|
||||
channel, err := loader.Channel("name", r.L.Channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ChannelResolver{C: channel}, nil
|
||||
}
|
||||
|
||||
// Title resolves Log.title
|
||||
func (r *LogResolver) Title() string {
|
||||
return r.L.Title
|
||||
}
|
||||
|
||||
// Event resolves Log.event
|
||||
func (r *LogResolver) Event() string {
|
||||
return r.L.Event
|
||||
}
|
||||
|
||||
// Description resolves Log.description
|
||||
func (r *LogResolver) Description() string {
|
||||
return r.L.Description
|
||||
}
|
||||
|
||||
// Open resolves Log.open
|
||||
func (r *LogResolver) Open() bool {
|
||||
return r.L.Open
|
||||
}
|
||||
|
||||
// Characters resolves Log.characters
|
||||
func (r *LogResolver) Characters(ctx context.Context) ([]*CharacterResolver, error) {
|
||||
loader := loader.FromContext(ctx)
|
||||
if loader == nil {
|
||||
return nil, errors.New("no loader")
|
||||
}
|
||||
|
||||
chars, err := loader.Characters("id", r.L.CharacterIDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*CharacterResolver, 0, len(chars))
|
||||
for i := range chars {
|
||||
if chars[i].ID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
resolvers = append(resolvers, &CharacterResolver{C: chars[i]})
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
|
||||
// Posts resolves Log.posts
|
||||
func (r *LogResolver) Posts(ctx context.Context, args *LogPostArgs) ([]*PostResolver, error) {
|
||||
var kinds []string
|
||||
if args.Kinds != nil {
|
||||
kinds = *args.Kinds
|
||||
}
|
||||
|
||||
posts, err := r.L.Posts(kinds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*PostResolver, len(posts))
|
||||
for i := range posts {
|
||||
resolvers[i] = &PostResolver{posts[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostResolver for the Post graphql type
|
||||
type PostResolver struct{ P log.Post }
|
||||
|
||||
// PostArgs is an arg
|
||||
type PostArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// Post implements the post query
|
||||
func (r *QueryResolver) Post(ctx context.Context, args *PostArgs) (*PostResolver, error) {
|
||||
post, err := log.FindPostID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PostResolver{P: post}, nil
|
||||
}
|
||||
|
||||
// PostsArgs is an arg
|
||||
type PostsArgs struct {
|
||||
IDs []string
|
||||
}
|
||||
|
||||
// Posts implements the posts query
|
||||
func (r *QueryResolver) Posts(ctx context.Context, args *PostsArgs) ([]*PostResolver, error) {
|
||||
posts, err := log.ListPostIDs(args.IDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*PostResolver, len(posts))
|
||||
for i := range resolvers {
|
||||
resolvers[i] = &PostResolver{P: posts[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
|
||||
// PostAddInput is an input
|
||||
type PostAddInput struct {
|
||||
LogID string
|
||||
Time string
|
||||
Kind string
|
||||
Nick string
|
||||
Text string
|
||||
}
|
||||
|
||||
// AddPost resolves the addPost mutation
|
||||
func (r *MutationResolver) AddPost(ctx context.Context, args struct{ Input *PostAddInput }) (*PostResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("post.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
postTime, err := time.Parse(time.RFC3339Nano, args.Input.Time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log, err := log.FindID(args.Input.LogID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
post, err := log.NewPost(postTime, args.Input.Kind, args.Input.Nick, args.Input.Text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
change.Submit("Post", "add", user.ID, post.ID, map[string]interface{}{
|
||||
"logId": post.LogID,
|
||||
"time": post.Time,
|
||||
"kind": post.Kind,
|
||||
"nick": post.Nick,
|
||||
"text": post.Text,
|
||||
"position": post.Position,
|
||||
})
|
||||
|
||||
go log.UpdateCharacters()
|
||||
|
||||
return &PostResolver{P: post}, nil
|
||||
}
|
||||
|
||||
// PostEditInput is an input
|
||||
type PostEditInput struct {
|
||||
ID string
|
||||
Time *string
|
||||
Kind *string
|
||||
Nick *string
|
||||
Text *string
|
||||
}
|
||||
|
||||
// EditPost resolves the editPost mutation
|
||||
func (r *MutationResolver) EditPost(ctx context.Context, args struct{ Input *PostEditInput }) (*PostResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("post.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
postTime := (*time.Time)(nil)
|
||||
if args.Input.Time != nil {
|
||||
t, err := time.Parse(time.RFC3339Nano, *args.Input.Time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
postTime = &t
|
||||
}
|
||||
|
||||
post, err := log.FindPostID(args.Input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = post.Edit(postTime, args.Input.Kind, args.Input.Nick, args.Input.Text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
change.Submit("Post", "edit", user.ID, post.ID, map[string]interface{}{
|
||||
"time": postTime,
|
||||
"kind": args.Input.Kind,
|
||||
"nick": args.Input.Nick,
|
||||
"text": args.Input.Text,
|
||||
})
|
||||
|
||||
go log.UpdateCharacters(post.LogID)
|
||||
|
||||
return &PostResolver{P: post}, nil
|
||||
}
|
||||
|
||||
// PostMoveInput is an input
|
||||
type PostMoveInput struct {
|
||||
ID string
|
||||
ToPosition int32
|
||||
}
|
||||
|
||||
// MovePost resolves the movePost mutation
|
||||
func (r *MutationResolver) MovePost(ctx context.Context, args struct{ Input *PostMoveInput }) (*PostResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("post.move") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
post, err := log.FindPostID(args.Input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = post.Move(int(args.Input.ToPosition))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
change.Submit("Post", "move", user.ID, post.ID, map[string]interface{}{
|
||||
"logId": post.LogID,
|
||||
"targetIndex": args.Input.ToPosition,
|
||||
})
|
||||
|
||||
return &PostResolver{P: post}, nil
|
||||
}
|
||||
|
||||
// PostRemoveArgs is an arg
|
||||
type PostRemoveArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// RemovePost resolves the removePost mutation
|
||||
func (r *MutationResolver) RemovePost(ctx context.Context, args PostRemoveArgs) (*PostResolver, error) {
|
||||
user := session.FromContext(ctx).User()
|
||||
if user == nil || !user.Permitted("post.remove") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
post, err := log.RemovePost(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
change.Submit("Post", "remove", user.ID, post.ID, map[string]interface{}{
|
||||
"logId": post.LogID,
|
||||
})
|
||||
|
||||
go log.UpdateCharacters(post.LogID)
|
||||
|
||||
return &PostResolver{P: post}, nil
|
||||
}
|
||||
|
||||
// ID resolves Post.id
|
||||
func (r *PostResolver) ID() string {
|
||||
return r.P.ID
|
||||
}
|
||||
|
||||
// LogID resolves Post.logId
|
||||
func (r *PostResolver) LogID() string {
|
||||
return r.P.LogID
|
||||
}
|
||||
|
||||
// Time resolves Post.time
|
||||
func (r *PostResolver) Time() string {
|
||||
return r.P.Time.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// Kind resolves Post.logId
|
||||
func (r *PostResolver) Kind() string {
|
||||
return r.P.Kind
|
||||
}
|
||||
|
||||
// Nick resolves Post.nick
|
||||
func (r *PostResolver) Nick() string {
|
||||
return r.P.Nick
|
||||
}
|
||||
|
||||
// Text resolves Post.text
|
||||
func (r *PostResolver) Text() string {
|
||||
return r.P.Text
|
||||
}
|
||||
|
||||
// Position resolves Post.text
|
||||
func (r *PostResolver) Position() int32 {
|
||||
return int32(r.P.Position)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package resolver
|
||||
|
||||
// The RootResolver brings queries and mutations together. The rest is just for readability.
|
||||
type RootResolver struct {
|
||||
MutationResolver
|
||||
QueryResolver
|
||||
}
|
||||
|
||||
// The QueryResolver is the entry point for all top-level read operations.
|
||||
type QueryResolver struct{}
|
||||
|
||||
// The MutationResolver is the entry point for all top-level mutation operations.
|
||||
type MutationResolver struct{}
|
||||
@@ -1,51 +0,0 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
)
|
||||
|
||||
// LoginArgs is args
|
||||
type LoginArgs struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// Session resolves query.session
|
||||
func (r *QueryResolver) Session(ctx context.Context) (*SessionResolver, error) {
|
||||
return &SessionResolver{S: session.FromContext(ctx)}, nil
|
||||
}
|
||||
|
||||
// Login resolves mutation.login
|
||||
func (r *MutationResolver) Login(ctx context.Context, args *LoginArgs) (*SessionResolver, error) {
|
||||
session := session.FromContext(ctx)
|
||||
|
||||
err := session.Login(args.Username, args.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SessionResolver{S: session}, nil
|
||||
}
|
||||
|
||||
// Logout resolves mutation.logout
|
||||
func (r *MutationResolver) Logout(ctx context.Context) (*SessionResolver, error) {
|
||||
session := session.FromContext(ctx)
|
||||
session.Logout()
|
||||
|
||||
return &SessionResolver{S: session}, nil
|
||||
}
|
||||
|
||||
// SessionResolver resolves Session
|
||||
type SessionResolver struct{ S *session.Session }
|
||||
|
||||
// User resolves Session.user
|
||||
func (r *SessionResolver) User() *UserResolver {
|
||||
user := r.S.User()
|
||||
if user == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &UserResolver{U: user}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package resolver
|
||||
@@ -0,0 +1,21 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,14 @@
|
||||
# User Interface
|
||||
|
||||
## Setup
|
||||
|
||||
This is a UI created with `create-react-app`, so the process of getting started on a new host is navigating to the directory and running these commands.
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
## Release
|
||||
|
||||
This is built and packaged with the parent project.
|
||||
Generated
+12036
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "rpdata-ui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"ajv": "^6.5.2",
|
||||
"apollo-boost": "^0.1.10",
|
||||
"graphql": "^0.13.2",
|
||||
"graphql-tag": "^2.9.2",
|
||||
"pluralize": "^7.0.0",
|
||||
"react": "^16.4.1",
|
||||
"react-apollo": "^2.1.9",
|
||||
"react-dom": "^16.4.1",
|
||||
"react-router": "^4.3.1",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-scripts": "1.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playlyfe/gql": "^2.6.0"
|
||||
},
|
||||
"proxy": {
|
||||
"/graphql": {
|
||||
"target": "http://127.0.0.1:17000"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Aite RP</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user