Compare commits

...
183 Commits
Author SHA1 Message Date
gisle 6ad0b9d467 fix character service not refreshing logs. 2020-10-07 20:33:56 +02:00
gisle 63bc262091 add sortByFictionalDate field to Story, and sorting by fictional date if set.
continuous-integration/drone/push Build is passing
2020-08-28 22:45:13 +02:00
gisle c4eabd69ac Merge branch 'master' of ssh+git://git.aiterp.net/rpdata/api
continuous-integration/drone/push Build is passing
2020-08-20 21:01:38 +02:00
gisle df982fac17 add IrcCloud log importer. 2020-08-20 20:58:16 +02:00
gisle 6fa2dc9800 fix chapter edit changes being reported as comment add. 2020-08-19 21:26:56 +02:00
gisle 6ce38d61b1 fix log importer not refreshing character list.
continuous-integration/drone/push Build is passing
2020-08-16 11:49:39 +02:00
gisle dc3f77f8da update drone ci.
continuous-integration/drone/push Build is passing
2020-08-09 16:09:11 +02:00
gisle 6321ea1893 update drone ci.
continuous-integration/drone/push Build is failing
2020-08-09 16:08:11 +02:00
gisle 9b346fb97b update drone ci.
continuous-integration/drone/push Build is passing
2020-08-09 16:07:44 +02:00
gisle ed21b7c8cd improve parser error. 2020-08-09 16:01:45 +02:00
gisle 47b7877ab4 add nextLogs and prevLogs suggestions to log. 2020-08-09 15:35:49 +02:00
gisle 757b0bc616 add a hackish migration for the changes expiration index. 2020-08-09 15:35:33 +02:00
gisle 5dbed6d632 UI for file stuff.
the build was successful
2019-09-15 16:34:16 +02:00
gisle c8ced52e1a File uploads, mostly. Also added extra behavior to addCharacter.
the build was successful
2019-08-31 22:46:55 +02:00
gisle 8a313b3bb2 More destruction. Utils too, since they cause weird go mod shenanigans.
the build was successful
2019-08-26 22:22:05 +02:00
gisle e690e7ab97 A lot of delete and failed scream tests.
the build was successful
2019-08-26 22:14:59 +02:00
gisle 0e70e37038 Moved unknown nicks to log service
the build was successful
2019-08-26 20:23:36 +02:00
gisle 3b10c4d9b1 go mod tidy
the build was successful
2019-08-25 16:14:29 +02:00
gisle 690eb4f121 Deleted a bunch of old db code.
the build was successful
2019-08-25 16:13:04 +02:00
gisle 2eed815993 Finished refactor of story/chapter/comment related stuff.
the build was successful
2019-08-25 16:10:10 +02:00
gisle 8c4670580c Story/chapter/comment repos + started on story service.
the build was successful
2019-08-18 08:59:38 +02:00
gisle 8fbaea2078 Added mergeLog mutation.
the build was successful
2019-08-16 12:32:50 +02:00
gisle 2a0e6534ff Added splitLog mutation.
the build was successful
2019-08-15 11:28:09 +02:00
gisle 4bd75b8240 Fixed character filter not being exclusive. 2019-08-15 11:28:02 +02:00
gisle ed38d576b2 Added fix for date bug.
the build was successful
2019-08-14 22:01:48 +02:00
gisle 51b2c9bfe0 Fixed log service bugs with importing and character list not updating.
the build was successful
2019-08-14 11:28:02 +02:00
gisle c0687d76ad Added log character refresh function to log service.
the build was successful
2019-08-07 12:26:59 +02:00
gisle 60dcd8a80f no idea
the build was successful
2019-08-06 13:51:51 +02:00
gisle 5bcc7479ff Early work on file stuff.
the build was successful
2019-07-25 23:21:25 +02:00
gisle 2c00358f4f Fixed advance by one date bug by treating any out of order posts as a date bump.
the build was successful
2019-07-20 15:20:39 +02:00
gisle acb6b637a5 Moved channels to new system, fixed change insert not working.
the build was successful
2019-06-24 20:30:14 +02:00
gisle f46b6f0774 Added channel repository and mongodb impl.
the build was successful
2019-06-20 22:32:35 +02:00
gisle 73a1c79903 Logs and posts are now on the new architecture.
the build was successful
2019-06-18 20:34:58 +02:00
gisle e194b6536e Removed PassAll from ChangeFilter and fixed sort not being descending order.
the build was successful
2019-06-16 11:46:23 +02:00
gisle f6ad0602c2 Replaced repositories.Bundle with a database interface.
the build was successful
2019-06-16 11:39:33 +02:00
gisle 021f67ea4b Changes to change system.
the build was successful
2019-06-16 10:32:34 +02:00
gisle 13f82399ef Removed ./idea/workspace.xml from git.
the build was successful
2019-06-10 19:29:38 +02:00
gisle 2a1423d28f Added character service and such.
the build was successful
2019-06-10 17:03:52 +02:00
gisle 7b253748fe Replaced custom Date scalar code with gqlgen's built-in Time scalar.
the build was successful
2019-06-09 12:49:18 +02:00
gisle 53c3d4597b Renamed queries package to resolvers.
the build was successful
2019-06-09 12:40:40 +02:00
gisle cdf4e6f667 Started work on new layered setup, making a huge mess.
the build was successful
2019-06-09 12:37:17 +02:00
gisle 6fa9f64f2d Instrumented the living s*** out of the graphql.
the build was successful
2019-05-21 19:35:29 +02:00
gisle 2fb391321e Fixed bug in resolver array conversion that caused results to all be copied of the last element. 2019-05-21 19:35:11 +02:00
gisle 015bdfd2e8 Updated resolvers to be compatible with gqlgen 0.9
the build was successful
2019-05-19 17:06:46 +02:00
gisle 9925907e93 Removed combine.sh, using new glob schema features instead.
the build was successful
2019-05-17 17:31:42 +02:00
gisle c7c418aeab Fixed Change.object resolver not returning anything. 2019-05-17 17:12:56 +02:00
gisle d5573e7f39 Added Prometheus metrics, don't forget to restrict access on /metrics endpoint. 2019-05-17 17:12:27 +02:00
gisle e794311eb8 Migrated rpdata-api to using go modules and a more recent version of
the build was successful
gqlgen

Squashed commit of the following:

commit 162ccf6bfb
Author: Gisle Aune <dev@gisle.me>
Date:   Fri May 17 11:51:23 2019 +0200

    The great module madness continus, now with pointers.

commit 7e3dc49635
Author: Gisle Aune <dev@gisle.me>
Date:   Thu May 16 15:57:08 2019 +0200

    Basic stuff, doesn't compile
