Compare commits

...
137 Commits
Author SHA1 Message Date
gisle 162ccf6bfb The great module madness continus, now with pointers.
the build was successful
2019-05-17 11:51:23 +02:00
gisle 7e3dc49635 Basic stuff, doesn't compile 2019-05-16 15:57:08 +02:00
gisle aff0d99842 graph2: Added exception for open logs query in complexity.
the build was successful
2019-04-27 11:37:01 +02:00
gisle 00150118d2 graph2: Added complexity to prevent queries that can take down the server.
the build was successful
2019-04-25 20:47:20 +02:00
gisle 332a1eb32b Fixed rpdata-as2storyimport not compiling. 2019-03-17 21:32:21 +01:00
gisle db4d46c368 Added story to comment changes.
the build was successful
2019-03-17 20:44:30 +01:00
gisle a8c8610ca2 models: Fixed wrong database mapping for comment source.
the build was successful
2019-01-17 21:19:13 +01:00
gisle d09c17d30d graph2: Changed addComment error message on can-comment check to be consistent with others.
the build was successful
2019-01-16 22:07:00 +01:00
gisle e3e3ea07db graph2: Changed Chapter to not return any comments if mode is disabled, even if there are comments.
the build was successful
2019-01-16 22:05:40 +01:00
gisle 22592439da graph2, models: Added removeComment mutation.
the build was successful
2019-01-16 22:03:28 +01:00
gisle dfa9fb4025 graph2: Changed Comment to return null instead of a zero-date if it's cleared.
the build was successful
2019-01-16 21:50:53 +01:00
gisle 0e2a68a2d4 graph2, models: Added editComment mutation. 2019-01-16 21:45:00 +01:00
gisle 1eefa65527 models: Fixed wrong database mapping for comment id. 2019-01-16 21:41:36 +01:00
gisle f9b6af58c9 graph2: Changed Comment character resolver to use loader.
the build was successful
2019-01-15 21:42:44 +01:00
gisle 56ecf48b57 graph2, models: Added addComment mutation.
the build was successful
2019-01-15 21:34:57 +01:00
gisle 06fbfc90c5 graph2, model: Added Comment type to Change. 2019-01-14 19:58:05 +01:00
gisle 39a4fef506 models, graph2: Added comment removal on chapter removal.
the build was successful
2019-01-14 18:01:43 +01:00
gisle f9fe0bb5f8 models, graph2: Added comment options to chapter mutations.
the build was successful
2019-01-14 17:55:38 +01:00
gisle d22ab387dd models, graph2: Added Comment type and queries, and hooked it up to chapter.
the build was successful
2019-01-14 17:03:16 +01:00
gisle 53a86c0b9c store, config: Added option to disable space.
the build was successful
2019-01-12 12:13:24 +01:00
gisle e0b93c6b4b graph2: Changed story tagging permissions to allow anyone to tag open stories.
the build was successful
2019-01-12 11:38:19 +01:00
gisle 1ea04f1577 importers: Fixed panic in mirclike.ParsePost if the post contains no text.
the build was successful
2019-01-05 10:33:50 +01:00
gisle 17fd3c60f8 rpdata-server: Fixed unhelpful error message being returned on panic.
the build was successful
2019-01-05 10:09:31 +01:00
gisle c5b62a42db graph2: Fixed data race in change logging for importLog.
the build was successful
2018-12-11 20:05:03 +01:00
gisle fa0926c311 rpdata-lb2logimport: Updated it to fix build errors related to change in logs.UpdateChracters.
the build was successful
2018-12-08 16:08:28 +01:00
gisle d21efc1c7f rpdata-logpaste: Removed the command since it's replaced by GraphQL API endpoint. 2018-12-08 16:07:44 +01:00
gisle 852030b141 docker: Trimmed down DockerFile. 2018-12-08 16:07:11 +01:00
gisle f7f0b76379 changes: Reversed ordering in result.
the build was successful
2018-12-05 21:00:52 +01:00
gisle 2711654816 posts: Fixed AddMany not returning the IDs in the result.
the build was successful
2018-12-05 20:58:37 +01:00
gisle b60897a597 unknownnicks, logs: Changed unknownnicks counter to use posts instead of logs as a metric to make characters stick out more.
the build was successful
2018-12-04 20:49:31 +01:00
gisle 80e6c048c9 graph2: Added unknownNick query
the build was successful
2018-12-04 20:33:21 +01:00
gisle 6310cbbc1e unknownnicks: Added unknown nicks model and hooked it up. 2018-12-04 20:17:48 +01:00
gisle 89a1c2366c changes: Fixed crash on disconnect when there are more than one client connected. 2018-12-03 20:11:15 +01:00
gisle ac2b79e88a posts, counter: Fixed post positions issues when a post is deleted.
the build was successful
2018-12-02 21:27:50 +01:00
gisle 31239330df graph2, changes, rpdata-server: Added changes subscription. 2018-12-02 21:17:49 +01:00
gisle a58535fcb9 graph2: Added importLog query and associated models and repository functions.
the build was successful
2018-12-01 16:52:06 +01:00
gisle 2a91b6f9bc logs: Added forumlog parsers.
the build was successful
2018-12-01 12:48:05 +01:00
gisle caa7727c02 logs: Added mirclike parsers.
the build was successful
2018-11-28 19:21:53 +01:00
gisle 047c9784ae rpdata-logpaste: Fixed characters not being updated.
the build was successful
2018-11-07 19:56:56 +01:00
gisle d038c1cfea Dockerfile: Added tzdata package to final container.
the build was successful
2018-11-07 19:48:03 +01:00
gisle 7e812bd615 rpdata-logpaste: Added new commandline tool.
the build was successful
2018-11-07 19:31:51 +01:00
gisle 947765884e models: Fixed earliestDate being exclusive of the earliest date in chnages.List.
the build was successful
2018-11-01 19:38:01 +01:00
gisle ffec008820 graph2: Added change type and query.
the build was successful
2018-10-31 21:38:45 +01:00
gisle 138b3e3eab graph2: Fixed post change on move not including all posts as objects. 2018-10-31 21:38:32 +01:00
gisle 929abfdbe4 package: Removed rpdata-ui. 2018-10-31 20:28:09 +01:00
gisle a8cd595876 graph2: Added change logging for story and chapter, fixed channel ops not being logged.
the build was successful
2018-10-29 19:12:23 +01:00
gisle 9ed3b32b42 models: Added support for multiple on changekeys.All, though only first is used for * key. 2018-10-29 17:51:39 +01:00
gisle 6bae52d652 models: Added benchmarks to changekeys, added shortcuts for common types there could be many of. 2018-10-29 17:44:48 +01:00
gisle cee370c2ac models: Changed change logging system to be allow for better querying in the future.
the build was successful
2018-10-28 12:18:17 +01:00
gisle 9affdbd20e graph2: Added move mutation to chapter.
the build was successful
2018-10-27 14:59:47 +02:00
gisle cf545cde3b graphql: Removed old graphql setup.
the build was successful
2018-10-27 10:16:25 +02:00
gisle b414cf118f rpdata-wikifileimport: Fixed it to use new models module.
the build was successful
2018-10-27 10:15:23 +02:00
gisle 4979b02104 models: Migrated last from model 2018-10-27 10:14:58 +02:00
gisle b7d5059d60 graph2: Fixed chapter using story permissions.
the build was successful
2018-10-16 19:01:11 +02:00
gisle 9fbdc767ad models2: Added 'post.remove' to default user permissionss.
the build was successful
2018-10-16 18:49:14 +02:00
gisle daeaaf5fa9 rpdata-as2storyimport: Fixed category vailidty check failing when it is valid, not when it isn't.
the build was successful
2018-10-16 18:30:24 +02:00
gisle d3277c7476 models2: Fixed text search not working.
the build was successful
2018-10-13 14:04:02 +02:00
gisle 90681c67b0 models2: Added post removal on log removal. 2018-10-13 11:43:53 +02:00
gisle bebd99833b models2: Fixed incorrect db mappings, fixed Edit not returning mutated model. 2018-10-11 22:26:08 +02:00
gisle 34f86d5037 graph2: Added logging changes for Channel, Log, Post and Character.
the build failed
2018-10-09 20:25:55 +02:00
gisle 8a85313762 docker: Updated DockerFile.
the build failed
2018-10-09 19:33:51 +02:00
gisle 017e68eae7 rpdata-ensurechannels: Ported to use new models package.
the build failed
2018-10-07 14:48:23 +02:00
gisle 1955ad0517 rpdata-lb2logimport: Changed script to use new models package.
the build failed
2018-10-07 14:45:44 +02:00
gisle 34ba5d5d3a models2: Fixed issue caused by last commit removing packages that were still barely in use. 2018-10-07 13:41:39 +02:00
gisle 8c8a251080 package: Removed old models packages that are ported over to the new format.
the build failed
2018-10-07 12:18:55 +02:00
gisle 73fe125870 models2: Added log character updates.
the build failed
2018-10-07 12:16:53 +02:00
gisle 04ec85aadf graph2: Removed limit on posts returned for Log.posts field. 2018-10-07 12:16:12 +02:00
gisle 59233ee549 models2: Fixed characters using wrong auto increment
the build failed
2018-10-07 10:56:29 +02:00
gisle 7aa0de9135 models2: Fixed error caused by empty change set in edit
the build failed
2018-10-06 14:02:39 +02:00
gisle 6dfbee7ff2 models2: Fixed editing characters erasing all unedited data.
the build failed
2018-10-06 13:55:16 +02:00
gisle 8a07c2ffdb graph2: Remove CharactersFilter logged property.
the build failed
2018-10-06 10:53:15 +02:00
gisle 0e9536b0a8 models2: Fixed StoryCategory UnmarshalGQL validating before writing the value.
the build failed
2018-10-03 20:56:18 +02:00
gisle b8e5ed5813 graph2: Added channel mutations.
the build failed
2018-10-01 22:20:52 +02:00
gisle 2317f66920 graph2: Added character mutations
the build failed
2018-10-01 22:05:12 +02:00
gisle fc488b938d graph2: Fixed last change closing all logs upon opening one, even the one just opened.
the build failed
2018-10-01 21:01:43 +02:00
gisle ceb2617571 graph2: Added closing of all other logs in the same channel if a log is opened.
the build failed
2018-10-01 20:22:37 +02:00
gisle aeb5b26627 graph2: Added Log mutations
the build failed
2018-09-30 17:05:26 +02:00
gisle 6aafc67790 rpdata-server: >_>
the build failed
2018-09-30 11:28:25 +02:00
gisle c6f8fd9faa git: readme
the build failed
2018-09-30 11:14:01 +02:00
gisle efa98b80cc rpdata-as2storyimport: Changed to use new models.
the build failed
2018-09-29 16:38:36 +02:00
gisle 707dcb43eb models2: Added IsValid() method to StoryCategory 2018-09-29 16:38:21 +02:00
gisle 9bcc28a0ea rpdata-graphiql: removed command
the build failed
2018-09-29 16:21:36 +02:00
gisle 2fceaa62ed rpdata-lb2charimport: Ported to using new models package
the build failed
2018-09-29 16:20:36 +02:00
gisle 86125c7ce9 graph2: Added add/remove nick mutations to character. 2018-09-29 16:20:08 +02:00
gisle bb28795cd5 graph2: Added post mutations. general: Did some cleaning.
the build failed
2018-09-29 15:54:22 +02:00
gisle 815f30597e graph2: Added removal of chapters upon story being deleted.
the build failed
2018-09-23 13:53:13 +02:00
gisle cd1f122502 graph2: Fixed inverse permission checks in story mutations.
the build failed
2018-09-22 15:31:53 +02:00
gisle 0e0d802a32 graph2: Added Story mutations
the build failed
2018-09-20 20:50:22 +02:00
gisle 3c42043ab6 models: Added Equals method to tag model. 2018-09-20 20:49:57 +02:00
gisle 8388ee2446 graph2: Added clearFictionalDate option to editChapter mutation input
the build failed
2018-09-19 22:19:39 +02:00
gisle 260c2e7f76 auth: Changed token checking to ignore disallowed permissions instead of invalidate the key
the build failed
2018-09-18 20:24:04 +02:00
gisle a4034b0faa graph2: Added Chapter mutations
the build failed
2018-09-17 20:11:57 +02:00
gisle c3a6798050 rpdata-server: Changed endpoint from /query to /graphql 2018-09-16 23:52:45 +02:00
gisle 2ab933149e auth: Moved token and user to models package, added graphql endpoint to check token
the build failed
2018-09-16 16:57:40 +02:00
gisle 07b94b3cab auth: Added list of permissions
the build failed
2018-09-16 12:55:28 +02:00
gisle 79171bf9a2 graph2: Added Story type and queries. 2018-09-16 12:55:14 +02:00
gisle bac57bc378 graph2: Added File type and queries.
the build failed
2018-09-16 11:30:18 +02:00
gisle 875dac9ecb graph2: Added Chapter type and queries, moved loader out of old graphql pkg.
the build failed
2018-09-16 10:33:55 +02:00
gisle 0768cfd0bd models: Painstakingly converted half the model package to a models & repostiry stucture. All binaries are probably dead at this point.
the build failed
2018-09-16 00:13:12 +02:00
gisle 2b05d90f4a graph2: Added Log type and queries, moved loader out of old graphql pkg.
the build failed
2018-09-15 13:14:05 +02:00
gisle 0444911e73 graph2: Added Post type and queries 2018-09-15 09:29:04 +02:00
gisle 1be4088a8f graph2: Added Channel type and queries
the build failed
2018-09-13 22:49:36 +02:00
gisle 9978d7380b graph, rpdata-server: Started on new GraphQL implementation.
the build failed
2018-09-13 21:13:05 +02:00
gisle 2e5162a29b auth: Fixed incorrect error reporting on permissions mismatching, added ensuring of user in auth.FindUser.
the build failed
2018-09-13 20:08:37 +02:00
gisle dac0060155 auth: Replaced old system with api key-based system.
the build failed
2018-09-12 21:13:14 +02:00
gisle 9d7fd498a9 Auth: Removed session/auth system from GraphQL API 2018-09-07 17:59:57 +02:00
gisle e0af2685ee Logs: Moved filtering logic out of GraphQL query resolver to the Log model.
the build failed
2018-09-02 13:46:12 +02:00
gisle 3184b60310 GraphQL: Optimized dataloaders by adding a 'priming' mechanic to make fewer trips to the database.
the build failed
2018-09-02 12:38:17 +02:00
gisle 675c09ef04 git: removed binary file from project root
the build failed
2018-08-30 20:32:05 +02:00
gisle 9707320960 git: updated gitignore
the build failed
2018-08-30 20:29:50 +02:00
gisle 20e53b4f5e GraphQL: Fixed stories resolver crashing if filter is nil.
the build failed
2018-08-30 20:21:00 +02:00
gisle 8bb4dea358 UI: Started on story content 2018-08-25 10:35:29 +02:00
gisle 75b2382f09 Changed colors to more closely match aitestory
the build was successful
2018-08-01 22:26:18 +02:00
gisle 1ca25eff8e Fixed warnings
the build was successful
2018-08-01 21:49:58 +02:00
gisle bf10ed1fe3 Added ListItem and changed StoryList ot use it, reorganized StoryMenu, added scrollbar styling, added moment dependency
the build was successful
2018-08-01 18:02:01 +02:00
gisle 54136be912 Removed leftover console.log()s 2018-08-01 15:51:38 +02:00
gisle 228c2bfeb4 Removed docker build on non-tag push from CI
the build was successful
2018-08-01 15:47:27 +02:00
gisle 2f98be5747 Removed default limit for stories query
the build was successful
2018-08-01 15:19:08 +02:00
gisle 2a40f97282 Added order to stories query response
the build was successful
2018-08-01 15:04:16 +02:00
gisle e5b4184106 Fixed query for category lists not getting parameters 2018-08-01 14:57:49 +02:00
gisle c348a90b70 Fixed nil deref error on no filter, fixed no results
the build was successful
2018-08-01 14:51:45 +02:00
gisle 132cf28f0f Added category to StoriesFilter
the build was successful
2018-08-01 14:38:09 +02:00
gisle d5df624188 Added open and unlsited to StoriesFilter
the build was successful
2018-08-01 14:27:28 +02:00
gisle ca98648274 Added router for story menu and queries for the lists 2018-08-01 14:07:54 +02:00
gisle 4642a8abec Added missing dpeencency (apollo-cache-inmemory), changed proxy to work within the local network.
the build was successful
2018-08-01 11:23:15 +02:00
gisle 0ec923d686 Added logged filter to characters query: character appears in logs.
the build was successful
2018-07-18 00:13:19 +02:00
gisle 388d5378e7 Cleaned up the ui code, addressed warnings
the build was successful
2018-07-16 20:24:01 +02:00
gisle 67e9e8f1e6 Renamed LogsIndex -> LogsRoot, removed hardcoded bg image from LogsRoot
the build was successful
2018-07-15 20:08:32 +02:00
gisle 06bc7e5a1b Updated StoryRoot graphql query according to api changes 2018-07-15 19:59:23 +02:00
gisle 237190e498 Added LoadingScreen for full-size loading placeholder 2018-07-15 19:58:12 +02:00
gisle b9f0fef966 Fixed Background landscape detection always returning the intiial result, reacitified the Background class 2018-07-15 19:57:28 +02:00
gisle ba1c1e0a4b Changed characters graphql query to use the filter format, changed description for files query to match the others'
the build was successful
2018-07-15 17:55:21 +02:00
gisle 18e77b8205 Changed .gitignore to include new bindata.go location 2018-07-15 17:14:35 +02:00
gisle d8871259a1 Changed channels graphql query to use filter, added eventName and locationName filters 2018-07-15 16:58:17 +02:00
gisle 26d7e36631 Changed files graphql query to use a filter argument to look more like other queries. 2018-07-15 15:34:58 +02:00
gisle 51489ed901 Changed log query's ID parameter to be compulsory 2018-07-15 15:16:47 +02:00
gisle ed3e384d55 Moved query and mutation resolvers into individual files
the build was successful
2018-07-15 15:10:35 +02:00
231 changed files with 7462 additions and 18293 deletions
+4 -4
View File
@@ -6,16 +6,16 @@ pipeline:
docker-release:
image: plugins/docker
repo: vmaple/rpdata
tags: latest
auto_tag: true
secrets: [ docker_username, docker_password ]
when:
event: tag
ref: refs/tags/*
docker-dev:
docker-release-latest:
image: plugins/docker
repo: vmaple/rpdata
tags: trunk
tags: latest
secrets: [ docker_username, docker_password ]
when:
event: push
event: tag
ref: refs/tags/*
+8 -2
View File
@@ -1,7 +1,13 @@
vendor
schema/bindata.go
graphql/schema/bindata.go
config.json
api
debug
build
.vscode
rpdata-graphiql
rpdata-*
!rpdata-*/
.vscode
generated.gql
generated.go
+16 -34
View File
@@ -1,50 +1,32 @@
## 1. Server
# Use Go 1.10
FROM golang:1.10 AS build-server
## 1. Build Server
# Use Go 1.12
FROM golang:1.12 AS build-server
# Load repository into docker
WORKDIR /go/src/git.aiterp.net/rpdata/api/
WORKDIR /project/
COPY . .
# Load build tools and dependencies
RUN go get -u github.com/golang/dep/cmd/dep
RUN go get -u github.com/jteeuwen/go-bindata/...
RUN dep ensure
# Install build tools and dependencies
RUN go generate ./...
# Build server and tools (CGO disabled to make them 100% static)
ENV CGO_ENABLED 0
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-graphiql ./cmd/rpdata-graphiql
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-lb2charimport ./cmd/rpdata-lb2charimport
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-lb2logimport ./cmd/rpdata-lb2logimport
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-wikifileimport ./cmd/rpdata-wikifileimport
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
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-server ./cmd/rpdata-server
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-lb2charimport ./cmd/rpdata-lb2charimport
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-lb2logimport ./cmd/rpdata-lb2logimport
# TODO: RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-wikifileimport ./cmd/rpdata-wikifileimport
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-ensurechannels ./cmd/rpdata-ensurechannels
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-as2storyimport ./cmd/rpdata-as2storyimport
## 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
## 2. Distribute
# Use alpine linux
FROM alpine:3.7
# Install certificates to enable HTTPS GET requests
RUN apk add --no-cache ca-certificates
# Install certificates to enable HTTPS GET requests, and tzdata for log imports.
RUN apk add --no-cache ca-certificates tzdata
# 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"]
# The server is the main entry point
CMD ["/usr/local/bin/rpdata-server"]
Generated
-178
View File
@@ -1,178 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "git.aiterp.net/aiterp/wikiauth"
packages = ["."]
revision = "33860de804ddcec4fc17998745233b4ec477e7c2"
[[projects]]
branch = "master"
name = "github.com/dustin/go-humanize"
packages = ["."]
revision = "bb3d318650d48840a39aa21a027c6630e198e626"
[[projects]]
branch = "master"
name = "github.com/globalsign/mgo"
packages = [
".",
"bson",
"internal/json",
"internal/sasl",
"internal/scram"
]
revision = "f76e4f9da92ecd56e3be26f5ba92580af1ef97b4"
[[projects]]
name = "github.com/go-ini/ini"
packages = ["."]
revision = "ace140f73450505f33e8b8418216792275ae82a7"
version = "v1.35.0"
[[projects]]
name = "github.com/go-sql-driver/mysql"
packages = ["."]
revision = "d523deb1b23d913de5bdada721a6071e71283618"
version = "v1.4.0"
[[projects]]
name = "github.com/graph-gophers/dataloader"
packages = ["."]
revision = "78139374585c29dcb97b8f33089ed11959e4be59"
version = "v5"
[[projects]]
branch = "master"
name = "github.com/graph-gophers/graphql-go"
packages = [
".",
"errors",
"internal/common",
"internal/exec",
"internal/exec/packer",
"internal/exec/resolvable",
"internal/exec/selected",
"internal/query",
"internal/schema",
"internal/validation",
"introspection",
"log",
"relay",
"trace"
]
revision = "9ebf33af539ab8cb832c7107bc0a978ca8dbc0de"
[[projects]]
branch = "master"
name = "github.com/jmoiron/sqlx"
packages = [
".",
"reflectx"
]
revision = "0dae4fefe7c0e190f7b5a78dac28a1c82cc8d849"
[[projects]]
name = "github.com/minio/minio-go"
packages = [
".",
"pkg/credentials",
"pkg/encrypt",
"pkg/s3signer",
"pkg/s3utils",
"pkg/set"
]
revision = "3d2d02921f0510e9d1f66ef77a265b8dddd36992"
version = "6.0.0"
[[projects]]
branch = "master"
name = "github.com/mitchellh/go-homedir"
packages = ["."]
revision = "b8bc1bf767474819792c23f32d8286a45736f1c6"
[[projects]]
name = "github.com/opentracing/opentracing-go"
packages = [
".",
"ext",
"log"
]
revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38"
version = "v1.0.2"
[[projects]]
name = "github.com/sadbox/mediawiki"
packages = ["."]
revision = "39fea8a1336076a961a300d1d95765dcd17e8a3c"
version = "v0.1"
[[projects]]
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.5"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = [
"argon2",
"blake2b",
"ssh/terminal"
]
revision = "d6449816ce06963d9d136eee5a56fca5b0616e7e"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = [
"context",
"idna",
"lex/httplex"
]
revision = "a35a21de978d84ffc92f010a153705b170b2f9d1"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = [
"unix",
"windows"
]
revision = "2f57af4873d00d535c5c9028850aa2152e6a5566"
[[projects]]
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable"
]
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
name = "google.golang.org/appengine"
packages = ["cloudsql"]
revision = "b1f26356af11148e710935ed1ac8a7f5702c7612"
version = "v1.1.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "16fd84401bc5796ffeaa895f31d099e1a23b363b15b87386877de0829ac9575b"
solver-name = "gps-cdcl"
solver-version = 1
-41
View File
@@ -1,41 +0,0 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
branch = "master"
name = "github.com/globalsign/mgo"
[[constraint]]
name = "github.com/minio/minio-go"
version = "6.0.0"
[prune]
go-tests = true
unused-packages = true
[[constraint]]
branch = "master"
name = "github.com/graph-gophers/graphql-go"
+2 -15
View File
@@ -1,18 +1,5 @@
# RPData API
Project RPData aims to centralize the AiteRP applications, and partially involve the wiki, into a common API. This will allow for easy linking and inlining between them. The primary consumers of this API will be the Logbot and the upcoming RPData website.
Project RPData aims to centralize the AiteRP applications, and partially involve the wiki into a common API. This will allow for easy linking and inlining between them. The primary consumers of this API will be the Logbot and the upcoming RPData website.
## Progress
### Complete
* Session
* Character
* Log
* Post
* File
### Remaining
* Story
### Future
* User (manual DB edits will suffice for now)
There is some renovation going on, and the packages `model` and `graphql` are going to be superceded by `models` and `graph2`
+13 -13
View File
@@ -8,7 +8,9 @@ import (
"time"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/model/story"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/chapters"
"git.aiterp.net/rpdata/api/models/stories"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
@@ -71,14 +73,20 @@ func main() {
log.Fatalln(err)
}
tags := make([]story.Tag, 0, 8)
tags := make([]models.Tag, 0, 8)
for _, tagResult := range tagResults {
if tagResult.PageID == result.ID {
tags = append(tags, story.Tag{Kind: tagResult.Type, Name: tagResult.Name})
tags = append(tags, models.Tag{Kind: models.TagKind(tagResult.Type), Name: tagResult.Name})
}
}
story, err := story.New(result.Name, result.Author[5:], result.Category, false, false, tags, publishDate, fictionalDate)
category := models.StoryCategory(result.Category)
if !category.IsValid() {
log.Println(result.Name, "does not have a valid category:", result.Category)
continue
}
story, err := stories.Add(result.Name, result.Author[5:], category, true, false, tags, publishDate, fictionalDate)
if err != nil {
log.Fatalln(err)
}
@@ -96,19 +104,11 @@ func main() {
}
}
chapter, err := story.AddChapter(title, result.Author[5:], result.Source, publishDate, fictionalDate)
chapter, err := chapters.Add(story, title, result.Author[5:], result.Source, publishDate, &fictionalDate, models.ChapterCommentModeDisabled)
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)
}
}
+7 -8
View File
@@ -4,10 +4,9 @@ import (
"fmt"
"os"
"git.aiterp.net/rpdata/api/model/channel"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/model/log"
"git.aiterp.net/rpdata/api/models/channels"
"git.aiterp.net/rpdata/api/models/logs"
)
func main() {
@@ -17,7 +16,7 @@ func main() {
return
}
logs, err := log.List(32768)
logs, err := logs.List(nil)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
@@ -25,18 +24,18 @@ func main() {
added := make(map[string]bool, 1024)
for _, log := range logs {
if added[log.Channel] {
if added[log.ChannelName] {
continue
}
_, err := channel.Ensure(log.Channel, false)
_, err := channels.Ensure(log.ChannelName, false)
if err != nil {
fmt.Fprintln(os.Stderr, log.ID, err)
continue
}
added[log.Channel] = true
added[log.ChannelName] = true
fmt.Println(log.Channel, "ensured")
fmt.Println(log.ChannelName, "ensured")
}
}
-2
View File
@@ -1,2 +0,0 @@
config.json
debug
-150
View File
@@ -1,150 +0,0 @@
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/model/change"
"git.aiterp.net/rpdata/api/model/file"
logModel "git.aiterp.net/rpdata/api/model/log"
graphql "github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
)
func main() {
err := store.Init()
if err != nil {
log.Fatalln("Failed to init store:", err)
}
n, err := logModel.UpdateAllCharacters()
if err != nil {
log.Println("Charcter updated stopped:", err)
}
log.Println("Updated characters on", n, "logs")
schema, err := graphql.ParseSchema(schema.String(), &resolver.RootResolver{}, graphql.MaxParallelism(48))
if err != nil {
log.Fatalln("Failed to parse schema:", err)
}
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.Write(page)
}))
relayHandler := &relay.Handler{Schema: schema}
http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
r = session.Load(w, r)
l := loader.New()
r = r.WithContext(l.ToContext(r.Context()))
relayHandler.ServeHTTP(w, r)
})
http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) {
r = session.Load(w, r)
sess := session.FromContext(r.Context())
user := sess.User()
if user == nil || !user.Permitted("file.upload") {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if err := r.ParseMultipartForm(16384); err != nil {
http.Error(w, "Internal (1): "+err.Error(), http.StatusInternalServerError)
return
}
formFile, header, err := r.FormFile("file")
if err != nil || header == nil || formFile == nil {
http.Error(w, "No file provided", http.StatusBadRequest)
return
}
file, err := file.Upload(r.Context(), header.Filename, header.Header.Get("Content-Type"), user.ID, header.Size, formFile)
if err != nil {
http.Error(w, "Internal (2): "+err.Error(), http.StatusInternalServerError)
return
}
json, err := json.Marshal(file)
if err != nil {
http.Error(w, "Internal (3): "+err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
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))
}
var page = []byte(`
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.10.2/graphiql.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.1.0/fetch.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.10.2/graphiql.js"></script>
</head>
<body style="width: 100%; height: 100%; margin: 0; overflow: hidden;">
<div id="graphiql" style="height: 100vh;">Loading...</div>
<script>
function graphQLFetcher(graphQLParams) {
return fetch("/graphql", {
method: "post",
body: JSON.stringify(graphQLParams),
credentials: "include",
}).then(function (response) {
return response.text();
}).then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}
});
}
ReactDOM.render(
React.createElement(GraphiQL, {fetcher: graphQLFetcher}),
document.getElementById("graphiql")
);
</script>
</body>
</html>
`)
+4 -4
View File
@@ -6,7 +6,7 @@ import (
"os"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/model/character"
"git.aiterp.net/rpdata/api/models/characters"
)
var fileName = flag.String("file", "./characters.json", "json file to load")
@@ -31,12 +31,12 @@ func main() {
nicksAdded, nicksFailed := 0, 0
for _, info := range infos {
char, err := character.New(info.Nicks[0], info.Name, info.ShortName, info.Author, "")
char, err := characters.Add(info.Nicks[0], info.Name, info.ShortName, info.Author, "")
if err != nil {
log.Println(info.Nicks[0], "failed to insert:", err)
charsFailed++
char, err = character.FindNick(info.Nicks[0])
char, err = characters.FindNick(info.Nicks[0])
if err != nil {
continue
}
@@ -46,7 +46,7 @@ func main() {
}
for _, alt := range info.Nicks[1:] {
err := char.AddNick(alt)
char, err = characters.AddNick(char, alt)
if err != nil {
log.Println(info.Nicks[0], "failed to add nick", alt, "error:", err)
nicksFailed++
+8 -6
View File
@@ -8,8 +8,10 @@ import (
"strings"
"time"
"git.aiterp.net/rpdata/api/models/posts"
"git.aiterp.net/rpdata/api/internal/store"
logModel "git.aiterp.net/rpdata/api/model/log"
"git.aiterp.net/rpdata/api/models/logs"
)
var prefixReplacer = strings.NewReplacer("+", "", "@", "", "!", "", "%", "")
@@ -47,13 +49,13 @@ func main() {
log.Fatalln(err)
}
l, err := logModel.New(date, channel, title, event, "", false)
l, err := logs.Add(date, channel, title, event, "", false)
if err != nil {
log.Println(err)
continue
}
_, err = l.NewPost(time.Now(), "annotation.info", "rpdata-lb2logimport", "This logfile is imported from aitelogs2 and may contain errors or wrong timestamps.")
_, err = posts.Add(l, time.Now(), "annotation.info", "rpdata-lb2logimport", "This logfile is imported from aitelogs2 and may contain errors or wrong timestamps.")
if err != nil {
log.Println(err)
}
@@ -63,7 +65,7 @@ func main() {
continue
}
_, err = l.NewPost(l.Date, "chars", prefixReplacer.Replace(line.Args[0]), line.Text)
_, err = posts.Add(l, l.Date, "chars", prefixReplacer.Replace(line.Args[0]), line.Text)
if err != nil {
log.Println(err)
}
@@ -92,13 +94,13 @@ func main() {
line.Verb = "SCENE"
}
_, err = l.NewPost(postTime, strings.ToLower(line.Verb), prefixReplacer.Replace(line.Args[0]), line.Text)
_, err = posts.Add(l, postTime, strings.ToLower(line.Verb), prefixReplacer.Replace(line.Args[0]), line.Text)
if err != nil {
log.Println(err)
}
}
err = l.UpdateCharacters()
l, err = logs.UpdateCharacters(l, nil)
if err != nil {
log.Println(err)
}
+101
View File
@@ -0,0 +1,101 @@
package main
import (
"context"
"fmt"
"log"
"net/http"
"runtime/debug"
"strings"
"git.aiterp.net/rpdata/api/graph2"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/internal/loader"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/changes"
"git.aiterp.net/rpdata/api/models/logs"
"github.com/99designs/gqlgen/handler"
)
func main() {
err := store.Init()
if err != nil {
log.Fatalln("Failed to init store:", err)
}
http.Handle("/", handler.Playground("RPData API", "/graphql"))
http.Handle("/graphql", queryHandler())
go func() {
err := logs.RunFullUpdate()
if err != nil {
log.Println(err)
}
log.Println("Characters updated")
}()
go logListedChanges()
log.Fatal(http.ListenAndServe(":8081", nil))
}
func logListedChanges() {
sub := changes.Subscribe(context.Background(), nil, true)
for change := range sub {
log.Printf("Change: Author=%#+v Model=%#+v Op=%#+v", change.Author, change.Model, change.Op)
for _, object := range change.Objects() {
switch object := object.(type) {
case models.Log:
log.Printf("\tLog: %s (%s)", object.ID, object.ShortID)
case models.Character:
log.Printf("\tCharacter: %s", object.ID)
case models.Channel:
log.Printf("\tChannel: %s", object.Name)
case models.Post:
log.Printf("\tPost: %s", object.ID)
case models.Story:
log.Printf("\tStory: %s", object.ID)
case models.Tag:
log.Printf("\tTag: (%s,%s)", object.Kind, object.Name)
case models.Chapter:
log.Printf("\tChapter: %s", object.ID)
case models.Comment:
log.Printf("\tComment: %s", object.ID)
}
}
}
log.Println("Change subscription closed.")
}
func queryHandler() http.HandlerFunc {
handler := handler.GraphQL(
graph2.New(),
handler.RecoverFunc(func(ctx context.Context, err interface{}) error {
log.Println(err)
log.Println(string(debug.Stack()))
return fmt.Errorf("The server failed to serve your request due to an internal error, it has been logged")
}),
handler.ComplexityLimit(300),
)
return func(w http.ResponseWriter, r *http.Request) {
l := loader.New()
r = r.WithContext(l.ToContext(r.Context()))
// >_>
if strings.HasPrefix(r.Header.Get("Authorization"), "Bearer of the curse") {
w.Header().Set("X-Emerald-Herald", "Seek souls. Larger, more powerful souls.")
w.Header().Add("X-Emerald-Herald", "Seek the king, that is the only way.")
w.Header().Add("X-Emerald-Herald", "Lest this land swallow you whole... As it has so many others.")
}
r = auth.RequestWithToken(r)
handler.ServeHTTP(w, r)
}
}
+3 -3
View File
@@ -9,7 +9,7 @@ import (
"git.aiterp.net/rpdata/api/internal/config"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/model/file"
"git.aiterp.net/rpdata/api/models/files"
)
func main() {
@@ -33,7 +33,7 @@ func main() {
store.Init()
for _, info := range data.Query.Allimages {
existing, err := file.FindName("wiki", info.Name, info.User)
existing, err := files.FindName("wiki", info.Name, info.User)
if err == nil && existing.ID != "" {
log.Println("Skipping", info.Name, "because it already exists")
continue
@@ -45,7 +45,7 @@ func main() {
continue
}
file, err := file.Insert(info.Name, "wiki", info.Mime, info.User, ts, info.Size, info.URL)
file, err := files.Insert(info.Name, "wiki", info.Mime, info.User, ts, info.Size, info.URL)
if err != nil {
log.Println("Skipping", info.Name, "error:", err)
continue
+34
View File
@@ -0,0 +1,34 @@
module git.aiterp.net/rpdata/api
go 1.12
require (
github.com/99designs/gqlgen v0.8.0
github.com/agnivade/levenshtein v1.0.1
github.com/davecgh/go-spew v1.1.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4
github.com/globalsign/mgo v0.0.0-20180403085842-f76e4f9da92e
github.com/go-ini/ini v1.35.0
github.com/go-sql-driver/mysql v1.4.0
github.com/gorilla/websocket v1.4.0
github.com/graph-gophers/dataloader v0.0.0-20180104184831-78139374585c
github.com/hashicorp/golang-lru v0.5.0
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0
github.com/minio/minio-go v0.0.0-20180409193742-3d2d02921f05
github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747
github.com/opentracing/opentracing-go v1.0.2
github.com/pkg/errors v0.8.1
github.com/pmezard/go-difflib v1.0.0
github.com/sirupsen/logrus v1.0.5
github.com/stretchr/testify v1.3.0
github.com/urfave/cli v1.20.0
github.com/vektah/gqlparser v1.1.0
golang.org/x/crypto v0.0.0-20180411161317-d6449816ce06
golang.org/x/net v0.0.0-20180416171110-a35a21de978d
golang.org/x/sys v0.0.0-20180416112224-2f57af4873d0
golang.org/x/text v0.3.0
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6
google.golang.org/appengine v1.1.0
gopkg.in/yaml.v2 v2.2.2
)
+90
View File
@@ -0,0 +1,90 @@
github.com/99designs/gqlgen v0.5.1 h1:cRsbpZgX83PrXb0/hj5EkNdmaVN4l/Eii81Z8LCexgY=
github.com/99designs/gqlgen v0.5.1/go.mod h1:KSQDfLlTTGmzlRgLGm6HeKKKo598l5E2svEM6Nz2Jnw=
github.com/99designs/gqlgen v0.8.0 h1:ZBteuSgeeFwn+mztjDqQZQdVa9pqslmPnDeCovcnc0Y=
github.com/99designs/gqlgen v0.8.0/go.mod h1:st7qHA6ssU3uRZkmv+wzrzgX4srvIqEIdE5iuRW8GhE=
github.com/99designs/gqlgen v0.8.3 h1:I6bMglXNKkn4KlvkSMzqZw53e1N2FF9Gud4NmsOxqiA=
github.com/99designs/gqlgen v0.8.3/go.mod h1:aLyJw9xUgdJxZ8EqNQxo2pGFhXXJ/hq8t7J4yn8TgI4=
github.com/agnivade/levenshtein v1.0.0 h1:q+77q31bLT5jhN3BHKA1276nUEdbz7XjDa0o5dRKcZ0=
github.com/agnivade/levenshtein v1.0.0/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/globalsign/mgo v0.0.0-20180403085842-f76e4f9da92e h1:hmrbHva/wKD6X4M7pgHVbg/KfVV1wwgr1NEkQaVD3rU=
github.com/globalsign/mgo v0.0.0-20180403085842-f76e4f9da92e/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-ini/ini v1.35.0 h1:D/my3+xOfqZMkJpciRcyqU7XMBUgiZa9qXjZIa8uv2k=
github.com/go-ini/ini v1.35.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/graph-gophers/dataloader v0.0.0-20180104184831-78139374585c h1:94S+uoVVMpQAEOrqGjCDyUdML4dJDkh6aC4MYmXECg4=
github.com/graph-gophers/dataloader v0.0.0-20180104184831-78139374585c/go.mod h1:jk4jk0c5ZISbKaMe8WsVopGB5/15GvGHMdMdPtwlRp4=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0 h1:5B0uxl2lzNRVkJVg+uGHxWtRt4C0Wjc6kJKo5XYx8xE=
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/minio/minio-go v0.0.0-20180409193742-3d2d02921f05 h1:YEyFCqmHcmbcTLqZ7yEAg7VIUat6PuDzGbIctZy888k=
github.com/minio/minio-go v0.0.0-20180409193742-3d2d02921f05/go.mod h1:7guKYtitv8dktvNUGrhzmNlA5wrAABTQXCoesZdFQO8=
github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 h1:eQox4Rh4ewJF+mqYPxCkmBAirRnPaHEB26UkNuPyjlk=
github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/vektah/dataloaden v0.2.0/go.mod h1:vxM6NuRlgiR0M6wbVTJeKp9vQIs81ZMfCYO+4yq/jbE=
github.com/vektah/gqlparser v0.0.0-20180831041411-14e83ae06ec1 h1:FYOXUtr3sYR9shto7Q/aQ1B0Onyk77aws9wGOORiz+I=
github.com/vektah/gqlparser v0.0.0-20180831041411-14e83ae06ec1/go.mod h1:K4QdSSpS2XiHHwzb18kWh3iBljB8rLC8okGXsnQy3Nc=
github.com/vektah/gqlparser v1.1.0 h1:3668p2gUlO+PiS81x957Rpr3/FPRWG6cxgCXAvTS1hw=
github.com/vektah/gqlparser v1.1.0/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
golang.org/x/crypto v0.0.0-20180411161317-d6449816ce06 h1:EOqG0JqGlLr+punVB69jvWCv/ErZKGlC7PMdyHfv+Bc=
golang.org/x/crypto v0.0.0-20180411161317-d6449816ce06/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180404174746-b3c676e531a6/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180416171110-a35a21de978d h1:O2P57H5Cc+d+DJos+iweraI9rmzMYwV+45vkZdDY0Oo=
golang.org/x/net v0.0.0-20180416171110-a35a21de978d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20180416112224-2f57af4873d0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1 h1:dzEuQYa6+a3gROnSlgly5ERUm4SZKJt+dh+4iSbO+bI=
golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6 h1:iZgcI2DDp6zW5v9Z/5+f0NuqoxNdmzg4hivjk2WLXpY=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
echo "# Generated by stiching together the files under schema/ – DO NOT EDIT" >generated.gql
cat schema/root.gql schema/**/*.gql >>generated.gql
+182
View File
@@ -0,0 +1,182 @@
package graph2
import (
input "git.aiterp.net/rpdata/api/graph2/input"
changes "git.aiterp.net/rpdata/api/models/changes"
channels "git.aiterp.net/rpdata/api/models/channels"
characters "git.aiterp.net/rpdata/api/models/characters"
files "git.aiterp.net/rpdata/api/models/files"
logs "git.aiterp.net/rpdata/api/models/logs"
posts "git.aiterp.net/rpdata/api/models/posts"
stories "git.aiterp.net/rpdata/api/models/stories"
)
func complexity() (cr ComplexityRoot) {
subListComplexity := 25
bigSublistComplexity := 75
hugeSublistComplexity := 100
findComplexity := 100
listComplexity := 200
mutationComplexity := 100
cr.Query.Character = func(childComplexity int, id *string, nick *string) int {
return childComplexity + findComplexity
}
cr.Query.Characters = func(childComplexity int, filter *characters.Filter) int {
return childComplexity + listComplexity
}
cr.Query.Channel = func(childComplexity int, name string) int {
return childComplexity + findComplexity
}
cr.Query.Channels = func(childComplexity int, filter *channels.Filter) int {
return childComplexity + listComplexity
}
cr.Query.Post = func(childComplexity int, id string) int {
return childComplexity + findComplexity
}
cr.Query.Posts = func(childComplexity int, filter *posts.Filter) int {
return childComplexity + listComplexity
}
cr.Query.UnknownNicks = func(childComplexity int, filter *input.UnknownNicksFilter) int {
return childComplexity + listComplexity
}
cr.Query.Log = func(childComplexity int, id string) int {
return childComplexity + findComplexity
}
cr.Query.Logs = func(childComplexity int, filter *logs.Filter) int {
if filter != nil && filter.Open != nil && *filter.Open == true {
return childComplexity + findComplexity
}
return childComplexity + listComplexity
}
cr.Query.Chapter = func(childComplexity int, id string) int {
return childComplexity + findComplexity
}
cr.Query.Comment = func(childComplexity int, id string) int {
return childComplexity + findComplexity
}
cr.Query.Tags = func(childComplexity int) int {
return childComplexity + listComplexity
}
cr.Query.Story = func(childComplexity int, id string) int {
return childComplexity + findComplexity
}
cr.Query.Stories = func(childComplexity int, filter *stories.Filter) int {
return childComplexity + listComplexity
}
cr.Query.File = func(childComplexity int, id string) int {
return childComplexity + findComplexity
}
cr.Query.Files = func(childComplexity int, filter *files.Filter) int {
return childComplexity + listComplexity
}
cr.Query.Changes = func(childComplexity int, filter *changes.Filter) int {
return childComplexity + listComplexity
}
cr.Query.Token = func(childComplexity int) int {
return childComplexity + findComplexity
}
cr.Mutation.AddStory = func(childComplexity int, input input.StoryAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddStoryTag = func(childComplexity int, input input.StoryTagAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemoveStoryTag = func(childComplexity int, input input.StoryTagRemoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.EditStory = func(childComplexity int, input input.StoryEditInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemoveStory = func(childComplexity int, input input.StoryRemoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddChapter = func(childComplexity int, input input.ChapterAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.EditChapter = func(childComplexity int, input input.ChapterEditInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.MoveChapter = func(childComplexity int, input input.ChapterMoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemoveChapter = func(childComplexity int, input input.ChapterRemoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddComment = func(childComplexity int, input input.CommentAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.EditComment = func(childComplexity int, input input.CommentEditInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemoveComment = func(childComplexity int, input input.CommentRemoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddLog = func(childComplexity int, input input.LogAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.ImportLog = func(childComplexity int, input input.LogImportInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.EditLog = func(childComplexity int, input input.LogEditInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemoveLog = func(childComplexity int, input input.LogRemoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddPost = func(childComplexity int, input input.PostAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.EditPost = func(childComplexity int, input input.PostEditInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.MovePost = func(childComplexity int, input input.PostMoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemovePost = func(childComplexity int, input input.PostRemoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddCharacter = func(childComplexity int, input input.CharacterAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddCharacterNick = func(childComplexity int, input input.CharacterNickInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemoveCharacterNick = func(childComplexity int, input input.CharacterNickInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.EditCharacter = func(childComplexity int, input input.CharacterEditInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemoveCharacter = func(childComplexity int, input input.CharacterRemoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddChannel = func(childComplexity int, input input.ChannelAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.EditChannel = func(childComplexity int, input input.ChannelEditInput) int {
return childComplexity + mutationComplexity
}
cr.Log.Characters = func(childComplexity int) int {
return childComplexity + subListComplexity
}
cr.Log.Posts = func(childComplexity int, kinds []string) int {
return childComplexity + hugeSublistComplexity
}
cr.Story.Chapters = func(childComplexity int) int {
return childComplexity + bigSublistComplexity
}
cr.Story.Tags = func(childComplexity int) int {
return childComplexity + subListComplexity
}
cr.Chapter.Comments = func(childComplexity int, limit *int) int {
return childComplexity + subListComplexity
}
return
}
+78
View File
@@ -0,0 +1,78 @@
schema: generated.gql
exec:
filename: generated.go
package: graph2
model:
filename: input/generated.go
package: input
models:
Tag:
model: git.aiterp.net/rpdata/api/models.Tag
TagInput: #It's the same as tags
model: git.aiterp.net/rpdata/api/models.Tag
TagKind:
model: git.aiterp.net/rpdata/api/models.TagKind
Character:
model: git.aiterp.net/rpdata/api/models.Character
CharactersFilter:
model: git.aiterp.net/rpdata/api/models/characters.Filter
Channel:
model: git.aiterp.net/rpdata/api/models.Channel
ChannelsFilter:
model: git.aiterp.net/rpdata/api/models/channels.Filter
UnknownNick:
model: git.aiterp.net/rpdata/api/models.UnknownNick
Post:
model: git.aiterp.net/rpdata/api/models.Post
PostsFilter:
model: git.aiterp.net/rpdata/api/models/posts.Filter
Log:
model: git.aiterp.net/rpdata/api/models.Log
LogsFilter:
model: git.aiterp.net/rpdata/api/models/logs.Filter
LogImporter:
model: git.aiterp.net/rpdata/api/models.LogImporter
Comment:
model: git.aiterp.net/rpdata/api/models.Comment
fields:
fictionalDate:
resolver: true
ChapterCommentMode:
model: git.aiterp.net/rpdata/api/models.ChapterCommentMode
Chapter:
model: git.aiterp.net/rpdata/api/models.Chapter
fields:
fictionalDate:
resolver: true
Story:
model: git.aiterp.net/rpdata/api/models.Story
fields:
fictionalDate:
resolver: true
StoryCategory:
model: git.aiterp.net/rpdata/api/models.StoryCategory
StoriesFilter:
model: git.aiterp.net/rpdata/api/models/stories.Filter
File:
model: git.aiterp.net/rpdata/api/models.File
FilesFilter:
model: git.aiterp.net/rpdata/api/models/files.Filter
Change:
model: git.aiterp.net/rpdata/api/models.Change
ChangeModel:
model: git.aiterp.net/rpdata/api/models.ChangeModel
ChangeKey:
model: git.aiterp.net/rpdata/api/models.ChangeKey
ChangeKeyInput: # It's the same as ChangeKey
model: git.aiterp.net/rpdata/api/models.ChangeKey
ChangesFilter:
model: git.aiterp.net/rpdata/api/models/changes.Filter
Token:
model: git.aiterp.net/rpdata/api/models.Token
User:
model: git.aiterp.net/rpdata/api/models.User
Date:
model: git.aiterp.net/rpdata/api/models/scalars.Date
+56
View File
@@ -0,0 +1,56 @@
package graph2
import (
"git.aiterp.net/rpdata/api/graph2/queries"
"git.aiterp.net/rpdata/api/graph2/types"
graphql "github.com/99designs/gqlgen/graphql"
)
//go:generate ./combine.sh
//go:generate go run github.com/99designs/gqlgen -v
// New creates a new GraphQL schema.
func New() graphql.ExecutableSchema {
return NewExecutableSchema(Config{
Resolvers: &rootResolver{},
Complexity: complexity(),
})
}
type rootResolver struct{}
func (r *rootResolver) Query() QueryResolver {
return &queries.Resolver
}
func (r *rootResolver) Mutation() MutationResolver {
return queries.MutationResolver
}
func (r *rootResolver) Subscription() SubscriptionResolver {
return queries.SubscriptionResolver
}
func (r *rootResolver) Log() LogResolver {
return &types.LogResolver
}
func (r *rootResolver) Comment() CommentResolver {
return &types.CommentResolver
}
func (r *rootResolver) Chapter() ChapterResolver {
return &types.ChapterResolver
}
func (r *rootResolver) Story() StoryResolver {
return &types.StoryResolver
}
func (r *rootResolver) Change() ChangeResolver {
return &types.ChangeResolver
}
func (r *rootResolver) Token() TokenResolver {
return &types.TokenResolver
}
+25
View File
@@ -0,0 +1,25 @@
package queries
import (
"context"
"errors"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/changes"
)
/// Queries
func (r *resolver) Changes(ctx context.Context, filter *changes.Filter) ([]models.Change, error) {
return changes.List(filter)
}
/// Subscriptions
func (r *subscriptionResolver) Changes(ctx context.Context, keys []models.ChangeKey) (<-chan *models.Change, error) {
if len(keys) == 0 {
return nil, errors.New("At least one key is required for a subscription")
}
return changes.Subscribe(ctx, keys, false), nil
}
+84
View File
@@ -0,0 +1,84 @@
package queries
import (
"context"
"errors"
"git.aiterp.net/rpdata/api/graph2/input"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/changekeys"
"git.aiterp.net/rpdata/api/models/changes"
"git.aiterp.net/rpdata/api/models/channels"
)
// Queries
func (r *resolver) Channel(ctx context.Context, name string) (*models.Channel, error) {
channel, err := channels.FindName(name)
if err != nil {
return nil, err
}
return &channel, nil
}
func (r *resolver) Channels(ctx context.Context, filter *channels.Filter) ([]models.Channel, error) {
return channels.List(filter)
}
// Mutations
func (r *mutationResolver) AddChannel(ctx context.Context, input input.ChannelAddInput) (*models.Channel, error) {
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.Permitted("channel.add") {
return nil, errors.New("You are not permitted to add channels")
}
logged := false
if input.Logged != nil {
logged = *input.Logged
}
hub := false
if input.Hub != nil {
hub = *input.Hub
}
eventName := ""
if input.EventName != nil {
eventName = *input.EventName
}
locationName := ""
if input.LocationName != nil {
locationName = *input.LocationName
}
channel, err := channels.Add(input.Name, logged, hub, eventName, locationName)
if err != nil {
return nil, errors.New("Failed to add channel: " + err.Error())
}
go changes.Submit("Channel", "add", token.UserID, true, changekeys.Listed(channel), channel)
return &channel, nil
}
func (r *mutationResolver) EditChannel(ctx context.Context, input input.ChannelEditInput) (*models.Channel, error) {
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.Permitted("channel.edit") {
return nil, errors.New("You are not permitted to edit channels")
}
channel, err := channels.FindName(input.Name)
if err != nil {
return nil, errors.New("Channel not found")
}
channel, err = channels.Edit(channel, input.Logged, input.Hub, input.EventName, input.LocationName)
if err != nil {
return nil, errors.New("Failed to edit channel: " + err.Error())
}
go changes.Submit("Channel", "edit", token.UserID, true, changekeys.Listed(channel), channel)
return &channel, nil
}
+177
View File
@@ -0,0 +1,177 @@
package queries
import (
"context"
"errors"
"time"
"git.aiterp.net/rpdata/api/graph2/input"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/changekeys"
"git.aiterp.net/rpdata/api/models/changes"
"git.aiterp.net/rpdata/api/models/chapters"
"git.aiterp.net/rpdata/api/models/comments"
"git.aiterp.net/rpdata/api/models/stories"
)
// Queries
func (r *resolver) Chapter(ctx context.Context, id string) (*models.Chapter, error) {
chapter, err := chapters.FindID(id)
if err != nil {
return nil, err
}
return &chapter, nil
}
// Mutations
func (r *mutationResolver) AddChapter(ctx context.Context, input input.ChapterAddInput) (*models.Chapter, error) {
story, err := stories.FindID(input.StoryID)
if err != nil {
return nil, errors.New("Story not found")
}
token := auth.TokenFromContext(ctx)
if !token.Permitted("member", "story.add") {
return nil, errors.New("Unauthorized")
}
author := token.UserID
if input.Author != nil && *input.Author != author {
if !token.Permitted("story.add") {
return nil, errors.New("False pretender")
}
author = *input.Author
}
if !story.Open && story.Author != author {
return nil, errors.New("Story is not open")
}
commentMode := models.ChapterCommentModeDisabled
if input.CommentMode != nil {
commentMode = *input.CommentMode
}
chapter, err := chapters.Add(story, input.Title, author, input.Source, time.Now(), input.FictionalDate, commentMode)
if err != nil {
return nil, errors.New("Failed to create chapter: " + err.Error())
}
go changes.Submit("Chapter", "add", token.UserID, story.Listed, changekeys.Listed(story, chapter), story, chapter)
return &chapter, nil
}
func (r *mutationResolver) MoveChapter(ctx context.Context, input input.ChapterMoveInput) (*models.Chapter, error) {
chapter, err := chapters.FindID(input.ID)
if err != nil {
return nil, errors.New("Chapter not found")
}
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.PermittedUser(chapter.Author, "member", "chapter.move") {
return nil, errors.New("You are not allowed to move this chapter")
}
target, err := stories.FindID(input.StoryID)
if err != nil {
return nil, errors.New("Target story not found")
}
if !target.Open && !token.PermittedUser(target.Author, "member", "chapter.move") {
return nil, errors.New("You are not permitted to move chapters to this story")
}
oldStoryID := chapter.StoryID
chapter, err = chapters.Move(chapter, target)
if err != nil {
return nil, errors.New("Failed to move chapter: " + err.Error())
}
go func() {
story, err := stories.FindID(chapter.StoryID)
if err != nil {
story.ID = chapter.StoryID
}
oldStory, err := stories.FindID(oldStoryID)
if err != nil {
oldStory.ID = oldStoryID
}
changes.Submit("Chapter", "move-out", chapter.Author, oldStory.Listed, changekeys.Many(oldStory, chapter), chapter)
changes.Submit("Chapter", "move-in", token.UserID, story.Listed, changekeys.Listed(story, chapter), story, chapter)
}()
return &chapter, nil
}
func (r *mutationResolver) EditChapter(ctx context.Context, input input.ChapterEditInput) (*models.Chapter, error) {
chapter, err := chapters.FindID(input.ID)
if err != nil {
return nil, errors.New("Chapter not found")
}
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.PermittedUser(chapter.Author, "member", "chapter.edit") {
return nil, errors.New("Unauthorized")
}
if input.ClearFictionalDate != nil && *input.ClearFictionalDate == true {
input.FictionalDate = &time.Time{}
}
chapter, err = chapters.Edit(chapter, input.Title, input.Source, input.FictionalDate, input.CommentMode, input.CommentsLocked)
if err != nil {
return nil, errors.New("Failed to edit chapter: " + err.Error())
}
go func() {
story, err := stories.FindID(chapter.StoryID)
if err != nil {
story.ID = chapter.StoryID
}
changes.Submit("Chapter", "edit", token.UserID, story.Listed, changekeys.Many(story, chapter), chapter)
}()
return &chapter, nil
}
func (r *mutationResolver) RemoveChapter(ctx context.Context, input input.ChapterRemoveInput) (*models.Chapter, error) {
chapter, err := chapters.FindID(input.ID)
if err != nil {
return nil, errors.New("Chapter not found")
}
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.PermittedUser(chapter.Author, "member", "chapter.remove") {
return nil, errors.New("Unauthorized")
}
chapter, err = chapters.Remove(chapter)
if err != nil {
return nil, errors.New("Failed to remove chapter: " + err.Error())
}
err = comments.RemoveChapter(chapter)
if err != nil {
return nil, errors.New("Chapter was removed, but comment removal failed: " + err.Error())
}
go func() {
story, err := stories.FindID(chapter.StoryID)
if err != nil {
story.ID = chapter.StoryID
}
changes.Submit("Chapter", "remove", token.UserID, story.Listed, changekeys.Many(story, chapter), chapter)
}()
return &chapter, nil
}
+184
View File
@@ -0,0 +1,184 @@
package queries
import (
"context"
"errors"
"strings"
"git.aiterp.net/rpdata/api/graph2/input"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/changekeys"
"git.aiterp.net/rpdata/api/models/changes"
"git.aiterp.net/rpdata/api/models/characters"
"git.aiterp.net/rpdata/api/models/logs"
)
// Queries
func (r *resolver) Character(ctx context.Context, id *string, nick *string) (*models.Character, error) {
if id != nil {
character, err := characters.FindID(*id)
if err != nil {
return nil, err
}
return &character, nil
} else if nick != nil {
character, err := characters.FindNick(*nick)
if err != nil {
return nil, err
}
return &character, nil
} else {
return nil, errors.New("You must specify either an ID or a nick")
}
}
func (r *resolver) Characters(ctx context.Context, filter *characters.Filter) ([]models.Character, error) {
return characters.List(filter)
}
// Mutations
func (r *mutationResolver) AddCharacter(ctx context.Context, input input.CharacterAddInput) (*models.Character, error) {
token := auth.TokenFromContext(ctx)
if !token.Permitted("member", "character.add") {
return nil, errors.New("You are not permitted to add characters")
}
if len(input.Name) < 2 || len(input.Nick) < 2 {
return nil, errors.New("You need to provide a name and a nick (min length: 2)")
}
shortName := ""
if input.ShortName != nil {
shortName = *input.ShortName
} else {
shortName = strings.SplitN(input.Name, " ", 2)[0]
}
description := ""
if input.Description != nil {
description = *input.Description
}
author := token.UserID
if input.Author != nil && *input.Author != author {
if !token.Permitted("character.add") {
return nil, errors.New("You are only permitted to add your own characters")
}
author = *input.Author
}
logs.ScheduleFullUpdate()
character, err := characters.Add(input.Nick, input.Name, shortName, author, description)
if err != nil {
return nil, errors.New("Adding character failed: " + err.Error())
}
go changes.Submit("Character", "add", token.UserID, true, changekeys.Listed(character), character)
return &character, nil
}
func (r *mutationResolver) AddCharacterNick(ctx context.Context, input input.CharacterNickInput) (*models.Character, error) {
character, err := characters.FindID(input.ID)
if err != nil {
return nil, errors.New("Character not found")
}
if len(input.Nick) < 2 {
return nil, errors.New("You need to provide a valid nick (min length: 2)")
}
token := auth.TokenFromContext(ctx)
if !token.PermittedUser(character.Author, "member", "character.edit") {
return nil, errors.New("You are not permitted to edit this character")
}
logs.ScheduleFullUpdate()
character, err = characters.AddNick(character, input.Nick)
if err != nil {
return nil, errors.New("Failed to add nick: " + err.Error())
}
go logs.ScheduleFullUpdate()
go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
return &character, nil
}
func (r *mutationResolver) RemoveCharacterNick(ctx context.Context, input input.CharacterNickInput) (*models.Character, error) {
character, err := characters.FindID(input.ID)
if err != nil {
return nil, errors.New("Character not found")
}
token := auth.TokenFromContext(ctx)
if !token.PermittedUser(character.Author, "member", "character.edit") {
return nil, errors.New("You are not permitted to edit this character")
}
character, err = characters.RemoveNick(character, input.Nick)
if err != nil {
return nil, errors.New("Failed to remove nick: " + err.Error())
}
go logs.ScheduleFullUpdate()
go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
return &character, nil
}
func (r *mutationResolver) EditCharacter(ctx context.Context, input input.CharacterEditInput) (*models.Character, error) {
character, err := characters.FindID(input.ID)
if err != nil {
return nil, errors.New("Character not found")
}
if input.Name != nil && len(*input.Name) < 2 {
return nil, errors.New("You need to provide a valid name (min length: 2)")
}
if input.ShortName != nil && len(*input.ShortName) < 2 {
return nil, errors.New("You need to provide a valid short name (min length: 2)")
}
token := auth.TokenFromContext(ctx)
if !token.PermittedUser(character.Author, "member", "character.edit") {
return nil, errors.New("You are not permitted to edit this character")
}
character, err = characters.Edit(character, input.Name, input.ShortName, input.Description)
if err != nil {
return nil, errors.New("Failed to edit character: " + err.Error())
}
go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
return &character, nil
}
func (r *mutationResolver) RemoveCharacter(ctx context.Context, input input.CharacterRemoveInput) (*models.Character, error) {
character, err := characters.FindID(input.ID)
if err != nil {
return nil, errors.New("Character not found")
}
token := auth.TokenFromContext(ctx)
if !token.PermittedUser(character.Author, "member", "character.remove") {
return nil, errors.New("You are not permitted to remove this character")
}
character, err = characters.Remove(character)
if err != nil {
return nil, errors.New("Failed to remove character: " + err.Error())
}
go changes.Submit("Character", "remove", token.UserID, true, changekeys.Listed(character), character)
return &character, nil
}
+180
View File
@@ -0,0 +1,180 @@
package queries
import (
"context"
"errors"
"log"
"time"
"git.aiterp.net/rpdata/api/models/stories"
"git.aiterp.net/rpdata/api/models/characters"
"git.aiterp.net/rpdata/api/models/changekeys"
"git.aiterp.net/rpdata/api/models/changes"
"git.aiterp.net/rpdata/api/graph2/input"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/models/chapters"
"git.aiterp.net/rpdata/api/models/comments"
"git.aiterp.net/rpdata/api/models"
)
// Queries
func (r *resolver) Comment(ctx context.Context, id string) (*models.Comment, error) {
comment, err := comments.Find(id)
if err != nil {
return nil, err
}
return &comment, nil
}
// Mutations
func (r *mutationResolver) AddComment(ctx context.Context, input input.CommentAddInput) (*models.Comment, error) {
chapter, err := chapters.FindID(input.ChapterID)
if err != nil {
return nil, errors.New("Chapter not found")
}
token := auth.TokenFromContext(ctx)
if !token.Permitted("member", "story.edit") {
return nil, errors.New("Unauthorized")
}
if !chapter.CanComment() {
return nil, errors.New("Comments are disabled or locked")
}
var characterPtr *models.Character
if input.CharacterID != nil {
character, err := characters.FindID(*input.CharacterID)
if err != nil {
return nil, errors.New("Character not found")
} else if character.Author != token.UserID {
return nil, errors.New("That is not your character")
}
characterPtr = &character
}
fictionalDate := time.Time{}
if input.FictionalDate != nil {
fictionalDate = *input.FictionalDate
}
subject := ""
if input.Subject != nil {
subject = *input.Subject
}
comment, err := comments.Add(chapter, subject, token.UserID, input.Source, input.CharacterName, characterPtr, time.Now(), fictionalDate)
if err != nil {
return nil, errors.New("Failed to add comment: " + err.Error())
}
go func() {
story, err := stories.FindID(chapter.StoryID)
if err != nil {
log.Println("WARNING: Couldn't log comment change:", err)
return
}
changes.Submit("Comment", "add", token.UserID, true, changekeys.Many(comment, chapter, models.Story{ID: chapter.StoryID}), comment, chapter, story)
}()
return &comment, nil
}
func (r *mutationResolver) EditComment(ctx context.Context, input input.CommentEditInput) (*models.Comment, error) {
comment, err := comments.Find(input.CommentID)
if err != nil {
return nil, errors.New("Comment not found")
}
token := auth.TokenFromContext(ctx)
if !token.PermittedUser(comment.Author, "member", "story.edit") {
return nil, errors.New("You cannot edit this comment")
}
chapter, err := chapters.FindID(comment.ChapterID)
if err != nil {
return nil, errors.New("Comment's chapter not found")
}
if !chapter.CanComment() {
return nil, errors.New("Comments are disabled or locked")
}
if input.ClearFictionalDate != nil && *input.ClearFictionalDate == true {
input.FictionalDate = &time.Time{}
}
if input.CharacterID != nil && *input.CharacterID != "" {
character, err := characters.FindID(*input.CharacterID)
if err != nil {
return nil, errors.New("Character not found")
} else if character.Author != token.UserID {
return nil, errors.New("That is not your character")
}
}
comment, err = comments.Edit(comment, input.Source, input.CharacterName, input.CharacterID, input.Subject, input.FictionalDate)
if err != nil {
return nil, errors.New("Could not post comment: " + err.Error())
}
go func() {
story, err := stories.FindID(chapter.StoryID)
if err != nil {
log.Println("WARNING: Couldn't log comment change:", err)
return
}
changes.Submit("Comment", "edit", token.UserID, true, changekeys.Many(comment, chapter, models.Story{ID: chapter.StoryID}), comment, chapter, story)
}()
return &comment, nil
}
func (r *mutationResolver) RemoveComment(ctx context.Context, input input.CommentRemoveInput) (*models.Comment, error) {
comment, err := comments.Find(input.CommentID)
if err != nil {
return nil, errors.New("Comment not found")
}
token := auth.TokenFromContext(ctx)
if !token.PermittedUser(comment.Author, "member", "story.edit") {
return nil, errors.New("You cannot remove this comment")
}
chapter, err := chapters.FindID(comment.ChapterID)
if err != nil {
return nil, errors.New("Comment's chapter not found")
}
if !chapter.CanComment() {
return nil, errors.New("Comments are disabled or locked")
}
err = comments.Remove(comment)
if err != nil {
return nil, errors.New("Failed to remove comment: " + err.Error())
}
go func() {
story, err := stories.FindID(chapter.StoryID)
if err != nil {
log.Println("WARNING: Couldn't log comment change:", err)
return
}
changes.Submit("Comment", "remove", token.UserID, true, changekeys.Many(comment, chapter, models.Story{ID: chapter.StoryID}), comment, chapter, story)
}()
return &comment, nil
}
+39
View File
@@ -0,0 +1,39 @@
package queries
import (
"context"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/files"
)
func (r *resolver) File(ctx context.Context, id string) (*models.File, error) {
file, err := files.FindID(id)
if err != nil {
return nil, err
}
return &file, nil
}
func (r *resolver) Files(ctx context.Context, filter *files.Filter) ([]models.File, error) {
token := auth.TokenFromContext(ctx)
if filter == nil {
filter = &files.Filter{}
}
// Only allow users to view public files that are not their own.
if token != nil {
if filter.Public == nil || *filter.Public == false {
filter.Author = &token.UserID
}
} else {
filter.Public = &trueValue
}
return files.List(filter)
}
var trueValue = true
+189
View File
@@ -0,0 +1,189 @@
package queries
import (
"context"
"errors"
"strings"
"time"
"git.aiterp.net/rpdata/api/models/channels"
"git.aiterp.net/rpdata/api/graph2/input"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/internal/loader"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/changekeys"
"git.aiterp.net/rpdata/api/models/changes"
"git.aiterp.net/rpdata/api/models/logs"
"github.com/99designs/gqlgen/graphql"
)
// Queries
func (r *resolver) Log(ctx context.Context, id string) (*models.Log, error) {
log, err := logs.FindID(id)
if err != nil {
return nil, err
}
return &log, nil
}
func (r *resolver) Logs(ctx context.Context, filter *logs.Filter) ([]models.Log, error) {
logs, err := logs.List(filter)
if err != nil {
return nil, err
}
reqCtx := graphql.GetRequestContext(ctx)
maybeCharacters := strings.Contains(reqCtx.RawQuery, "characters")
maybeChannels := strings.Contains(reqCtx.RawQuery, "channels")
if len(logs) >= 100 && (maybeCharacters || maybeChannels) {
loader := loader.FromContext(ctx)
if loader == nil {
return nil, errors.New("no loader")
}
for _, log := range logs {
if maybeChannels {
loader.PrimeChannels("name", log.ChannelName)
}
if maybeCharacters {
loader.PrimeCharacters("id", log.CharacterIDs...)
}
}
}
return logs, nil
}
// Mutations
func (r *mutationResolver) AddLog(ctx context.Context, input input.LogAddInput) (*models.Log, error) {
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.Permitted("log.add") {
return nil, errors.New("You are not permitted to add logs")
}
open := input.Open != nil && *input.Open == true
title := ""
if input.Title != nil {
title = *input.Title
}
event := ""
if input.Event != nil {
event = *input.Event
}
description := ""
if input.Description != nil {
description = *input.Description
}
existingChannel, _ := channels.FindName(input.Channel)
log, err := logs.Add(input.Date, input.Channel, title, event, description, open)
if !token.Authenticated() || !token.Permitted("log.add") {
return nil, errors.New("Failed to create log: " + err.Error())
}
go func() {
if existingChannel.Name == "" {
channel, err := channels.FindName(input.Channel)
if err == nil {
changes.Submit("Channel", "add", token.UserID, true, changekeys.Listed(channel), channel)
}
}
changes.Submit("Log", "add", token.UserID, true, changekeys.Listed(log), log)
}()
return &log, nil
}
func (r *mutationResolver) ImportLog(ctx context.Context, input input.LogImportInput) ([]models.Log, error) {
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.Permitted("log.add") {
return nil, errors.New("You are not permitted to add logs")
}
date := time.Time{}
if input.Date != nil {
date = *input.Date
}
tz := time.UTC
if input.Timezone != nil {
parsedTZ, err := time.LoadLocation(*input.Timezone)
if err != nil {
return nil, errors.New("Unknown timezone: " + *input.Timezone)
}
tz = parsedTZ
}
results, err := logs.Import(input.Importer, date, tz, input.ChannelName, input.Data)
if err != nil {
return nil, err
}
newLogs := make([]models.Log, 0, len(results))
for _, result := range results {
go func(result logs.ImportedLog) {
changes.Submit("Log", "add", token.UserID, true, changekeys.Many(result.Log), result.Log)
changes.Submit("Post", "add", token.UserID, true, changekeys.Many(result.Log, result.Posts), result.Posts)
}(result)
log, err := logs.UpdateCharacters(result.Log, nil)
if err != nil {
log = result.Log
}
newLogs = append(newLogs, log)
}
return newLogs, nil
}
func (r *mutationResolver) EditLog(ctx context.Context, input input.LogEditInput) (*models.Log, error) {
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.Permitted("log.edit") {
return nil, errors.New("You are not permitted to edit logs")
}
log, err := logs.FindID(input.ID)
if err != nil {
return nil, errors.New("Log not found")
}
log, err = logs.Edit(log, input.Title, input.Event, input.Description, input.Open)
if err != nil {
return nil, errors.New("Failed to edit log: " + err.Error())
}
go changes.Submit("Log", "edit", token.UserID, true, changekeys.Listed(log), log)
return &log, nil
}
func (r *mutationResolver) RemoveLog(ctx context.Context, input input.LogRemoveInput) (*models.Log, error) {
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.Permitted("log.remove") {
return nil, errors.New("You are not permitted to remove logs")
}
log, err := logs.FindID(input.ID)
if err != nil {
return nil, errors.New("Log not found")
}
log, err = logs.Remove(log)
if err != nil {
return nil, errors.New("Failed to remove log: " + err.Error())
}
go changes.Submit("Log", "remove", token.UserID, true, changekeys.Listed(log), log)
return &log, nil
}
+167
View File
@@ -0,0 +1,167 @@
package queries
import (
"context"
"errors"
"git.aiterp.net/rpdata/api/models/changekeys"
"git.aiterp.net/rpdata/api/models/changes"
"git.aiterp.net/rpdata/api/models/logs"
"git.aiterp.net/rpdata/api/graph2/input"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/posts"
)
// Queries
func (r *resolver) Post(ctx context.Context, id string) (*models.Post, error) {
post, err := posts.FindID(id)
if err != nil {
return nil, err
}
return &post, nil
}
func (r *resolver) Posts(ctx context.Context, filter *posts.Filter) ([]models.Post, error) {
// Some sanity checks to avoid querying an insame amount of posts.
if filter == nil {
filter = &posts.Filter{Limit: 256}
} else {
if (filter.Limit <= 0 || filter.Limit > 256) && filter.LogID == nil {
return nil, errors.New("a limit of 0 (no limit) or >256 without a logId is not allowed")
}
if len(filter.Kind) > 32 {
return nil, errors.New("You cannot specify more than 32 kinds")
}
if len(filter.ID) > 32 {
return nil, errors.New("You cannot specify more than 32 IDs")
}
}
return posts.List(filter)
}
// Mutation
func (r *mutationResolver) AddPost(ctx context.Context, input input.PostAddInput) (*models.Post, error) {
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.Permitted("post.add") {
return nil, errors.New("You are not permitted to edit logs")
}
log, err := logs.FindID(input.LogID)
if err != nil {
return nil, err
}
post, err := posts.Add(log, input.Time, input.Kind, input.Nick, input.Text)
if err != nil {
return nil, err
}
go logs.UpdateCharacters(log, nil)
go changes.Submit("Post", "add", token.UserID, true, changekeys.Many(log, post), post)
return &post, nil
}
func (r *mutationResolver) EditPost(ctx context.Context, input input.PostEditInput) (*models.Post, error) {
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.Permitted("post.edit") {
return nil, errors.New("You are not permitted to edit logs")
}
post, err := posts.FindID(input.ID)
if err != nil {
return nil, errors.New("Post not found")
}
if input.Nick != nil {
go func() {
log, err := logs.FindID(post.LogID)
if err != nil {
return
}
logs.UpdateCharacters(log, nil)
}()
}
post, err = posts.Edit(post, input.Time, input.Kind, input.Nick, input.Text)
if err != nil {
return nil, errors.New("Adding post failed: " + err.Error())
}
go func() {
log, err := logs.FindID(post.LogID)
if err != nil {
log = models.Log{ID: post.LogID}
}
changes.Submit("Post", "edit", token.UserID, true, changekeys.Many(log, post), post)
}()
return &post, nil
}
func (r *mutationResolver) MovePost(ctx context.Context, input input.PostMoveInput) ([]models.Post, error) {
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.Permitted("post.move") {
return nil, errors.New("You are not permitted to edit logs")
}
post, err := posts.FindID(input.ID)
if err != nil {
return nil, errors.New("Post not found")
}
posts, err := posts.Move(post, input.ToPosition)
if err != nil {
return nil, errors.New("Moving posts failed: " + err.Error())
}
go func() {
log, err := logs.FindID(post.LogID)
if err != nil {
log = models.Log{ID: post.LogID}
}
changes.Submit("Post", "move", token.UserID, true, changekeys.Many(log, posts), posts)
}()
return posts, nil
}
func (r *mutationResolver) RemovePost(ctx context.Context, input input.PostRemoveInput) (*models.Post, error) {
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.Permitted("post.remove") {
return nil, errors.New("You are not permitted to edit logs")
}
post, err := posts.FindID(input.ID)
if err != nil {
return nil, errors.New("Post not found (before removing, of course)")
}
post, err = posts.Remove(post)
if err != nil {
return nil, errors.New("Could not remove post: " + err.Error())
}
go func() {
log, err := logs.FindID(post.LogID)
if err != nil {
return
}
logs.UpdateCharacters(log, nil)
changes.Submit("Post", "remove", token.UserID, true, changekeys.Many(log, post), post)
}()
return &post, nil
}
+14
View File
@@ -0,0 +1,14 @@
package queries
type resolver struct{}
type mutationResolver struct{}
type subscriptionResolver struct{}
// Resolver has all the queries
var Resolver resolver
// MutationResolver brings the mutagens.
var MutationResolver *mutationResolver
// SubscriptionResolver has the real-time magic.
var SubscriptionResolver *subscriptionResolver
+186
View File
@@ -0,0 +1,186 @@
package queries
import (
"context"
"errors"
"time"
"git.aiterp.net/rpdata/api/models/changekeys"
"git.aiterp.net/rpdata/api/models/changes"
"git.aiterp.net/rpdata/api/graph2/input"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/chapters"
"git.aiterp.net/rpdata/api/models/stories"
)
func (r *resolver) Story(ctx context.Context, id string) (*models.Story, error) {
story, err := stories.FindID(id)
if err != nil {
return nil, err
}
return &story, nil
}
func (r *resolver) Stories(ctx context.Context, filter *stories.Filter) ([]models.Story, error) {
if filter != nil {
if filter.Unlisted != nil && *filter.Unlisted == true {
token := auth.TokenFromContext(ctx)
if !token.Authenticated() {
return nil, errors.New("You are not permitted to view unlisted stories")
}
if !token.Permitted("story.unlisted") {
filter.Author = &token.UserID
}
}
}
return stories.List(filter)
}
// Mutations
func (r *mutationResolver) AddStory(ctx context.Context, input input.StoryAddInput) (*models.Story, error) {
token := auth.TokenFromContext(ctx)
if token == nil || !token.Permitted("member", "story.add") {
return nil, errors.New("Permission denied")
}
author := token.UserID
if input.Author != nil && *input.Author != author {
if !token.Permitted("story.add") {
return nil, errors.New("You are not permitted to add a story in another author's name")
}
author = *input.Author
}
fictionalDate := time.Time{}
if input.FictionalDate != nil {
fictionalDate = *input.FictionalDate
}
listed := input.Listed != nil && *input.Listed
open := input.Open != nil && *input.Open
story, err := stories.Add(input.Name, author, input.Category, listed, open, input.Tags, time.Now(), fictionalDate)
if err != nil {
return nil, errors.New("Failed to add story: " + err.Error())
}
go changes.Submit("Story", "add", token.UserID, story.Listed, changekeys.Listed(story), story)
return &story, nil
}
func (r *mutationResolver) AddStoryTag(ctx context.Context, input input.StoryTagAddInput) (*models.Story, error) {
token := auth.TokenFromContext(ctx)
story, err := stories.FindID(input.ID)
if err != nil {
return nil, errors.New("Story not found")
}
if story.Open {
if !token.Permitted("member") {
return nil, errors.New("You are not permitted to edit this story")
}
} else {
if !token.PermittedUser(story.Author, "member", "story.edit") {
return nil, errors.New("You are not permitted to edit this story")
}
}
story, err = stories.AddTag(story, input.Tag)
if err != nil {
return nil, errors.New("Failed to add story: " + err.Error())
}
go changes.Submit("Story", "tag", token.UserID, story.Listed, changekeys.Listed(story), story, input.Tag)
return &story, nil
}
func (r *mutationResolver) RemoveStoryTag(ctx context.Context, input input.StoryTagRemoveInput) (*models.Story, error) {
token := auth.TokenFromContext(ctx)
story, err := stories.FindID(input.ID)
if err != nil {
return nil, errors.New("Story not found")
}
if story.Open {
if !token.Permitted("member") {
return nil, errors.New("You are not permitted to edit this story")
}
} else {
if !token.PermittedUser(story.Author, "member", "story.edit") {
return nil, errors.New("You are not permitted to edit this story")
}
}
story, err = stories.RemoveTag(story, input.Tag)
if err != nil {
return nil, errors.New("Failed to add story: " + err.Error())
}
go changes.Submit("Story", "untag", token.UserID, story.Listed, changekeys.Listed(story), story, input.Tag)
return &story, nil
}
func (r *mutationResolver) EditStory(ctx context.Context, input input.StoryEditInput) (*models.Story, error) {
token := auth.TokenFromContext(ctx)
story, err := stories.FindID(input.ID)
if err != nil {
return nil, errors.New("Story not found")
}
if !token.PermittedUser(story.Author, "member", "story.edit") {
return nil, errors.New("You are not permitted to remove this story")
}
if input.ClearFictionalDate != nil && *input.ClearFictionalDate {
input.FictionalDate = &time.Time{}
}
story, err = stories.Edit(story, input.Name, input.Category, input.Listed, input.Open, input.FictionalDate)
if err != nil {
return nil, errors.New("Failed to add story: " + err.Error())
}
go changes.Submit("Story", "edit", token.UserID, story.Listed, changekeys.Listed(story), story)
return &story, nil
}
func (r *mutationResolver) RemoveStory(ctx context.Context, input input.StoryRemoveInput) (*models.Story, error) {
token := auth.TokenFromContext(ctx)
story, err := stories.FindID(input.ID)
if err != nil {
return nil, errors.New("Story not found")
}
if !token.PermittedUser(story.Author, "member", "story.remove") {
return nil, errors.New("You are not permitted to remove this story")
}
story, err = stories.Remove(story)
if err != nil {
return nil, err
}
err = chapters.RemoveStory(story)
if err != nil {
return nil, errors.New("Failed to remove chapters, but story is removed: " + err.Error())
}
go changes.Submit("Story", "remove", token.UserID, story.Listed, changekeys.Listed(story), story)
return &story, nil
}
+12
View File
@@ -0,0 +1,12 @@
package queries
import (
"context"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/tags"
)
func (r *resolver) Tags(ctx context.Context) ([]models.Tag, error) {
return tags.List()
}
+18
View File
@@ -0,0 +1,18 @@
package queries
import (
"context"
"errors"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/models"
)
func (r *resolver) Token(ctx context.Context) (*models.Token, error) {
token := auth.TokenFromContext(ctx)
if !token.Authenticated() {
return nil, errors.New("No (valid) token")
}
return token, nil
}
+23
View File
@@ -0,0 +1,23 @@
package queries
import (
"context"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/unknownnicks"
"git.aiterp.net/rpdata/api/graph2/input"
)
/// Queries
func (r *resolver) UnknownNicks(ctx context.Context, filter *input.UnknownNicksFilter) ([]models.UnknownNick, error) {
limit := 100
if filter != nil {
if filter.Limit > 0 {
limit = filter.Limit
}
}
return unknownnicks.List(limit)
}
+172
View File
@@ -0,0 +1,172 @@
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
type Query {
# Find character by either an ID or a nick.
character(id: String, nick: String): Character!
# Find characters
characters(filter: CharactersFilter): [Character!]!
# Find channel by name
channel(name: String!): Channel!
# Find channels
channels(filter: ChannelsFilter): [Channel!]!
# Find post by ID.
post(id: String!): Post!
# Find posts
posts(filter: PostsFilter): [Post!]!
"List unknown nicks"
unknownNicks(filter: UnknownNicksFilter): [UnknownNick!]!
# Find log by ID
log(id: String!): Log!
# Find logs
logs(filter: LogsFilter): [Log!]!
# Find story chapter by ID
chapter(id: String!): Chapter!
# Find comment by ID
comment(id: String!): Comment!
# Find all distinct tags used in stories
tags: [Tag!]!
# Find story by ID
story(id: String!): Story!
# Find stories
stories(filter: StoriesFilter): [Story!]!
# Find file by ID
file(id: String!): File!
# Find files
files(filter: FilesFilter): [File!]!
# Find changes
changes(filter: ChangesFilter): [Change!]!
# Get information about the token, useful for debugging.
token: Token!
}
type Mutation {
# 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(input: StoryRemoveInput!): Story!
# Add a chapter to a story
addChapter(input: ChapterAddInput!): Chapter!
# Edit a chapter
editChapter(input: ChapterEditInput!): Chapter!
# Move a chapter
moveChapter(input: ChapterMoveInput!): Chapter!
# Remove a chapter
removeChapter(input: ChapterRemoveInput!): Chapter!
"Add a comment to a chapter."
addComment(input: CommentAddInput!): Comment!
"Edit a comment in a chapter."
editComment(input: CommentEditInput!): Comment!
"Remove a comemnt in a chapter."
removeComment(input: CommentRemoveInput!): Comment!
# Add a new log
addLog(input: LogAddInput!): Log!
# Import a log
importLog(input: LogImportInput!): [Log!]!
# Edit a log
editLog(input: LogEditInput!): Log!
# Remove a log
removeLog(input: LogRemoveInput!): Log!
# Add a post
addPost(input: PostAddInput!): Post!
# Edit a post
editPost(input: PostEditInput!): Post!
# Move a post. All affected posts will be returned.
movePost(input: PostMoveInput!): [Post!]!
# Remove a post
removePost(input: PostRemoveInput!): Post!
# Add a new character
addCharacter(input: CharacterAddInput!): Character!
# Add nick to character
addCharacterNick(input: CharacterNickInput!): Character!
# Remove nick from character
removeCharacterNick(input: CharacterNickInput!): Character!
# Edit character
editCharacter(input: CharacterEditInput!): Character!
# Remove a character
removeCharacter(input: CharacterRemoveInput!): Character!
# Add a channel
addChannel(input: ChannelAddInput!): Channel!
# Edit a channel
editChannel(input: ChannelEditInput!): Channel!
}
type Subscription {
"""
Changes subscribes to the changes matching the following keys.
"""
changes(keys: [ChangeKeyInput!]!): Change!
}
# A Date represents a RFC3339 encoded date with up to millisecond precision.
scalar Date
+81
View File
@@ -0,0 +1,81 @@
"""
A change represents a a change in the history.
"""
type Change {
"A unique ID of the change."
id: String!
"The model of the changed object."
model: ChangeModel!
"The operation performed."
op: String!
"The author that performed the change."
author: String!
"Whether the change will be listed without a matching key."
listed: Boolean!
"The keys indexing this change."
keys: [ChangeKey!]!
"The date when the change happened."
date: Date
"The changed objects."
objects: [ChangeObject!]!
}
union ChangeObject = Character | Channel | Log | Post | Story | Tag | Chapter | Comment
"""
A change key is a key associated with a change, used for filtering
and subscription. A log post edit has keys for both the log and post,
making it suitable to use it to patch an active log file.
"""
type ChangeKey {
"The model the key is for."
model: ChangeModel!
"The model's id, or * if it's a key indicating a model list should be updated."
id: String!
}
"""
See ChangeKey
"""
input ChangeKeyInput {
"The model the key is for."
model: ChangeModel!
"The model's id, or * if it's a key indicating a model list should be updated."
id: String!
}
"""
A model related to the change.
"""
enum ChangeModel {
Character
Channel
Log
Post
Story
Tag
Chapter
}
"""
Optional filter for the changes query.
"""
input ChangesFilter {
"The keys to query for."
keys: [ChangeKeyInput!]
"Only show changes more recent than this date."
earliestDate: Date
"Limit the amount of results."
limit: Int
}
@@ -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!
+115
View File
@@ -0,0 +1,115 @@
# 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: Date!
# The in-universe date and time of the chapter
fictionalDate: Date
# The date of edit.
editedDate: Date!
"The comment mode."
commentMode: ChapterCommentMode!
"Whether the author has locket comments."
commentsLocked: Boolean!
"A shorthand for checking whether a logged-in user can comment on this chapter."
canComment: Boolean!
"Get all chapter comments."
comments(limit: Int): [Comment!]!
}
"""
The possibles modes of comments for a chapter.
"""
enum ChapterCommentMode {
"Comments are disabled and hidden."
Disabled
"Comments should be shown as typical news article comments."
Article
"Comments should be shown in a compact form suitable for many small messages."
Chat
"Comments are going to be shown as omni-tool messages."
Message
}
# 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: Date
"The comment mode for the chapter."
commentMode: ChapterCommentMode
}
# 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: Date
# Remove the fictional date for a chapter
clearFictionalDate: Boolean
"Set the comment mode for the chapter"
commentMode: ChapterCommentMode
"Set whether the chapter comments are locked"
commentsLocked: Boolean
}
# Input for moveChapter mutation
input ChapterMoveInput {
# The chapter to move
id: String!
# The story to move it to
storyId: String!
}
# Input for removeChapter mutation
input ChapterRemoveInput {
# The chapter to remove
id: 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
@@ -50,6 +68,7 @@ input CharacterNickInput {
nick: String!
}
# Inpuit for editCharacter mutation
input CharacterEditInput {
# The id for the character to edit
id: String!
@@ -62,4 +81,11 @@ input CharacterEditInput {
# A short description for the character
description: String
}
}
# Input for removeCharacter mutation
input CharacterRemoveInput {
# The id for the character to edit
id: String!
}
+91
View File
@@ -0,0 +1,91 @@
"""
A Comment represents a comment to a story chapter.
"""
type Comment {
"A unique ID of the change."
id: String!
"subject"
subject: String!
"The chapter ID the comment belongs to."
chapterId: String!
"The comment's author."
author: String!
"The displayed name of the character. This may be the same as character.name, but it does not need to be."
characterName: String!
"The character associated with the comment."
character: Character
"The fictional (IC) date of the comment."
fictionalDate: Date
"The date of creation."
createdDate: Date!
"The date of the last edit."
editedDate: Date!
"The markdown source of the comment."
source: String!
}
"""
Input for the addComment mutation
"""
input CommentAddInput {
"What chapter to comment on."
chapterId: String!
"The markdown source of the comment."
source: String!
"The name of the character associated with this comment."
characterName: String!
"Optioanlly, a character ID."
characterId: String
"Optional in-universe date of the comment."
fictionalDate: Date
"Optional subject to add to the comment, only shown in some modes."
subject: String
}
"""
Input for the editComment mutation
"""
input CommentEditInput {
"What comment to edit."
commentId: String!
"Change the markdown source."
source: String
"Change the name of the character associated with this comment."
characterName: String
"Change the character assocaited with this comment."
characterId: String
"Change the optional in-universe date of the comment."
fictionalDate: Date
"Set to clear the fictional date."
clearFictionalDate: Boolean
"Change the optional subject for the comment, only shown in some modes."
subject: String
}
"""
Input for the removeComment mutation
"""
input CommentRemoveInput {
"What comment to edit."
commentId: String!
}
@@ -7,7 +7,7 @@ type File {
kind: String!
# The time of uploading
time: String!
time: Date!
# Whether the file is publicly listable. Someone with knowledge of the ID
# will still be able to view it, however.
@@ -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.
mimeType: [String!]
}
# Input for editFile mutation
input EditFileInput {
# The file's unique ID
@@ -8,7 +8,7 @@ type Log {
shortId: String!
# The date for a log.
date: String!
date: Date!
# The channel of a log.
channelName: String!
@@ -21,7 +21,7 @@ type Log {
# The log's event, which is the same as the tags in the previous logbot site.
# Empty string means that it's no event.
event: String!
eventName: String!
# The description of a session, which is empty if unset.
description: String!
@@ -50,17 +50,17 @@ input LogsFilter {
# Search post content
search: String
# Limit to only open logs
# Limit by whether it's open or not
open: Boolean
# Limit the amount of posts
# Limit the amount of results you get back
limit: Int
}
# Input for addLog mutation
input LogAddInput {
# The date of the log, in RFC3339 format with up to nanosecond precision
date: String!
# The date of the log.
date: Date!
# The channel where the log is set
channel: String!
@@ -96,37 +96,40 @@ input LogEditInput {
open: Boolean
}
# A LogHeader is a cut-down version of Log that does not allow getting the actual posts.
type LogHeader {
# A unique identifier for the log.
# Input for removeLog mutation
input LogRemoveInput {
# The id of the log to remove
id: String!
}
# A secondary unique identifier for the log. This is a lot shorter and more suitable for storage when
# the link doesn't need to be as expressive.
shortId: String!
# The date for a log.
date: String!
# The channel name of a log.
"""
Input for importLog mutation.
"""
input LogImportInput {
"The channel of the log, alwyas required."
channelName: String!
# Information about the channel this log is in.
channel: Channel!
# The session's title.
title: String!
"Which importer to use."
importer: LogImporter!
# The log's event, which is the same as the tags in the previous logbot site.
# Empty string means that it's no event.
event: String!
"The timezone of the import, all log and post dates will be treated as it. It will be UTC if none is specified."
timezone: String
# The description of a session, which is empty if unset.
description: String!
"The date of the log, if not provided in the log body."
date: Date
# Whether the log session is open.
open: Boolean!
# The characters involved in the log file.
characters: [Character!]!
"The log body itself."
data: String!
}
enum LogImporter {
"""
mIRC-like: This importer parses logs that copied from mIRC posts without spaces.
"""
MircLike
"""
Forum log: This importer parses the format on the forum. The displayed log, not the post source.
"""
ForumLog
}
@@ -7,7 +7,7 @@ type Post {
logId: String!
# The date and time of posting
time: String!
time: Date!
# The kind of post this is. Only "text", "scene" and "action" are RP, while others are annotations and 'commands'.
kind: String!
@@ -23,12 +23,12 @@ type Post {
}
# Input for the addPost mutation
input AddPostInput {
input PostAddInput {
# The log's ID that this post should be a part of
logId: String!
# The date and time of posting, in a RFC3339 format with up to a nanosecond's precision
time: String!
time: Date!
# The kind of post this is. Only "text", "scene" and "action" are RP, while others are annotations and 'commands'.
kind: String!
@@ -41,12 +41,12 @@ input AddPostInput {
}
# Input for the editPost mutation
input EditPostInput {
input PostEditInput {
# The Post ID
id: String!
# The date and time of posting, in a RFC3339 format with up to a nanosecond's precision
time: String
time: Date
# The kind of post this is. Only "text", "scene" and "action" are RP, while others are annotations and 'commands'.
kind: String
@@ -59,10 +59,24 @@ input EditPostInput {
}
# Input for the movePost mutation
input MovePostInput {
input PostMoveInput {
# The Post ID
id: String!
# Target index
toPosition: Int!
}
# Input for the removePost mutation
input PostRemoveInput {
# The Post ID
id: String!
}
# Filter for posts query
input PostsFilter {
id: [String!]
kind: [String!]
logId: String
limit: Int
}
@@ -25,14 +25,14 @@ type Story {
# The chapters of this story
chapters: [Chapter!]!
# The date the story was created (RFC3339 format).
createdDate: String!
# The date the story was created.
createdDate: Date!
# The date the story is set in (RFC3339 format).
fictionalDate: String!
# The date the story is set in.
fictionalDate: Date
# The date of the last major update to the story (RFC3339 format). This being bumped means a new chapter has been added.
updatedDate: String!
# The date of the last major update to the story. This being bumped means a new chapter has been added.
updatedDate: Date!
}
# Filter for stories query.
@@ -40,14 +40,23 @@ input StoriesFilter {
# What author to query for
author: String
# What category to query for
category: StoryCategory
# What tags to query for
tags: [TagInput!]
# If true, it will only show unlisted page you have access to
unlisted: Boolean
# Whether the page is open for additions by other users
open: Boolean
# The earliest fictionalDate
earliestFictionalDate: String
earliestFictionalDate: Date
# The latest fictionalDate
latestFictionalDate: String
latestFictionalDate: Date
# The max amount of stories to get (default: 30)
limit: Int
@@ -75,8 +84,8 @@ input StoryAddInput {
# Set which tags the story should have.
tags: [TagInput!]
# Set the fictional date of the story (RFC3339 format).
fictionalDate: String
# Set the fictional date of the story.
fictionalDate: Date
}
# Input for the editStory mutation
@@ -101,8 +110,11 @@ input StoryEditInput {
# 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
# Set the fictional date of the story.
fictionalDate: Date
# Clear the fictional date of the story.
clearFictionalDate: Boolean
}
# Input for the addStoryTag mutation
@@ -123,20 +135,26 @@ input StoryTagRemoveInput {
tag: TagInput!
}
# Input for the removeStory mutation
input StoryRemoveInput {
# What story to remove.
id: String!
}
# Possible values for Story.category
enum StoryCategory {
# Description and content of a document or item
Document
# General information
Info
# News stories
News
# Description and content of a document or item
Document
# Information about something going on in the background that may or may not inform RP
Background
# General information
Info
# A short story
Story
}
@@ -33,4 +33,5 @@ enum TagKind {
# None of the above, but it does still tie multiple stories together. The new story/chapter format may obsolete this tag kind.
Series
}
}
+8
View File
@@ -0,0 +1,8 @@
# Token represents the data parsed and validated from the bearer token.
type Token {
# The user represented in the token.
user: User!
# The permissions for the token.
permissions: [String!]!
}
+20
View File
@@ -0,0 +1,20 @@
"""
An unknown nick is a nick found by the logbot that's not associated with
any characters. Many are going to be false positives, but most should be
useful in pointing out which characters are missing.
"""
type UnknownNick {
"The IRC nick that's unknown."
nick: String!
"The amount of logs that contains the unknown nick."
score: Int!
}
"""
Input for the unknownNicks query.
"""
input UnknownNicksFilter {
"The highest amount of unknown nicks to return."
limit: Int!
}
+8
View File
@@ -0,0 +1,8 @@
# A user represents a member on the RPData platform.
type User {
# The user's unique ID / username
id: String!
# What the user is permitted to do.
permissions: [String!]!
}
+26
View File
@@ -0,0 +1,26 @@
package types
import (
"context"
"git.aiterp.net/rpdata/api/graph2/input"
"git.aiterp.net/rpdata/api/models"
)
type changeResolver struct{}
func (r *changeResolver) Objects(ctx context.Context, obj *models.Change) ([]input.ChangeObject, error) {
objects := obj.Objects()
results := make([]input.ChangeObject, 0, len(objects))
for _, object := range objects {
if changeObject, ok := object.(input.ChangeObject); ok {
results = append(results, changeObject)
}
}
return results, nil
}
// ChangeResolver is a resolver
var ChangeResolver changeResolver
+40
View File
@@ -0,0 +1,40 @@
package types
import (
"context"
"errors"
"time"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/comments"
)
type chapterResolver struct{}
func (r *chapterResolver) FictionalDate(ctx context.Context, chapter *models.Chapter) (*time.Time, error) {
if chapter.FictionalDate.IsZero() {
return nil, nil
}
return &chapter.FictionalDate, nil
}
func (r *chapterResolver) Comments(ctx context.Context, chapter *models.Chapter, limit *int) ([]models.Comment, error) {
limitValue := 0
if limit != nil {
if *limit < 0 {
return nil, errors.New("Limit cannot be negative")
}
limitValue = *limit
}
if !chapter.CommentMode.IsEnabled() {
return nil, nil
}
return comments.ListChapterID(chapter.ID, limitValue)
}
// ChapterResolver is a resolver
var ChapterResolver chapterResolver
+41
View File
@@ -0,0 +1,41 @@
package types
import (
"context"
"errors"
"time"
"git.aiterp.net/rpdata/api/internal/loader"
"git.aiterp.net/rpdata/api/models"
)
type commentResolver struct{}
func (r *commentResolver) Character(ctx context.Context, obj *models.Comment) (*models.Character, error) {
if obj.CharacterID == "" {
return nil, nil
}
loader := loader.FromContext(ctx)
if loader == nil {
return nil, errors.New("no loader")
}
character, err := loader.Character("id", obj.CharacterID)
if err != nil {
return nil, err
}
return &character, nil
}
func (r *commentResolver) FictionalDate(ctx context.Context, obj *models.Comment) (*time.Time, error) {
if obj.FictionalDate.IsZero() {
return nil, nil
}
return &obj.FictionalDate, nil
}
// CommentResolver is a resolver
var CommentResolver commentResolver
+41
View File
@@ -0,0 +1,41 @@
package types
import (
"context"
"errors"
"git.aiterp.net/rpdata/api/internal/loader"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/posts"
)
type logResolver struct{}
func (r *logResolver) Channel(ctx context.Context, log *models.Log) (*models.Channel, error) {
loader := loader.FromContext(ctx)
if loader == nil {
return nil, errors.New("no loader")
}
return loader.Channel("name", log.ChannelName)
}
func (r *logResolver) Characters(ctx context.Context, log *models.Log) ([]models.Character, error) {
loader := loader.FromContext(ctx)
if loader == nil {
return nil, errors.New("no loader")
}
return loader.Characters("id", log.CharacterIDs...)
}
func (r *logResolver) Posts(ctx context.Context, log *models.Log, kinds []string) ([]models.Post, error) {
return posts.List(&posts.Filter{
LogID: &log.ShortID,
Kind: kinds,
Limit: 0,
})
}
// LogResolver is a resolver
var LogResolver logResolver
+27
View File
@@ -0,0 +1,27 @@
package types
import (
"context"
"time"
"git.aiterp.net/rpdata/api/models/chapters"
"git.aiterp.net/rpdata/api/models"
)
type storyResolver struct{}
func (r *storyResolver) FictionalDate(ctx context.Context, story *models.Story) (*time.Time, error) {
if story.FictionalDate.IsZero() {
return nil, nil
}
return &story.FictionalDate, nil
}
func (r *storyResolver) Chapters(ctx context.Context, story *models.Story) ([]models.Chapter, error) {
return chapters.ListStoryID(story.ID)
}
// StoryResolver is a resolver
var StoryResolver storyResolver
+22
View File
@@ -0,0 +1,22 @@
package types
import (
"context"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/users"
)
type tokenResolver struct{}
func (r *tokenResolver) User(ctx context.Context, token *models.Token) (*models.User, error) {
user, err := users.Find(token.UserID)
if err != nil {
return nil, err
}
return &user, nil
}
// TokenResolver is a resolver
var TokenResolver tokenResolver
-156
View File
@@ -1,156 +0,0 @@
package resolver
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"
)
// 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) (*types.ChannelResolver, error) {
channel, err := channel.FindName(args.Name)
if err != nil {
return nil, err
}
return &types.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) ([]*types.ChannelResolver, error) {
channels, err := channel.List(args.Logged != nil && *args.Logged)
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
}
// 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
}
// 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
}
// RemoveChannel resolves the editChannel mutation
func (r *MutationResolver) RemoveChannel(ctx context.Context, args ChannelArgs) (*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
}
-161
View File
@@ -1,161 +0,0 @@
package resolver
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"
)
// ChapterArgs is args for chapter query
type ChapterArgs struct {
ID string
}
// Chapter implements 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
}
// AddChapterArgs is args for the addChapter mutation
type AddChapterArgs struct {
Input *struct {
StoryID string
Title string
Author *string
Source string
FictionalDate *string
}
}
// AddChapter implements 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
}
// EditChapterArgs is args for the editChapter mutation
type EditChapterArgs struct {
Input *struct {
ID string
Title *string
Source *string
FictionalDate *string
}
}
// EditChapter implements 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
}
// DeleteChapterArgs is args for the addChapter mutation
type DeleteChapterArgs struct {
ID string
}
// RemoveChapter implements the removeChapter mutation
func (r *MutationResolver) RemoveChapter(ctx context.Context, args *DeleteChapterArgs) (*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
}
-299
View File
@@ -1,299 +0,0 @@
package resolver
import (
"context"
"errors"
"strings"
"git.aiterp.net/rpdata/api/graphql/loader"
"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"
)
// CharacterArgs is an arg
type CharacterArgs struct {
ID *string
Nick *string
}
// Character resolver
func (r *QueryResolver) Character(ctx context.Context, args *CharacterArgs) (*types.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 &types.CharacterResolver{C: char}, nil
}
// CharactersArgs is an arg
type CharactersArgs struct {
IDs *[]string
Nicks *[]string
Author *string
}
// Characters resolves the characters query
func (r *QueryResolver) Characters(ctx context.Context, args *CharactersArgs) ([]*types.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([]*types.CharacterResolver, 0, len(chars))
for i := range chars {
if chars[i].ID == "" {
continue
}
resolvers = append(resolvers, &types.CharacterResolver{C: chars[i]})
}
return resolvers, nil
}
// 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
}
// 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 }) (*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
}
// RemoveCharacterNick resolves the removeCharacterNick mutation
func (r *MutationResolver) RemoveCharacterNick(ctx context.Context, args struct{ Input *CharacterNickInput }) (*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
}
// 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
}
// RemoveCharacter resolves the removeCharacter mutation
func (r *MutationResolver) RemoveCharacter(ctx context.Context, args struct{ ID string }) (*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
}
-15
View File
@@ -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")
-115
View File
@@ -1,115 +0,0 @@
package resolver
import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/model/file"
)
// FileArgs is an arg
type FileArgs struct {
ID string
}
// File implements 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
}
// 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) ([]*types.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([]*types.FileResolver, len(files))
for i := range files {
resolvers[i] = &types.FileResolver{F: files[i]}
}
return resolvers, nil
}
// 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
}
// RemoveFile resolves the removeFIle mutation
func (r *MutationResolver) RemoveFile(ctx context.Context, args *FileArgs) (*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
}
-229
View File
@@ -1,229 +0,0 @@
package resolver
import (
"context"
"time"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/model/change"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/model/log"
)
// LogArgs is an arg
type LogArgs struct {
ID *string
}
// Log finds log
func (r *QueryResolver) Log(ctx context.Context, args *LogArgs) (*types.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 &types.LogResolver{L: l}, nil
}
// LogQueryArgs is args for the logs query
type LogQueryArgs 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 *LogQueryArgs) ([]*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
}
// 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
}
// LogEditArgs is an input
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
}
// RemoveLog resolves the removeLog mutation
func (r *MutationResolver) RemoveLog(ctx context.Context, args *struct{ ID string }) (*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
}
-208
View File
@@ -1,208 +0,0 @@
package resolver
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"
)
// PostArgs is an arg
type PostArgs struct {
ID string
}
// Post implements 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
}
// PostsArgs is an arg
type PostsArgs struct {
IDs []string
}
// Posts implements 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
}
// 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
}
// 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
}
// 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
}
// PostRemoveArgs is an arg
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
}
-13
View File
@@ -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{}
-39
View File
@@ -1,39 +0,0 @@
package resolver
import (
"context"
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"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) (*types.SessionResolver, error) {
return &types.SessionResolver{S: session.FromContext(ctx)}, nil
}
// Login resolves mutation.login
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
}
// 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
}
-329
View File
@@ -1,329 +0,0 @@
package resolver
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"
)
// StoryArgs is args for story query
type StoryArgs struct {
ID string
}
// Story implements 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
}
// StoriesArg is args for stories query
type StoriesArg struct {
Filter *struct {
Author *string
Tags *[]TagInput
EarliestFictionalDate *string
LatestFictionalDate *string
Limit *int32
}
}
// Stories implements 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
}
// StoryAddArgs is args for the addStory mutation
type StoryAddArgs struct {
Input *struct {
Name string
Category string
Author *string
Open *bool
Listed *bool
Tags *[]TagInput
FictionalDate *string
}
}
// AddStory implements 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
}
// StoryTagAddArgs is args for the addStoryTag mutation
type StoryTagAddArgs struct {
Input *struct {
ID string
Tag TagInput
}
}
// AddStoryTag implements 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
}
// StoryTagRemoveArgs is args for the removeStoryTag mutation
type StoryTagRemoveArgs struct {
Input *struct {
ID string
Tag TagInput
}
}
// RemoveStoryTag implements 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
}
// 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 implements 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
}
// StoryRemoveArgs is args for the removeStory mutation
type StoryRemoveArgs struct {
ID string
}
// RemoveStory implements 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
}
-27
View File
@@ -1,27 +0,0 @@
package resolver
import (
"git.aiterp.net/rpdata/api/graphql/resolver/types"
"git.aiterp.net/rpdata/api/model/story"
)
// TagInput resolves the TagInput input
type TagInput struct {
Kind string
Name string
}
// Tags implements 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
}
-39
View File
@@ -1,39 +0,0 @@
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
}
-50
View File
@@ -1,50 +0,0 @@
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)
}
-45
View File
@@ -1,45 +0,0 @@
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
}
-59
View File
@@ -1,59 +0,0 @@
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
}
-117
View File
@@ -1,117 +0,0 @@
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
}
-45
View File
@@ -1,45 +0,0 @@
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)
}
-16
View File
@@ -1,16 +0,0 @@
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}
}
-80
View File
@@ -1,80 +0,0 @@
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)
}
-16
View File
@@ -1,16 +0,0 @@
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
}
-16
View File
@@ -1,16 +0,0 @@
package types
import "git.aiterp.net/rpdata/api/internal/session"
// UserResolver resulves the user type
type UserResolver struct{ U *session.User }
// ID resolves User.id
func (r *UserResolver) ID() string {
return r.U.ID
}
// Permissions resolves User.permissions
func (r *UserResolver) Permissions() []string {
return r.U.Permissions
}
-490
View File
@@ -1,490 +0,0 @@
// Code generated by go-bindata.
// sources:
// root.graphql
// types/change.graphql
// types/channel.graphql
// types/chapter.graphql
// types/character.graphql
// types/file.graphql
// types/log.graphql
// types/post.graphql
// types/session.graphql
// types/story.graphql
// types/tag.graphql
// types/user.graphql
// DO NOT EDIT!
package schema
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
type asset struct {
bytes []byte
info os.FileInfo
}
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
func (fi bindataFileInfo) Name() string {
return fi.name
}
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
func (fi bindataFileInfo) IsDir() bool {
return false
}
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var _rootGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x9c\x57\xcd\x6e\xeb\x38\x0f\xdd\xfb\x29\x18\x74\xd1\x14\x08\xf2\x00\xd9\xf5\xeb\xbd\xc5\x17\x20\x9d\xe9\x4c\x32\xab\xa2\xc0\x55\x6d\xc6\x26\xaa\x48\x1e\x51\x6e\x10\x5c\xdc\x77\x1f\x50\xf2\x8f\xec\x3a\x1d\x60\x76\xa6\x4c\x1e\x1e\x52\xf4\x61\x72\x03\x87\x0a\xe1\x8f\x06\xdd\x05\xfc\xa5\x46\x70\x58\x3b\x64\x34\x9e\xc1\x57\x62\xaa\x02\xd0\x78\x77\x81\xda\x92\x9c\x92\xf1\x36\xbc\xba\x7f\xde\xae\xb3\x10\x13\xc3\x7f\x66\x00\x37\xf0\x48\xa6\x80\xbc\x52\x4e\xe5\x1e\x1d\xbc\x5d\x00\xc9\x57\xe8\x40\x19\xd8\x7e\x03\xeb\x40\x81\xa1\xfc\x7d\x9d\xc1\xe0\xb6\xa4\x62\x03\x7b\xef\xc8\x94\xab\xf0\xb6\xb3\xee\x36\xf0\xd0\x39\x65\x33\xf8\x9c\x26\x00\x4d\xec\xc1\x1e\x81\x0a\x8e\x30\x1c\xf2\x19\x50\x8d\xaf\xac\x5b\xc3\xef\x46\x5f\xc0\x1a\x84\x5a\x39\x75\x42\x21\xa8\x3c\x28\xf0\x74\xc2\x94\x0f\x2f\xa9\xe0\x0d\xbc\x44\x12\x8b\xd7\x16\x6d\x74\x12\x31\x13\xa2\x2f\x3d\xd3\xc5\xeb\x22\x03\x18\xf3\x35\x06\xb5\x90\x35\xaa\x4b\x25\x27\x4b\x31\x3b\x8c\x45\xac\x56\xce\x93\x58\xa5\x75\xe7\xcd\x6b\xd8\xd7\x98\xd3\xf1\x42\xa6\x84\x1f\xda\x96\x25\x16\x1b\xf0\xae\xc1\x1f\xe0\x90\xbd\xa3\xbc\xbd\x37\x46\xe5\xf2\x0a\xbc\x05\x2b\x35\x47\xd7\xf5\x90\x98\x97\x5d\xf4\xff\xac\xd5\xa8\x4c\x5b\x80\xbc\x5b\xbc\x2e\x00\xc6\x05\x68\x5b\x0a\xf9\xed\xb7\x0c\xe4\x39\xb9\xaf\xbb\x0d\xec\x6c\x39\x76\xe5\xe8\xc5\xcb\x23\x69\x8f\x2e\x78\xf0\x63\x78\x96\x34\x3b\x5b\xfe\x1f\x55\x11\xfb\x94\x44\xd6\x96\x7d\xcc\x22\x44\xc5\x4a\xf2\x48\x73\x9e\x2d\xfb\x89\x3f\xc7\x00\x5e\xc3\xd6\xdf\x32\x9c\x50\x19\x2f\x55\x2b\xad\xed\x19\x6c\x18\x8c\x5a\x39\xcf\x32\x17\xd2\x98\xbf\xb6\xf2\x5a\x93\x79\x0f\x6e\x90\xeb\x86\x65\x0e\xec\x31\xe2\xad\x00\xd7\xe5\x1a\x8e\x61\x50\x9d\xb5\x27\x28\x90\x73\x47\xb5\x27\x6b\xc2\xb1\xaf\x30\x70\x78\x52\x75\x0d\x5c\xd9\x46\x17\x40\x1e\xf0\x43\xc6\x1d\x73\x7b\x42\x99\xa8\x8a\x4c\xb9\x86\x43\x45\x0c\x85\x45\x06\x63\x3d\x54\xea\x43\xde\xc5\xae\xac\x80\xc9\xe4\x08\x34\xe2\xfd\x86\xf0\x77\x83\x8e\xb0\x00\x32\x81\xb0\x34\xf2\x56\x6e\xb7\xb6\x86\xf1\x96\x81\x51\x63\x1e\xd8\x30\xfa\xae\x53\xd3\x89\x95\x76\xbd\x48\xbf\x26\x3d\x3e\x92\xc6\xfe\x26\xc5\x98\xb4\xf8\x91\x34\x76\xee\x1a\x19\x4a\xf4\x61\x02\xd5\x87\x22\xad\xde\x34\x86\x20\x69\xf7\x11\xc8\xe4\xba\x29\xf0\xb9\x79\xd3\x94\x03\x09\x33\xbf\x92\x56\x9c\x49\x6b\x70\xe8\x1b\x67\xa0\x8e\x6f\x43\x14\x28\x86\x33\x6a\xdd\xa6\xe6\xe5\x08\xa1\x1f\xc5\x15\x9c\xe8\x84\x87\x4b\x8d\x69\x45\x52\x90\x70\x9a\x14\xc4\xde\xba\x4b\x5f\x51\xb0\x26\x25\xed\xe5\x6c\x12\x41\xc8\xad\x37\xe1\x30\xa6\xfb\x68\x0f\x93\x1a\x42\x67\x13\xe6\x95\xaa\x5b\x7d\x0b\x89\x5b\x7b\x92\xfa\x21\x9e\x66\x93\xcf\xb9\x20\xf6\x64\x72\x0f\x5e\x95\x0c\x0d\xc7\xcb\x1e\x78\xc9\xf1\x06\x5e\x0e\xaa\x9c\xe4\xce\x1b\xe7\xd0\x78\x60\x64\x26\x6b\xa4\x84\xf8\xb4\x81\x7d\x7c\x58\x64\xbf\xb2\x2c\x8a\xfa\x53\xe3\x55\x18\x93\xa9\xae\x9f\x1d\x79\xfc\x57\x51\xef\xc3\xa3\xae\xdf\x17\x85\x28\x37\x9e\x07\x95\xcc\x00\x54\x51\x3c\x0c\x22\x6e\xea\xc6\x27\x82\x7d\x5f\x14\x5b\x39\x5a\xa4\x2a\xbe\xc8\x7a\x38\xd1\x54\x99\xf9\x6b\x80\xbf\x51\xfe\xfe\x09\x54\x0e\xe7\x50\x83\x62\xdd\xc0\x9f\x78\xb2\x1f\x18\xa1\x8f\xce\x9e\x46\xe0\x2e\xbc\xfc\x6f\xf8\x01\xfd\x7b\x41\x7e\x84\x88\x05\xf9\xeb\x0d\x10\xf7\xab\x58\x2d\x53\xf5\x15\xc3\xcf\xe3\xd4\x43\x24\x97\xd2\x4a\x7a\xdf\xbd\xb0\x58\x06\x2e\x62\x4e\xae\x22\xa8\x7c\x52\x52\x8a\xd1\x96\x34\x03\x32\x2d\x27\x41\x49\x8b\x69\x71\xfa\x52\xbe\xda\x73\xe3\x42\x64\xba\xb4\x2d\x63\x21\x3b\x59\x33\x31\xff\xce\x96\x69\x01\x3b\x5b\x8e\xc9\xc7\x18\x21\x3e\x0e\x1a\x11\x1e\xa2\x7a\xb2\x31\x2e\x12\xdd\x8d\xb6\xda\x10\x90\xd0\x13\x9d\x8d\xdc\x9e\xc3\x6e\x8a\x79\xee\xa3\xd9\xe7\x09\xa2\x3b\xa2\xd7\xc6\x09\xbf\x34\xf0\x7b\x6b\xcf\x45\x3e\x45\x82\x6d\xa4\xf0\x4b\x23\x9f\x5a\x7b\x2e\xb2\x2f\xae\x8d\x8d\xd5\x3d\xcf\x2f\xd3\xae\xbe\x96\xa8\x88\x72\x4b\xf4\x31\xac\x86\x81\xa8\xd8\x7d\xba\x20\xc3\x93\x74\x6d\x6c\x4c\xf7\x38\xbf\x58\xc6\xed\x0c\x42\x1a\xfb\xb9\x8f\xb2\x1d\xd3\x05\x23\xbd\xef\xa8\xc2\xdd\x17\x9e\x04\x8b\x50\x26\x00\x07\x55\x8e\x30\x0e\xaa\xbc\x0e\xd3\x13\x4f\x91\x22\xfb\x6b\x60\x31\xe4\x0a\x5e\xdb\xc2\xae\x28\xe9\xe1\xe7\xaa\x46\x03\xf9\x15\x9f\x31\x97\xb9\x85\xf6\x49\x02\xc2\x3a\x0a\x3f\x69\x92\xc6\x3e\x74\x6b\xa9\xff\x90\xeb\xcf\xc2\x5c\x4f\x04\xae\x87\x1b\xd4\x60\x06\x64\xaa\x06\xf5\xbc\xb4\xd5\x13\x61\xbb\xbe\x25\xbb\x8a\x76\xb6\x04\x32\xf1\x27\x24\x99\x65\xc3\xe8\x46\xea\xb1\x82\x5a\x31\x9f\xad\x1b\x37\xa5\xdb\x7f\x6d\x3f\x05\xc5\x36\x3e\xc2\xd8\xc6\x2f\xef\xc6\x3b\x92\xf3\x0a\x4f\x0a\x7e\x8a\xb7\xfc\xe2\xba\x6c\xe2\xdf\x18\xf9\xdc\xda\xcd\xb7\xe9\x77\x60\xf6\xeb\x9f\x00\x00\x00\xff\xff\x7a\x45\x45\x35\x28\x0d\x00\x00")
func rootGraphqlBytes() ([]byte, error) {
return bindataRead(
_rootGraphql,
"root.graphql",
)
}
func rootGraphql() (*asset, error) {
bytes, err := rootGraphqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "root.graphql", size: 3368, mode: os.FileMode(420), modTime: time.Unix(1531585622, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _typesChangeGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x34\xcc\x21\xae\x02\x31\x18\x85\x51\xdf\x55\x7c\x2f\xcf\xb3\x00\x1c\x41\xb1\x8c\x9f\xe9\x85\x36\x90\xb6\x99\xde\x11\x13\xc2\xde\x09\x02\x7b\xc4\xf9\xe7\xc4\xb9\x44\xbb\x8b\x3a\x09\x46\xac\xa6\xdf\x70\x11\xa5\x4e\xf7\x75\xc7\x25\xcc\x12\x8d\xab\xd8\xa6\x32\xee\x3c\xa4\xc1\xf2\xac\x6a\x9e\x6c\xe3\x4b\x39\xac\x43\xf2\x3e\xf4\x1b\x5f\x09\x6a\x3e\x72\x69\xfe\x4b\xef\x4f\x00\x00\x00\xff\xff\xac\xbf\xc1\xe6\x6b\x00\x00\x00")
func typesChangeGraphqlBytes() ([]byte, error) {
return bindataRead(
_typesChangeGraphql,
"types/change.graphql",
)
}
func typesChangeGraphql() (*asset, error) {
bytes, err := typesChangeGraphqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "types/change.graphql", size: 107, mode: os.FileMode(420), modTime: time.Unix(1525610729, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _typesChannelGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x90\x31\x4b\xc4\x40\x10\x85\xfb\xfd\x15\x4f\xae\xb0\xf1\x17\x5c\xa7\x87\x45\x1a\x0b\x15\x6c\x6f\x93\x9d\x64\x07\xf6\x66\x42\x76\x56\x10\xf1\xbf\x4b\xf6\x8c\xa7\x16\x72\x78\x95\x70\x55\xf2\xd8\x37\x8f\x8f\x6f\x85\x46\x7a\x9d\x76\xde\x58\x05\xbe\xd5\x62\xf0\x82\xe6\x7e\x83\x2e\x7a\x11\x4a\xce\x5e\x46\xc2\x66\x1f\xf0\xea\x80\x15\x1e\x23\x2d\xcf\x97\x19\xe2\x77\xe4\x50\x3f\x6b\x3c\xd8\xc4\x32\x5c\xb8\xda\x7b\x8a\x64\x91\x26\xd8\xa1\x8f\x1c\xb5\xa4\x80\x96\x90\x74\x18\x28\x38\x7c\xfc\xac\x71\xa3\x9a\xc8\xcb\x2f\xc7\x9c\xe1\x11\x4b\xfb\x09\x87\x39\xfd\xbc\x9c\xf1\xe8\x99\xc4\x2a\xd3\x15\x74\xc2\x56\x4a\x4a\x5b\x70\x0f\x51\xa1\x79\x27\x8f\xd4\x71\xcf\x15\xa0\x96\xef\xbe\xf0\x1f\x76\x92\x76\x7b\x37\x47\x4e\x2d\xfd\x6f\x6b\x6f\xce\xb1\x8c\xc5\x16\x8d\xd7\x21\x34\x35\x1f\xad\x13\x38\x41\xe8\x9f\x7d\xfe\x13\x9d\xb7\x81\xed\xec\xf3\x04\x9f\xef\x01\x00\x00\xff\xff\xed\x4a\x80\xd4\x06\x04\x00\x00")
func typesChannelGraphqlBytes() ([]byte, error) {
return bindataRead(
_typesChannelGraphql,
"types/channel.graphql",
)
}
func typesChannelGraphql() (*asset, error) {
bytes, err := typesChannelGraphqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "types/channel.graphql", size: 1030, mode: os.FileMode(420), modTime: time.Unix(1529172367, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _typesChapterGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x84\x54\xb1\x6e\xdb\x30\x10\xdd\xf9\x15\xaf\xf0\x90\x25\x75\xf7\x6e\x41\x92\xc1\x53\x87\x04\xe8\x7c\x31\xcf\xd2\xa1\xd2\x51\x20\x4f\x36\x84\xa2\xff\x5e\x90\x94\xa3\x54\xb1\xdd\x49\xc2\xe9\xdd\xe3\xdd\x7b\x4f\xdc\xe0\x01\x8f\x2d\x0d\xc6\x11\x92\x60\x2d\xa3\x27\x51\xec\x83\x1a\xab\xe1\x2d\xf8\x09\xe1\x00\x42\xb2\x10\xa7\xad\xb3\x69\xe0\xf7\x8e\xdf\x0e\xd8\xe0\xb5\x65\xec\x6b\xe5\x2e\x61\xf7\xe4\x00\xf1\xdf\xf1\x62\x51\xb4\xf9\xe2\x2e\x60\x4c\xac\x63\x87\xfa\xbc\x89\xa4\xd1\xda\x10\x1d\xe6\x97\x9b\xd8\x14\xc6\xb8\xcf\xb4\xf5\x65\x85\xfd\xd9\xb2\x96\xfd\xe6\x06\x9c\x28\x61\x08\xc9\xd8\x43\x54\x4c\xa8\x9b\x1c\xb0\x8f\x4c\xc6\xfe\x89\xec\xd2\x60\xa2\x5f\x47\x95\x23\xc7\xc4\xf0\x64\x0c\x52\x0f\x93\x9e\xb3\x46\x1f\xc8\x1d\x70\x90\xbd\x49\x50\xea\xae\x50\x95\xf6\x70\x00\x7b\xb1\xad\x43\x79\xae\xcf\xfd\xe3\xdc\x06\x3b\x1d\x46\xc3\x21\x44\x90\xf7\x67\xe5\xfb\xd1\x28\xd3\x3b\x29\x5f\xe7\xf2\x83\xf7\x15\xbd\x18\x53\x6c\x83\x85\xdc\x0c\x7a\x5f\xde\x42\xd6\x29\x7f\xdb\x5d\xb2\xaa\x18\x93\xbb\x1a\x39\xf2\xc7\xc5\xb6\xd8\x19\xf6\xa4\xd0\x60\x78\x63\x74\x7c\x30\x70\x3f\xd8\x74\xc3\xce\xea\x5d\xa6\x4b\x5c\x37\x59\x33\x9e\xa4\xeb\x30\xa6\x7a\x54\x17\x9a\xa6\x98\x92\x2b\x11\xbb\xa7\xfb\x22\x73\xd0\x62\xd0\xa6\x54\x13\x4e\x62\x2d\x68\x18\x62\x18\xa2\x64\x2d\x07\x8e\xbd\xa4\x24\x41\x53\x99\x30\x7b\x0b\x4a\x20\x0d\xd6\x72\x9c\xa7\xd8\xe2\xf9\x58\x83\xa0\xf7\x10\xbb\x4b\x0b\x6f\x1e\x4c\xfa\x21\x44\x13\x6d\xbe\xf5\xe1\x28\xda\x9c\x7f\x84\x2d\x5e\x5b\x39\xc7\x11\x4a\x3d\xd7\x99\x67\x19\xc6\xc4\x1e\xa7\x9c\xb0\x3c\x7c\xee\xcb\x8b\x14\xd6\x34\xbe\xcd\x63\x7d\x4a\xf1\xa2\x50\x4f\xf1\x97\x0f\x27\x9d\xa3\xbb\x68\x54\x4f\xbf\x96\xe9\x1f\x43\x8d\x58\x37\xdd\x83\x52\x92\x46\x41\x4b\xf0\x6a\xc4\x56\x72\x5f\x4b\xe6\x3a\x6b\x39\x8e\xb7\xc3\xf6\xec\xc5\xd6\x69\x5b\xe2\x55\x08\x2e\xdd\x04\x8f\x2d\x69\xf3\x4f\xa6\xae\x5d\x08\x9f\xf0\x57\x7e\xf0\x8a\x7b\x61\x2b\xa0\x0b\xeb\xd3\xff\x97\xff\x1b\x00\x00\xff\xff\x48\xf2\x61\xf4\x08\x05\x00\x00")
func typesChapterGraphqlBytes() ([]byte, error) {
return bindataRead(
_typesChapterGraphql,
"types/chapter.graphql",
)
}
func typesChapterGraphql() (*asset, error) {
bytes, err := typesChapterGraphqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "types/chapter.graphql", size: 1288, mode: os.FileMode(420), modTime: time.Unix(1530984073, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _typesCharacterGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x54\xbd\x72\xdc\x3c\x0c\xec\xf5\x14\xeb\x71\xf1\x35\xb6\xbf\xde\xdd\xc5\x4e\xe1\xc6\xc9\x38\xee\x32\x29\x60\x11\x3a\x61\x4c\x91\x0a\x01\xda\xa3\x78\xee\xdd\x33\x94\xee\xcf\x3a\x5d\x91\xee\x8e\x5c\x2c\x80\xdd\xa5\x2e\xb1\xc2\x5d\x4b\x89\x6a\xe3\x84\xc4\x7d\x62\xe5\x60\x0a\x0a\x78\xfa\x8e\x7a\x77\x55\xd9\xd0\xf3\x11\xf2\xa3\x02\x4a\x6d\x0e\xf2\x3b\x33\xc4\x71\x30\x69\x84\x13\x9a\x98\x60\x2d\x1f\x95\x02\xe2\x6e\xf1\xc3\x92\x84\xf5\x45\x35\x16\x3e\xb7\x8c\x3e\x49\x47\x69\xc0\xc3\xd3\x1d\x82\xd4\xaf\x78\x61\x1f\xc3\x5a\xc2\x1a\x16\x4f\x28\x0a\x62\x47\x32\x71\xac\xbc\xdf\xd7\x2a\x48\x35\xd6\x42\xc6\x0e\xef\x62\x2d\xac\x15\x3d\x21\xd0\x5b\xfc\xdc\xce\xf1\xeb\x68\x92\x3d\xec\x3f\x05\x65\x6b\x63\xc1\x4f\x3f\x16\xe6\x3e\x46\x07\xea\xb8\x70\x53\xc7\x0b\xc8\x72\x5c\x76\x71\xa2\xbd\xa7\x01\xef\x2d\x07\x68\x4f\x35\x43\x14\x5a\x53\xaa\xf9\x0a\x59\x33\x79\x3f\x8c\x1b\x37\x92\xd4\xfe\x5f\xcb\x1b\x87\x1d\xb7\xb6\x31\xd9\xe3\x69\x83\xd5\x74\x03\xc7\x5a\x27\xe9\x4d\x62\x40\x6c\x4e\x74\x3b\xba\x3e\x10\x6c\xaa\xea\x12\x0f\xa1\xcf\x36\xda\x45\xce\x15\xd5\xf7\x65\x5a\xc9\x78\xb7\xb7\x7b\xe5\xdc\x84\xfe\x38\xef\xde\x6e\xd9\xc4\x75\x5c\x07\xf9\xc3\x33\x0b\xf0\x32\xcc\x6c\xfc\x67\x4d\xbf\x95\x35\x28\xf8\x69\x73\x0e\xec\x46\xd4\x0d\xbe\x0c\x70\xdc\x50\xf6\x76\x05\x31\x64\x65\x3d\xc8\x09\x8b\xaf\x1c\x20\x61\x3c\x3a\xa7\xea\xd4\xe1\xfe\x48\xcc\x51\x99\xc3\x64\x37\xcb\x62\x7e\x9e\xcc\x0f\x57\xd0\x9e\x6b\x69\x06\x50\x88\xd6\x72\xda\x06\xe9\x06\xcf\x45\x8e\xc0\xec\x74\x82\x90\x47\xcf\xa9\x13\x55\x89\x41\x21\x0d\xc4\xca\xfa\xd1\x46\xca\x21\xe6\x84\xf8\x1e\xca\x36\x69\x3b\xf5\xe7\x4c\x2e\xd8\xf8\x58\x9c\xa0\xe0\x90\xb8\x8b\x6f\x3c\xfe\xed\xb2\x51\x99\x78\x6e\x6a\xb9\x9c\xbb\xfa\x70\xbf\x94\xa1\xc5\xe7\x3b\x9a\x6e\xb1\x74\x45\x4c\xdb\x86\x27\x0e\x6f\xaa\x79\xdb\xaf\x4e\x6c\xde\x56\xdc\xe9\x67\xa3\x70\xb3\x13\x3b\xd7\xbf\xc9\xde\x4f\xa1\x9b\x4f\x8c\xeb\xeb\xa2\xe2\x78\xa8\xe4\x29\x09\x85\x03\xfc\x62\x96\xad\xe5\x10\x4e\x6f\x6b\xca\x74\x4b\x56\xde\x6b\x56\x76\x25\x45\x75\xec\x7a\xaa\x0d\x5e\xd4\xf4\x7c\x94\x96\xde\xe7\xd2\xb7\x71\x29\x53\x9b\xbf\x01\x00\x00\xff\xff\x54\xc3\xda\x87\x97\x05\x00\x00")
func typesCharacterGraphqlBytes() ([]byte, error) {
return bindataRead(
_typesCharacterGraphql,
"types/character.graphql",
)
}
func typesCharacterGraphql() (*asset, error) {
bytes, err := typesCharacterGraphqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "types/character.graphql", size: 1431, mode: os.FileMode(420), modTime: time.Unix(1526204279, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _typesFileGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x52\xb1\x8e\x13\x31\x10\xed\xfd\x15\x8f\x4b\x41\x73\xca\x07\xa4\x03\x71\x48\x91\x48\xc3\x1d\xa2\xf6\x9e\x67\xe3\x51\x6c\xcf\x62\x8f\xb3\x0a\xa7\xfb\x77\x64\xef\x1e\x8a\x50\x0a\xe8\xa8\x56\x3b\x7e\xef\xcd\x9b\x37\xb3\xc1\x07\x7c\xe6\x40\x78\x96\xa4\x96\x53\x01\xa7\x51\x72\xb4\xca\x92\x60\x07\xa9\x0a\x8b\xb1\x21\x6c\x72\x98\x3d\x65\x82\x0a\x9c\xcc\x29\x88\x75\x60\xdd\x1a\xbd\x4c\xb4\xa8\xbc\x18\x60\x83\x27\x4f\x9d\xf2\xbe\xa0\x26\xfe\x51\x09\xfb\x4f\x06\x60\xb7\xc3\xa3\x66\x4e\xc7\x77\xe6\x37\xee\xc4\xc9\x41\xc6\x8e\xdf\xe2\x20\x45\x31\x73\x08\x18\x08\x77\x75\x6a\x2d\xee\xee\x31\x54\x45\x91\x48\x50\x6f\x15\x36\x13\x26\xc9\x4a\x0e\x63\x96\x08\xf5\x84\x99\x4f\xbc\x10\xbd\x3d\x13\x44\x3d\x65\x9c\x6d\xa8\x54\x30\x4a\x86\x7a\x2e\x5b\x83\xde\xee\x86\x0b\xe5\x48\xcd\xc5\xd2\x91\xd3\xd1\xa0\xd7\xfe\x80\x7e\xf7\xd4\x85\x75\x1d\x10\x5c\x30\xd5\x21\xf0\x73\xb8\x20\x70\x51\x3b\xb4\x29\x1e\x25\x92\xa4\x66\x4a\x3d\x4e\x49\xe6\x40\xee\xd8\xf5\x1b\xb1\x67\xb1\x59\xcc\x16\x5d\x67\x6d\xc4\x96\xeb\x99\x69\x06\xeb\x3d\xbc\xcc\x74\xa6\xdc\x3c\x2f\x1d\x76\xf8\x28\x12\xc8\xa6\x2b\xdb\x6b\xc8\xc9\x46\x32\xe8\x9f\x1b\xb3\x1d\xf6\x87\x07\xf4\x15\xad\x06\x1a\xcb\x00\x91\x23\x3d\x5d\xa6\x5b\x94\x55\xb7\xf0\x4f\x02\x27\x0c\x17\xa5\x62\xd0\xff\x77\xd8\x27\xbd\x82\x2e\x89\x51\x36\x80\xad\xea\x25\xdf\x90\xfb\xf6\xf5\xcb\xdb\xe5\x5c\x05\xe7\xa5\x28\x39\x03\xd4\x1c\xde\x48\xe6\xd5\x98\x0d\xf6\x69\xaa\xda\xd7\x46\x8e\xb5\x1f\x56\xac\xda\x4f\xd2\x70\x7f\x7b\x58\xeb\x0b\xf2\xc5\xfc\xc3\xd1\xfd\x17\x3b\xfc\x9b\x15\x9a\xd7\x5f\x01\x00\x00\xff\xff\xe4\xc2\x5b\xa8\x9d\x03\x00\x00")
func typesFileGraphqlBytes() ([]byte, error) {
return bindataRead(
_typesFileGraphql,
"types/file.graphql",
)
}
func typesFileGraphql() (*asset, error) {
bytes, err := typesFileGraphqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "types/file.graphql", size: 925, mode: os.FileMode(420), modTime: time.Unix(1528836115, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _typesLogGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x56\xc1\x6e\xdc\x46\x0c\xbd\xeb\x2b\x98\xf8\xe0\x04\x48\xd3\x83\x4f\xdd\x9b\x6b\x24\xa8\x81\xa0\x0d\x92\x00\x3d\x18\x39\xcc\x6a\xb8\x2b\x22\x23\x52\x99\xa1\x76\xb3\x08\xf2\xef\x05\x29\xc9\x5e\x29\x5b\x64\xd1\x43\x91\x83\x4f\x9e\x1d\x93\x8f\xe4\x23\xf9\x46\x17\x70\x0d\x6f\x64\x0b\x54\x40\x1b\x84\xa7\x1b\x4a\xf8\x14\x64\x03\x81\xe1\xdd\x5b\x28\x58\x0a\x09\x57\x7a\xe8\xd0\xed\xbe\x56\x00\xe6\xd3\x33\x7d\xee\x11\x28\x22\x2b\x6d\x08\x33\x6c\x24\x3b\x44\x92\xed\xcb\x0a\x80\xe2\x0a\xde\x6b\x26\xde\x3e\xa9\x46\x9f\x82\xb5\x70\x0c\xf9\xf0\x03\x6f\xf8\xd0\x50\xb1\x8c\x02\x24\x51\x28\x8d\x64\xc5\x0c\x81\x23\xb4\x92\x11\x4a\x4f\x1a\xd6\x09\xdd\xa9\xa8\xe4\xb0\x45\xd8\x37\xc8\x1e\xc7\x51\x88\x3f\x41\x14\x2c\x7c\xa9\xc0\x88\x11\x54\x60\x8d\x10\x0a\xe0\x97\x2e\x5b\x51\x3b\xb4\x2c\x1d\xfb\x76\x99\xea\x87\x06\x21\x06\x1d\x02\x84\xa9\x22\xbb\x39\x61\x58\x37\x81\x19\x93\x73\x36\x99\x8e\x77\x7f\x86\x76\xe9\x71\xcb\x1b\xc9\x6d\x50\x12\x86\xb0\x96\x5e\x3d\xdf\x09\x43\xad\xf0\x34\xb4\x83\xf8\x08\x69\x05\x37\xc3\xe1\x28\xf0\xd8\x9b\xcb\x02\x4a\x9a\xbc\x1c\x3f\x9c\xc8\x31\xc9\xf6\xb2\x00\xee\x90\xf5\x05\xec\x1b\xaa\x9b\xa9\xe1\x25\xb4\x4e\x8b\x9d\x35\x6c\x2d\xac\x9f\xbb\x8c\x3b\x92\xde\xb3\x59\x5b\x13\x48\x3d\xc2\x05\xbc\x6a\x3b\x3d\x40\xf1\x10\xd0\x62\x60\x73\x0e\x0a\xa4\x97\x05\x58\x86\x28\x66\xea\x87\x53\xcc\x62\xa9\x33\x75\x4e\x81\x93\x36\x16\x72\x94\x19\x7a\x0c\xda\x40\xcf\x05\x1d\xec\xc8\x69\x01\xf9\x77\x83\xda\xe0\xfd\xf4\x4c\x68\x06\x23\x1d\x3a\x89\xf6\x77\x05\xbf\x8b\x24\x0c\x3c\x6f\x5d\x0e\xb5\x62\xb6\xb2\x77\x92\x76\x18\xa7\xfa\x0d\xc9\x76\x61\xec\xc1\x68\xb6\x82\xbb\x9b\xe9\xc7\x93\x8f\x47\x48\x9d\x14\x2d\x56\xcd\xe8\x6b\xae\x53\x3d\x75\x60\x9b\xbd\x0d\x25\xc5\x8c\x11\xd6\x07\xf8\x44\x1c\x8b\x41\xbb\xdf\x33\xff\xb9\xba\x1b\xcb\xfa\xf8\x7c\x05\x77\x6f\xa5\xa8\x45\xf8\x56\x55\x17\xf0\xda\x5d\x7d\x1a\x93\x6c\x0b\x7c\xee\x31\x1f\x2a\xe2\xae\x57\x5b\xca\x32\xfe\x7f\xd8\xcd\x71\x50\x8a\x8d\x7c\xa2\x96\x14\x32\x96\x3e\xa9\x5f\x3c\x23\xae\x53\x6f\xd3\xff\xfc\x61\xb8\xac\xac\x29\x76\x05\x30\x74\xd9\x9a\x77\x0e\x86\x77\x79\x86\x30\x65\x31\x51\x7b\x12\x03\xbf\xcc\xf3\x78\x20\x78\x8e\xf3\x1e\x43\xae\x1b\xe7\x09\x6a\x61\x45\x56\x5b\x5b\xbf\x9d\x06\x61\xb0\x7c\xe3\x31\x54\x40\x38\x1d\xbc\xe5\x4e\xd6\xb2\xfd\x33\xe3\x06\x21\xb4\xd2\xb3\x5a\xeb\xbc\x17\x15\x0c\xc9\xae\xe0\x96\x75\x60\xff\xd6\x79\x76\x29\x88\xd1\x34\xb0\xed\xd5\x17\xf8\xa1\x03\xd7\x31\x0e\x56\x5f\xe7\xfa\xf1\x30\x10\x2f\x6c\xb0\xde\xbd\xbe\xb9\xba\xba\xfa\x0d\x06\x09\x80\x3d\x69\x03\x7d\x67\x39\x73\x60\x19\xe4\xd1\x36\xaf\x26\x17\xdd\x1f\x4a\xce\xbe\xc1\x8c\xf7\xe3\x4a\x05\x0a\xea\xb1\x66\xcc\x1c\xff\xf2\xe5\x09\x69\x35\x29\xc2\xa0\x16\x4b\xd5\x58\x1a\x2f\xd7\x6b\x5c\xab\xd3\xb4\x7e\x1f\xc3\xc7\x03\x38\xb4\xb8\x54\x84\xa5\xc7\xf5\x20\xc6\x4b\x79\x18\xe3\x9e\x96\x80\xb3\x1b\xf4\x2a\x92\x2e\x3b\x44\x71\x8e\xff\xdd\x8b\x75\x06\x4f\xe7\x96\xf9\x9f\x8a\x1b\x09\x42\xfe\xb5\x4e\x52\xf0\xc8\x7a\x4e\xbd\x93\xe0\xcf\xf8\x1f\x18\x22\xe6\xe1\xe9\xac\x7b\xfd\x25\xca\x9e\x61\x87\xb9\x8c\xf1\x8c\x1c\x17\x6b\x7b\x1b\x81\x45\x21\xa4\x24\x7b\xd8\xa2\xaa\xc9\xb9\x2f\x44\xad\x7d\x48\xc3\x36\xbc\xbc\x7f\xf8\x47\xe4\xc7\xe7\xff\xdf\x76\xd1\x7a\xff\xbf\x7d\x03\x8c\x22\xfd\xf8\x15\xf0\x53\x7c\x05\x7c\xab\xfe\x09\x00\x00\xff\xff\xd3\x31\x19\xf7\x46\x0b\x00\x00")
func typesLogGraphqlBytes() ([]byte, error) {
return bindataRead(
_typesLogGraphql,
"types/log.graphql",
)
}
func typesLogGraphql() (*asset, error) {
bytes, err := typesLogGraphqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "types/log.graphql", size: 2886, mode: os.FileMode(420), modTime: time.Unix(1531585338, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _typesPostGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x54\xc1\x6a\xdc\x40\x0c\xbd\xfb\x2b\x5e\x36\x87\xbd\x6c\x73\xc9\xa9\xb9\x85\xa6\x05\x43\x4b\x97\x34\x3f\x30\x99\xd1\x7a\x86\xb5\x25\x33\x92\x93\x2c\xa5\xff\x5e\x66\xbc\x21\x10\xbc\x87\x96\x04\xda\x93\x41\x7a\xa3\xf7\xa4\xf7\xf0\x39\xae\xb1\x15\x35\x24\x85\xc3\xe8\xb2\x41\x76\x70\xe8\xa5\x6b\xec\x30\xd2\xdc\xfc\xd9\x00\xe7\xb8\x8b\x84\x51\xd4\xd6\x8a\xf6\xa6\x01\x52\xb8\xc2\x0f\xcb\x89\xbb\xb3\xe6\x35\xe0\xab\x74\x68\x6f\x2e\x70\x17\x93\x96\xd9\x16\x09\xbe\x17\x25\x35\x58\x4c\xdc\xc1\xa4\xd0\x24\xde\xe3\xde\xf9\x3d\x34\xb1\xa7\xd2\x52\x5c\x6f\x5b\x74\xd9\x8d\x11\x41\x48\x79\x6d\x88\xee\x81\xe0\xf8\x00\x7f\xf0\x3d\xe9\x45\x83\x22\xb0\x5d\xe2\x0f\xce\x0a\x34\xc0\xd2\x40\x65\x97\x22\x28\x71\xd7\xa0\x56\x16\x5e\xec\x13\x87\x67\xe0\x2c\x20\xe9\x05\xbe\x73\x7f\xc0\xca\xe8\xc9\x56\x1b\xac\xd4\x13\xd3\xaa\xce\x5d\x39\x6f\x49\x78\x05\x97\x09\xb7\xdb\x0d\x1e\x63\xea\x09\x62\x91\xb2\xd6\xa2\x63\x16\x73\x05\xa4\xf5\xc5\xda\xcb\x30\x38\x0e\xba\x2e\xc2\x0b\xdd\x82\x0a\x1f\x5d\x76\xde\x28\x83\x93\xdf\x37\xa8\x9f\xd3\xf7\x2d\xc2\x2a\xb5\x8f\x18\xa7\x3c\x8a\x12\x02\x8d\xc4\x41\x21\x5c\xcf\x5d\x88\xca\xd6\xf4\x64\xa7\xe7\x8c\xa2\xa9\x28\xdd\x60\x52\x0a\xd8\x49\x46\x26\xc9\x81\xf2\x7c\xb3\xe7\xfe\x15\x5a\xb6\xb3\xe6\x57\xd3\x9c\xa3\xe5\x71\xb2\x0a\x2d\x34\x2e\x84\x9a\x91\x61\x9a\x77\x6e\x52\x6d\x5f\xcf\xe5\x19\xfb\x92\x9f\x5e\xba\x1a\x1f\x58\x74\xc7\x6b\xd7\xbb\x6b\x94\xa9\x0f\xb8\xa7\x97\x14\xfe\x85\xc9\x1b\x24\x86\xc3\xed\x97\x4f\x97\x97\x97\x1f\x8b\xc4\xc1\x19\x1e\x93\x45\x4c\xe3\x9c\x38\x76\x2c\x4a\x5e\x38\x94\xed\x33\xf9\xa4\x45\xf4\x7f\x95\x8e\x0d\x24\xe3\xf8\x0c\x89\x1f\x64\x4f\xb9\xfa\xc1\xc2\x1f\x6e\xb7\x50\x9b\x76\xbb\x77\x8b\xd0\x42\x06\x28\x24\x5b\x0a\xc1\xe7\x63\xfd\x75\x0a\x2a\xf6\xf4\x3f\xe4\xbd\xed\xfd\x27\xdc\x7d\x4b\x73\xdf\xc8\xdb\x25\x6b\x07\x79\xa0\x25\x6b\xbf\x1d\xeb\x7f\x62\xad\xcb\x1d\x19\x12\x07\x7a\x2a\xcc\xb2\x7d\xf5\x73\xf9\x1d\x00\x00\xff\xff\x60\x35\xc5\x5a\x8d\x06\x00\x00")
func typesPostGraphqlBytes() ([]byte, error) {
return bindataRead(
_typesPostGraphql,
"types/post.graphql",
)
}
func typesPostGraphql() (*asset, error) {
bytes, err := typesPostGraphqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "types/post.graphql", size: 1677, mode: os.FileMode(420), modTime: time.Unix(1530961143, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _typesSessionGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x44\xcc\x4b\x0a\x02\x31\x10\x06\xe1\x7d\x4e\x51\xe0\xd6\x13\x78\x0d\xf5\x00\x83\xfe\x4e\x02\xa1\x33\x74\x77\x16\x22\xde\x5d\x7c\x80\xdb\xe2\xa3\x76\x9c\xaa\x08\x45\xb4\x61\xb8\x36\x57\xc8\x32\xc8\x2a\x2e\xd3\x5d\x96\xf4\xb1\x36\x23\x72\x49\x95\xbc\x6f\xe2\xf8\xf3\x8f\x02\xdf\xc3\x0c\x39\x59\x97\xa4\xc5\xdb\xaf\xba\xd2\x6c\x8f\xcd\xde\x69\x37\x6c\xe4\x3f\x17\x3e\xfe\xc0\x39\xe4\xe5\xf9\x0a\x00\x00\xff\xff\x3a\x24\x24\xaf\x84\x00\x00\x00")
func typesSessionGraphqlBytes() ([]byte, error) {
return bindataRead(
_typesSessionGraphql,
"types/session.graphql",
)
}
func typesSessionGraphql() (*asset, error) {
bytes, err := typesSessionGraphqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "types/session.graphql", size: 132, mode: os.FileMode(420), modTime: time.Unix(1525515204, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _typesStoryGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x57\x4f\x6f\xe3\xb6\x13\xbd\xeb\x53\x4c\x90\xc3\xee\x02\x81\x7f\x3f\x20\xa7\xfa\x96\x4d\x9a\xc2\x97\x62\x91\x04\xe8\x61\xb1\x87\xb1\x38\x96\xd8\x50\xa4\x96\x1c\xc6\x35\x82\xfd\xee\xc5\x90\x94\x64\x3b\xb2\xd3\x43\x0b\xb4\x17\xc3\x12\x87\xf3\xf7\xbd\x47\xea\x12\x6e\xe0\x91\x9d\xdf\x81\x0e\x80\xd0\x6b\xaa\x09\xdc\x06\x6a\x67\x99\x2c\x03\xb7\xc8\x57\x10\xad\xd1\xcf\x04\x5b\xfd\xac\x01\x3d\xeb\xda\x50\xb8\x02\xf4\x04\xd6\x31\x04\xd7\x11\xb7\xda\x36\xc9\x1a\x42\xeb\xa2\x51\xb0\x26\x70\x5e\x91\x27\x05\xeb\x1d\xb0\xee\x08\xb4\x0d\x4c\xa8\xc4\x3f\x6b\x36\xb4\x80\x15\x43\x8d\xb6\xba\x4c\xf1\x50\x5b\xe8\xa2\x61\xdd\x1b\x82\xba\xc5\x9e\xc9\x07\xd9\xec\xac\xf8\x82\xce\x79\x02\x8c\xec\x5a\x1f\x2a\xde\xf5\x54\x32\x7f\xad\x00\x2e\xe1\xa9\x25\x08\xf2\xfc\x21\xc0\xea\xae\x02\xd0\x6a\x09\x8f\xec\xb5\x6d\x2e\xaa\x37\x16\x16\x3b\xba\x82\x6d\xab\xeb\x16\xb6\xda\x18\xc9\x7a\x0b\xb1\x07\x6d\x81\x5b\x02\xa3\x03\x87\x45\x05\xc9\xf0\x8c\x1f\x8c\xdc\x3a\x5f\x41\xf9\x73\x64\xf9\x5b\x4b\xdc\x92\x07\x97\x7e\x63\x90\x7a\x3a\xdc\x01\x2a\xf5\x3f\x52\x9a\xa7\x2a\xd9\xa5\xb0\xc9\xad\x84\x75\x3d\xd9\x25\x7c\x76\xce\x10\xda\x23\x6f\xd9\xcf\x56\x73\xeb\x22\x03\x82\xd2\x9e\x6a\x06\xa3\xed\xb3\x74\x13\x36\xda\x2a\xe0\x56\x87\xc9\x9d\x94\x43\xea\xd8\xa1\x14\x52\x23\x53\x23\x5d\x94\xa1\x0c\x19\x54\x30\xbe\x5f\xe6\x26\xdf\x96\xc7\xbd\x9d\x8c\x4d\x80\x8d\xf3\x39\x54\xd9\x26\x2f\x97\xf0\xf5\x09\x9b\x8b\x6f\xfb\x51\x86\x3a\x53\x94\x21\x33\x09\x53\x16\x96\xf0\xf5\x36\xff\x3d\xd8\xa7\x90\x69\x4a\x0b\xb6\x18\xa0\xf6\x84\x4c\x0a\x3e\x3e\xdc\xdf\x5e\x5f\x5f\xff\x24\x29\x74\xc8\x9f\xa4\xcc\xb2\x76\x87\x3c\x37\xb3\x23\x67\x92\x05\xb1\x0c\x7c\xce\xd5\x46\xd7\xac\x9d\x45\x73\xce\x59\xe9\x99\xc1\xc0\xd0\xe1\xef\xce\x43\xec\x73\x94\xbd\x71\xbe\x75\x0f\x4f\xd2\x82\x35\x09\x65\xd6\xb1\xeb\x49\x41\x47\x68\x85\x80\x96\xb6\x43\x4f\xa0\x45\x31\x22\x2b\x70\x21\x25\x49\x65\xef\x47\xf5\xfd\xa8\xaa\x4b\xb8\xd7\x46\xb6\xc8\x38\x24\xa8\xa6\x00\xdf\x23\xc9\xf0\xb5\xed\x23\xa7\x21\x6a\x0a\xc5\xec\xb5\xe0\x09\xb9\xe0\x56\x12\x4e\xf6\xe2\xe1\x0d\x9a\xab\xc9\x3c\x0d\xfd\xc8\x78\x9a\xf9\x4a\x62\x5d\x7c\x9b\xba\x44\xe8\x8d\xa6\xc0\x87\xed\xac\x60\x5c\xb8\x9f\x6b\xf3\xb4\xdf\x20\xcf\xed\xce\xaf\xdf\xd9\xdb\xe1\x1f\x80\x9d\x8b\x96\x65\x4e\x43\x57\xd8\x41\x43\x0c\x1f\x15\x6d\x30\x1a\x5e\xc2\xf5\xff\x3f\x25\x82\x74\x9a\x97\xb0\xb2\x9c\xfb\x99\x2a\x29\xe8\x26\x19\x40\x96\x9a\x2e\x32\x4a\xcc\xbd\xae\xee\x6e\x94\xca\xd6\xaf\x90\x62\x3f\x12\xa7\x4d\xa2\x1c\x07\xac\x9a\xd5\x1b\x0c\xc9\x20\xe9\xe1\x15\xa0\x55\xf2\x06\x21\xc4\xa6\xa1\x20\xa1\x4a\x93\x2f\x93\xd9\x46\xfb\x30\xaa\x46\x41\xd0\x88\x8e\x19\xa9\x1a\x72\x19\x59\x3e\x54\x24\x38\x1b\xd5\xe1\x3c\xd7\x6f\x94\xda\x03\x73\xb4\x8a\x3c\xa0\xcd\x92\x26\x21\x0b\x9c\x3d\x7d\x8f\xda\x53\xd8\xd3\x31\x54\x0a\x7a\xf2\x9d\x0e\x41\x3b\x9b\xab\x2b\xa7\x83\x9c\x1b\x6b\x4a\xfe\x71\x1d\x03\xa9\x45\x9a\x19\xa6\x89\xe6\x0e\xad\x09\x8c\x6b\x1a\x52\x49\xea\xf2\x1c\x6a\x8e\x68\x32\xcd\x9d\xff\x10\x92\x14\xc2\xea\x6e\x71\x02\xb2\x37\xc6\xb8\xed\x81\xfa\xb2\x83\xde\x85\xbf\x2a\xbc\x67\xbc\x04\xa2\xac\x64\x3d\x36\x72\xb2\x01\xda\x5d\x3e\x35\xe4\xa8\x0a\x84\xbe\x6e\xc1\x53\x88\x26\x9f\x23\x47\x0a\x3c\x8d\x27\x63\x22\xf3\x6a\xec\x72\xe9\x52\x8b\x2f\xb4\x38\xcd\xb0\x61\xba\x23\x3d\x0e\x54\xe9\x84\xf8\x9c\xd2\xb6\x39\xdc\xcb\x19\x75\x1a\xf8\x3f\x2b\xcd\x05\xf9\x93\x40\xe4\xa8\xec\xd2\xde\xb9\x53\xf8\x5f\x41\x8f\xbf\x81\x1d\xff\x79\x72\xdc\xb6\x68\x1b\x82\x6d\xb9\x55\xb0\x03\x9c\x80\x9e\x30\x2e\x29\x0e\x44\x99\xa7\xc6\x5b\x1f\x69\x70\xd3\x11\xbf\x7f\x9f\xca\x25\x1e\x30\xe3\x1c\x31\xfe\x51\x64\x0f\x8a\xfe\x84\xcd\x2c\xb6\x9f\xb0\x99\x74\x7d\x06\xdd\x58\xe6\xce\xd8\x00\xbb\xc5\xa9\xeb\x66\x5e\x16\xeb\x42\xe3\x25\x8c\x24\x9e\x4b\xcb\x53\xe7\x5e\xe8\xbd\xcc\x1e\x92\xd5\xe9\xe4\xb2\x97\x31\xbf\x8d\x77\xdd\x7b\x19\xe6\x2d\xa7\x92\xfc\xe2\x42\xd0\x6b\x43\xf0\x82\x26\x52\xbe\xf5\xa5\x64\x16\x03\x39\x2a\xb2\xb1\x3b\x24\x48\x49\xed\x8e\x42\xed\x75\x9f\xa0\x2b\x00\x18\xbe\x2c\xdc\x46\xae\xae\xae\x8e\x5d\x7a\xf2\xa0\x99\xba\x0a\xe0\xae\xbc\xca\x29\xfe\x4a\xdb\x30\x9c\xdd\x15\xa4\xc7\xbc\xb0\xb2\x79\xec\xc9\xed\x5a\x2e\xc2\xd3\x67\x48\xe3\xe4\xd7\xd9\x01\x7c\x6b\xac\x9f\x1b\xef\x62\xba\x16\x23\xa7\x2b\xb8\x7c\x4f\xe0\x2e\x51\x4d\x27\x57\xf0\xf0\xa5\x02\xf8\x3c\x9a\xe6\x30\xbf\x90\x25\x8f\xa6\xd8\xe4\x61\x40\x0a\x5e\xf8\x2f\x80\xf7\x3c\xde\x67\x53\x07\xaa\x1f\x7f\x06\x00\x00\xff\xff\x3b\xb1\x09\x16\x57\x0d\x00\x00")
func typesStoryGraphqlBytes() ([]byte, error) {
return bindataRead(
_typesStoryGraphql,
"types/story.graphql",
)
}
func typesStoryGraphql() (*asset, error) {
bytes, err := typesStoryGraphqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "types/story.graphql", size: 3415, mode: os.FileMode(420), modTime: time.Unix(1531585348, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _typesTagGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x52\x41\x73\xd5\x3c\x0c\xbc\xe7\x57\x6c\xa7\x87\xef\x92\xef\x71\xef\xad\xc3\x70\x60\x60\xe0\xd0\xfe\x01\x3d\x47\x89\x3d\xd8\x52\xc6\x52\x52\x1e\x4c\xff\x3b\x63\xa7\x8f\x02\x67\x38\xd9\x71\xa4\xd5\xee\x6a\x6f\x71\x8f\x47\x5a\x90\x0c\x84\xc2\x24\x06\x9d\x41\x66\x1a\x12\x79\x92\x05\xe6\x5a\x13\x1b\x3c\x92\x23\xd2\xce\x98\xd8\x29\x65\x43\x12\x04\x2d\x45\x05\x4f\xc9\x23\x54\x18\x24\xea\x91\xeb\x69\xf0\xcb\xca\x1d\xf8\xfb\x00\xdc\xe2\x31\x32\x9c\x96\xff\x0c\x5f\x92\x4c\x03\xfa\x71\xd7\x0a\x3e\x24\x99\x6e\x86\x3f\x8a\x84\x0a\x0f\xe8\xc7\x1d\x1e\xbc\x26\x59\x6e\x86\xe7\x61\xf8\x57\x6c\x93\xac\x9b\x37\xe4\xf7\xfd\xf2\xb7\x39\xe7\xac\x4f\x3c\x61\xa7\xbc\xb1\x61\xd6\xda\x40\x4e\x1d\x95\x65\x2b\x57\xc8\x97\xb9\xf7\x02\xad\x0b\x49\xfa\x46\x9e\x54\x0e\xb1\x81\x3c\x44\x50\xce\x70\xae\xa5\x63\x24\xf9\x7f\x93\xb4\x73\x35\x46\xd0\xba\x6a\xed\xf5\x36\xc2\x99\x8a\x8d\x58\xaa\x6e\xab\x8d\x08\x5b\x76\x1b\x5b\x4f\x60\x1b\xc1\x1e\x4e\xa7\xd3\x00\x7c\xfe\x65\xca\xa1\xe6\x1e\x21\x52\xa5\xe0\x5c\x9b\x2a\x58\xd4\x2d\x4f\x87\x8f\x1e\x19\xfc\x95\x82\x63\xde\x72\xee\x3a\x9b\xf9\xed\xf9\x67\x53\x43\x7d\x7b\xfd\xb8\x42\x66\x0d\xaf\x42\xe4\xe2\xb1\xed\x69\xae\x5a\x40\x58\x33\x09\x3b\x5c\x41\x02\x36\xa7\x73\x4e\x16\x0b\x8b\x9f\xf0\x18\x93\xa1\xd0\x05\xba\x73\xcd\xb4\x1e\x7b\xa3\xdf\xdd\x19\x41\x32\x21\xcd\x30\x1d\x71\x56\x8f\x7d\x68\xb3\xb6\x47\xc3\x69\xb1\xab\x8a\x33\x63\x33\x9e\x1a\xc7\x8f\x2f\x94\x86\xab\xe1\xbc\xb3\xf8\xe1\xf4\x9a\xd5\xa1\xb5\xdd\xa8\x7a\xcf\x57\x7f\x6b\x7d\xef\x5a\xd9\xd1\xf4\xa9\x05\xe8\x45\x3f\x9d\x75\xe7\x11\xe7\xcd\x91\x1c\x93\xb2\xc1\x3c\xb5\x5d\x25\x46\xd9\xb2\xa7\x35\xf3\x6b\x34\x75\xe1\x1e\xbb\x1e\x1e\xe1\xa7\xfe\xe7\xf2\x26\x44\x5a\x9b\xf1\xb3\xd6\x42\x7e\x28\x3f\x9b\x66\xf6\x66\x7e\xb2\xbe\x91\xa6\xac\x31\x79\xe0\x86\x35\x3c\xff\x08\x00\x00\xff\xff\x1f\xbf\x7a\x64\xc0\x03\x00\x00")
func typesTagGraphqlBytes() ([]byte, error) {
return bindataRead(
_typesTagGraphql,
"types/tag.graphql",
)
}
func typesTagGraphql() (*asset, error) {
bytes, err := typesTagGraphqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "types/tag.graphql", size: 960, mode: os.FileMode(420), modTime: time.Unix(1530963938, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _typesUserGraphql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4c\xcc\x41\x0a\xc2\x40\x0c\x85\xe1\x7d\x4e\xf1\x8a\xb7\xe8\x35\xd4\x95\xb8\x18\x6c\x6c\xdf\xa2\x99\x21\x89\x88\x88\x77\x97\x0e\x5d\x74\x19\xf2\xbd\xff\x84\xcb\xa2\xb8\x86\x3a\xf2\xd3\x14\x0c\x3c\xab\x83\x96\xea\xe5\x91\xb4\x19\x6f\xe6\x82\xd7\x26\x6a\x4b\x56\x0b\x14\x9b\x10\x9a\xdb\x37\xa4\xcf\x7a\xe0\x2b\x40\xef\xd1\xbb\xb7\xb2\xaa\x00\x9c\x46\x9c\xd3\x69\xf3\x20\x07\xd1\xd4\x57\x46\xb0\x9a\xe0\x70\xc4\x88\xdb\xae\xef\x83\xfc\xfe\x01\x00\x00\xff\xff\x74\x0a\x0a\x1c\xa0\x00\x00\x00")
func typesUserGraphqlBytes() ([]byte, error) {
return bindataRead(
_typesUserGraphql,
"types/user.graphql",
)
}
func typesUserGraphql() (*asset, error) {
bytes, err := typesUserGraphqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "types/user.graphql", size: 160, mode: os.FileMode(420), modTime: time.Unix(1525514069, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"root.graphql": rootGraphql,
"types/change.graphql": typesChangeGraphql,
"types/channel.graphql": typesChannelGraphql,
"types/chapter.graphql": typesChapterGraphql,
"types/character.graphql": typesCharacterGraphql,
"types/file.graphql": typesFileGraphql,
"types/log.graphql": typesLogGraphql,
"types/post.graphql": typesPostGraphql,
"types/session.graphql": typesSessionGraphql,
"types/story.graphql": typesStoryGraphql,
"types/tag.graphql": typesTagGraphql,
"types/user.graphql": typesUserGraphql,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"root.graphql": &bintree{rootGraphql, map[string]*bintree{}},
"types": &bintree{nil, map[string]*bintree{
"change.graphql": &bintree{typesChangeGraphql, map[string]*bintree{}},
"channel.graphql": &bintree{typesChannelGraphql, map[string]*bintree{}},
"chapter.graphql": &bintree{typesChapterGraphql, map[string]*bintree{}},
"character.graphql": &bintree{typesCharacterGraphql, map[string]*bintree{}},
"file.graphql": &bintree{typesFileGraphql, map[string]*bintree{}},
"log.graphql": &bintree{typesLogGraphql, map[string]*bintree{}},
"post.graphql": &bintree{typesPostGraphql, map[string]*bintree{}},
"session.graphql": &bintree{typesSessionGraphql, map[string]*bintree{}},
"story.graphql": &bintree{typesStoryGraphql, map[string]*bintree{}},
"tag.graphql": &bintree{typesTagGraphql, map[string]*bintree{}},
"user.graphql": &bintree{typesUserGraphql, map[string]*bintree{}},
}},
}}
// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}
-152
View File
@@ -1,152 +0,0 @@
# The Query type represents the read entry points into the API.
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 channel by name
channel(name: String!): Channel
# Find all channels. Specifying `logged: true` restricts the search to only logged.
channels(logged: Boolean): [Channel!]!
# Find log by ID
log(id: String): Log
# Find logs
logs(filter: LogsFilter): [LogHeader!]!
# 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. 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 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
session: Session!
}
# The Mutation type represents write entry points into the API.
type Mutation {
# Add a new character
addCharacter(input: CharacterAddInput!): Character!
# Add nick to character
addCharacterNick(input: CharacterNickInput!): Character!
# Remove nick from character
removeCharacterNick(input: CharacterNickInput!): Character!
# Edit character
editCharacter(input: CharacterEditInput!): Character!
# Remove a character
removeCharacter(id: String!): Character!
# Add a channel
addChannel(input: ChannelAddInput!): Channel!
# Edit a channel
editChannel(input: ChannelEditInput!): Channel!
# Remove a channel
removeChannel(name: String!): Channel!
# Add a new log
addLog(input: LogAddInput!): Log!
# Edit a log
editLog(input: LogEditInput!): Log!
# Remove a log
removeLog(id: String!): Log!
# Add a post
addPost(input: AddPostInput!): Post!
# Edit a post
editPost(input: EditPostInput!): Post!
# Move a post
movePost(input: MovePostInput!): Post!
# Remove a post
removePost(id: String!): Post!
# Edit a file
editFile(input: EditFileInput!): File!
# Remove a 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
login(username: String!, password: String!): Session!
# Log out
logout(): Session!
}
schema {
query: Query
mutation: Mutation
}
-21
View File
@@ -1,21 +0,0 @@
package schema
//go:generate go-bindata -ignore=\.go -pkg=schema -o=bindata.go ./...
import "bytes"
// String gets the schema
func String() string {
buf := bytes.Buffer{}
for _, name := range AssetNames() {
b := MustAsset(name)
buf.Write(b)
// Add a newline if the file does not end in a newline.
if len(b) > 0 && b[len(b)-1] != '\n' {
buf.WriteByte('\n')
}
}
return buf.String()
}
-4
View File
@@ -1,4 +0,0 @@
# A Change is a part of the history that can be used to keep clients up to date.
type Change {
id: Int!
}
-59
View File
@@ -1,59 +0,0 @@
# 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
}
-5
View File
@@ -1,5 +0,0 @@
# The session represents the current login state
type Session {
# The user that is logged in, null if not logged in
user: User
}
-8
View File
@@ -1,8 +0,0 @@
# The User type is for interacting with user options and settings
type User {
# Their username
id: String!
# Their permission
permissions: [String!]!
}
+109
View File
@@ -0,0 +1,109 @@
package auth
import (
"crypto/rand"
"encoding/binary"
"errors"
"strconv"
"git.aiterp.net/rpdata/api/internal/store"
"github.com/globalsign/mgo"
)
var keyCollection *mgo.Collection
// A Key contains a JWT secret and the limitations of it. There are two types of
// keys, single-user keys and wildcard keys. The former is used to authenticate
// a single user (e.g. the logbot) through an API while the latter is only for
// services that can be trusted to perform its own authentication (a frontend).
type Key struct {
ID string `bson:"_id"`
Name string `bson:"name"`
User string `bson:"user"`
Secret string `bson:"secret"`
}
// ValidForUser returns true if the key's user is the same as
// the user, or it's a wildcard key.
func (key *Key) ValidForUser(user string) bool {
return key.User == user || key.User == "*"
}
// FindKey finds a key by kid (key ID)
func FindKey(kid string) (Key, error) {
key := Key{}
err := keyCollection.FindId(kid).One(&key)
return key, err
}
// NewKey generates a new key for the user and name. This
// does not allow generating wildcard keys, they have to be
// manually inserted into the DB.
func NewKey(name, user string) (*Key, error) {
if user == "*" {
return nil, errors.New("auth: wildcard keys not allowed")
}
secret, err := makeKeySecret()
if err != nil {
return nil, err
}
key := &Key{
ID: makeKeyID(),
Name: name,
User: user,
Secret: secret,
}
if err := keyCollection.Insert(key); err != nil {
return nil, err
}
return key, nil
}
func init() {
store.HandleInit(func(db *mgo.Database) {
keyCollection = db.C("auth.keys")
keyCollection.EnsureIndexKey("user")
})
}
// makeKeyID makes a random story ID that's 16 characters long
func makeKeyID() string {
result := "K"
offset := 0
data := make([]byte, 32)
rand.Read(data)
for len(result) < 16 {
result += strconv.FormatUint(binary.LittleEndian.Uint64(data[offset:]), 36)
offset += 8
if offset >= 32 {
rand.Read(data)
offset = 0
}
}
return result[:16]
}
func makeKeySecret() (string, error) {
data := make([]byte, 64)
alphabet := "0123456789abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSUTVWXYZ-_"
_, err := rand.Read(data)
if err != nil {
return "", err
}
for i := range data {
data[i] = alphabet[data[i]%64]
}
return string(data), nil
}
@@ -1,16 +1,4 @@
package session
// DefaultPermissions gets the default permissions
func DefaultPermissions() []string {
return []string{
"member",
"log.edit",
"log.reorder",
"post.edit",
"post.move",
"file.upload",
}
}
package auth
// AllPermissions gets all permissions and their purpose
func AllPermissions() map[string]string {
@@ -30,8 +18,10 @@ func AllPermissions() map[string]string {
"post.edit": "Can edit posts",
"post.move": "Can move posts",
"post.remove": "Can remove posts",
"story.add": "Can add non-owned stories",
"story.edit": "Can edit non-owned stories",
"story.remove": "Can remove non-owned stories",
"story.unlisted": "Can view non-owned unlisted stories",
"file.upload": "Can upload files",
"file.edit": "Can edit non-owned files",
"file.remove": "Can remove non-owned files",
+154
View File
@@ -0,0 +1,154 @@
package auth
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"time"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/users"
jwt "github.com/dgrijalva/jwt-go"
)
var contextKey = &struct{ data string }{"Token Context Key"}
// ErrNoKid is returned if the key id is missing from the jwt token header,
var ErrNoKid = errors.New("Missing \"kid\" field in token")
// ErrKeyNotFound is returned if the key wasn't found.
var ErrKeyNotFound = errors.New("Key not found")
// ErrInvalidClaims is returned by parseClaims if the claims cannot be parsed
var ErrInvalidClaims = errors.New("Invalid claims in token")
// ErrExpired is returned by parseClaims if the expiry date is in the past
var ErrExpired = errors.New("Claims have already expired")
// ErrWrongUser is returned by CheckToken if the key cannot represent this user
var ErrWrongUser = errors.New("Key is not valid for this user")
// ErrWrongPermissions is returned by CheckToken if the key cannot claim one or more of its permissions
var ErrWrongPermissions = errors.New("User does not have these permissions")
// ErrDeletedUser is returned by CheckToken if the key can represent this user, but the user doesn't exist.
var ErrDeletedUser = errors.New("User was not found")
// TokenFromContext gets the token from context.
func TokenFromContext(ctx context.Context) *models.Token {
token, ok := ctx.Value(contextKey).(*models.Token)
if !ok {
return nil
}
return token
}
// RequestWithToken either returns the request, or the request with a new context that
// has the token.
func RequestWithToken(r *http.Request) *http.Request {
header := r.Header.Get("Authorization")
if header == "" {
return r
}
if !strings.HasPrefix(header, "Bearer ") {
return r
}
token, err := CheckToken(header[7:])
if err != nil {
return r
}
return r.WithContext(context.WithValue(r.Context(), contextKey, &token))
}
// CheckToken reads the token string and returns a token if everything is kosher.
func CheckToken(tokenString string) (token models.Token, err error) {
var key Key
jwtToken, err := jwt.Parse(tokenString, func(jwtToken *jwt.Token) (interface{}, error) {
if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", jwtToken.Header["alg"])
}
kid, ok := jwtToken.Header["kid"].(string)
if !ok {
return nil, ErrNoKid
}
key, err = FindKey(kid)
if err != nil || key.ID == "" {
return nil, ErrKeyNotFound
}
return []byte(key.Secret), nil
})
if err != nil {
return models.Token{}, err
}
userid, permissions, err := parseClaims(jwtToken.Claims)
if err != nil {
return models.Token{}, err
}
if !key.ValidForUser(userid) {
return models.Token{}, ErrWrongUser
}
user, err := users.Ensure(userid)
if err != nil {
return models.Token{}, ErrDeletedUser
}
acceptedPermissions := make([]string, 0, 8)
for _, permission := range permissions {
found := false
for _, userPermission := range user.Permissions {
if permission == userPermission {
found = true
break
}
}
if found {
acceptedPermissions = append(acceptedPermissions, permission)
}
}
return models.Token{UserID: user.ID, Permissions: acceptedPermissions}, nil
}
func parseClaims(jwtClaims jwt.Claims) (userid string, permissions []string, err error) {
mapClaims, ok := jwtClaims.(jwt.MapClaims)
if !ok {
return "", nil, ErrInvalidClaims
}
if !mapClaims.VerifyExpiresAt(time.Now().Unix(), true) {
return "", nil, ErrExpired
}
if userid, ok = mapClaims["user"].(string); !ok {
return "", nil, ErrInvalidClaims
}
if claimedPermissions, ok := mapClaims["permissions"].([]interface{}); ok {
for _, permission := range claimedPermissions {
if permission, ok := permission.(string); ok {
permissions = append(permissions, permission)
}
}
}
if len(permissions) == 0 {
return "", nil, ErrInvalidClaims
}
return
}
+1
View File
@@ -14,6 +14,7 @@ var global *Config
// Config is configuration
type Config struct {
Space struct {
Enabled bool `json:"enabled"`
Host string `json:"host"`
AccessKey string `json:"accessKey"`
SecretKey string `json:"secretKey"`
@@ -30,6 +30,24 @@ func Next(category, name string) (int, error) {
return doc.Value, nil
}
// NextMany gets the next value of a counter, or an error if it hasn't, and increments by a specified value.
// Any value `returned` to `returned+(increment-1)` should then be safe to use.
func NextMany(category, name string, increment int) (int, error) {
id := category + "." + name
doc := counter{}
_, err := collection.Find(bson.M{"_id": id}).Apply(mgo.Change{
Update: bson.M{"$inc": bson.M{"value": increment}},
Upsert: true,
ReturnNew: true,
}, &doc)
if err != nil {
return -1, err
}
return doc.Value, nil
}
func init() {
store.HandleInit(func(db *mgo.Database) {
collection = db.C("core.counters")
+101
View File
@@ -0,0 +1,101 @@
package forumlog
import (
"bufio"
"fmt"
"strings"
"time"
"git.aiterp.net/rpdata/api/internal/importers/mirclike"
"git.aiterp.net/rpdata/api/models"
)
// A ParsedLog contains the parsed log header and its posts.
type ParsedLog struct {
Log models.Log
Posts []models.Post
}
// ParseLogs parses the logs from the data.
func ParseLogs(data string, tz *time.Location) ([]ParsedLog, error) {
metadata := ParseMetadata(data)
results := make([]ParsedLog, 0, len(metadata["Date"]))
scanner := bufio.NewScanner(strings.NewReader(data))
for i, dateStr := range metadata["Date"] {
// Parse date
date, err := time.ParseInLocation("January 2, 2006", dateStr, tz)
if err != nil {
return nil, fmt.Errorf("Failed to parse date #%d: %#+v is not the in the correct format of \"January 2, 2006\"", i+1, dateStr)
}
// Parse posts
posts := make([]models.Post, 0, 128)
parsing := false
prev := ""
prevPost := models.Post{}
for scanner.Scan() {
line := strings.Trim(scanner.Text(), "\r\t  ")
// Skip lines with links to other logs using the --> and <-- notation.
if strings.HasPrefix(line, "-") || strings.HasPrefix(line, "<") {
prev = line
continue
}
// If parsing and reaching a double empty-line, the session is done.
if parsing && len(line) < 2 && len(prev) < 2 {
break
}
// Skip empty lines, but record them as thep revious for the above check.
if len(line) < 2 {
prev = line
continue
}
// If not parsing, skip until the first mirclike post.
if !parsing {
if strings.HasPrefix(line, "[") {
parsing = true
} else {
prev = line
continue
}
}
// Parse the post.
post, err := mirclike.ParsePost(line, date, prevPost)
if err != nil {
summary := ""
for _, ru := range line {
summary += string(ru)
if len(summary) > 60 {
summary += "..."
break
}
}
return nil, fmt.Errorf("Failed to parse post: %s", summary)
}
posts = append(posts, post)
prevPost = post
prev = line
}
// No posts means there's a problem.
if len(posts) == 0 {
return nil, fmt.Errorf("Session %d (%s) has no posts (too many dates?)", i+1, dateStr)
}
// Add it.
results = append(results, ParsedLog{
Log: models.Log{Date: posts[0].Time},
Posts: posts,
})
}
return results, nil
}
+151
View File
@@ -0,0 +1,151 @@
package forumlog_test
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"git.aiterp.net/rpdata/api/internal/importers/forumlog"
"git.aiterp.net/rpdata/api/models"
)
func TestParseLogs(t *testing.T) {
results, err := forumlog.ParseLogs(testLog, time.UTC)
if err != nil {
t.Fatalf("Parse: %s", err)
}
assert.Equal(t, 2, len(results), "Amount of results.")
assert.Equal(t, testLogPosts[0], results[0].Posts, "First log's posts.")
assert.Equal(t, testLogPosts[1], results[1].Posts, "Second log's posts.")
}
func TestParseLogsErrors(t *testing.T) {
_, err1 := forumlog.ParseLogs(brokenLogNoPosts, time.UTC)
_, err2 := forumlog.ParseLogs(brokenLogBrokenPost, time.UTC)
_, err3 := forumlog.ParseLogs(brokenLogBrokenDate, time.UTC)
t.Log("Should be about no posts:", err1)
t.Log("Should be about a broken post:", err2)
t.Log("Should be about a broken date:", err3)
assert.NotEqual(t, err1, nil, "Should be no posts")
assert.NotEqual(t, err2, nil, "Should be about a bad post")
assert.NotEqual(t, err3, nil, "Should be about a broken date")
}
var testLog = `
Date:
July 25, 2013
July 26, 2013
GM:
Tyranniac
Length:
92 posts
Characters:
Fera'Sel nar Veltar - Baphomet
Renala T’Iavay - Gisle
Steven Briggs - Dante
NPCs:
Receptionist
Relevant RPs:
<-- Hub - Miner's Respite (July 16)
--> Event - Hinpinn's Salvage Mission (August 5)
[21:28] * @Tyranniac | The Hnipinn Minerals local administrative center is a rather small building located next to the refinery. The area is mostly asphalt and industrial surroundings. The gate in the fence surrounding the refinery is nearby, with a transport truck just being admitted by the guard - rather heavily armed for corporate security. Despite not being that late, it's rather dark due to the rain-bearing clouds that have just started emptying their content. The windows of the office glow with invitingly warm light. A receptionist can be seen working through the glass door.
[21:46] * Steve_Briggs approaches the building at a brisk jog, pulling up the collar of his jacket to block as much of the rain as possible. Stopping just short of the door, he holds back and awaits Renala. "Well, hopefully this is the universe's way of getting our bad luck out of the way early." he says, glancing up at the sky. "Good things to come!" he adds with a smile.
[21:46] * Steve_Briggs approaches the building at a brisk jog, pulling up the collar of his jacket to block as much of the rain as possible. Stopping just short of the door, he holds back and awaits Renala. "Well, hopefully this is the universe's way of getting our bad luck out of the way early." he says, glancing up at the sky. "Good things to come!" he adds with a smile.
--> Stuff and things
<-- Stuff and things
[21:46] * Steve_Briggs approaches the building at a brisk jog, pulling up the collar of his jacket to block as much of the rain as possible. Stopping just short of the door, he holds back and awaits Renala. "Well, hopefully this is the universe's way of getting our bad luck out of the way early." he says, glancing up at the sky. "Good things to come!" he adds with a smile.
`
var testLogPosts = [][]models.Post{
{
{
ID: "UNASSIGNED",
LogID: "UNASSIGNED",
Time: parseDate(nil, "2013-07-25 21:28:00"),
Kind: "action",
Nick: "Tyranniac",
Text: "| The Hnipinn Minerals local administrative center is a rather small building located next to the refinery. The area is mostly asphalt and industrial surroundings. The gate in the fence surrounding the refinery is nearby, with a transport truck just being admitted by the guard - rather heavily armed for corporate security. Despite not being that late, it's rather dark due to the rain-bearing clouds that have just started emptying their content. The windows of the office glow with invitingly warm light. A receptionist can be seen working through the glass door.",
Position: 1,
},
{
ID: "UNASSIGNED",
LogID: "UNASSIGNED",
Time: parseDate(nil, "2013-07-25 21:46:00"),
Kind: "action",
Nick: "Steve_Briggs",
Text: `approaches the building at a brisk jog, pulling up the collar of his jacket to block as much of the rain as possible. Stopping just short of the door, he holds back and awaits Renala. "Well, hopefully this is the universe's way of getting our bad luck out of the way early." he says, glancing up at the sky. "Good things to come!" he adds with a smile.`,
Position: 2,
},
},
{
{
ID: "UNASSIGNED",
LogID: "UNASSIGNED",
Time: parseDate(nil, "2013-07-26 21:46:00"),
Kind: "action",
Nick: "Steve_Briggs",
Text: `approaches the building at a brisk jog, pulling up the collar of his jacket to block as much of the rain as possible. Stopping just short of the door, he holds back and awaits Renala. "Well, hopefully this is the universe's way of getting our bad luck out of the way early." he says, glancing up at the sky. "Good things to come!" he adds with a smile.`,
Position: 1,
},
{
ID: "UNASSIGNED",
LogID: "UNASSIGNED",
Time: parseDate(nil, "2013-07-26 21:46:00"),
Kind: "action",
Nick: "Steve_Briggs",
Text: `approaches the building at a brisk jog, pulling up the collar of his jacket to block as much of the rain as possible. Stopping just short of the door, he holds back and awaits Renala. "Well, hopefully this is the universe's way of getting our bad luck out of the way early." he says, glancing up at the sky. "Good things to come!" he adds with a smile.`,
Position: 2,
},
},
}
var brokenLogNoPosts = `
Date:
September 27, 2014
`
var brokenLogBrokenPost = `
Date:
September 10, 2038
[12:38] * Some_Character is lasered by a reaper, and turns to ash before even knowing what happened.
[12:4 * Some_Other_Character also meets the same fate, because the Reapers are no joke.
`
var brokenLogBrokenDate = `
Date:
10 September, 2038
[12:34] * =Scene= Stuff happens.
`
func parseDate(t *testing.T, date string) time.Time {
result, err := time.Parse("2006-01-02 15:04:05", date)
if err != nil {
if t != nil {
t.Fatal("Could not parse date", date, err)
} else {
panic("Could not parse date: " + err.Error())
}
}
return result
}
+35
View File
@@ -0,0 +1,35 @@
package forumlog
import (
"bufio"
"strings"
)
// ParseMetadata parses metadata, discards the broken parts, and returns the
// parsed data as a map (`m`) and the position of the first IRC post (`n`)
func ParseMetadata(data string) map[string][]string {
result := make(map[string][]string)
key := ""
scanner := bufio.NewScanner(strings.NewReader(data))
for scanner.Scan() {
line := strings.Trim(scanner.Text(), "\r\t  ")
if strings.HasPrefix(line, "[") {
break
}
if len(line) < 1 {
key = ""
continue
}
if key == "" {
split := strings.Split(line, ":")
key = split[0]
} else {
result[key] = append(result[key], line)
}
}
return result
}
@@ -0,0 +1,81 @@
package forumlog_test
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"git.aiterp.net/rpdata/api/internal/importers/forumlog"
)
func TestParseMetadata(t *testing.T) {
data := forumlog.ParseMetadata(testData1)
assert.Equal(t, data["Date"], []string{
"August 11, 2014",
"August 12, 2014",
"August 13, 2014",
})
assert.Equal(t, data["Location"], []string{
"Aroste, an island off the Derraian coastline.",
})
assert.Equal(t, data["GM"], []string{
"Gisle",
})
assert.Equal(t, data["Length"], []string{
"847 posts",
})
assert.Equal(t, len(data["Stuff"]), 0)
for key := range data {
if strings.HasPrefix(key, "[") {
t.Error("It should have stopped at the IRC post, yet this key exists:", key)
}
}
}
var testData1 = `
Date: August 2185
August 11, 2014
August 12, 2014
August 13, 2014
Location:
Aroste, an island off the Derraian coastline.
GM:
Gisle
Length:
847 posts
Characters:
Calyx Vadris - Osiris
Damien Monroe - Bowe
Jason Wolfe - Dante
Renala T'Iavay - Gisle
Victoria Steels - MCB280
NPCs:
Recurring:
Halisi - Tyranniac
Jattic - Dante
Jelvan Darennon - Gisle
Leah - Dante
Marissa T'Evin - Gisle
Philip Lacour - Tyranniac
One-off:
Reidas Falten (Turian - Guard meeting them on their trip to the building)
Sarian T'Dera (Asari - Asari opening fire on Calyx, Leah and Renala)
Mariam Adams (Human - The receptionist.)
Prerix Falten (Turian2 - Victoria's chair)
Jonathan Lyng (OtherHuman - The one opening fire on Leah entering the garage)
Alerena Teris (Teris - Asari met in the maintenance corridor)
<-- Redorck Agency - August 11, 2014
[13:19] * Leah looks over at Damien. "Haven't seen you around the agency." she says curiously, "New hire?"
`
+48
View File
@@ -0,0 +1,48 @@
package mirclike
import (
"errors"
"strings"
"time"
"git.aiterp.net/rpdata/api/models"
)
// ErrEmptyLog is returned by ParseLog if there are no (valid) posts in the log.
var ErrEmptyLog = errors.New("No valid posts found in log")
// ParseLog parses the log and returns the things that can be gleamed from them.
func ParseLog(data string, date time.Time, strict bool) (models.Log, []models.Post, error) {
lines := strings.Split(data, "\n")
posts := make([]models.Post, 0, len(lines))
prev := models.Post{}
for _, line := range lines {
line = strings.Trim(line, "\r\t  ")
if len(line) < 1 {
continue
}
post, err := ParsePost(line, date, prev)
if err != nil {
if strict {
return models.Log{}, nil, err
}
continue
}
posts = append(posts, post)
prev = post
}
if len(posts) == 0 {
return models.Log{}, nil, ErrEmptyLog
}
log := models.Log{
Date: posts[0].Time,
}
return log, posts, nil
}
+69
View File
@@ -0,0 +1,69 @@
package mirclike_test
import (
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"git.aiterp.net/rpdata/api/internal/importers/mirclike"
"git.aiterp.net/rpdata/api/models"
)
var testLog = strings.Join([]string{
"[11:21] * Va`ynna_Atana returns to the apartment at the end of another long day. She hangs up her jacket and a green scarf next to the door before going straight to the bathroom; she leaves the door open, though. She walked home with Uvena this time, but the two parted ways downstairs.",
"",
"[11:21] <=Scene=> The weather outside is not pleasant in the slightest. The storm is still going on, visibility is reduced and the worst gusts of wind can be felt inside the apartment as a faint shudder. ",
"",
"[11:",
" [11:27] <Test> Stuff and things.",
}, "\r\n")
var testLogPosts = []models.Post{
{
ID: "UNASSIGNED",
LogID: "UNASSIGNED",
Time: parseDate(nil, "2018-05-11 11:21:00"),
Kind: "action",
Nick: "Va`ynna_Atana",
Position: 1,
Text: "returns to the apartment at the end of another long day. She hangs up her jacket and a green scarf next to the door before going straight to the bathroom; she leaves the door open, though. She walked home with Uvena this time, but the two parted ways downstairs.",
},
{
ID: "UNASSIGNED",
LogID: "UNASSIGNED",
Time: parseDate(nil, "2018-05-11 11:21:00"),
Kind: "scene",
Nick: "=Scene=",
Position: 2,
Text: "The weather outside is not pleasant in the slightest. The storm is still going on, visibility is reduced and the worst gusts of wind can be felt inside the apartment as a faint shudder.",
},
{
ID: "UNASSIGNED",
LogID: "UNASSIGNED",
Time: parseDate(nil, "2018-05-11 11:27:00"),
Kind: "text",
Nick: "Test",
Position: 3,
Text: "Stuff and things.",
},
}
func TestParseLog(t *testing.T) {
log, posts, err := mirclike.ParseLog(testLog, parseDate(t, "2018-05-11 00:00:00"), false)
if err != nil {
t.Fatal("ParseLog", err)
}
assert.Equal(t, testLogPosts, posts)
assert.Equal(t, posts[0].Time, log.Date, "Log's date should be the first post's.")
}
func TestParseLogErrors(t *testing.T) {
_, _, err1 := mirclike.ParseLog("\n\n\n\n\n \n\t\r\n", time.Time{}, false)
_, _, err2 := mirclike.ParseLog("\n\n\n\n\n[14:57]* Stuff \n\t\r\n", time.Time{}, true)
assert.Equal(t, mirclike.ErrEmptyLog, err1)
assert.Equal(t, mirclike.ErrNotPost, err2)
}
+98
View File
@@ -0,0 +1,98 @@
package mirclike
import (
"errors"
"strconv"
"strings"
"time"
"git.aiterp.net/rpdata/api/models"
)
// ErrNotPost is returned by parsePost if the line is empty or not a post.
var ErrNotPost = errors.New("not a post")
// ParsePost parses a post from a mirc-like line. If the previous post is included (it can be empty), it will be used
// to determine whether midnight has passed.
func ParsePost(line string, date time.Time, prev models.Post) (models.Post, error) {
// Do basic validation
line = strings.Trim(line, "  \t\n\r")
if len(line) == 0 || !strings.HasPrefix(line, "[") {
return models.Post{}, ErrNotPost
}
// Parse timestamp
tsEndIndex := strings.IndexByte(line, ']')
if tsEndIndex == -1 || len(line) < tsEndIndex+5 {
return models.Post{}, ErrNotPost
}
tsStr := line[1:tsEndIndex]
tsSplit := strings.Split(tsStr, ":")
tsUnits := make([]int, len(tsSplit))
if len(tsSplit) < 2 {
return models.Post{}, ErrNotPost
}
for i := range tsSplit {
n, err := strconv.Atoi(tsSplit[i])
if err != nil {
return models.Post{}, ErrNotPost
}
tsUnits[i] = n
}
if len(tsUnits) == 2 {
tsUnits = append(tsUnits, 0)
}
// Determine timestamp from parsed data and previous post.
ts := time.Date(date.Year(), date.Month(), date.Day(), tsUnits[0], tsUnits[1], tsUnits[2], 0, date.Location())
if !prev.Time.IsZero() && prev.Time.Sub(ts) > 30*time.Minute {
ts = time.Date(prev.Time.Year(), prev.Time.Month(), prev.Time.Day()+1, tsUnits[0], tsUnits[1], tsUnits[2], 0, date.Location())
}
if line[tsEndIndex+2] == '*' {
split := strings.SplitN(line[tsEndIndex+4:], " ", 2)
if len(split) == 1 {
return models.Post{}, ErrNotPost
}
post := models.Post{
ID: "UNASSIGNED",
LogID: "UNASSIGNED",
Time: ts,
Kind: "action",
Nick: strings.TrimLeft(split[0], "+@!~"),
Text: split[1],
Position: prev.Position + 1,
}
if post.Nick[0] == '=' {
post.Kind = "scene"
}
return post, nil
} else if line[tsEndIndex+2] == '<' {
split := strings.SplitN(line[tsEndIndex+2:], " ", 2)
if len(split) == 1 {
return models.Post{}, ErrNotPost
}
post := models.Post{
ID: "UNASSIGNED",
LogID: "UNASSIGNED",
Time: ts,
Kind: "text",
Nick: strings.TrimLeft(split[0][1:len(split[0])-1], "+@!~"),
Text: split[1],
Position: prev.Position + 1,
}
if post.Nick[0] == '=' {
post.Kind = "scene"
}
return post, nil
} else {
return models.Post{}, ErrNotPost
}
}
+130
View File
@@ -0,0 +1,130 @@
package mirclike_test
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"git.aiterp.net/rpdata/api/internal/importers/mirclike"
"git.aiterp.net/rpdata/api/models"
)
func TestParsePost(t *testing.T) {
table := []struct {
Input string
TS string
Kind string
Nick string
Text string
}{
{
"[12:34] * Stuff does things.",
"12:34:00", "action", "Stuff", "does things.",
},
{
"[12:34] <Stuff> Things said.",
"12:34:00", "text", "Stuff", "Things said.",
},
{
"[13:36:59] <Stuff> Things said.",
"13:36:59", "text", "Stuff", "Things said.",
},
{
"[23:59] <=Scene=> Scenery and such.",
"23:59:00", "scene", "=Scene=", "Scenery and such.",
},
{
"[01:10:11] * =Scene= Scenery and such from the forum or mIRC using my old script.",
"01:10:11", "scene", "=Scene=", "Scenery and such from the forum or mIRC using my old script.",
},
}
for i, row := range table {
t.Run(fmt.Sprintf("Row_%d", i), func(t *testing.T) {
post, err := mirclike.ParsePost(row.Input, time.Now(), models.Post{})
if err != nil {
t.Fatal("Could not parse post:", err)
}
assert.Equal(t, row.TS, post.Time.Format("15:04:05"), "Timestamps should match.")
assert.Equal(t, row.Kind, post.Kind, "Kinds should match.")
assert.Equal(t, row.Nick, post.Nick, "Kinds should match.")
assert.Equal(t, row.Text, post.Text, "Kinds should match.")
})
}
}
func TestParsePostErrors(t *testing.T) {
table := []struct {
Input string
Err error
}{
{"[12:34] <Stuff> Things said.", nil},
{"[12:34] >Stuff> Things said.", mirclike.ErrNotPost},
{"12:34] <Stuff> Things said.", mirclike.ErrNotPost},
{"* Stuff Things said.", mirclike.ErrNotPost},
{"", mirclike.ErrNotPost},
{"[12:34 <Stuff> Things said.", mirclike.ErrNotPost},
{"[TE:XT] <Stuff> Things said.", mirclike.ErrNotPost},
{"[10] <Stuff> Things said.", mirclike.ErrNotPost},
{"[12:34:56:789] <Stuff> Things said.", nil},
{"[12:34:56.789] <Stuff> Things said.", mirclike.ErrNotPost},
{"[12:34] <Stuff>", mirclike.ErrNotPost},
{"[12:34] * Stuff", mirclike.ErrNotPost},
{"[12:34] =Scene=", mirclike.ErrNotPost},
{"[12:34] <=Scene=>", mirclike.ErrNotPost},
}
for i, row := range table {
t.Run(fmt.Sprintf("Row_%d", i), func(t *testing.T) {
_, err := mirclike.ParsePost(row.Input, time.Now(), models.Post{})
assert.Equal(t, row.Err, err, "Error should match")
})
}
}
func TestParseNextDay(t *testing.T) {
table := []struct {
Prev time.Time
TS string
Time time.Time
}{
{Prev: parseDate(t, "2019-01-12 12:00:00"), TS: "12:01", Time: parseDate(t, "2019-01-12 12:01:00")},
{Prev: parseDate(t, "2019-01-12 12:00:00"), TS: "11:53:13", Time: parseDate(t, "2019-01-12 11:53:13")},
{Prev: parseDate(t, "2019-04-08 23:51:59"), TS: "00:09", Time: parseDate(t, "2019-04-09 00:09:00")},
{Prev: parseDate(t, "2019-01-12 12:00:00"), TS: "11:29:59", Time: parseDate(t, "2019-01-13 11:29:59")},
}
for i, row := range table {
t.Run(fmt.Sprintf("Row_%d", i), func(t *testing.T) {
input := fmt.Sprintf("[%s] * Stuff does things.", row.TS)
post, err := mirclike.ParsePost(input, row.Prev, models.Post{Time: row.Prev})
if err != nil {
t.Fatal("Could not parse post:", err)
}
assert.Equal(t, row.Time, post.Time)
})
}
}
func parseDate(t *testing.T, date string) time.Time {
result, err := time.Parse("2006-01-02 15:04:05", date)
if err != nil {
if t != nil {
t.Fatal("Could not parse date", date, err)
} else {
panic("Could not parse date: " + err.Error())
}
}
return result
}
func formatDate(date time.Time) string {
return date.UTC().Format("2006-01-02 15:04:05")
}
@@ -5,42 +5,54 @@ import (
"errors"
"strings"
"git.aiterp.net/rpdata/api/model/channel"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/channels"
"github.com/graph-gophers/dataloader"
)
// Channel gets a character by key
func (loader *Loader) Channel(key, value string) (channel.Channel, error) {
func (loader *Loader) Channel(key, value string) (*models.Channel, error) {
if !strings.HasPrefix(key, "Channel.") {
key = "Channel." + key
}
if loader.loaders[key] == nil {
return channel.Channel{}, errors.New("unsupported key")
return nil, errors.New("unsupported key")
}
loader.loadPrimed(key)
thunk := loader.loaders[key].Load(loader.ctx, dataloader.StringKey(value))
res, err := thunk()
if err != nil {
return channel.Channel{}, err
return nil, err
}
channel, ok := res.(channel.Channel)
channel, ok := res.(models.Channel)
if !ok {
return channel, errors.New("incorrect type")
return nil, errors.New("incorrect type")
}
return channel, nil
return &channel, nil
}
// PrimeChannels primes channels for loading along with the first one.
func (loader *Loader) PrimeChannels(key string, values ...string) {
if !strings.HasPrefix(key, "Channel.") {
key = "Channel." + key
}
loader.prime(key, values)
}
func channelNameBatch(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
var results []*dataloader.Result
names := keys.Keys()
channels, err := channel.ListNames(names...)
channels, err := channels.ListNames(names...)
if err != nil {
for range names {
results = append(results, &dataloader.Result{Data: channel.Channel{}, Error: err})
results = append(results, &dataloader.Result{Data: models.Channel{}, Error: err})
}
return results
@@ -58,7 +70,7 @@ func channelNameBatch(ctx context.Context, keys dataloader.Keys) []*dataloader.R
}
if !found {
results = append(results, &dataloader.Result{Data: channel.Channel{}, Error: err})
results = append(results, &dataloader.Result{Data: models.Channel{}, Error: err})
}
}
@@ -5,36 +5,39 @@ import (
"errors"
"strings"
"git.aiterp.net/rpdata/api/model/character"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/characters"
"github.com/graph-gophers/dataloader"
)
// Character gets a character by key
func (loader *Loader) Character(key, value string) (character.Character, error) {
func (loader *Loader) Character(key, value string) (models.Character, error) {
if !strings.HasPrefix(key, "Character.") {
key = "Character." + key
}
loader.loadPrimed(key)
if loader.loaders[key] == nil {
return character.Character{}, errors.New("unsupported key")
return models.Character{}, errors.New("unsupported key")
}
thunk := loader.loaders[key].Load(loader.ctx, dataloader.StringKey(value))
res, err := thunk()
if err != nil {
return character.Character{}, err
return models.Character{}, err
}
char, ok := res.(character.Character)
char, ok := res.(models.Character)
if !ok {
return character.Character{}, errors.New("incorrect type")
return models.Character{}, errors.New("incorrect type")
}
return char, nil
}
// Characters gets characters by key
func (loader *Loader) Characters(key string, values ...string) ([]character.Character, error) {
func (loader *Loader) Characters(key string, values ...string) ([]models.Character, error) {
if !strings.HasPrefix(key, "Character.") {
key = "Character." + key
}
@@ -43,6 +46,8 @@ func (loader *Loader) Characters(key string, values ...string) ([]character.Char
return nil, errors.New("unsupported key")
}
loader.loadPrimed(key)
thunk := loader.loaders[key].LoadMany(loader.ctx, dataloader.NewKeysFromStrings(values))
res, errs := thunk()
for _, err := range errs {
@@ -51,10 +56,10 @@ func (loader *Loader) Characters(key string, values ...string) ([]character.Char
}
}
chars := make([]character.Character, len(res))
chars := make([]models.Character, len(res))
for i := range res {
char, ok := res[i].(character.Character)
char, ok := res[i].(models.Character)
if !ok {
return nil, errors.New("incorrect type")
}
@@ -65,11 +70,22 @@ func (loader *Loader) Characters(key string, values ...string) ([]character.Char
return chars, nil
}
// PrimeCharacters adds a set of characters to be loaded if, and only if, characters
// are going to be loaded. This will fill up the cache and speed up subsequent dataloader
// runs.
func (loader *Loader) PrimeCharacters(key string, values ...string) {
if !strings.HasPrefix(key, "Character.") {
key = "Character." + key
}
loader.prime(key, values)
}
func characterIDBatch(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
results := make([]*dataloader.Result, 0, len(keys))
ids := keys.Keys()
characters, err := character.ListIDs(ids...)
characters, err := characters.List(&characters.Filter{IDs: ids})
if err != nil {
for range ids {
results = append(results, &dataloader.Result{Error: err})
@@ -90,7 +106,7 @@ func characterIDBatch(ctx context.Context, keys dataloader.Keys) []*dataloader.R
}
if !found {
results = append(results, &dataloader.Result{Data: character.Character{}, Error: ErrNotFound})
results = append(results, &dataloader.Result{Data: models.Character{}, Error: ErrNotFound})
}
}
@@ -101,7 +117,7 @@ func characterNickBatch(ctx context.Context, keys dataloader.Keys) []*dataloader
var results []*dataloader.Result
nicks := keys.Keys()
characters, err := character.ListNicks(nicks...)
characters, err := characters.List(&characters.Filter{Nicks: nicks})
if err != nil {
for range nicks {
results = append(results, &dataloader.Result{Error: err})
@@ -122,7 +138,7 @@ func characterNickBatch(ctx context.Context, keys dataloader.Keys) []*dataloader
}
if !found {
results = append(results, &dataloader.Result{Data: character.Character{}, Error: err})
results = append(results, &dataloader.Result{Data: models.Character{}, Error: err})
}
}
@@ -3,6 +3,7 @@ package loader
import (
"context"
"errors"
"sync"
"time"
"github.com/graph-gophers/dataloader"
@@ -16,8 +17,11 @@ var ErrNotFound = errors.New("not found")
// A Loader is a collection of data loaders and functions to act on them. It's supposed to be
// request-scoped, and will thus keep things cached indefinitely.
type Loader struct {
mutex sync.Mutex
ctx context.Context
loaders map[string]*dataloader.Loader
primedKeys map[string]map[string]bool
}
// New initializes the loader.
@@ -25,10 +29,11 @@ func New() *Loader {
return &Loader{
ctx: context.Background(),
loaders: map[string]*dataloader.Loader{
"Character.id": dataloader.NewBatchedLoader(characterIDBatch, dataloader.WithWait(time.Millisecond)),
"Character.nick": dataloader.NewBatchedLoader(characterNickBatch, dataloader.WithWait(time.Millisecond)),
"Channel.name": dataloader.NewBatchedLoader(channelNameBatch, dataloader.WithWait(time.Millisecond)),
"Character.id": dataloader.NewBatchedLoader(characterIDBatch, dataloader.WithWait(time.Millisecond*2)),
"Character.nick": dataloader.NewBatchedLoader(characterNickBatch, dataloader.WithWait(time.Millisecond*2)),
"Channel.name": dataloader.NewBatchedLoader(channelNameBatch, dataloader.WithWait(time.Millisecond*2)),
},
primedKeys: make(map[string]map[string]bool),
}
}
@@ -47,3 +52,29 @@ func (loader *Loader) ToContext(ctx context.Context) context.Context {
loader.ctx = ctx
return context.WithValue(ctx, &contextKey, loader)
}
func (loader *Loader) prime(key string, values []string) {
loader.mutex.Lock()
if loader.primedKeys[key] == nil {
loader.primedKeys[key] = make(map[string]bool)
}
for _, value := range values {
loader.primedKeys[key][value] = true
}
loader.mutex.Unlock()
}
func (loader *Loader) loadPrimed(key string) {
loader.mutex.Lock()
if len(loader.primedKeys[key]) > 0 {
primedKeys := make([]string, 0, len(loader.primedKeys[key]))
for key := range loader.primedKeys[key] {
primedKeys = append(primedKeys, key)
}
loader.loaders[key].LoadMany(loader.ctx, dataloader.NewKeysFromStrings(primedKeys))
loader.primedKeys[key] = nil
}
loader.mutex.Unlock()
}
-20
View File
@@ -1,20 +0,0 @@
package session
import "context"
type contextKeyType struct{ name string }
func (ck *contextKeyType) String() string {
return ck.name
}
var contextKey = &contextKeyType{name: "session context key"}
// FromContext gets a session fron the context.
func FromContext(ctx context.Context) *Session {
return ctx.Value(contextKey).(*Session)
}
func contextWithSession(parent context.Context, session *Session) context.Context {
return context.WithValue(parent, contextKey, session)
}
-182
View File
@@ -1,182 +0,0 @@
package session
import (
"crypto/rand"
"encoding/hex"
"log"
"net/http"
"strings"
"sync"
"time"
"git.aiterp.net/aiterp/wikiauth"
"git.aiterp.net/rpdata/api/internal/config"
"git.aiterp.net/rpdata/api/internal/store"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
)
var sessionCollection *mgo.Collection
// A Session represents a login session.
type Session struct {
mutex sync.Mutex
ID string `bson:"_id"`
Time time.Time `bson:"time"`
UserID string `bson:"userId"`
user *User
w http.ResponseWriter
}
// Load loads a session from a cookie, returning either `r` or a request
// with the session context.
func Load(w http.ResponseWriter, r *http.Request) *http.Request {
cookie, err := r.Cookie("aiterp_session")
if err != nil {
return r.WithContext(contextWithSession(r.Context(), &Session{w: w}))
}
id := cookie.Value
session := Session{}
err = sessionCollection.FindId(id).One(&session)
if err != nil || time.Since(session.Time) > time.Hour*168 {
return r.WithContext(contextWithSession(r.Context(), &Session{w: w}))
}
if session.ID != "" && time.Since(session.Time) > time.Second*30 {
session.Time = time.Now()
go sessionCollection.UpdateId(id, bson.M{"$set": bson.M{"time": session.Time}})
}
cookie.Expires = time.Now().Add(time.Hour * 168)
http.SetCookie(w, cookie)
session.w = w
return r.WithContext(contextWithSession(r.Context(), &session))
}
// Login logs a user in.
func (session *Session) Login(username, password string) error {
auth := wikiauth.New(config.Global().Wiki.URL)
err := auth.Login(username, password)
if err != nil {
return err
}
// Allow bot passwords
username = strings.SplitN(username, "@", 2)[0]
data := make([]byte, 32)
_, err = rand.Read(data)
if err != nil {
return err
}
session.ID = hex.EncodeToString(data)
session.UserID = username
session.Time = time.Now()
err = sessionCollection.Insert(&session)
if err != nil {
return err
}
http.SetCookie(session.w, &http.Cookie{
Name: "aiterp_session",
Value: session.ID,
Expires: time.Now().Add(time.Hour * 2160), // 90 days
HttpOnly: true,
})
user, err := FindUser(session.UserID)
if err == mgo.ErrNotFound {
user = User{ID: username, Nick: "", Permissions: DefaultPermissions()}
err := userCollection.Insert(user)
if err != nil {
return err
}
} else if err != nil {
return err
}
return nil
}
// Logout logs out the session
func (session *Session) Logout() {
http.SetCookie(session.w, &http.Cookie{
Name: "aiterp_session",
Value: "",
Expires: time.Unix(0, 0),
HttpOnly: true,
})
session.mutex.Lock()
session.user = nil
session.UserID = ""
session.ID = ""
session.mutex.Unlock()
sessionCollection.RemoveId(session.ID)
}
// User gets the user information for the session.
func (session *Session) User() *User {
session.mutex.Lock()
defer session.mutex.Unlock()
if session.user != nil {
return session.user
}
if session.UserID == "" {
return nil
}
user, err := FindUser(session.UserID)
if err != nil {
return nil
}
return &user
}
// NameOrPermitted is a shorthand for checking the username OR permissions, e.g. to check
// if a logged in user can edit a certain post.
func (session *Session) NameOrPermitted(userid string, permissions ...string) bool {
if session.UserID == userid {
return true
}
user := session.User()
if user == nil {
return false
}
return user.Permitted()
}
func init() {
store.HandleInit(func(db *mgo.Database) {
sessionCollection = db.C("core.sessions")
sessionCollection.EnsureIndexKey("nick")
sessionCollection.EnsureIndexKey("userId")
err := sessionCollection.EnsureIndex(mgo.Index{
Name: "time",
Key: []string{"time"},
ExpireAfter: time.Hour * 168,
})
if err != nil {
log.Fatalln(err)
}
})
}
+5 -3
View File
@@ -26,9 +26,11 @@ func Init() error {
}
sconf := conf.Space
err = ConnectSpace(sconf.Host, sconf.AccessKey, sconf.SecretKey, sconf.Bucket, sconf.MaxSize, sconf.Root)
if err != nil {
return err
if sconf.Enabled {
err = ConnectSpace(sconf.Host, sconf.AccessKey, sconf.SecretKey, sconf.Bucket, sconf.MaxSize, sconf.Root)
if err != nil {
return err
}
}
hasInitialized = true

Some files were not shown because too many files have changed in this diff Show More