working on custom collection demo package

This commit is contained in:
Sacha Greif 2016-11-18 16:01:27 +09:00
parent ae568422a8
commit e1744f9f93
28 changed files with 488 additions and 63 deletions

View file

@ -0,0 +1 @@
import './lib/modules.js';

View file

@ -0,0 +1,5 @@
const Movies = new Mongo.Collection("movies");
Movies.typeName = 'Movie';
export default Movies;

View file

@ -0,0 +1,51 @@
import Telescope from 'meteor/nova:lib';
import React, { PropTypes, Component } from 'react';
import NovaForm from "meteor/nova:forms";
import { Button } from 'react-bootstrap';
import { Accounts } from 'meteor/std:accounts-ui';
import { ModalTrigger } from "meteor/nova:core";
import Movies from '../collection.js';
class Movie extends Component {
renderEdit() {
const movie = this.props;
const component = (
<ModalTrigger
label="Edit Movie"
component={<Button bsStyle="primary">Edit Movie</Button>}
>
<NovaForm
collection={Movies}
currentUser={this.props.currentUser}
document={movie}
mutationName="moviesEdit"
/>
</ModalTrigger>
);
return (
<div className="item-actions">
{this.props.currentUser && this.props.currentUser._id === movie.userId ? component : ""}
</div>
)
}
render() {
const movie = this.props;
return (
<div key={movie.name} style={{paddingBottom: "15px",marginBottom: "15px", borderBottom: "1px solid #ccc"}}>
<h2>{movie.name} ({movie.year})</h2>
<p>{movie.review} by <strong>{movie.user && movie.user.username}</strong></p>
{this.renderEdit()}
</div>
)
}
};
export default Movie;

View file

@ -0,0 +1,47 @@
import Telescope from 'meteor/nova:lib';
import React, { PropTypes, Component } from 'react';
import NovaForm from "meteor/nova:forms";
import { Button } from 'react-bootstrap';
import { Accounts } from 'meteor/std:accounts-ui';
import { ModalTrigger } from "meteor/nova:core";
import withMoviesList from '../containers/withMoviesList';
import Movie from './Movie.jsx';
import Movies from '../collection.js';
const LoadMore = props => <a href="#" className="load-more button button--primary" onClick={props.loadMore}>Load More ({props.count}/{props.totalCount})</a>
class MoviesList extends Component {
renderNew() {
const component = (
<div className="add-movie">
<ModalTrigger
title="Add Movie"
component={<Button bsStyle="primary">Add Movie</Button>}
>
<NovaForm
collection={Movies}
mutationName="moviesNew"
currentUser={this.props.currentUser}
/>
</ModalTrigger>
<hr/>
</div>
)
return !!this.props.currentUser ? component : "";
}
render() {
return (
<div className="movies">
{this.renderNew()}
{this.props.results && this.props.results.map(movie => <Movie key={movie._id} {...movie} currentUser={this.props.currentUser}/>)}
{this.props.hasMore ? (this.props.ready ? <LoadMore {...this.props}/> : <p>Loading</p>) : <p>No more movies</p>}
</div>
)
}
};
export default withMoviesList(MoviesList);

View file

@ -0,0 +1,30 @@
import Telescope from 'meteor/nova:lib';
import React, { PropTypes, Component } from 'react';
import NovaForm from "meteor/nova:forms";
import { Button } from 'react-bootstrap';
import { Accounts } from 'meteor/std:accounts-ui';
import { ModalTrigger, Messages, FlashContainer } from "meteor/nova:core";
import MoviesList from './MoviesList.jsx';
class MoviesWrapper extends Component {
render() {
return (
<div className="wrapper">
{/*<div style={{maxWidth: "300px"}}>
<Accounts.ui.LoginForm />
</div>
<FlashContainer component={Telescope.components.FlashMessages}/>
*/}
<div className="main">
<MoviesList />
</div>
</div>
)
}
}
export default MoviesWrapper;

View file