2019-05-17 12:20: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
206 changed files with 10525 additions and 19345 deletions
+34 -16
View File
@@ -1,21 +1,39 @@
workspace:
base: /go
path: src/git.aiterp.net/rpdata/api
name: rpdata-api
pipeline:
docker-release:
kind: pipeline
type: docker
steps:
- name: go-test
image: golang:1.14
commands:
- go generate ./...
- go test -v ./...
- name: docker-release-tag
image: plugins/docker
repo: vmaple/rpdata
tags: latest
auto_tag: true
secrets: [ docker_username, docker_password ]
settings:
auto_tag: true
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: r.vmaple.dev/rpdata/rpdata-api
registry: r.vmaple.dev
when:
event: tag
ref: refs/tags/*
docker-dev:
event:
- tag
- name: docker-release-latest
image: plugins/docker
repo: vmaple/rpdata
tags: trunk
secrets: [ docker_username, docker_password ]
settings:
auto_tag: false
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: r.vmaple.dev/rpdata/rpdata-api
registry: r.vmaple.dev
when:
event: push
event:
- tag
+13 -2
View File
@@ -1,7 +1,18 @@
vendor
schema/bindata.go
graphql/schema/bindata.go
config.json
api
debug
build
.vscode
rpdata-graphiql
rpdata-*
!rpdata-*/
.vscode
.idea
/main
/main.exe
/graph2/graphcore/*_gen.go
generated.gql
generated.go
+12 -35
View File
@@ -1,50 +1,27 @@
## 1. Server
# Use Go 1.10
FROM golang:1.10 AS build-server
## 1. Build Server
# Use Go 1.14
FROM golang:1.14 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
## 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
FROM alpine:3.12
# 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`
-139
View File
@@ -1,139 +0,0 @@
package main
import (
"flag"
"fmt"
"log"
"strings"
"time"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/model/story"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
var flagHost = flag.String("host", "127.0.0.1:3306", "SQL host")
var flagDB = flag.String("database", "aitestory", "SQL database")
var flagUser = flag.String("user", "aitestory", "SQL user")
var flagPassword = flag.String("password", "", "SQL password")
func main() {
flag.Parse()
db, err := sqlx.Connect("mysql", fmt.Sprintf("%s:%s@(%s)/%s", *flagUser, *flagPassword, *flagHost, *flagDB))
if err != nil {
log.Fatalln(err)
}
err = db.Ping()
if err != nil {
log.Fatalln(err)
}
store.Init()
results := make([]storyResult, 0, 64)
rows, err := db.Queryx("SELECT * FROM page WHERE unlisted=0;")
for rows.Next() {
result := storyResult{}
err := rows.StructScan(&result)
if err != nil {
log.Fatalln(err)
}
results = append(results, result)
}
tagResults := make([]tagResult, 0, 256)
rows, err = db.Queryx("SELECT page_id,type,name FROM page_tag LEFT JOIN tag ON tag_id=tag.id;")
for rows.Next() {
result := tagResult{}
err := rows.StructScan(&result)
if err != nil {
log.Fatalln(err)
}
tagResults = append(tagResults, result)
}
for _, result := range results {
fictionalDate, err := time.Parse("2006-01-02 15:04:05", result.FictionalDate)
if err != nil {
if result.FictionalDate != "0000-00-00 00:00:00" {
log.Fatalln(err)
}
}
if fictionalDate.Year() < 1800 {
fictionalDate = time.Time{}
}
publishDate, err := time.Parse("2006-01-02 15:04:05", result.PublishDate)
if err != nil {
log.Fatalln(err)
}
tags := make([]story.Tag, 0, 8)
for _, tagResult := range tagResults {
if tagResult.PageID == result.ID {
tags = append(tags, story.Tag{Kind: tagResult.Type, Name: tagResult.Name})
}
}
story, err := story.New(result.Name, result.Author[5:], result.Category, false, false, tags, publishDate, fictionalDate)
if err != nil {
log.Fatalln(err)
}
// Change the story title
title := result.Name
if strings.HasPrefix(result.Source, "#") {
firstNewline := strings.Index(result.Source, "\n")
result.Name = strings.Replace(result.Source[1:firstNewline], "\r", "", -1)
result.Source = result.Source[firstNewline+1:]
result.Source = strings.Replace(result.Source, "\r\n", "\n", -1)
if strings.HasPrefix(result.Source, "\r") || strings.HasPrefix(result.Source, "\n") {
result.Source = result.Source[1:]
}
}
chapter, err := story.AddChapter(title, result.Author[5:], result.Source, publishDate, fictionalDate)
if err != nil {
log.Fatalln(err)
}
if !result.Unlisted {
listed := true
err := story.Edit(nil, nil, &listed, nil, nil)
if err != nil {
log.Fatalln(err)
}
}
fmt.Println(result.ID, "->", story.ID, chapter.ID)
}
}
type tagResult struct {
PageID string `db:"page_id"`
Type string `db:"type"`
Name string `db:"name"`
}
type storyResult struct {
ID string `db:"id"`
Name string `db:"name"`
Author string `db:"author"`
Category string `db:"category"`
FictionalDate string `db:"fictional_date"`
PublishDate string `db:"publish_date"`
EditDate string `db:"edit_date"`
Unlisted bool `db:"unlisted"`
Dated bool `db:"dated"`
Spesific bool `db:"specific"`
Indexed bool `db:"indexed"`
Published bool `db:"published"`
Type string `db:"type"`
Source string `db:"source"`
Cache string `db:"cache"`
BackgroundURL *string `db:"background_url"`
}
-42
View File
@@ -1,42 +0,0 @@
package main
import (
"fmt"
"os"
"git.aiterp.net/rpdata/api/model/channel"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/model/log"
)
func main() {
err := store.Init()
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
logs, err := log.List(32768)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
added := make(map[string]bool, 1024)
for _, log := range logs {
if added[log.Channel] {
continue
}
_, err := channel.Ensure(log.Channel, false)
if err != nil {
fmt.Fprintln(os.Stderr, log.ID, err)
continue
}
added[log.Channel] = true
fmt.Println(log.Channel, "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>
`)
-3
View File
@@ -1,3 +0,0 @@
characters.json
characters.cson
debug
-55
View File
@@ -1,55 +0,0 @@
package main
import (
"encoding/json"
"io"
"strings"
)
type charInfo struct {
Nicks []string `json:"nicks"`
Name string `json:"name"`
Author string `json:"player"`
ShortName string `json:"first"`
}
func load(reader io.Reader) ([]charInfo, error) {
data := make(map[string]interface{})
err := json.NewDecoder(reader).Decode(&data)
if err != nil {
return nil, err
}
links := make(map[string]string, len(data))
infos := make([]charInfo, 0, 64)
for key, value := range data {
if info, ok := value.(map[string]interface{}); ok {
name := info["name"].(string)
author := info["player"].(string)
shortName, ok := info["first"].(string)
if !ok {
shortName = strings.SplitN(name, " ", 2)[0]
}
infos = append(infos, charInfo{
Nicks: []string{key},
Name: name,
Author: author,
ShortName: shortName,
})
} else if nick, ok := value.(string); ok {
links[key] = nick
}
}
for key, value := range links {
for i := range infos {
if infos[i].Nicks[0] == value {
infos[i].Nicks = append(infos[i].Nicks, key)
}
}
}
return infos, nil
}
-62
View File
@@ -1,62 +0,0 @@
package main
import (
"flag"
"log"
"os"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/model/character"
)
var fileName = flag.String("file", "./characters.json", "json file to load")
func main() {
file, err := os.Open(*fileName)
if err != nil {
log.Fatalln("Open:", err)
}
infos, err := load(file)
if err != nil {
log.Fatalln("Parse:", err)
}
err = store.Init()
if err != nil {
log.Fatalln("Store:", err)
}
charsAdded, charsFailed := 0, 0
nicksAdded, nicksFailed := 0, 0
for _, info := range infos {
char, err := character.New(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])
if err != nil {
continue
}
} else {
log.Println(info.Nicks[0], "added")
charsAdded++
}
for _, alt := range info.Nicks[1:] {
err := char.AddNick(alt)
if err != nil {
log.Println(info.Nicks[0], "failed to add nick", alt, "error:", err)
nicksFailed++
} else {
log.Println(info.Nicks[0], "addded nick", alt)
nicksAdded++
}
}
}
log.Printf("Characters – %d/%d", charsAdded, charsFailed+charsAdded)
log.Printf("Alt. Nicks – %d/%d", nicksAdded, nicksFailed+nicksAdded)
}
-2
View File
@@ -1,2 +0,0 @@
logs
debug
-85
View File
@@ -1,85 +0,0 @@
package main
import (
"bufio"
"errors"
"io"
"strconv"
"strings"
"time"
)
type logLine struct {
Verb string
Args []string
Text string
}
func parseFile(reader io.Reader) []logLine {
bufReader := bufio.NewReader(reader)
results := make([]logLine, 0, 512)
for {
line, err := bufReader.ReadString('\n')
if err == io.EOF {
if len(line) < 2 {
break
}
} else if err != nil {
break
}
if len(line) <= 2 {
continue
}
line = strings.Replace(line, "\n", "", 1)
line = strings.Replace(line, "\r", "", 1)
results = append(results, parseLine(line))
}
return results
}
func parseLine(line string) logLine {
textSplit := strings.SplitN(line, " :", 2)
tokens := strings.Split(textSplit[0], " ")
ll := logLine{
Verb: tokens[0],
}
if len(tokens) > 1 {
ll.Args = tokens[1:]
}
if len(textSplit) > 1 {
ll.Text = textSplit[1]
}
return ll
}
func parseFilename(fname string) (date time.Time, channel string, err error) {
date, err = time.ParseInLocation("2006-01-02_150405", fname[:17], time.Local)
if err != nil {
return
}
ms, err := strconv.Atoi(fname[17:20])
if err != nil {
return
}
date = date.Add(time.Duration(ms) * time.Millisecond)
if len(fname) < 23 {
err = errors.New("filename too short")
return
}
channel = fname[21:]
return
}
-108
View File
@@ -1,108 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"path"
"strings"
"time"
"git.aiterp.net/rpdata/api/internal/store"
logModel "git.aiterp.net/rpdata/api/model/log"
)
var prefixReplacer = strings.NewReplacer("+", "", "@", "", "!", "", "%", "")
func main() {
err := store.Init()
if err != nil {
log.Fatalln(err)
}
for _, filepath := range os.Args[1:] {
name := strings.Replace(path.Base(filepath), ".txt", "", 1)
file, err := os.Open(filepath)
if err != nil {
log.Println(filepath, err)
continue
}
logLines := parseFile(file)
file.Close()
// Get title and event
title := ""
event := ""
for _, line := range logLines {
if line.Verb == "TITLE" {
title = line.Text
}
if line.Verb == "TAG" {
event = line.Text
}
}
date, channel, err := parseFilename(name)
if err != nil {
log.Fatalln(err)
}
l, err := logModel.New(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.")
if err != nil {
log.Println(err)
}
for _, line := range logLines {
if line.Verb != "CHARS" {
continue
}
_, err = l.NewPost(l.Date, "chars", prefixReplacer.Replace(line.Args[0]), line.Text)
if err != nil {
log.Println(err)
}
}
for _, line := range logLines {
if line.Verb != "SCENE" && line.Verb != "ACTION" && line.Verb != "TEXT" {
continue
}
postTime, err := time.ParseInLocation("2006-01-02 15:04:05", date.Format("2006-01-02")+" "+line.Args[1], time.Local)
diff := postTime.Sub(date)
if err != nil {
log.Println(err)
continue
}
if diff < 0 {
if diff > -time.Second {
postTime = postTime.Add(diff)
} else {
postTime = postTime.Add(time.Hour * 24)
}
}
if line.Args[0][0] == '=' {
line.Verb = "SCENE"
}
_, err = l.NewPost(postTime, strings.ToLower(line.Verb), prefixReplacer.Replace(line.Args[0]), line.Text)
if err != nil {
log.Println(err)
}
}
err = l.UpdateCharacters()
if err != nil {
log.Println(err)
}
fmt.Println(l.ID, "completed")
}
}
+126
View File
@@ -0,0 +1,126 @@
package main
import (
"context"
"fmt"
"git.aiterp.net/rpdata/api/space"
"log"
"net/http"
"runtime/debug"
"strings"
"git.aiterp.net/rpdata/api/database"
"git.aiterp.net/rpdata/api/graph2"
"git.aiterp.net/rpdata/api/internal/config"
"git.aiterp.net/rpdata/api/internal/instrumentation"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/services"
"github.com/99designs/gqlgen/handler"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
cfg := config.Global()
db, err := database.Init(cfg.Database)
if err != nil {
log.Fatalln("Failed to init db:", err)
}
defer db.Close(context.Background())
var spaceClient *space.Client
if cfg.Space.Enabled {
spaceClient, err = space.Connect(cfg.Space)
if err != nil {
log.Fatalln("Failed to init space:", err)
}
log.Println("Space loaded")
} else {
log.Println("Space is disabled, file upload will not work!")
}
serviceBundle := services.NewBundle(db, spaceClient)
instrumentation.Register()
http.Handle("/", handler.Playground("RPData API", "/graphql"))
http.Handle("/graphql", queryHandler(serviceBundle))
http.Handle("/metrics", promhttp.Handler())
go func() {
err := serviceBundle.Logs.RefreshAllLogCharacters(context.Background())
if err != nil {
log.Println("Refresh Characters:", err)
}
err = serviceBundle.Logs.FixImportDateBug(context.Background())
if err != nil {
log.Println("Fix date bug:", err)
}
}()
go logListedChanges(serviceBundle.Changes)
log.Fatal(http.ListenAndServe(":8081", nil))
}
func logListedChanges(changes *services.ChangeService) {
sub := changes.Subscribe(context.Background(), models.ChangeFilter{})
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(services *services.Bundle) http.HandlerFunc {
handler := handler.GraphQL(
graph2.New(services),
handler.UploadMaxSize(10485760),
handler.UploadMaxMemory(1048576),
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),
handler.RequestMiddleware(instrumentation.RequestMiddleware()),
handler.ResolverMiddleware(instrumentation.ResolverMiddleware()),
)
return func(w http.ResponseWriter, r *http.Request) {
// >_>
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 = services.Auth.RequestWithToken(r)
handler.ServeHTTP(w, r)
}
}
-77
View File
@@ -1,77 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"git.aiterp.net/rpdata/api/internal/config"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/model/file"
)
func main() {
client := http.Client{Timeout: time.Second * 30}
res, err := client.Get(config.Global().Wiki.URL + "?action=query&list=allimages&ailimit=5&aiprop=dimensions|mime|url|user|timestamp&format=json&ailimit=500")
if err != nil {
log.Fatalln(err)
}
if res.StatusCode != 200 {
log.Fatalln(res.Status)
}
data := ResponseData{}
err = json.NewDecoder(res.Body).Decode(&data)
if err != nil {
log.Fatalln(err)
}
store.Init()
for _, info := range data.Query.Allimages {
existing, err := file.FindName("wiki", info.Name, info.User)
if err == nil && existing.ID != "" {
log.Println("Skipping", info.Name, "because it already exists")
continue
}
ts, err := time.Parse(time.RFC3339, info.Timestamp)
if err != nil {
log.Println("Skipping", info.Name, "error:", err)
continue
}
file, err := file.Insert(info.Name, "wiki", info.Mime, info.User, ts, info.Size, info.URL)
if err != nil {
log.Println("Skipping", info.Name, "error:", err)
continue
}
fmt.Println(file.Name, "inserted ( id:", file.ID, ")")
}
}
// The ResponseData from the wiki looks like this
type ResponseData struct {
Batchcomplete string `json:"batchcomplete"`
Query struct {
Allimages []struct {
Descriptionshorturl string `json:"descriptionshorturl"`
Descriptionurl string `json:"descriptionurl"`
Height int `json:"height"`
Mime string `json:"mime"`
Name string `json:"name"`
Ns int `json:"ns"`
Size int64 `json:"size"`
Timestamp string `json:"timestamp"`
Title string `json:"title"`
URL string `json:"url"`
User string `json:"user"`
Width int `json:"width"`
} `json:"allimages"`
} `json:"query"`
}
+40
View File
@@ -0,0 +1,40 @@
package database
import (
"context"
"errors"
"git.aiterp.net/rpdata/api/database/mongodb"
"git.aiterp.net/rpdata/api/internal/config"
"git.aiterp.net/rpdata/api/repositories"
)
// ErrDriverUnrecognized is returned if the driver is not recognized
var ErrDriverUnrecognized = errors.New("driver not recognized, check installed version or your configuration")
type Database interface {
Changes() repositories.ChangeRepository
Channels() repositories.ChannelRepository
Characters() repositories.CharacterRepository
Tags() repositories.TagRepository
Logs() repositories.LogRepository
Posts() repositories.PostRepository
Stories() repositories.StoryRepository
Chapters() repositories.ChapterRepository
Comments() repositories.CommentRepository
Keys() repositories.KeyRepository
Users() repositories.UserRepository
Files() repositories.FileRepository
Close(ctx context.Context) error
}
// Init sets up the database.
func Init(config config.Database) (Database, error) {
switch config.Driver {
case "mongo", "mgo", "mongodb":
return mongodb.Init(config)
default:
return nil, ErrDriverUnrecognized
}
}
+109
View File
@@ -0,0 +1,109 @@
package mongodb
import (
"context"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/repositories"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
"strconv"
"time"
)
type changeRepository struct {
changes *mgo.Collection
idCounter *counter
}
func (r *changeRepository) Find(ctx context.Context, id string) (*models.Change, error) {
change := new(models.Change)
err := r.changes.FindId(id).One(change)
if err != nil {
return nil, err
}
return change, nil
}
func (r *changeRepository) List(ctx context.Context, filter models.ChangeFilter) ([]*models.Change, error) {
query := bson.M{}
limit := 0
if filter.EarliestDate != nil && !filter.EarliestDate.IsZero() {
query["date"] = bson.M{"$gte": *filter.EarliestDate}
}
if len(filter.Keys) > 0 {
query["keys"] = bson.M{"$in": filter.Keys}
} else {
query["listed"] = true
}
if filter.Author != nil && *filter.Author != "" {
query["author"] = *filter.Author
}
if filter.Limit != nil {
limit = *filter.Limit
}
initialSize := 64
if limit > 0 && limit < 256 {
initialSize = limit
}
changes := make([]*models.Change, 0, initialSize)
err := r.changes.Find(query).Sort("-date").Limit(limit).All(&changes)
if err != nil {
return nil, err
}
return changes, nil
}
func (r *changeRepository) Insert(ctx context.Context, change models.Change) (*models.Change, error) {
next, err := r.idCounter.Increment(1)
if err != nil {
return nil, err
}
change.ID = "Change_" + strconv.Itoa(next)
err = r.changes.Insert(&change)
if err != nil {
return nil, err
}
return &change, nil
}
func (r *changeRepository) Remove(ctx context.Context, change models.Change) error {
return r.changes.RemoveId(change.ID)
}
func newChangeRepository(db *mgo.Database) (repositories.ChangeRepository, error) {
collection := db.C("common.changes")
// Delete the old index if it exists.
_ = collection.DropIndexName("date_1")
err := collection.EnsureIndex(mgo.Index{
Name: "expiry",
Key: []string{"date"},
ExpireAfter: time.Hour * 2400, // 100 days
})
if err != nil {
return nil, err
}
err = collection.EnsureIndexKey("author")
if err != nil {
return nil, err
}
err = collection.EnsureIndexKey("keys")
if err != nil {
return nil, err
}
return &changeRepository{
changes: collection,
idCounter: newCounter(db, "auto_increment", "Change"),
}, nil
}
+107
View File
@@ -0,0 +1,107 @@
package mongodb
import (
"context"
"git.aiterp.net/rpdata/api/models"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
)
func newChannelRepository(db *mgo.Database) (*channelRepository, error) {
collection := db.C("common.channels")
err := collection.EnsureIndexKey("logged")
if err != nil {
return nil, err
}
err = collection.EnsureIndexKey("event")
if err != nil {
return nil, err
}
err = collection.EnsureIndexKey("location")
if err != nil {
return nil, err
}
return &channelRepository{
channels: collection,
}, nil
}
type channelRepository struct {
channels *mgo.Collection
}
func (r *channelRepository) Find(ctx context.Context, name string) (*models.Channel, error) {
channel := new(models.Channel)
err := r.channels.FindId(name).One(channel)
if err != nil {
return nil, err
}
return channel, nil
}
func (r *channelRepository) List(ctx context.Context, filter models.ChannelFilter) ([]*models.Channel, error) {
query := bson.M{}
if filter.Logged != nil {
query["logged"] = *filter.Logged
}
if filter.EventName != nil {
query["eventName"] = *filter.EventName
}
if filter.LocationName != nil {
query["locationName"] = *filter.LocationName
}
if len(filter.Names) > 0 {
query["_id"] = bson.M{"$in": filter.Names}
}
channels := make([]*models.Channel, 0, 32)
err := r.channels.Find(query).Limit(filter.Limit).Sort("_id").All(&channels)
if err != nil {
if err == mgo.ErrNotFound {
return channels, nil
}
return nil, err
}
return channels, nil
}
func (r *channelRepository) Insert(ctx context.Context, channel models.Channel) (*models.Channel, error) {
err := r.channels.Insert(channel)
if err != nil {
return nil, err
}
return &channel, nil
}
func (r *channelRepository) Update(ctx context.Context, channel models.Channel, update models.ChannelUpdate) (*models.Channel, error) {
updateBson := bson.M{}
if update.Logged != nil {
updateBson["logged"] = *update.Logged
channel.Logged = *update.Logged
}
if update.LocationName != nil {
updateBson["locationName"] = *update.LocationName
channel.LocationName = *update.LocationName
}
if update.EventName != nil {
updateBson["eventName"] = *update.EventName
channel.EventName = *update.EventName
}
err := r.channels.UpdateId(channel.Name, bson.M{"$set": updateBson})
if err != nil {
return nil, err
}
return &channel, nil
}
func (r *channelRepository) Remove(ctx context.Context, channel models.Channel) error {
return r.channels.RemoveId(channel.Name)
}
+137
View File
@@ -0,0 +1,137 @@
package mongodb
import (
"context"
"git.aiterp.net/rpdata/api/internal/generate"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/repositories"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
"log"
)
type chapterRepository struct {
chapters *mgo.Collection
comments *mgo.Collection
}
func newChapterRepository(db *mgo.Database) (repositories.ChapterRepository, error) {
collection := db.C("story.chapters")
err := collection.EnsureIndexKey("storyId")
if err != nil {
return nil, err
}
err = collection.EnsureIndexKey("author")
if err != nil {
return nil, err
}
err = collection.EnsureIndexKey("createdDate")
if err != nil {
return nil, err
}
return &chapterRepository{
chapters: collection,
comments: db.C("story.comments"),
}, nil
}
func (r *chapterRepository) Find(ctx context.Context, id string) (*models.Chapter, error) {
chapter := new(models.Chapter)
err := r.chapters.FindId(id).One(chapter)
if err != nil {
return nil, err
}
return chapter, nil
}
func (r *chapterRepository) List(ctx context.Context, filter models.ChapterFilter) ([]*models.Chapter, error) {
query := bson.M{}
if filter.StoryID != nil {
query["storyId"] = *filter.StoryID
}
chapters := make([]*models.Chapter, 0, 32)
err := r.chapters.Find(query).Sort("createdDate").Limit(filter.Limit).All(&chapters)
if err != nil {
if err == mgo.ErrNotFound {
return chapters, nil
}
return nil, err
}
return chapters, nil
}
func (r *chapterRepository) Insert(ctx context.Context, chapter models.Chapter) (*models.Chapter, error) {
chapter.ID = generate.StoryID()
err := r.chapters.Insert(chapter)
if err != nil {
return nil, err
}
return &chapter, nil
}
func (r *chapterRepository) Update(ctx context.Context, chapter models.Chapter, update models.ChapterUpdate) (*models.Chapter, error) {
updateBson := bson.M{}
if update.Title != nil {
updateBson["title"] = *update.Title
chapter.Title = *update.Title
}
if update.Source != nil {
updateBson["source"] = *update.Source
chapter.Source = *update.Source
}
if update.FictionalDate != nil {
updateBson["fictionalDate"] = *update.FictionalDate
chapter.FictionalDate = *update.FictionalDate
}
if update.CommentMode != nil {
updateBson["commentMode"] = *update.CommentMode
chapter.CommentMode = *update.CommentMode
}
if update.CommentsLocked != nil {
updateBson["commentsLocked"] = *update.CommentsLocked
chapter.CommentsLocked = *update.CommentsLocked
}
err := r.chapters.UpdateId(chapter.ID, bson.M{"$set": updateBson})
if err != nil {
return nil, err
}
return &chapter, nil
}
func (r *chapterRepository) Move(ctx context.Context, chapter models.Chapter, from, to models.Story) (*models.Chapter, error) {
err := r.chapters.UpdateId(chapter.ID, bson.M{"$set": bson.M{"storyId": to.ID}})
if err != nil {
return nil, err
}
chapter.StoryID = to.ID
return &chapter, nil
}
func (r *chapterRepository) Delete(ctx context.Context, chapter models.Chapter) error {
err := r.chapters.RemoveId(chapter.ID)
if err != nil {
return err
}
c, err := r.comments.RemoveAll(bson.M{"chapterId": chapter.ID})
if err != nil {
log.Println("Failed to remove comments:", err)
return nil
}
log.Printf("Removed chapter %s (%d comments)", chapter.ID, c.Removed)
return nil
}
+248
View File
@@ -0,0 +1,248 @@
package mongodb
import (
"context"
"errors"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
"sort"
"strconv"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/repositories"
)
type characterRepository struct {
characters *mgo.Collection
cidCounter *counter
}
func newCharacterRepository(db *mgo.Database) (repositories.CharacterRepository, error) {
collection := db.C("common.characters")
err := collection.EnsureIndexKey("name")
if err != nil {
return nil, err
}
err = collection.EnsureIndexKey("shortName")
if err != nil {
return nil, err
}
err = collection.EnsureIndexKey("author")
if err != nil {
return nil, err
}
err = collection.EnsureIndex(mgo.Index{
Key: []string{"nicks"},
Unique: true,
DropDups: true,
})
if err != nil {
return nil, err
}
err = collection.EnsureIndex(mgo.Index{
Key: []string{"$text:description"},
})
if err != nil {
return nil, err
}
return &characterRepository{
characters: collection,
cidCounter: newCounter(db, "auto_increment", "Character"),
}, nil
}
func (r *characterRepository) Find(ctx context.Context, id string) (*models.Character, error) {
character := new(models.Character)
err := r.characters.FindId(id).One(character)
if err != nil {
return nil, err
}
return character, nil
}
func (r *characterRepository) FindNick(ctx context.Context, nick string) (*models.Character, error) {
character := new(models.Character)
err := r.characters.Find(bson.M{"nick": nick}).One(character)
if err != nil {
return nil, err
}
return character, nil
}
func (r *characterRepository) FindName(ctx context.Context, name string) (*models.Character, error) {
query := bson.M{
"$or": []bson.M{
{"shortName": name},
{"name": name},
},
}
// Look for all characters matching query
characters := make([]*models.Character, 0, 8)
err := r.characters.Find(query).All(&characters)
if err != nil {
if err == mgo.ErrNotFound {
return nil, repositories.ErrNotFound
}
return nil, err
} else if len(characters) == 0 {
return nil, repositories.ErrNotFound
}
// Prioritize exact match
for _, character := range characters {
if character.Name == name {
return character, nil
}
}
return characters[0], nil
}
func (r *characterRepository) List(ctx context.Context, filter models.CharacterFilter) ([]*models.Character, error) {
query := bson.M{}
if filter.Author != nil {
query["author"] = *filter.Author
}
if len(filter.IDs) > 0 {
query["_id"] = bson.M{"$in": filter.IDs}
}
if len(filter.Nicks) > 0 {
query["nicks"] = bson.M{"$in": filter.Nicks}
}
if len(filter.Names) > 0 {
query["$or"] = []bson.M{
{"name": bson.M{"$in": filter.Names}},
{"shortName": bson.M{"$in": filter.Names}},
}
}
if filter.Search != nil {
query["$text"] = bson.M{"$search": *filter.Search}
}
characters := make([]*models.Character, 0, 32)
err := r.characters.Find(query).Limit(filter.Limit).All(&characters)
if err != nil {
if err == mgo.ErrNotFound {
return characters, nil
}
return nil, err
}
sort.Slice(characters, func(i, j int) bool {
ni, _ := strconv.Atoi(characters[i].ID[1:])
nj, _ := strconv.Atoi(characters[j].ID[1:])
return ni < nj
})
return characters, nil
}
func (r *characterRepository) Insert(ctx context.Context, character models.Character) (*models.Character, error) {
nextId, err := r.cidCounter.Increment(1)
if err != nil {
return nil, err
}
character.ID = "C" + strconv.Itoa(nextId)
err = r.characters.Insert(&character)
if err != nil {
return nil, err
}
return &character, nil
}
func (r *characterRepository) Update(ctx context.Context, character models.Character, update models.CharacterUpdate) (*models.Character, error) {
updateBson := bson.M{}
if update.Name != nil {
updateBson["name"] = *update.Name
character.Name = *update.Name
}
if update.ShortName != nil {
updateBson["shortName"] = *update.ShortName
character.ShortName = *update.ShortName
}
if update.Description != nil {
updateBson["description"] = *update.Description
character.Description = *update.Description
}
err := r.characters.UpdateId(character.ID, bson.M{"$set": updateBson})
if err != nil {
return nil, err
}
return &character, nil
}
func (r *characterRepository) AddNick(ctx context.Context, character models.Character, nick string) (*models.Character, error) {
if character.HasNick(nick) {
return nil, errors.New("nick already exist")
}
match := bson.M{
"_id": character.ID,
"nicks": bson.M{"$ne": nick},
}
err := r.characters.Update(match, bson.M{"$push": bson.M{"nicks": nick}})
if err == mgo.ErrNotFound {
return nil, repositories.ErrNotFound
} else if err != nil {
return nil, err
}
newNicks := make([]string, len(character.Nicks), len(character.Nicks)+1)
copy(newNicks, character.Nicks)
character.Nicks = append(newNicks, nick)
return &character, nil
}
func (r *characterRepository) RemoveNick(ctx context.Context, character models.Character, nick string) (*models.Character, error) {
if !character.HasNick(nick) {
return nil, errors.New("nick does not exist")
}
match := bson.M{
"_id": character.ID,
"nicks": nick,
}
err := r.characters.Update(match, bson.M{"$pull": bson.M{"nicks": nick}})
if err == mgo.ErrNotFound {
return nil, repositories.ErrNotFound
} else if mErr, ok := err.(*mgo.LastError); ok && mErr.Code == 11000 {
return nil, errors.New("The nick belongs to another character already")
} else if err != nil {
return nil, err
}
newNicks := make([]string, len(character.Nicks), len(character.Nicks)+1)
copy(newNicks, character.Nicks)
for i := range newNicks {
if newNicks[i] == nick {
newNicks = append(newNicks[:i], newNicks[i+1:]...)
break
}
}
character.Nicks = newNicks
return &character, nil
}
func (r *characterRepository) Delete(ctx context.Context, character models.Character) error {
return r.characters.RemoveId(character.ID)
}
+134
View File
@@ -0,0 +1,134 @@
package mongodb
import (
"context"
"git.aiterp.net/rpdata/api/internal/generate"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/repositories"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
"log"
)
type commentRepository struct {
comments *mgo.Collection
}
func newCommentRepository(db *mgo.Database) (repositories.CommentRepository, error) {
collection := db.C("story.comments")
err := collection.EnsureIndexKey("chapterId")
if err != nil {
return nil, err
}
err = collection.EnsureIndexKey("author")
if err != nil {
return nil, err
}
err = collection.EnsureIndexKey("createdDate")
if err != nil {
return nil, err
}
r := &commentRepository{
comments: collection,
}
go r.fixFieldTypo()
return r, nil
}
func (r *commentRepository) Find(ctx context.Context, id string) (*models.Comment, error) {
comment := new(models.Comment)
err := r.comments.FindId(id).One(comment)
if err != nil {
return nil, err
}
return comment, nil
}
func (r *commentRepository) List(ctx context.Context, filter models.CommentFilter) ([]*models.Comment, error) {
query := bson.M{}
if filter.ChapterID != nil {
query["chapterId"] = *filter.ChapterID
}
comments := make([]*models.Comment, 0, 32)
err := r.comments.Find(query).Sort("createdDate").Limit(filter.Limit).All(&comments)
if err != nil {
if err == mgo.ErrNotFound {
return comments, nil
}
return nil, err
}
return comments, nil
}
func (r *commentRepository) Insert(ctx context.Context, comment models.Comment) (*models.Comment, error) {
comment.ID = generate.CommentID()
err := r.comments.Insert(comment)
if err != nil {
return nil, err
}
return &comment, nil
}
func (r *commentRepository) Update(ctx context.Context, comment models.Comment, update models.CommentUpdate) (*models.Comment, error) {
updateBson := bson.M{}
if update.Subject != nil {
updateBson["subject"] = *update.Subject
comment.Subject = *update.Subject
}
if update.Source != nil {
updateBson["source"] = *update.Source
comment.Source = *update.Source
}
if update.FictionalDate != nil {
updateBson["fictionalDate"] = *update.FictionalDate
comment.FictionalDate = *update.FictionalDate
}
if update.CharacterID != nil {
updateBson["characterId"] = *update.CharacterID
comment.CharacterID = *update.CharacterID
}
if update.CharacterName != nil {
updateBson["characterName"] = *update.CharacterName
comment.CharacterName = *update.CharacterName
}
err := r.comments.UpdateId(comment.ID, bson.M{"$set": updateBson})
if err != nil {
return nil, err
}
return &comment, nil
}
func (r *commentRepository) Delete(ctx context.Context, comment models.Comment) error {
return r.comments.RemoveId(comment.ID)
}
func (r *commentRepository) fixFieldTypo() {
c, err := r.comments.UpdateAll(bson.M{
"editeddDate": bson.M{"$ne": nil},
}, bson.M{
"$rename": bson.M{"editeddDate": "editedDate"},
})
if err != nil {
if err == mgo.ErrNotFound {
return
}
log.Println("Failed to run name typo fix:", err)
return
}
if c.Updated > 0 {
log.Println("Fixed editeddDate field name typo in", c.Updated, "comments")
}
}
+249
View File
@@ -0,0 +1,249 @@
package mongodb
import (
"context"
"fmt"
"github.com/globalsign/mgo/bson"
"time"
"git.aiterp.net/rpdata/api/internal/config"
"git.aiterp.net/rpdata/api/repositories"
"github.com/globalsign/mgo"
)
type MongoDB struct {
session *mgo.Session
changes repositories.ChangeRepository
channels *channelRepository
characters repositories.CharacterRepository
tags repositories.TagRepository
logs *logRepository
posts *postRepository
files *fileRepository
stories repositories.StoryRepository
chapters repositories.ChapterRepository
comments repositories.CommentRepository
keys repositories.KeyRepository
users repositories.UserRepository
}
func (m *MongoDB) Changes() repositories.ChangeRepository {
return m.changes
}
func (m *MongoDB) Characters() repositories.CharacterRepository {
return m.characters
}
func (m *MongoDB) Tags() repositories.TagRepository {
return m.tags
}
func (m *MongoDB) Logs() repositories.LogRepository {
return m.logs
}
func (m *MongoDB) Posts() repositories.PostRepository {
return m.posts
}
func (m *MongoDB) Channels() repositories.ChannelRepository {
return m.channels
}
func (m *MongoDB) Files() repositories.FileRepository {
return m.files
}
func (m *MongoDB) Stories() repositories.StoryRepository {
return m.stories
}
func (m *MongoDB) Chapters() repositories.ChapterRepository {
return m.chapters
}
func (m *MongoDB) Comments() repositories.CommentRepository {
return m.comments
}
func (m *MongoDB) Keys() repositories.KeyRepository {
return m.keys
}
func (m *MongoDB) Users() repositories.UserRepository {
return m.users
}
func (m *MongoDB) Close(ctx context.Context) error {
m.session.Close()
return nil
}
// Init initializes the mongodb database
func Init(cfg config.Database) (*MongoDB, error) {
port := cfg.Port
if port <= 0 {
port = 27017
}
session, err := mgo.DialWithInfo(&mgo.DialInfo{
Addrs: []string{fmt.Sprintf("%s:%d", cfg.Host, port)},
Timeout: 30 * time.Second,
Database: cfg.Db,
Username: cfg.Username,
Password: cfg.Password,
Mechanism: cfg.Mechanism,
Source: cfg.Db,
})
if err != nil {
return nil, err
}
db := session.DB(cfg.Db)
characters, err := newCharacterRepository(db)
if err != nil {
session.Close()
return nil, err
}
channels, err := newChannelRepository(db)
if err != nil {
session.Close()
return nil, err
}
changes, err := newChangeRepository(db)
if err != nil {
session.Close()
return nil, err
}
logs, err := newLogRepository(db)
if err != nil {
session.Close()
return nil, err
}
posts, err := newPostRepository(db)
if err != nil {
session.Close()
return nil, err
}
files, err := newFileRepository(db)
if err != nil {
session.Close()
return nil, err
}
stories, err := newStoryRepository(db)
if err != nil {
session.Close()
return nil, err
}
chapters, err := newChapterRepository(db)
if err != nil {
session.Close()
return nil, err
}
comments, err := newCommentRepository(db)
if err != nil {
session.Close()
return nil, err
}
keys, err := newKeyRepository(db)
if err != nil {
session.Close()
return nil, err
}
users, err := newUserRepository(db)
if err != nil {
session.Close()
return nil, err
}
go posts.fixPositions(logs)
return &MongoDB{
session: session,
changes: changes,
characters: characters,
channels: channels,
tags: newTagRepository(db),
stories: stories,
chapters: chapters,
comments: comments,
logs: logs,
posts: posts,
files: files,
keys: keys,
users: users,
}, nil
}
type counter struct {
coll *mgo.Collection
category string
name string
}
func newCounter(db *mgo.Database, category, name string) *counter {
return &counter{
coll: db.C("core.counters"),
category: category,
name: name,
}
}
func (c *counter) WithName(name string) *counter {
return &counter{
coll: c.coll,
category: c.category,
name: name,
}
}
func (c *counter) WithCategory(category string) *counter {
return &counter{
coll: c.coll,
category: category,
name: c.name,
}
}
func (c *counter) With(category, name string) *counter {
return &counter{
coll: c.coll,
category: category,
name: name,
}
}
func (c *counter) Increment(amount int) (int, error) {
type counterDoc struct {
ID string `bson:"_id"`
Value int `bson:"value"`
}
id := c.category + "." + c.name
doc := counterDoc{}
_, err := c.coll.Find(bson.M{"_id": id}).Apply(mgo.Change{
Update: bson.M{"$inc": bson.M{"value": amount}},
Upsert: true,
ReturnNew: true,
}, &doc)
if err != nil {
return -1, err
}
return doc.Value, nil
}
+105
View File
@@ -0,0 +1,105 @@
package mongodb
import (
"context"
"git.aiterp.net/rpdata/api/internal/generate"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/repositories"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
)
type fileRepository struct {
files *mgo.Collection
}
func (r *fileRepository) Find(ctx context.Context, id string) (*models.File, error) {
file := new(models.File)
err := r.files.FindId(id).One(file)
if err != nil {
if err == mgo.ErrNotFound {
return nil, repositories.ErrNotFound
}
return nil, err
}
return file, nil
}
func (r *fileRepository) List(ctx context.Context, filter models.FileFilter) ([]*models.File, error) {
query := bson.M{}
if filter.Author != nil {
query["author"] = *filter.Author
}
if filter.MimeTypes != nil {
query["mimeType"] = bson.M{"$in": filter.MimeTypes}
}
if filter.Public != nil {
query["public"] = *filter.Public
}
files := make([]*models.File, 0, 16)
err := r.files.Find(query).Sort("-date").All(&files)
if err != nil {
return nil, err
}
return files, nil
}
func (r *fileRepository) Insert(ctx context.Context, file models.File) (*models.File, error) {
file.ID = generate.FileID()
err := r.files.Insert(&file)
if err != nil {
return nil, err
}
return &file, nil
}
func (r *fileRepository) Update(ctx context.Context, file models.File, update models.FileUpdate) (*models.File, error) {
updateBson := bson.M{}
if update.Public != nil {
updateBson["public"] = *update.Public
file.Public = *update.Public
}
if update.Name != nil {
updateBson["name"] = *update.Name
file.Name = *update.Name
}
err := r.files.UpdateId(file.ID, bson.M{"$set": updateBson})
if err != nil {
return nil, err
}
return &file, nil
}
func (r *fileRepository) Delete(ctx context.Context, file models.File) error {
return r.files.RemoveId(file.ID)
}
func newFileRepository(db *mgo.Database) (*fileRepository, error) {
files := db.C("file.headers")
err := files.EnsureIndexKey("author")
if err != nil {
return nil, err
}
err = files.EnsureIndexKey("mimeType")
if err != nil {
return nil, err
}
err = files.EnsureIndexKey("public")
if err != nil {
return nil, err
}
return &fileRepository{files: files}, nil
}
+69
View File
@@ -0,0 +1,69 @@
package mongodb
import (
"context"
"git.aiterp.net/rpdata/api/internal/generate"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/repositories"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
)
type keyRepository struct {
keys *mgo.Collection
}
func newKeyRepository(db *mgo.Database) (repositories.KeyRepository, error) {
collection := db.C("auth.keys")
err := collection.EnsureIndexKey("user")
if err != nil {
return nil, err
}
return &keyRepository{keys: collection}, nil
}
func (r *keyRepository) Find(ctx context.Context, id string) (*models.Key, error) {
key := models.Key{}
err := r.keys.FindId(id).One(&key)
if err != nil {
return nil, err
}
return &key, err
}
func (r *keyRepository) List(ctx context.Context, filter models.KeyFilter) ([]*models.Key, error) {
query := bson.M{}
if filter.UserID != nil {
query["user"] = *filter.UserID
}
keys := make([]*models.Key, 0, 4)
err := r.keys.Find(query).All(&keys)
if err != nil {
if err == mgo.ErrNotFound {
return nil, nil
}
return nil, err
}
return keys, nil
}
func (r *keyRepository) Insert(ctx context.Context, key models.Key) (*models.Key, error) {
key.ID = generate.KeyID()
err := r.keys.Insert(&key)
if err != nil {
return nil, err
}
return &key, nil
}
func (r *keyRepository) Delete(ctx context.Context, key models.Key) error {
return r.keys.RemoveId(key.ID)
}
+204
View File
@@ -0,0 +1,204 @@
package mongodb
import (
"context"
"errors"
"git.aiterp.net/rpdata/api/internal/generate"
"git.aiterp.net/rpdata/api/models"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
"strconv"
"sync"
)
type logRepository struct {
openMutex sync.Mutex
logs *mgo.Collection
posts *mgo.Collection
shortIdCounter *counter
}
func newLogRepository(db *mgo.Database) (*logRepository, error) {
logs := db.C("logbot3.logs")
posts := db.C("logbot3.posts")
err := logs.EnsureIndexKey("date")
if err != nil {
return nil, err
}
err = logs.EnsureIndexKey("channel")
if err != nil {
return nil, err
}
err = logs.EnsureIndexKey("characterIds")
if err != nil {
return nil, err
}
err = logs.EnsureIndexKey("event")
if err != nil {
return nil, err
}
err = logs.EnsureIndex(mgo.Index{Key: []string{"channel", "open"}})
if err != nil {
return nil, err
}
err = logs.EnsureIndex(mgo.Index{
Key: []string{"shortId"},
Unique: true,
DropDups: true,
})
if err != nil {
return nil, err
}
return &logRepository{
logs: logs,
posts: posts,
shortIdCounter: newCounter(db, "auto_increment", "Log"),
}, nil
}
func (r *logRepository) Find(ctx context.Context, id string) (*models.Log, error) {
log := new(models.Log)
err := r.logs.Find(bson.M{"$or": []bson.M{{"_id": id}, {"shortId": id}}}).One(log)
if err != nil {
return nil, err
}
return log, nil
}
func (r *logRepository) List(ctx context.Context, filter models.LogFilter) ([]*models.Log, error) {
query := bson.M{}
if filter.Search != nil {
searchQuery := bson.M{
"$text": bson.M{"$search": *filter.Search},
"logId": bson.M{"$ne": nil},
}
logIds := make([]string, 0, 64)
err := r.posts.Find(searchQuery).Distinct("logId", &logIds)
if err != nil {
return nil, err
}
query["shortId"] = bson.M{"$in": logIds}
}
if filter.Open != nil {
r.openMutex.Lock()
defer r.openMutex.Unlock()
query["open"] = filter.Open
}
if len(filter.Characters) > 0 {
query["characterIds"] = bson.M{"$all": filter.Characters}
}
if len(filter.Channels) > 0 {
query["channel"] = bson.M{"$in": filter.Channels}
}
if len(filter.Events) > 0 {
query["event"] = bson.M{"$in": filter.Events}
}
if filter.MinDate != nil && filter.MaxDate != nil {
query["date"] = bson.M{"$gte": *filter.MinDate, "$lte": *filter.MaxDate}
} else if filter.MinDate != nil {
query["date"] = bson.M{"$gte": *filter.MinDate}
} else if filter.MaxDate != nil {
query["date"] = bson.M{"$lt": *filter.MaxDate}
}
logs := make([]*models.Log, 0, 32)
err := r.logs.Find(query).Sort("-date").Limit(filter.Limit).All(&logs)
if err != nil {
if err == mgo.ErrNotFound {
return logs, nil
}
return nil, err
}
return logs, nil
}
func (r *logRepository) Insert(ctx context.Context, log models.Log) (*models.Log, error) {
nextShortId, err := r.shortIdCounter.Increment(1)
if err != nil {
return nil, err
}
log.ID = generate.LogID(log)
log.ShortID = "L" + strconv.Itoa(nextShortId)
if log.Open {
// There can be only one open log in the same channel.
r.openMutex.Lock()
defer r.openMutex.Unlock()
_, err = r.logs.UpdateAll(bson.M{"channel": log.ChannelName, "open": true}, bson.M{"$set": bson.M{"open": false}})
if err != nil {
return nil, errors.New("Cannot close other logs: " + err.Error())
}
}
err = r.logs.Insert(&log)
if err != nil {
return nil, err
}
return &log, nil
}
func (r *logRepository) Update(ctx context.Context, log models.Log, update models.LogUpdate) (*models.Log, error) {
updateBson := bson.M{}
if update.Open != nil {
if *update.Open == true {
// There can be only one open log in the same channel.
r.openMutex.Lock()
defer r.openMutex.Unlock()
_, err := r.logs.UpdateAll(bson.M{"channel": log.ChannelName, "open": true}, bson.M{"$set": bson.M{"open": false}})
if err != nil {
return nil, errors.New("Cannot close other logs: " + err.Error())
}
}
updateBson["open"] = *update.Open
log.Open = *update.Open
}
if update.Title != nil {
updateBson["title"] = *update.Title
log.Title = *update.Title
}
if update.Description != nil {
updateBson["description"] = *update.Description
log.Description = *update.Description
}
if update.EventName != nil {
updateBson["event"] = *update.EventName
log.EventName = *update.EventName
}
if update.CharacterIDs != nil {
updateBson["characterIds"] = update.CharacterIDs
log.CharacterIDs = update.CharacterIDs
}
err := r.logs.UpdateId(log.ID, bson.M{"$set": updateBson})
if err != nil {
return nil, err
}
return &log, nil
}
func (r *logRepository) Delete(ctx context.Context, log models.Log) error {
err := r.logs.RemoveId(log.ID)
if err != nil {
return err
}
_, _ = r.posts.RemoveAll(bson.M{"logId": log.ShortID})
return nil
}
+345
View File
@@ -0,0 +1,345 @@
package mongodb
import (
"context"
"git.aiterp.net/rpdata/api/internal/generate"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/repositories"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
"log"
"strings"
"sync"
"time"
)
type postRepository struct {
logs *mgo.Collection
posts *mgo.Collection
orderMutex sync.Mutex
}
func newPostRepository(db *mgo.Database) (*postRepository, error) {
posts := db.C("logbot3.posts")
err := posts.EnsureIndexKey("logId")
if err != nil {
return nil, err
}
err = posts.EnsureIndexKey("time")
if err != nil {
return nil, err
}
err = posts.EnsureIndexKey("kind")
if err != nil {
return nil, err
}
err = posts.EnsureIndexKey("position")
if err != nil {
return nil, err
}
err = posts.EnsureIndex(mgo.Index{
Key: []string{"$text:text"},
})
if err != nil {
return nil, err
}
return &postRepository{
posts: posts,
logs: db.C("logbot3.logs"),
}, nil
}
func (r *postRepository) Find(ctx context.Context, id string) (*models.Post, error) {
post := new(models.Post)
err := r.posts.FindId(id).One(post)
if err != nil {
return nil, err
}
return post, nil
}
func (r *postRepository) List(ctx context.Context, filter models.PostFilter) ([]*models.Post, error) {
query := bson.M{}
if filter.LogID != nil && *filter.LogID != "" {
logId := *filter.LogID
if !strings.HasPrefix(logId, "L") {
// Resolve long id to short id
log := new(models.Log)
err := r.logs.FindId(logId).Select(bson.M{"logId": 1, "_id": 1}).One(log)
if err != nil {
return nil, err
}
logId = log.ShortID
}
query["logId"] = logId
}
if len(filter.IDs) > 0 {
query["_id"] = bson.M{"$in": filter.IDs}
}
if len(filter.Kinds) > 0 {
query["kind"] = bson.M{"$in": filter.Kinds}
}
if filter.Search != nil {
query["$text"] = bson.M{"$search": *filter.Search}
}
posts := make([]*models.Post, 0, 32)
err := r.posts.Find(query).Sort("-logId", "position").Limit(filter.Limit).All(&posts)
if err != nil {
if err == mgo.ErrNotFound {
return []*models.Post{}, nil
}
return nil, err
}
return posts, nil
}
func (r *postRepository) Insert(ctx context.Context, post models.Post) (*models.Post, error) {
r.orderMutex.Lock()
defer r.orderMutex.Unlock()
lastPost := new(models.Post)
err := r.posts.Find(bson.M{"logId": post.LogID}).Sort("-position").One(lastPost)
if err != nil && err != mgo.ErrNotFound {
return nil, err
}
post.ID = generate.PostID()
post.Position = lastPost.Position + 1 // Position 1 is first position, so this is safe.
err = r.posts.Insert(post)
if err != nil {
return nil, err
}
return &post, nil
}
func (r *postRepository) InsertMany(ctx context.Context, posts ...*models.Post) ([]*models.Post, error) {
if len(posts) == 0 {
return []*models.Post{}, nil
}
logId := posts[0].LogID
for _, post := range posts {
if post.LogID != logId {
return nil, repositories.ErrParentMismatch
}
post.ID = generate.PostID()
}
r.orderMutex.Lock()
defer r.orderMutex.Unlock()
lastPost := new(models.Post)
err := r.posts.Find(bson.M{"logId": posts[0].LogID}).Sort("-position").One(lastPost)
if err != nil && err != mgo.ErrNotFound {
return nil, err
}
docs := make([]interface{}, len(posts))
for i := range posts {
posts[i].Position = lastPost.Position + 1 + i
docs[i] = posts[i]
}
err = r.posts.Insert(docs...)
if err != nil && err != mgo.ErrNotFound {
return nil, err
}
return posts, nil
}
func (r *postRepository) Update(ctx context.Context, post models.Post, update models.PostUpdate) (*models.Post, error) {
updateBson := bson.M{}
if update.Time != nil {
updateBson["time"] = *update.Time
post.Time = *update.Time
}
if update.Kind != nil {
updateBson["kind"] = *update.Kind
post.Kind = *update.Kind
}
if update.Nick != nil {
updateBson["nick"] = *update.Nick
post.Nick = *update.Nick
}
if update.Text != nil {
updateBson["text"] = *update.Text
post.Text = *update.Text
}
err := r.posts.UpdateId(post.ID, bson.M{"$set": updateBson})
if err != nil {
return nil, err
}
return &post, nil
}
func (r *postRepository) Move(ctx context.Context, post models.Post, position int) ([]*models.Post, error) {
// If only MongoDB transactions weren't awful, this function would have been safe.
// Since it isn't, then good luck.
// Validate lower position bound.
if position < 1 {
return nil, repositories.ErrInvalidPosition
}
// Determine the operations on adjacent posts
var resultFilter bson.M
var pushFilter bson.M
var increment int
if post.Position > position {
pushFilter = bson.M{"position": bson.M{
"$gte": position,
"$lt": post.Position,
}}
increment = 1
resultFilter = bson.M{"position": bson.M{
"$lte": post.Position,
"$gte": position,
}}
} else {
pushFilter = bson.M{"position": bson.M{
"$lte": position,
"$gt": post.Position,
}}
increment = -1
resultFilter = bson.M{"position": bson.M{
"$lte": position,
"$gte": post.Position,
}}
}
pushFilter["logId"] = post.LogID
resultFilter["logId"] = post.LogID
// From here on out, sync is required
r.orderMutex.Lock()
defer r.orderMutex.Unlock()
// Detect ninja shenanigans
post2 := new(models.Post)
err := r.posts.FindId(post.ID).One(post2)
if err != nil && post2.Position != post.Position {
return nil, repositories.ErrNotFound
}
// Validate upper position bound
lastPost := new(models.Post)
err = r.posts.Find(bson.M{"logId": post.LogID}).Sort("-position").One(lastPost)
if err != nil && err != mgo.ErrNotFound {
return nil, err
}
if position > lastPost.Position {
return nil, repositories.ErrInvalidPosition
}
// Move the posts
changeInfo, err := r.posts.UpdateAll(pushFilter, bson.M{"$inc": bson.M{"position": increment}})
if err != nil && err != mgo.ErrNotFound {
return nil, err
}
err = r.posts.UpdateId(post.ID, bson.M{"$set": bson.M{"position": position}})
if err != nil {
// Try to undo it
_, err := r.posts.UpdateAll(pushFilter, bson.M{"$inc": bson.M{"position": -increment}})
if err != nil && err != mgo.ErrNotFound {
return nil, err
}
return nil, err
}
results := make([]*models.Post, 0, changeInfo.Matched+1)
err = r.posts.Find(resultFilter).All(&results)
if err != nil {
return nil, err
}
return results, nil
}
func (r *postRepository) Delete(ctx context.Context, post models.Post) error {
r.orderMutex.Lock()
defer r.orderMutex.Unlock()
err := r.posts.RemoveId(post.ID)
if err != nil {
return err
}
_, _ = r.posts.UpdateAll(bson.M{"logId": post.LogID, "position": bson.M{"$gt": post.Position}}, bson.M{"$inc": bson.M{"position": -1}})
return nil
}
func (r *postRepository) fixPositions(logRepo *logRepository) {
disorders := make([]int, 0, 16)
diffs := make([]int, 0, 16)
startTime := time.Now()
timeout, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
logs, err := logRepo.List(timeout, models.LogFilter{})
if err != nil {
log.Println("Failed to get logs for position fix:", err)
}
log.Println("Starting log position fixing, this should not take longer than 10 seconds.")
for _, l := range logs {
r.orderMutex.Lock()
posts, err := r.List(timeout, models.PostFilter{LogID: &l.ShortID})
if err != nil {
r.orderMutex.Unlock()
continue
}
disorders = disorders[:0]
diffs = diffs[:0]
for i, post := range posts {
if post.Position != (i + 1) {
disorders = append(disorders, i)
diffs = append(diffs, post.Position-(i+1))
}
}
if len(disorders) > 0 {
log.Println(len(disorders), "order errors detected in", l.ID)
ops := 0
for i, post := range posts {
if (i + 1) != posts[i].Position {
ops++
err := r.posts.UpdateId(post.ID, bson.M{"$set": bson.M{"position": i + 1}})
if err != nil {
log.Println(l.ShortID, "fix failed after", ops, "ops:", err)
break
}
}
}
log.Println(l.ShortID, "fixed after", ops, "ops.")
}
r.orderMutex.Unlock()
}
log.Println("Log position fixing finished in", time.Since(startTime))
}
+214
View File
@@ -0,0 +1,214 @@
package mongodb
import (
"context"
"git.aiterp.net/rpdata/api/internal/generate"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/repositories"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
"log"
)
type storyRepository struct {
stories *mgo.Collection
chapters *mgo.Collection
comments *mgo.Collection
}
func newStoryRepository(db *mgo.Database) (repositories.StoryRepository, error) {
collection := db.C("story.stories")
err := collection.EnsureIndexKey("tags")
if err != nil {
return nil, err
}
err = collection.EnsureIndexKey("author")
if err != nil {
return nil, err
}
err = collection.EnsureIndexKey("updatedDate")
if err != nil {
return nil, err
}
err = collection.EnsureIndexKey("fictionalDate")
if err != nil {
return nil, err
}
err = collection.EnsureIndexKey("listed")
if err != nil {
return nil, err
}
return &storyRepository{
stories: collection,
chapters: db.C("story.chapters"),
comments: db.C("story.comments"),
}, nil
}
func (r *storyRepository) Find(ctx context.Context, id string) (*models.Story, error) {
story := new(models.Story)
err := r.stories.FindId(id).One(story)
if err != nil {
return nil, err
}
return story, nil
}
func (r *storyRepository) List(ctx context.Context, filter models.StoryFilter) ([]*models.Story, error) {
query := bson.M{}
if filter.Author != nil {
query["author"] = *filter.Author
}
if filter.Category != nil {
query["category"] = *filter.Category
}
if filter.Open != nil {
query["open"] = *filter.Open
}
if len(filter.Tags) > 0 {
query["tags"] = bson.M{"$all": filter.Tags}
}
if filter.Unlisted != nil {
query["listed"] = !*filter.Unlisted
} else {
query["listed"] = true
}
if !filter.EarliestFictionalDate.IsZero() && !filter.LatestFictionalDate.IsZero() {
query["fictionalDate"] = bson.M{
"$gte": filter.EarliestFictionalDate,
"$lte": filter.LatestFictionalDate,
}
} else if !filter.EarliestFictionalDate.IsZero() {
query["fictionalDate"] = bson.M{
"$gte": filter.EarliestFictionalDate,
}
} else if !filter.LatestFictionalDate.IsZero() {
query["fictionalDate"] = bson.M{
"$lte": filter.LatestFictionalDate,
}
}
stories := make([]*models.Story, 0, 32)
err := r.stories.Find(query).Sort("-updatedDate").Limit(filter.Limit).All(&stories)
if err != nil {
if err == mgo.ErrNotFound {
return stories, nil
}
return nil, err
}
return stories, nil
}
func (r *storyRepository) Insert(ctx context.Context, story models.Story) (*models.Story, error) {
story.ID = generate.StoryID()
err := r.stories.Insert(story)
if err != nil {
return nil, err
}
return &story, nil
}
func (r *storyRepository) Update(ctx context.Context, story models.Story, update models.StoryUpdate) (*models.Story, error) {
updateBson := bson.M{}
if update.Name != nil {
updateBson["name"] = *update.Name
story.Name = *update.Name
}
if update.Open != nil {
updateBson["open"] = *update.Open
story.Open = *update.Open
}
if update.Category != nil {
updateBson["category"] = *update.Category
story.Category = *update.Category
}
if update.Author != nil {
updateBson["author"] = *update.Author
story.Author = *update.Author
}
if update.FictionalDate != nil {
updateBson["fictionalDate"] = *update.FictionalDate
story.FictionalDate = *update.FictionalDate
}
if update.UpdatedDate != nil {
updateBson["updatedDate"] = *update.UpdatedDate
story.UpdatedDate = *update.UpdatedDate
}
if update.Listed != nil {
updateBson["listed"] = *update.Listed
story.Listed = *update.Listed
}
if update.SortByFictionalDate != nil {
updateBson["sortByFictionalDate"] = *update.SortByFictionalDate
story.SortByFictionalDate = *update.SortByFictionalDate
}
err := r.stories.UpdateId(story.ID, bson.M{"$set": updateBson})
if err != nil {
return nil, err
}
return &story, nil
}
func (r *storyRepository) AddTag(ctx context.Context, story models.Story, tag models.Tag) error {
ci, err := r.stories.UpdateAll(bson.M{"_id": story.ID}, bson.M{"$addToSet": bson.M{"tags": tag}})
if err != nil {
return err
}
if ci.Updated == 0 {
return repositories.ErrTagExists
}
return nil
}
func (r *storyRepository) RemoveTag(ctx context.Context, story models.Story, tag models.Tag) error {
ci, err := r.stories.UpdateAll(bson.M{"_id": story.ID}, bson.M{"$pull": bson.M{"tags": tag}})
if err != nil {
return err
}
if ci.Updated == 0 {
return repositories.ErrTagDoesNotExist
}
return nil
}
func (r *storyRepository) Delete(ctx context.Context, story models.Story) error {
err := r.stories.RemoveId(story.ID)
if err != nil {
return err
}
chapterIds := make([]string, 0, 8)
err = r.chapters.Find(bson.M{"storyId": story.ID}).Distinct("_id", &chapterIds)
if err != nil {
log.Println("Failed to find chapterIds:", err)
return nil
}
if len(chapterIds) > 0 {
c1, err := r.chapters.RemoveAll(bson.M{"storyId": story.ID})
if err != nil {
log.Println("Failed to remove chapters:", err)
return nil
}
c2, err := r.comments.RemoveAll(bson.M{"chapterId": bson.M{"$in": chapterIds}})
if err != nil {
log.Println("Failed to remove comments:", err)
return nil
}
log.Printf("Removed story %s (%d chapters, %d comments)", story.ID, c1.Removed, c2.Removed)
}
return nil
}
+78
View File
@@ -0,0 +1,78 @@
package mongodb
import (
"context"
"sort"
"strings"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/repositories"
"github.com/globalsign/mgo"
"go.mongodb.org/mongo-driver/bson"
)
type tagRepository struct {
stories *mgo.Collection
}
func newTagRepository(db *mgo.Database) repositories.TagRepository {
return &tagRepository{
stories: db.C("story.stories"),
}
}
func (r *tagRepository) Find(ctx context.Context, kind models.TagKind, name string) (*models.Tag, error) {
tags := make([]*models.Tag, 0, 1)
err := r.stories.Find(bson.M{"listed": true, "tags": bson.M{"kind": kind, "name": name}}).Distinct("tag", &tags)
if err != nil {
return nil, err
} else if len(tags) == 0 {
return nil, repositories.ErrNotFound
}
for _, tag := range tags {
if tag.Kind == kind && tag.Name == name {
return tag, nil
}
}
return nil, repositories.ErrNotFound
}
func (r *tagRepository) List(ctx context.Context, filter models.TagFilter) ([]*models.Tag, error) {
tags := make([]*models.Tag, 0, 64)
query := bson.M{"listed": true, "tags": bson.M{"$ne": nil}}
if filter.Kind != nil {
query["tags.kind"] = *filter.Kind
}
err := r.stories.Find(query).Distinct("tags", &tags)
if err != nil {
return nil, err
}
if filter.Kind != nil {
// While it's unsorted, delete any incorrect tags with replacement.
for i := 0; i < len(tags); i++ {
if tags[i].Kind != *filter.Kind {
tags[i] = tags[len(tags)-1]
tags = tags[:len(tags)-1]
i--
}
}
}
sort.Slice(tags, func(i, j int) bool {
if filter.Kind == nil {
kindCmp := strings.Compare(string(tags[i].Kind), string(tags[j].Kind))
if kindCmp != 0 {
return kindCmp < 0
}
}
return strings.Compare(tags[i].Name, tags[j].Name) < 0
})
return tags, nil
}
+41
View File
@@ -0,0 +1,41 @@
package mongodb
import (
"context"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/repositories"
"github.com/globalsign/mgo"
)
type userRepository struct {
users *mgo.Collection
}
func newUserRepository(db *mgo.Database) (repositories.UserRepository, error) {
collection := db.C("core.users")
return &userRepository{users: collection}, nil
}
func (r *userRepository) Find(ctx context.Context, id string) (*models.User, error) {
user := new(models.User)
err := r.users.FindId(id).One(user)
if err != nil {
if err == mgo.ErrNotFound {
return nil, repositories.ErrNotFound
}
return nil, err
}
return user, nil
}
func (r *userRepository) Insert(ctx context.Context, user models.User) (*models.User, error) {
err := r.users.Insert(user)
if err != nil {
return nil, err
}
return &user, nil
}
+35
View File
@@ -0,0 +1,35 @@
module git.aiterp.net/rpdata/api
go 1.12
require (
github.com/99designs/gqlgen v0.9.0
github.com/beorn7/perks v1.0.0 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/globalsign/mgo v0.0.0-20180403085842-f76e4f9da92e
github.com/go-ini/ini v1.35.0 // indirect
github.com/golang/protobuf v1.3.1 // indirect
github.com/google/go-cmp v0.3.0 // indirect
github.com/gorilla/websocket v1.4.0 // indirect
github.com/h2non/filetype v1.0.8
github.com/minio/minio-go v0.0.0-20180409193742-3d2d02921f05
github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 // indirect
github.com/prometheus/client_golang v0.9.1
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect
github.com/prometheus/common v0.4.0 // indirect
github.com/prometheus/procfs v0.0.0-20190517135640-51af30a78b0e // indirect
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a // indirect
github.com/stretchr/testify v1.3.0
github.com/tidwall/pretty v1.0.0 // indirect
github.com/vektah/dataloaden v0.3.0
github.com/vektah/gqlparser v1.1.2
go.mongodb.org/mongo-driver v1.0.3
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 // indirect
golang.org/x/net v0.0.0-20190514140710-3ec191127204 // indirect
golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/text v0.3.0 // indirect
gopkg.in/ini.v1 v1.42.0 // indirect
gopkg.in/yaml.v2 v2.2.2
)
replace golang.org/x/net => golang.org/x/net v0.0.0-20180416171110-a35a21de978d
+142
View File
@@ -0,0 +1,142 @@
github.com/99designs/gqlgen v0.9.0 h1:g1arBPML74Vqv0L3Q+TqIhGXLspV+2MYtRLkBxuZrlE=
github.com/99designs/gqlgen v0.9.0/go.mod h1:HrrG7ic9EgLPsULxsZh/Ti+p0HNWgR3XRuvnD0pb5KY=
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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/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-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
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/h2non/filetype v1.0.8 h1:le8gpf+FQA0/DlDABbtisA1KiTS0Xi+YSC/E8yY3Y14=
github.com/h2non/filetype v1.0.8/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU=
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/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1 h1:K47Rk0v/fkEfwfQet2KWhscE0cJzjgCCDBG2KHZoVno=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190517135640-51af30a78b0e h1:zK8d1aZ+gw/Ne4uMfZTFRxj08PUOp+gGwm4HWUeGI1k=
github.com/prometheus/procfs v0.0.0-20190517135640-51af30a78b0e/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
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.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/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 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
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.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/dataloaden v0.3.0 h1:ZfVN2QD6swgvp+tDqdH/OIT/wu3Dhu0cus0k5gIZS84=
github.com/vektah/dataloaden v0.3.0/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
go.mongodb.org/mongo-driver v1.0.3 h1:GKoji1ld3tw2aC+GX1wbr/J2fX13yNacEYoJ8Nhr0yU=
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5 h1:mzjBh+S5frKOsOBobWIMAbXavqjmgO17k/2puhcFR94=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20190125232054-d66bd3c5d5a6 h1:iZgcI2DDp6zW5v9Z/5+f0NuqoxNdmzg4hivjk2WLXpY=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd h1:oMEQDWVXVNpceQoVd1JN3CQ7LYJJzs5qWqZIUcxXHHw=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
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=
+186
View File
@@ -0,0 +1,186 @@
package graph2
import (
"git.aiterp.net/rpdata/api/graph2/graphcore"
"git.aiterp.net/rpdata/api/models"
)
func complexity() (cr graphcore.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 *models.CharacterFilter) int {
return childComplexity + listComplexity
}
cr.Query.Channel = func(childComplexity int, name string) int {
return childComplexity + findComplexity
}
cr.Query.Channels = func(childComplexity int, filter *models.ChannelFilter) int {
return childComplexity + listComplexity
}
cr.Query.Post = func(childComplexity int, id string) int {
return childComplexity + findComplexity
}
cr.Query.Posts = func(childComplexity int, filter *models.PostFilter) int {
return childComplexity + listComplexity
}
cr.Query.UnknownNicks = func(childComplexity int, filter *graphcore.UnknownNicksFilter) int {
return childComplexity + listComplexity
}
cr.Query.Log = func(childComplexity int, id string) int {
return childComplexity + findComplexity
}
cr.Query.Logs = func(childComplexity int, filter *models.LogFilter) int {
if filter != nil && ((filter.Open != nil && *filter.Open == true) || (filter.Limit <= 10)) {
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, filter *graphcore.TagFilter) int {
return childComplexity + listComplexity
}
cr.Query.Story = func(childComplexity int, id string) int {
return childComplexity + findComplexity
}
cr.Query.Stories = func(childComplexity int, filter *models.StoryFilter) int {
if filter != nil && filter.Limit <= 10 {
return childComplexity + findComplexity
}
return childComplexity + listComplexity
}
cr.Query.File = func(childComplexity int, id string) int {
return childComplexity + findComplexity
}
cr.Query.Files = func(childComplexity int, filter *models.FileFilter) int {
return childComplexity + listComplexity
}
cr.Query.Changes = func(childComplexity int, filter *models.ChangeFilter) int {
return childComplexity + listComplexity
}
cr.Query.Token = func(childComplexity int) int {
return childComplexity + findComplexity
}
cr.Mutation.AddStory = func(childComplexity int, input graphcore.StoryAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddStoryTag = func(childComplexity int, input graphcore.StoryTagAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemoveStoryTag = func(childComplexity int, input graphcore.StoryTagRemoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.EditStory = func(childComplexity int, input graphcore.StoryEditInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemoveStory = func(childComplexity int, input graphcore.StoryRemoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddChapter = func(childComplexity int, input graphcore.ChapterAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.EditChapter = func(childComplexity int, input graphcore.ChapterEditInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.MoveChapter = func(childComplexity int, input graphcore.ChapterMoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemoveChapter = func(childComplexity int, input graphcore.ChapterRemoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddComment = func(childComplexity int, input graphcore.CommentAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.EditComment = func(childComplexity int, input graphcore.CommentEditInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemoveComment = func(childComplexity int, input graphcore.CommentRemoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddLog = func(childComplexity int, input graphcore.LogAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.ImportLog = func(childComplexity int, input graphcore.LogImportInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.EditLog = func(childComplexity int, input graphcore.LogEditInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemoveLog = func(childComplexity int, input graphcore.LogRemoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddPost = func(childComplexity int, input graphcore.PostAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.EditPost = func(childComplexity int, input graphcore.PostEditInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.MovePost = func(childComplexity int, input graphcore.PostMoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemovePost = func(childComplexity int, input graphcore.PostRemoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddCharacter = func(childComplexity int, input graphcore.CharacterAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddCharacterNick = func(childComplexity int, input graphcore.CharacterNickInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemoveCharacterNick = func(childComplexity int, input graphcore.CharacterNickInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.EditCharacter = func(childComplexity int, input graphcore.CharacterEditInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.RemoveCharacter = func(childComplexity int, input graphcore.CharacterRemoveInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.AddChannel = func(childComplexity int, input graphcore.ChannelAddInput) int {
return childComplexity + mutationComplexity
}
cr.Mutation.EditChannel = func(childComplexity int, input graphcore.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.Log.NextLogs = func(childComplexity int) int {
return childComplexity + (subListComplexity / 2)
}
cr.Log.PrevLogs = func(childComplexity int) int {
return childComplexity + (subListComplexity / 2)
}
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
}
+71
View File
@@ -0,0 +1,71 @@
schema:
- schema/root.gql
- schema/types/*.gql
exec:
filename: graphcore/exec_gen.go
package: graphcore
model:
filename: graphcore/input_gen.go
package: graphcore
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.CharacterFilter
Channel:
model: git.aiterp.net/rpdata/api/models.Channel
ChannelsFilter:
model: git.aiterp.net/rpdata/api/models.ChannelFilter
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.PostFilter
Log:
model: git.aiterp.net/rpdata/api/models.Log
LogsFilter:
model: git.aiterp.net/rpdata/api/models.LogFilter
LogImporter:
model: git.aiterp.net/rpdata/api/models.LogImporter
LogSuggestion:
model: git.aiterp.net/rpdata/api/models.LogSuggestion
Comment:
model: git.aiterp.net/rpdata/api/models.Comment
ChapterCommentMode:
model: git.aiterp.net/rpdata/api/models.ChapterCommentMode
Chapter:
model: git.aiterp.net/rpdata/api/models.Chapter
Story:
model: git.aiterp.net/rpdata/api/models.Story
StoryCategory:
model: git.aiterp.net/rpdata/api/models.StoryCategory
StoriesFilter:
model: git.aiterp.net/rpdata/api/models.StoryFilter
File:
model: git.aiterp.net/rpdata/api/models.File
FilesFilter:
model: git.aiterp.net/rpdata/api/models.FileFilter
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.ChangeFilter
Token:
model: git.aiterp.net/rpdata/api/models.Token
User:
model: git.aiterp.net/rpdata/api/models.User
+59
View File
@@ -0,0 +1,59 @@
package graph2
import (
"git.aiterp.net/rpdata/api/graph2/graphcore"
"git.aiterp.net/rpdata/api/graph2/resolvers"
"git.aiterp.net/rpdata/api/graph2/types"
"git.aiterp.net/rpdata/api/services"
graphql "github.com/99designs/gqlgen/graphql"
)
//go:generate go run github.com/99designs/gqlgen -v
// New creates a new GraphQL schema.
func New(s *services.Bundle) graphql.ExecutableSchema {
return graphcore.NewExecutableSchema(graphcore.Config{
Resolvers: &rootResolver{s},
Complexity: complexity(),
})
}
type rootResolver struct {
s *services.Bundle
}
func (r *rootResolver) Query() graphcore.QueryResolver {
return resolvers.QueryResolver(r.s)
}
func (r *rootResolver) Mutation() graphcore.MutationResolver {
return resolvers.MutationResolver(r.s)
}
func (r *rootResolver) Subscription() graphcore.SubscriptionResolver {
return resolvers.SubscriptionResolver(r.s)
}
func (r *rootResolver) Log() graphcore.LogResolver {
return types.LogResolver(r.s)
}
func (r *rootResolver) Comment() graphcore.CommentResolver {
return types.CommentResolver(r.s)
}
func (r *rootResolver) Chapter() graphcore.ChapterResolver {
return types.ChapterResolver(r.s)
}
func (r *rootResolver) Story() graphcore.StoryResolver {
return types.StoryResolver(r.s)
}
func (r *rootResolver) Change() graphcore.ChangeResolver {
return &types.ChangeResolver
}
func (r *rootResolver) Token() graphcore.TokenResolver {
return types.TokenResolver(r.s)
}
+26
View File
@@ -0,0 +1,26 @@
package resolvers
import (
"context"
"git.aiterp.net/rpdata/api/models"
)
/// Queries
func (r *queryResolver) Changes(ctx context.Context, filter *models.ChangeFilter) ([]*models.Change, error) {
if filter == nil {
filter = &models.ChangeFilter{}
}
return r.s.Changes.List(ctx, *filter)
}
/// Subscriptions
func (r *subscriptionResolver) Changes(ctx context.Context, filter *models.ChangeFilter) (<-chan *models.Change, error) {
if filter == nil {
filter = &models.ChangeFilter{}
}
return r.s.Changes.Subscribe(ctx, *filter), nil
}
+49
View File
@@ -0,0 +1,49 @@
package resolvers
import (
"context"
"git.aiterp.net/rpdata/api/graph2/graphcore"
"git.aiterp.net/rpdata/api/models"
)
// Queries
func (r *queryResolver) Channel(ctx context.Context, name string) (*models.Channel, error) {
return r.s.Channels.Find(ctx, name)
}
func (r *queryResolver) Channels(ctx context.Context, filter *models.ChannelFilter) ([]*models.Channel, error) {
if filter == nil {
filter = &models.ChannelFilter{}
}
return r.s.Channels.List(ctx, *filter)
}
// Mutations
func (r *mutationResolver) AddChannel(ctx context.Context, input graphcore.ChannelAddInput) (*models.Channel, error) {
logged := input.Logged != nil && *input.Logged
hub := input.Hub != nil && *input.Hub
locationName := ""
if input.LocationName != nil {
locationName = *input.LocationName
}
eventName := ""
if input.LocationName != nil {
eventName = *input.EventName
}
return r.s.Channels.Create(ctx, input.Name, logged, hub, eventName, locationName)
}
func (r *mutationResolver) EditChannel(ctx context.Context, input graphcore.ChannelEditInput) (*models.Channel, error) {
return r.s.Channels.Update(ctx, input.Name, models.ChannelUpdate{
Logged: input.Logged,
Hub: input.Hub,
EventName: input.EventName,
LocationName: input.LocationName,
})
}
+71
View File
@@ -0,0 +1,71 @@
package resolvers
import (
"context"
"time"
"git.aiterp.net/rpdata/api/graph2/graphcore"
"git.aiterp.net/rpdata/api/models"
)
// Queries
func (r *queryResolver) Chapter(ctx context.Context, id string) (*models.Chapter, error) {
return r.s.Stories.FindChapter(ctx, id)
}
// Mutations
func (r *mutationResolver) AddChapter(ctx context.Context, input graphcore.ChapterAddInput) (*models.Chapter, error) {
story, err := r.s.Stories.FindStory(ctx, input.StoryID)
if err != nil {
return nil, err
}
commentMode := models.ChapterCommentModeDisabled
if input.CommentMode != nil {
commentMode = *input.CommentMode
}
return r.s.Stories.CreateChapter(ctx, *story, input.Title, input.Source, input.Author, time.Now(), input.FictionalDate, commentMode)
}
func (r *mutationResolver) MoveChapter(ctx context.Context, input graphcore.ChapterMoveInput) (*models.Chapter, error) {
chapter, err := r.s.Stories.FindChapter(ctx, input.ID)
if err != nil {
return nil, err
}
from, err := r.s.Stories.FindStory(ctx, chapter.StoryID)
if err != nil {
return nil, err
}
to, err := r.s.Stories.FindStory(ctx, input.StoryID)
if err != nil {
return nil, err
}
return r.s.Stories.MoveChapter(ctx, chapter, *from, *to)
}
func (r *mutationResolver) EditChapter(ctx context.Context, input graphcore.ChapterEditInput) (*models.Chapter, error) {
chapter, err := r.s.Stories.FindChapter(ctx, input.ID)
if err != nil {
return nil, err
}
return r.s.Stories.EditChapter(ctx, chapter, input.Title, input.Source, input.FictionalDate, input.CommentMode, input.CommentsLocked)
}
func (r *mutationResolver) RemoveChapter(ctx context.Context, input graphcore.ChapterRemoveInput) (*models.Chapter, error) {
chapter, err := r.s.Stories.FindChapter(ctx, input.ID)
if err != nil {
return nil, err
}
err = r.s.Stories.RemoveChapter(ctx, chapter)
if err != nil {
return nil, err
}
return chapter, nil
}
+65
View File
@@ -0,0 +1,65 @@
package resolvers
import (
"context"
"errors"
"git.aiterp.net/rpdata/api/graph2/graphcore"
"git.aiterp.net/rpdata/api/models"
)
// Queries
func (r *queryResolver) Character(ctx context.Context, id *string, nick *string) (*models.Character, error) {
if id != nil {
return r.s.Characters.Find(ctx, *id)
} else if nick != nil {
return r.s.Characters.FindNick(ctx, *nick)
} else {
return nil, errors.New("You must specify either an ID or a nick")
}
}
func (r *queryResolver) Characters(ctx context.Context, filter *models.CharacterFilter) ([]*models.Character, error) {
if filter == nil {
filter = &models.CharacterFilter{}
}
return r.s.Characters.List(ctx, *filter)
}
// Mutations
func (r *mutationResolver) AddCharacter(ctx context.Context, input graphcore.CharacterAddInput) (*models.Character, error) {
author := ""
if input.Author != nil {
author = *input.Author
}
shortName := ""
if input.ShortName != nil {
shortName = *input.ShortName
}
description := ""
if input.Description != nil {
description = *input.Description
}
return r.s.Characters.Create(ctx, input.Nick, input.Name, shortName, author, description)
}
func (r *mutationResolver) AddCharacterNick(ctx context.Context, input graphcore.CharacterNickInput) (*models.Character, error) {
return r.s.Characters.AddNick(ctx, input.ID, input.Nick)
}
func (r *mutationResolver) RemoveCharacterNick(ctx context.Context, input graphcore.CharacterNickInput) (*models.Character, error) {
return r.s.Characters.RemoveNick(ctx, input.ID, input.Nick)
}
func (r *mutationResolver) EditCharacter(ctx context.Context, input graphcore.CharacterEditInput) (*models.Character, error) {
return r.s.Characters.Update(ctx, input.ID, input.Name, input.ShortName, input.Description)
}
func (r *mutationResolver) RemoveCharacter(ctx context.Context, input graphcore.CharacterRemoveInput) (*models.Character, error) {
return r.s.Characters.Delete(ctx, input.ID)
}
+64
View File
@@ -0,0 +1,64 @@
package resolvers
import (
"context"
"time"
"git.aiterp.net/rpdata/api/graph2/graphcore"
"git.aiterp.net/rpdata/api/models"
)
// Queries
func (r *queryResolver) Comment(ctx context.Context, id string) (*models.Comment, error) {
return r.s.Stories.FindComment(ctx, id)
}
// Mutations
func (r *mutationResolver) AddComment(ctx context.Context, input graphcore.CommentAddInput) (*models.Comment, error) {
chapter, err := r.s.Stories.FindChapter(ctx, input.ChapterID)
if err != nil {
return nil, err
}
subject := ""
if input.Subject != nil {
subject = *input.Subject
}
fictionalDate := time.Time{}
if input.FictionalDate != nil {
fictionalDate = *input.FictionalDate
}
return r.s.Stories.CreateComment(ctx, *chapter, subject, "", input.Source, input.CharacterName, input.CharacterID, time.Now(), fictionalDate)
}
func (r *mutationResolver) EditComment(ctx context.Context, input graphcore.CommentEditInput) (*models.Comment, error) {
comment, err := r.s.Stories.FindComment(ctx, input.CommentID)
if err != nil {
return nil, err
}
fictionalDate := input.FictionalDate
if input.ClearFictionalDate != nil && *input.ClearFictionalDate {
fictionalDate = &time.Time{}
}
return r.s.Stories.EditComment(ctx, comment, input.Source, input.CharacterName, input.CharacterID, input.Subject, fictionalDate)
}
func (r *mutationResolver) RemoveComment(ctx context.Context, input graphcore.CommentRemoveInput) (*models.Comment, error) {
comment, err := r.s.Stories.FindComment(ctx, input.CommentID)
if err != nil {
return nil, err
}
err = r.s.Stories.RemoveComment(ctx, comment)
if err != nil {
return nil, err
}
return comment, nil
}
+39
View File
@@ -0,0 +1,39 @@
package resolvers
import (
"context"
"git.aiterp.net/rpdata/api/graph2/graphcore"
"git.aiterp.net/rpdata/api/models"
)
// Queries
func (r *queryResolver) File(ctx context.Context, id string) (*models.File, error) {
return r.s.Files.Find(ctx, id)
}
func (r *queryResolver) Files(ctx context.Context, filter *models.FileFilter) ([]*models.File, error) {
if filter == nil {
public := true
filter = &models.FileFilter{
Public: &public,
}
}
return r.s.Files.List(ctx, *filter)
}
// Mutations
func (r *mutationResolver) UploadFile(ctx context.Context, input *graphcore.FileUploadInput) (*models.File, error) {
return r.s.Files.Upload(ctx, input.File.File, input.Name, input.Public, input.File.Size)
}
func (r *mutationResolver) EditFile(ctx context.Context, input *graphcore.FileEditInput) (*models.File, error) {
return r.s.Files.Edit(ctx, input.ID, input.Name, input.Public)
}
func (r *mutationResolver) RemoveFile(ctx context.Context, input *graphcore.FileRemoveInput) (*models.File, error) {
return r.s.Files.Remove(ctx, input.ID)
}
+89
View File
@@ -0,0 +1,89 @@
package resolvers
import (
"context"
"errors"
"time"
"git.aiterp.net/rpdata/api/graph2/graphcore"
"git.aiterp.net/rpdata/api/models"
)
// Queries
func (r *queryResolver) Log(ctx context.Context, id string) (*models.Log, error) {
return r.s.Logs.Find(ctx, id)
}
func (r *queryResolver) Logs(ctx context.Context, filter *models.LogFilter) ([]*models.Log, error) {
return r.s.Logs.List(ctx, filter)
}
// Mutations
func (r *mutationResolver) AddLog(ctx context.Context, input graphcore.LogAddInput) (*models.Log, error) {
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
}
return r.s.Logs.Create(ctx, title, description, input.Channel, event, open)
}
func (r *mutationResolver) ImportLog(ctx context.Context, input graphcore.LogImportInput) ([]*models.Log, error) {
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
}
sessionThreshold := time.Hour * 12
if input.SessionThresholdMs != nil {
sessionThreshold = time.Duration(*input.SessionThresholdMs) * time.Millisecond
}
return r.s.Logs.Import(ctx, input.Importer, date, tz, input.ChannelName, sessionThreshold, input.Data)
}
func (r *mutationResolver) SplitLog(ctx context.Context, input graphcore.LogSplitInput) (*models.Log, error) {
return r.s.Logs.SplitLog(ctx, input.LogID, input.StartPostID)
}
func (r *mutationResolver) MergeLog(ctx context.Context, input graphcore.LogMergeInput) (*models.Log, error) {
removeAfter := input.RemoveAfter != nil && *input.RemoveAfter
return r.s.Logs.MergeLogs(ctx, input.TargetLogID, input.SourceLogID, removeAfter)
}
func (r *mutationResolver) EditLog(ctx context.Context, input graphcore.LogEditInput) (*models.Log, error) {
update := models.LogUpdate{
Open: input.Open,
Description: input.Description,
EventName: input.Event,
Title: input.Title,
}
return r.s.Logs.Update(ctx, input.ID, update)
}
func (r *mutationResolver) RemoveLog(ctx context.Context, input graphcore.LogRemoveInput) (*models.Log, error) {
return r.s.Logs.Delete(ctx, input.ID)
}
+40
View File
@@ -0,0 +1,40 @@
package resolvers
import (
"context"
"git.aiterp.net/rpdata/api/graph2/graphcore"
"git.aiterp.net/rpdata/api/models"
)
// Queries
func (r *queryResolver) Post(ctx context.Context, id string) (*models.Post, error) {
return r.s.Logs.FindPosts(ctx, id)
}
func (r *queryResolver) Posts(ctx context.Context, filter *models.PostFilter) ([]*models.Post, error) {
return r.s.Logs.ListPosts(ctx, filter)
}
// Mutation
func (r *mutationResolver) AddPost(ctx context.Context, input graphcore.PostAddInput) (*models.Post, error) {
return r.s.Logs.AddPost(ctx, input.LogID, input.Time, input.Kind, input.Nick, input.Text)
}
func (r *mutationResolver) EditPost(ctx context.Context, input graphcore.PostEditInput) (*models.Post, error) {
return r.s.Logs.EditPost(ctx, input.ID, models.PostUpdate{
Time: input.Time,
Kind: input.Kind,
Nick: input.Nick,
Text: input.Text,
})
}
func (r *mutationResolver) MovePost(ctx context.Context, input graphcore.PostMoveInput) ([]*models.Post, error) {
return r.s.Logs.MovePost(ctx, input.ID, input.ToPosition)
}
func (r *mutationResolver) RemovePost(ctx context.Context, input graphcore.PostRemoveInput) (*models.Post, error) {
return r.s.Logs.DeletePost(ctx, input.ID)
}
+26
View File
@@ -0,0 +1,26 @@
package resolvers
import (
"git.aiterp.net/rpdata/api/graph2/graphcore"
"git.aiterp.net/rpdata/api/services"
)
type queryResolver struct{ s *services.Bundle }
type mutationResolver struct{ s *services.Bundle }
type subscriptionResolver struct{ s *services.Bundle }
// QueryResolver has all the queries
func QueryResolver(s *services.Bundle) graphcore.QueryResolver {
return &queryResolver{s: s}
}
// MutationResolver brings the radioactive goop
func MutationResolver(s *services.Bundle) graphcore.MutationResolver {
return &mutationResolver{s: s}
}
// SubscriptionResolver has the real-time magic.
func SubscriptionResolver(s *services.Bundle) graphcore.SubscriptionResolver {
return &subscriptionResolver{s: s}
}
+83
View File
@@ -0,0 +1,83 @@
package resolvers
import (
"context"
"time"
"git.aiterp.net/rpdata/api/graph2/graphcore"
"git.aiterp.net/rpdata/api/models"
)
func (r *queryResolver) Story(ctx context.Context, id string) (*models.Story, error) {
return r.s.Stories.FindStory(ctx, id)
}
func (r *queryResolver) Stories(ctx context.Context, filter *models.StoryFilter) ([]*models.Story, error) {
if filter == nil {
filter = &models.StoryFilter{}
}
return r.s.Stories.ListStories(ctx, *filter)
}
// Mutations
func (r *mutationResolver) AddStory(ctx context.Context, input graphcore.StoryAddInput) (*models.Story, error) {
listed := input.Listed != nil && *input.Listed
open := input.Open != nil && *input.Open
tags := make([]models.Tag, len(input.Tags))
for i, tag := range input.Tags {
tags[i] = *tag
}
fictionalDate := time.Time{}
if input.FictionalDate != nil {
fictionalDate = *input.FictionalDate
}
sortByFictionalDate := input.SortByFictionalDate != nil && *input.SortByFictionalDate
return r.s.Stories.CreateStory(ctx, input.Name, input.Author, input.Category, listed, open, tags, time.Now(), fictionalDate, sortByFictionalDate)
}
func (r *mutationResolver) AddStoryTag(ctx context.Context, input graphcore.StoryTagAddInput) (*models.Story, error) {
story, err := r.s.Stories.FindStory(ctx, input.ID)
if err != nil {
return nil, err
}
return r.s.Stories.AddStoryTag(ctx, *story, *input.Tag)
}
func (r *mutationResolver) RemoveStoryTag(ctx context.Context, input graphcore.StoryTagRemoveInput) (*models.Story, error) {
story, err := r.s.Stories.FindStory(ctx, input.ID)
if err != nil {
return nil, err
}
return r.s.Stories.RemoveStoryTag(ctx, *story, *input.Tag)
}
func (r *mutationResolver) EditStory(ctx context.Context, input graphcore.StoryEditInput) (*models.Story, error) {
story, err := r.s.Stories.FindStory(ctx, input.ID)
if err != nil {
return nil, err
}
return r.s.Stories.EditStory(ctx, story, input.Name, input.Category, input.Listed, input.Open, input.FictionalDate, input.SortByFictionalDate)
}
func (r *mutationResolver) RemoveStory(ctx context.Context, input graphcore.StoryRemoveInput) (*models.Story, error) {
story, err := r.s.Stories.FindStory(ctx, input.ID)
if err != nil {
return nil, err
}
err = r.s.Stories.RemoveStory(ctx, story)
if err != nil {
return nil, err
}
return story, err
}
+17
View File
@@ -0,0 +1,17 @@
package resolvers
import (
"context"
"git.aiterp.net/rpdata/api/graph2/graphcore"
"git.aiterp.net/rpdata/api/models"
)
func (r *queryResolver) Tags(ctx context.Context, filter *graphcore.TagFilter) ([]*models.Tag, error) {
tagFilter := models.TagFilter{}
if filter != nil && filter.Kind != nil {
tagFilter.Kind = filter.Kind
}
return r.s.Tags.ListTags(ctx, tagFilter)
}
+17
View File
@@ -0,0 +1,17 @@
package resolvers
import (
"context"
"errors"
"git.aiterp.net/rpdata/api/models"
)
func (r *queryResolver) Token(ctx context.Context) (*models.Token, error) {
token := r.s.Auth.TokenFromContext(ctx)
if !token.Authenticated() {
return nil, errors.New("no (valid) token")
}
return token, nil
}
+24
View File
@@ -0,0 +1,24 @@
package resolvers
import (
"context"
"git.aiterp.net/rpdata/api/graph2/graphcore"
"git.aiterp.net/rpdata/api/models"
)
/// Queries
func (r *queryResolver) UnknownNicks(ctx context.Context, filter *graphcore.UnknownNicksFilter) ([]*models.UnknownNick, error) {
limit := 20
if filter != nil {
limit = filter.Limit
}
nicks := r.s.Logs.ListUnknownNicks()
if limit > 0 && len(nicks) > limit {
nicks = nicks[:limit]
}
return nicks, nil
}
+190
View File
@@ -0,0 +1,190 @@
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(filter: TagFilter): [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 comment in a chapter."
removeComment(input: CommentRemoveInput!): Comment!
# Add a new log
addLog(input: LogAddInput!): Log!
# Import a log
importLog(input: LogImportInput!): [Log!]!
# Split a log
splitLog(input: LogSplitInput!): Log!
# Merge a log
mergeLog(input: LogMergeInput!): 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!
"Upload a file"
uploadFile(input: FileUploadInput): File!
"Edit a file"
editFile(input: FileEditInput): File!
"Remove a file"
removeFile(input: FileRemoveInput): File!
}
type Subscription {
"""
Changes subscribes to the changes matching the following keys.
"""
changes(filter: ChangesFilter): Change!
}
# A Time represents a RFC3339 encoded date with up to millisecond precision.
scalar Time
"""A GraphQL file upload."""
scalar Upload
+86
View File
@@ -0,0 +1,86 @@
"""
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: Time
"The changed objects."
objects: [ChangeObject!]!
}
union ChangeObject = Character | Channel | Log | Post | Story | Tag | Chapter | Comment | File
"""
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
Comment
File
}
"""
Optional filter for the changes query.
"""
input ChangesFilter {
"The keys to query for."
keys: [ChangeKeyInput!]
"Only show changes by this author"
author: String
"Only show changes more recent than this date."
earliestDate: Time
"Limit the amount of results. This even goes for subscriptions!"
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: Time!
# The in-universe date and time of the chapter
fictionalDate: Time
# The date of edit.
editedDate: Time!
"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: Time
"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: Time
# 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: Time
"The date of creation."
createdDate: Time!
"The date of the last edit."
editedDate: Time!
"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: Time
"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: Time
"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: Time!
# Whether the file is publicly listable. Someone with knowledge of the ID
# will still be able to view it, however.
@@ -29,9 +29,30 @@ type File {
url: String
}
# Filter for the files quiery.
input FilesFilter {
# If set, this will limit the results to either public or private files.
public: Boolean
# Limit the MIME types of the files.
mimeTypes: [String!]
}
# Input for uploadFile mutation
input FileUploadInput {
"The file name"
name: String!
# Whether the file should be public
public: Boolean!
# The actual file upload
file: Upload!
}
# Input for editFile mutation
input EditFileInput {
# The file's unique ID
input FileEditInput {
# The file's unique ID
id: String!
# Whether the file is publicly listable. Someone with knowledge of the ID
@@ -40,4 +61,10 @@ input EditFileInput {
# The file's name
name: String
}
# Input for removeFile mutation
input FileRemoveInput {
# The file's unique ID
id: String!
}
@@ -8,7 +8,7 @@ type Log {
shortId: String!
# The date for a log.
date: String!
date: Time!
# 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!
@@ -34,6 +34,20 @@ type Log {
# The posts of the logfile, which can be filtered by kinds.
posts(kinds:[String!]): [Post!]!
# Suggested next logs.
nextLogs: [LogSuggestion!]!
# Suggested previous logs.
prevLogs: [LogSuggestion!]!
}
# Suggested log
type LogSuggestion {
log: Log!
hasEvent: Boolean!
hasChannel: Boolean!
characters: [Character!]!
}
# Filter for logs query
@@ -50,17 +64,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: Time!
# The channel where the log is set
channel: String!
@@ -96,37 +110,73 @@ 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: Time
# Whether the log session is open.
open: Boolean!
"Session threwhold"
sessionThresholdMs: Int
# The characters involved in the log file.
characters: [Character!]!
"The log body itself."
data: String!
}
"""
Input for splitLog mutation.
"""
input LogSplitInput {
"Log ID"
logId: String!
"Split out all posts from and including the post with this ID"
startPostId: String!
}
"""
Input for mergeLog mutation.
"""
input LogMergeInput {
"Target log ID"
targetLogId: String!
"Source log ID"
sourceLogId: String!
"Remove the source log after merging"
removeAfter: Boolean
}
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
"""
IRCCloud: This importer parses irccloud exports.
"""
IrcCloud
}
@@ -7,7 +7,7 @@ type Post {
logId: String!
# The date and time of posting
time: String!
time: Time!
# 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: Time!
# 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: Time
# 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 {
ids: [String!]
kinds: [String!]
logId: String
limit: Int
}
@@ -25,14 +25,17 @@ 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: Time!
# The date the story is set in (RFC3339 format).
fictionalDate: String!
# The date the story is set in.
fictionalDate: Time
# 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: Time!
# Whether to sort by fictional date.
sortByFictionalDate: Boolean!
}
# Filter for stories query.
@@ -40,14 +43,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: Time
# The latest fictionalDate
latestFictionalDate: String
latestFictionalDate: Time
# The max amount of stories to get (default: 30)
limit: Int
@@ -75,8 +87,11 @@ 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: Time
# Whether to sort by fictional date.
sortByFictionalDate: Boolean
}
# Input for the editStory mutation
@@ -101,8 +116,14 @@ 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: Time
# Clear the fictional date of the story.
clearFictionalDate: Boolean
# Whether to sort by fictional date.
sortByFictionalDate: Boolean
}
# Input for the addStoryTag mutation
@@ -123,20 +144,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
}
@@ -16,6 +16,15 @@ input TagInput {
name: String!
}
"""
A TagFilter is a filter for the tags query.
"""
input TagFilter {
"Filter by kind"
kind: TagKind
}
# Allowed values for Tag.kind
enum TagKind {
# An organization is a catch all term for in-universe corporations, teams, groups, cults, forces, etc...
@@ -33,4 +42,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!]!
}
+29
View File
@@ -0,0 +1,29 @@
package types
import (
"context"
"log"
"git.aiterp.net/rpdata/api/graph2/graphcore"
"git.aiterp.net/rpdata/api/models"
)
type changeResolver struct{}
func (r *changeResolver) Objects(ctx context.Context, obj *models.Change) ([]graphcore.ChangeObject, error) {
objects := obj.Objects()
results := make([]graphcore.ChangeObject, 0, len(objects))
for _, object := range objects {
if changeObject, ok := object.(graphcore.ChangeObject); ok {
results = append(results, changeObject)
} else {
log.Printf("Type %T needs ChangeObject impl", object)
}
}
return results, nil
}
// ChangeResolver is a resolver
var ChangeResolver changeResolver
+35
View File
@@ -0,0 +1,35 @@
package types
import (
"context"
"git.aiterp.net/rpdata/api/services"
"time"
"git.aiterp.net/rpdata/api/models"
)
type chapterResolver struct {
stories *services.StoryService
}
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) {
if limit == nil {
limit = new(int)
*limit = 0
}
return r.stories.ListComments(ctx, *chapter, *limit)
}
// ChapterResolver is a resolver
func ChapterResolver(s *services.Bundle) *chapterResolver {
return &chapterResolver{stories: s.Stories}
}
+45
View File
@@ -0,0 +1,45 @@
package types
import (
"context"
"git.aiterp.net/rpdata/api/repositories"
"git.aiterp.net/rpdata/api/services"
"github.com/globalsign/mgo"
"time"
"git.aiterp.net/rpdata/api/models"
)
type commentResolver struct {
characters *services.CharacterService
}
func (r *commentResolver) Character(ctx context.Context, obj *models.Comment) (*models.Character, error) {
if obj.CharacterID == "" {
return nil, nil
}
character, err := r.characters.Find(ctx, obj.CharacterID)
if err != nil {
if err == repositories.ErrNotFound || err == mgo.ErrNotFound {
return nil, 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
func CommentResolver(s *services.Bundle) *commentResolver {
return &commentResolver{characters: s.Characters}
}
+45
View File
@@ -0,0 +1,45 @@
package types
import (
"context"
"git.aiterp.net/rpdata/api/services"
"git.aiterp.net/rpdata/api/models"
)
type logResolver struct {
logs *services.LogService
characters *services.CharacterService
channels *services.ChannelService
}
func (r *logResolver) Channel(ctx context.Context, log *models.Log) (*models.Channel, error) {
return r.channels.Find(ctx, log.ChannelName)
}
func (r *logResolver) Characters(ctx context.Context, log *models.Log) ([]*models.Character, error) {
if len(log.CharacterIDs) == 0 {
return []*models.Character{}, nil
}
return r.characters.List(ctx, models.CharacterFilter{
IDs: log.CharacterIDs,
})
}
func (r *logResolver) Posts(ctx context.Context, log *models.Log, kinds []string) ([]*models.Post, error) {
return r.logs.ListPosts(ctx, &models.PostFilter{LogID: &log.ShortID})
}
func (r *logResolver) NextLogs(ctx context.Context, obj *models.Log) ([]*models.LogSuggestion, error) {
return r.logs.NextLogs(ctx, obj)
}
func (r *logResolver) PrevLogs(ctx context.Context, obj *models.Log) ([]*models.LogSuggestion, error) {
return r.logs.PrevLogs(ctx, obj)
}
// LogResolver is a resolver
func LogResolver(s *services.Bundle) *logResolver {
return &logResolver{characters: s.Characters, logs: s.Logs, channels: s.Channels}
}
+30
View File
@@ -0,0 +1,30 @@
package types
import (
"context"
"git.aiterp.net/rpdata/api/services"
"time"
"git.aiterp.net/rpdata/api/models"
)
type storyResolver struct {
stories *services.StoryService
}
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 r.stories.ListChapters(ctx, *story)
}
// StoryResolver is a resolver
func StoryResolver(s *services.Bundle) *storyResolver {
return &storyResolver{stories: s.Stories}
}
+21
View File
@@ -0,0 +1,21 @@
package types
import (
"context"
"git.aiterp.net/rpdata/api/services"
"git.aiterp.net/rpdata/api/models"
)
type tokenResolver struct {
auth *services.AuthService
}
func (r *tokenResolver) User(ctx context.Context, token *models.Token) (*models.User, error) {
return r.auth.FindUser(ctx, token.UserID)
}
// TokenResolver is a resolver
func TokenResolver(s *services.Bundle) *tokenResolver {
return &tokenResolver{auth: s.Auth}
}
-66
View File
@@ -1,66 +0,0 @@
package loader
import (
"context"
"errors"
"strings"
"git.aiterp.net/rpdata/api/model/channel"
"github.com/graph-gophers/dataloader"
)
// Channel gets a character by key
func (loader *Loader) Channel(key, value string) (channel.Channel, error) {
if !strings.HasPrefix(key, "Channel.") {
key = "Channel." + key
}
if loader.loaders[key] == nil {
return channel.Channel{}, errors.New("unsupported key")
}
thunk := loader.loaders[key].Load(loader.ctx, dataloader.StringKey(value))
res, err := thunk()
if err != nil {
return channel.Channel{}, err
}
channel, ok := res.(channel.Channel)
if !ok {
return channel, errors.New("incorrect type")
}
return channel, nil
}
func channelNameBatch(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
var results []*dataloader.Result
names := keys.Keys()
channels, err := channel.ListNames(names...)
if err != nil {
for range names {
results = append(results, &dataloader.Result{Data: channel.Channel{}, Error: err})
}
return results
}
for _, name := range names {
found := false
for i := range channels {
if channels[i].Name == name {
results = append(results, &dataloader.Result{Data: channels[i]})
found = true
break
}
}
if !found {
results = append(results, &dataloader.Result{Data: channel.Channel{}, Error: err})
}
}
return results
}
-130
View File
@@ -1,130 +0,0 @@
package loader
import (
"context"
"errors"
"strings"
"git.aiterp.net/rpdata/api/model/character"
"github.com/graph-gophers/dataloader"
)
// Character gets a character by key
func (loader *Loader) Character(key, value string) (character.Character, error) {
if !strings.HasPrefix(key, "Character.") {
key = "Character." + key
}
if loader.loaders[key] == nil {
return character.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
}
char, ok := res.(character.Character)
if !ok {
return character.Character{}, errors.New("incorrect type")
}
return char, nil
}
// Characters gets characters by key
func (loader *Loader) Characters(key string, values ...string) ([]character.Character, error) {
if !strings.HasPrefix(key, "Character.") {
key = "Character." + key
}
if loader.loaders[key] == nil {
return nil, errors.New("unsupported key")
}
thunk := loader.loaders[key].LoadMany(loader.ctx, dataloader.NewKeysFromStrings(values))
res, errs := thunk()
for _, err := range errs {
if err != nil && err != ErrNotFound {
return nil, err
}
}
chars := make([]character.Character, len(res))
for i := range res {
char, ok := res[i].(character.Character)
if !ok {
return nil, errors.New("incorrect type")
}
chars[i] = char
}
return chars, nil
}
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...)
if err != nil {
for range ids {
results = append(results, &dataloader.Result{Error: err})
}
return results
}
for _, id := range ids {
found := false
for _, character := range characters {
if character.ID == id {
results = append(results, &dataloader.Result{Data: character})
found = true
break
}
}
if !found {
results = append(results, &dataloader.Result{Data: character.Character{}, Error: ErrNotFound})
}
}
return results
}
func characterNickBatch(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
var results []*dataloader.Result
nicks := keys.Keys()
characters, err := character.ListNicks(nicks...)
if err != nil {
for range nicks {
results = append(results, &dataloader.Result{Error: err})
}
return results
}
for _, nick := range nicks {
found := false
for i := range characters {
if characters[i].HasNick(nick) {
results = append(results, &dataloader.Result{Data: characters[i]})
found = true
break
}
}
if !found {
results = append(results, &dataloader.Result{Data: character.Character{}, Error: err})
}
}
return results
}
-49
View File
@@ -1,49 +0,0 @@
package loader
import (
"context"
"errors"
"time"
"github.com/graph-gophers/dataloader"
)
var contextKey struct{}
// ErrNotFound is returned in batches when one or more things weren't found. Usually harmless.
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 {
ctx context.Context
loaders map[string]*dataloader.Loader
}
// New initializes the loader.
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)),
},
}
}
// FromContext gets the Loader from context.
func FromContext(ctx context.Context) *Loader {
value := ctx.Value(&contextKey)
if value == nil {
return nil
}
return value.(*Loader)
}
// ToContext gets a context with the loader as a value
func (loader *Loader) ToContext(ctx context.Context) context.Context {
loader.ctx = ctx
return context.WithValue(ctx, &contextKey, loader)
}
-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
}

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