@ -0,0 +1,15 @@
import { createFragment } from 'apollo-client';
import gql from 'graphql-tag';
import Movies from '../collection.js';
// create fragments used to specify which information to query for
const fullMovieInfo = createFragment(gql`
fragment fullMovieInfo on Movie {
_id
name
createdAt
year
}
`)
export { fullMovieInfo };

View file

@ -0,0 +1,53 @@
import Telescope from 'meteor/nova:lib';
import React, { PropTypes, Component } from 'react';
import Movies from '../collection.js';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import { fullMovieInfo } from './fragments.js';
export default function withMoviesList (component, options) {
return graphql(gql`
query getMoviesList($offset: Int, $limit: Int) {
movies(offset: $offset, limit: $limit) {
...fullMovieInfo
}
}
`, {
options(ownProps) {
return {
variables: {
offset: 0,
limit: 10
},
fragments: fullMovieInfo,
pollInterval: 20000,
};
},
props(props) {
const {data: {loading, movies, moviesListTotal, fetchMore}} = props;
return {
loading,
results: movies,
totalCount: moviesListTotal,
count: movies && movies.length,
loadMore() {
// basically, rerun the query 'getPostsList' with a new offset
return fetchMore({
variables: { offset: movies.length },
updateQuery(previousResults, { fetchMoreResult }) {
// no more post to fetch
if (!fetchMoreResult.data) {
return previousResults;
}
// return the previous results "augmented" with more
return {...previousResults, movies: [...previousResults.movies, ...fetchMoreResult.data.movies]};
},
});
},
...props.ownProps // pass on the props down to the wrapped component
};
},
})(component);
}

View file

@ -0,0 +1,6 @@
import './collection.js';
import './mutations.js';
import './permissions.js';
import './resolvers.js';
import './routes.js';
import './schema.js';

View file

@ -0,0 +1,54 @@
import Telescope, { newMutation, editMutation, removeMutation } from 'meteor/nova:lib';
import Movies from './collection.js'
// Resolvers
Movies.mutations = {
moviesNew(root, {document}, context) {
return newMutation({
action: 'movies.new',
collection: context.Movies,
document: document,
currentUser: context.currentUser,
validate: true
});
},
moviesEdit(root, {documentId, set, unset}, context) {
const document = context.Movies.findOne(documentId);
const action = Users.owns(context.currentUser, document) ? 'posts.edit.own' : 'posts.edit.all';
return editMutation({
action: action,
collection: context.Movies,
documentId: documentId,
set: set,
unset: unset,
currentUser: context.currentUser,
validate: true
});
},
moviesRemove(root, {documentId}, context) {
const document = context.Movies.findOne(documentId);
const action = Users.owns(context.currentUser, document) ? 'movies.remove.own' : 'movies.remove.all';
return removeMutation({
action: action,
collection: context.Movies,
documentId: documentId,
currentUser: context.currentUser,
validate: true
});
},
};
// GraphQL mutations
Telescope.graphQL.addMutation('moviesNew(document: moviesInput) : Movie');
Telescope.graphQL.addMutation('moviesEdit(documentId: String, set: moviesInput, unset: moviesUnset) : Movie');
Telescope.graphQL.addMutation('moviesRemove(documentId: String) : Movie');
export default Movies.mutations;

View file

@ -0,0 +1,14 @@
import Users from 'meteor/nova:users';
const defaultActions = [
"movies.new",
"movies.edit.own",
"movies.remove.own",
];
Users.groups.default.can(defaultActions);
const adminActions = [
"movies.edit.all",
"movies.remove.all"
];
Users.groups.admins.can(adminActions);

View file

@ -0,0 +1,40 @@
import Telescope from 'meteor/nova:lib';
import mutations from './mutations.js';
const resolvers = {
// Movie: {
// user(post, args, context) {
// return context.Users.findOne({ _id: post.userId }, { fields: context.getViewableFields(context.currentUser, context.Users) });
// },
// },
Query: {
movies(root, {offset, limit}, context, info) {
const protectedLimit = (limit < 1 || limit > 10) ? 10 : limit;
let options = {};
options.limit = protectedLimit;
options.skip = offset;
// keep only fields that should be viewable by current user
options.fields = context.getViewableFields(context.currentUser, context.Movies);
return context.Movies.find({}, options).fetch();
},
moviesListTotal(root, args, context) {
return context.Movies.find().count();
},
movie(root, args, context) {
return context.Movies.findOne({_id: args._id}, { fields: context.getViewableFields(context.currentUser, context.Movies) });
},
},
Mutation: mutations
};
// add resolvers
Telescope.graphQL.addResolvers(resolvers);
// define GraphQL queries
Telescope.graphQL.addQuery(`
movies(offset: Int, limit: Int): [Movie]
moviesListTotal(foo: Int): Int
movie(_id: String): Movie
`);
export default resolvers;

View file

@ -0,0 +1,5 @@
import Telescope from 'meteor/nova:lib';
import MoviesWrapper from './components/MoviesWrapper.jsx';
// add new "/movies" route that loads the MoviesWrapper component
Telescope.routes.add({ name: "movies", path: "/movies", component: MoviesWrapper });

View file

@ -0,0 +1,66 @@
import Telescope from 'meteor/nova:lib';
import Users from 'meteor/nova:users';
import Movies from './collection.js';
const alwaysPublic = user => true;
const isLoggedIn = user => !!user;
const canEdit = Users.canEdit;
// define schema
const schema = new SimpleSchema({
_id: {
type: String,
optional: true,
viewableIf: alwaysPublic
},
name: {
type: String,
control: "text",
viewableIf: alwaysPublic,
insertableIf: isLoggedIn,
editableIf: canEdit
},
createdAt: {
type: Date,
viewableIf: alwaysPublic,
autoValue: (documentOrModifier) => {
if (documentOrModifier && !documentOrModifier.$set) return new Date() // if this is an insert, set createdAt to current timestamp
}
},
year: {
type: String,
optional: true,
control: "text",
viewableIf: alwaysPublic,
insertableIf: isLoggedIn,
editableIf: canEdit
},
review: {
type: String,
control: "textarea",
viewableIf: alwaysPublic,
insertableIf: isLoggedIn,
editableIf: canEdit
},
privateComments: {
type: String,
optional: true,
control: "textarea",
viewableIf: alwaysPublic, //fixme
insertableIf: isLoggedIn,
editableIf: canEdit
},
userId: {
type: String,
viewableIf: alwaysPublic,
}
});
// attach schema to collection
Movies.attachSchema(schema);
// generate GraphQL schema from SimpleSchema schema
Telescope.graphQL.addCollection(Movies);
// make collection available to resolvers via their context
Telescope.graphQL.addToContext({ Movies });

View file

@ -0,0 +1,45 @@
import Movies from './collection.js';
import Users from 'meteor/nova:users';
import Telescope, { newMutation, editMutation, removeMutation } from 'meteor/nova:lib';
const seedData = [
{
name: 'Star Wars',
year: '1973',
review: `A classic.`,
privateComments: `Actually, I don't really like Star Wars…`
},
{
name: 'Die Hard',
year: '1987',
review: `A must-see if you like action movies.`,
privateComments: `I love Bruce Willis so much!`
},
{
name: 'Terminator',
year: '1983',
review: `Once again, Schwarzenegger shows why he's the boss.`,
privateComments: `Terminator is my favorite movie ever. `
},
{
name: 'Jaws',
year: '1971',
review: 'The original blockbuster.',
privateComments: `I'm scared of sharks…`
},
]
Meteor.startup(function () {
const currentUser = Users.findOne();
if (Movies.find().fetch().length === 0) {
seedData.forEach(document => {
newMutation({
action: 'movies.new',
collection: Movies,
document: document,
currentUser: currentUser,
validate: false
});
});
}
});

View file

@ -0,0 +1,19 @@
Package.describe({
name: "custom-collection-demo",
summary: "Telescope components package",
version: "0.27.4-nova",
git: "https://github.com/TelescopeJS/Telescope.git"
});
Package.onUse(function (api) {
api.versionsFrom(['METEOR@1.0']);
api.use([
'nova:core@0.27.4-nova',
]);
api.mainModule("server.js", "server");
api.mainModule("client.js", "client");
});

View file

@ -0,0 +1,3 @@
import './lib/modules.js';
import './lib/seed.js';

View file

@ -35,7 +35,6 @@ Posts.addField(
];
}
},
publish: true // make that field public and send it to the client
}
}
);

View file

@ -5,15 +5,22 @@ import { makeExecutableSchema } from 'graphql-tools';
import { meteorClientConfig, client } from './client.js';
import { createApolloServer } from './server.js';
import typeDefs from './schema';
import generateTypeDefs from './schema';
const schema = makeExecutableSchema({
typeDefs,
resolvers: Telescope.graphQL.resolvers,
});
Meteor.startup(function () {
const typeDefs = generateTypeDefs();
createApolloServer({
schema,
Telescope.graphQL.finalSchema = typeDefs;
const schema = makeExecutableSchema({
typeDefs,
resolvers: Telescope.graphQL.resolvers,
});
createApolloServer({
schema,
});
});
export { meteorClientConfig, client };

View file

@ -1,14 +1,16 @@
import Telescope from 'meteor/nova:lib';
export default schema = [`
${Telescope.graphQL.getCollectionsSchemas()}
${Telescope.graphQL.getAdditionalSchemas()}
const generateTypeDefs = () => [`
${Telescope.graphQL.getCollectionsSchemas()}
${Telescope.graphQL.getAdditionalSchemas()}
type Query {
${Telescope.graphQL.queries.join('\n')}
}
type Query {
${Telescope.graphQL.queries.join('\n')}
}
type Mutation {
${Telescope.graphQL.mutations.join('\n')}
}
type Mutation {
${Telescope.graphQL.mutations.join('\n')}
}
`];
export default generateTypeDefs;

View file

@ -1,31 +0,0 @@
Package.describe({
name: "nova:demo",
summary: "Telescope components package",
version: "0.27.4-nova",
git: "https://github.com/TelescopeJS/Telescope.git"
});
Package.onUse(function (api) {
api.versionsFrom(['METEOR@1.0']);
api.use([
// Nova packages
'nova:core@0.27.4-nova',
'utilities:react-list-container@0.1.10',
// third-party packages
// 'alt:react-accounts-ui@1.1.0'
]);
api.addFiles([
'demo-app.jsx'
], ['client', 'server']);
api.export([
"Movies"
], ['client', 'server'])
});

View file

@ -37,10 +37,12 @@ const newMutation = ({ action, collection, document, currentUser, validate }) =>
console.log(collection._name)
console.log(document)
const collectionName = collection._name;
const schema = collection.simpleSchema()._schema;
// add userId to document if needed
if (!document.userId) document.userId = currentUser._id;
// if document is not trusted, run validation steps
if (validate) {
@ -60,9 +62,6 @@ const newMutation = ({ action, collection, document, currentUser, validate }) =>
// validate document against schema
collection.simpleSchema().namedContext(`${collectionName}.new`).validate(document);
// add userId to document
document.userId = currentUser._id;
// run validation callbacks
document = Telescope.callbacks.run(`${collectionName}.new.validate`, document, currentUser);
}

View file

@ -14,6 +14,5 @@ import './methods.js';
import './permissions.js';
import './resolvers.js';
import './mutations.js';
import './queries.js';
export default Posts;

View file

@ -1,8 +0,0 @@
import Telescope from 'meteor/nova:lib';
import Posts from './collection.js';
Telescope.graphQL.addQuery(`
posts(terms: Terms, offset: Int, limit: Int): [Post]
postsListTotal(terms: Terms): Int
post(_id: String): Post
`);

View file

@ -38,3 +38,9 @@ export default resolvers = {
};
Telescope.graphQL.addResolvers(resolvers);
Telescope.graphQL.addQuery(`
posts(terms: Terms, offset: Int, limit: Int): [Post]
postsListTotal(terms: Terms): Int
post(_id: String): Post
`);

View file

@ -421,5 +421,3 @@ const termsSchema = `
Telescope.graphQL.addSchema(termsSchema);
Telescope.graphQL.addToContext({ Posts });