Skip to content

Commit c5decf8

Browse files
committed
Merge branch 'develop'
2 parents d2beb2e + d880d68 commit c5decf8

File tree

52 files changed

+1120
-750
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1120
-750
lines changed

client/style/globals/responsive.import.less

+4-1
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,13 @@
6060
}
6161

6262
@media screen and (max-width: 1300px) {
63-
6463
.navbar-header {
6564
float: none;
6665
}
66+
67+
.translation-available-flag {
68+
display: none;
69+
}
6770
}
6871

6972
@media screen and (max-width: 1280px) {

imports/graphql/bll/adminService.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default class AdminService {
2+
constructor(props) {
3+
this.token = props.token;
4+
this.user = Meteor.users.findOne({
5+
'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(props.token),
6+
});
7+
this.userIsAdmin = this.user.roles.indexOf('admin') !== -1;
8+
}
9+
}

imports/graphql/bll/annotations.js

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { Meteor } from 'meteor/meteor';
2+
3+
import Comments from '/imports/models/comments';
4+
import Books from '/imports/models/books';
5+
6+
export default class AnnotationService {
7+
constructor(props) {
8+
this.token = props.token;
9+
this.user = Meteor.users.findOne({
10+
'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(props.token),
11+
});
12+
}
13+
14+
hasAnnotationPermission(chapterUrl) {
15+
const book = Books.findOne({'chapters.url': chapterUrl});
16+
const authorizedBooks = this.user.canAnnotateBooks || [];
17+
return !!(this.user && (book || ~authorizedBooks.indexOf(book._id)));
18+
}
19+
20+
hasAnnotationRevisionPermission(annotationId) {
21+
const comment = Comments.findOne({_id: annotationId, users: this.user._id});
22+
return !!comment;
23+
}
24+
25+
rewriteRevision(revision) {
26+
if (revision instanceof Array) {
27+
const newRevision = [];
28+
revision.map(singleRevision => {
29+
newRevision.push({
30+
tenantId: singleRevision.tenantId,
31+
text: singleRevision.text,
32+
created: new Date(),
33+
updated: new Date()
34+
});
35+
});
36+
return newRevision;
37+
}
38+
return {
39+
tenantId: revision.tenantId,
40+
text: revision.text,
41+
created: new Date(),
42+
updated: new Date()
43+
};
44+
}
45+
46+
createAnnotation(annotation) {
47+
const newAnnotation = annotation;
48+
newAnnotation.revisions = this.rewriteRevision(annotation.revisions);
49+
50+
if (this.hasAnnotationPermission(annotation.bookChapterUrl)) {
51+
const commentId = Comments.insert({...newAnnotation});
52+
return Comments.findOne(commentId);
53+
}
54+
return new Error('Not authorized');
55+
}
56+
57+
deleteAnnotation(annotationId) {
58+
const annotation = Comments.findOne(annotationId);
59+
if (this.hasAnnotationPermission(annotation.bookChapterUrl)) {
60+
return Comments.remove({_id: annotationId});
61+
}
62+
return new Error('Not authorized');
63+
}
64+
65+
addRevision(annotationId, revision) {
66+
const newRevision = this.rewriteRevision(revision);
67+
if (this.hasAnnotationRevisionPermission(annotationId)) {
68+
return Comments.update({_id: annotationId}, {
69+
$addToSet: {
70+
revisions: newRevision
71+
}
72+
});
73+
}
74+
return new Error('Not authorized');
75+
}
76+
}

imports/graphql/bll/books.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import Books from '/imports/models/books';
2+
import AdminService from './adminService';
3+
4+
export default class BookService extends AdminService {
5+
constructor(props) {
6+
super(props);
7+
}
8+
9+
rewriteChapter(chapter) {
10+
if (chapter instanceof Array) {
11+
const newChapter = [];
12+
chapter.map(singleChapter => {
13+
newChapter.push({
14+
title: singleChapter.title,
15+
slug: singleChapter.slug,
16+
url: singleChapter.url,
17+
n: singleChapter.n
18+
});
19+
});
20+
return newChapter;
21+
}
22+
return {
23+
title: chapter.title,
24+
slug: chapter.slug,
25+
url: chapter.url,
26+
n: chapter.n
27+
};
28+
}
29+
30+
bookInsert(book) {
31+
if (this.userIsAdmin) {
32+
const newBook = book;
33+
newBook.chapters = this.rewriteChapter(book.chapters);
34+
35+
const bookId = Books.insert({...newBook});
36+
return Books.findOne(bookId);
37+
}
38+
return new Error('Not authorized');
39+
}
40+
41+
bookUpdate(_id, book) {
42+
if (this.userIsAdmin) {
43+
const newBook = book;
44+
newBook.chapters = this.rewriteChapter(book.chapters);
45+
Books.update({_id}, {$set: newBook});
46+
}
47+
return new Error('Not authorized');
48+
}
49+
50+
bookRemove(_id) {
51+
if (this.userIsAdmin) {
52+
return Books.remove(_id);
53+
}
54+
return new Error('Not authorized');
55+
}
56+
}
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { GraphQLString, GraphQLNonNull, GraphQLID } from 'graphql';
2+
import {Meteor} from 'meteor/meteor';
3+
// types
4+
import CommentType, {CommentInputType} from '/imports/graphql/types/models/comment';
5+
import { RemoveType } from '/imports/graphql/types/index';
6+
import { RevisionInputType } from '/imports/graphql/types/models/revision';
7+
8+
// models
9+
import Comments from '/imports/models/comments';
10+
import Books from '/imports/models/books';
11+
12+
// errors
13+
import { AuthenticationError } from '/imports/errors';
14+
15+
// bll
16+
import AnnotationService from '../bll/annotations';
17+
18+
const annotationMutationFields = {
19+
annotationCreate: {
20+
type: CommentType,
21+
description: 'Create new annotation',
22+
args: {
23+
comment: {
24+
type: CommentInputType
25+
}
26+
},
27+
async resolve(parent, { comment }, {token}) {
28+
const annotationService = new AnnotationService({token});
29+
return await annotationService.createAnnotation(comment);
30+
}
31+
},
32+
annotationDelete: {
33+
type: RemoveType,
34+
description: 'Remove annotation',
35+
args: {
36+
annotationId: {
37+
type: new GraphQLNonNull(GraphQLID)
38+
}
39+
},
40+
async resolve(parent, { annotationId }, {token}) {
41+
42+
const annotationService = new AnnotationService({token});
43+
return await annotationService.deleteAnnotation(annotationId);
44+
}
45+
},
46+
annotationAddRevision: {
47+
type: RemoveType,
48+
description: 'Remove annotation',
49+
args: {
50+
annotationId: {
51+
type: new GraphQLNonNull(GraphQLID)
52+
},
53+
revision: {
54+
type: new GraphQLNonNull(RevisionInputType)
55+
}
56+
},
57+
async resolve(parent, {annotationId, revision}, {token}) {
58+
const annotationService = new AnnotationService({token});
59+
return await annotationService.addRevision(annotationId, revision);
60+
}
61+
}
62+
};
63+
64+
export default annotationMutationFields;

imports/graphql/mutations/books.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { GraphQLString, GraphQLNonNull, GraphQLID } from 'graphql';
2+
import {Meteor} from 'meteor/meteor';
3+
// types
4+
import {BookType, BookInputType} from '/imports/graphql/types/models/book';
5+
import { RemoveType } from '/imports/graphql/types/index';
6+
7+
// errors
8+
import { AuthenticationError } from '/imports/errors';
9+
10+
// bll
11+
import BookService from '../bll/books';
12+
13+
const bookMutationFields = {
14+
bookCreate: {
15+
type: BookType,
16+
description: 'Create new book',
17+
args: {
18+
book: {
19+
type: BookInputType
20+
}
21+
},
22+
async resolve(parent, { book }, {token}) {
23+
const bookService = new BookService({token});
24+
return await bookService.bookInsert(book);
25+
}
26+
},
27+
bookUpdate: {
28+
type: BookType,
29+
description: 'Update book',
30+
args: {
31+
_id: {
32+
type: new GraphQLNonNull(GraphQLID)
33+
},
34+
book: {
35+
type: new GraphQLNonNull(BookInputType)
36+
}
37+
},
38+
async resolve(parent, { _id, book }, {token}) {
39+
const bookService = new BookService({token});
40+
return await bookService.bookUpdate(_id, book);
41+
}
42+
},
43+
bookRemove: {
44+
type: RemoveType,
45+
description: 'Remove book',
46+
args: {
47+
bookId: {
48+
type: new GraphQLNonNull(GraphQLID)
49+
}
50+
},
51+
async resolve(parent, { bookId }, {token}) {
52+
53+
const bookService = new BookService({token});
54+
return await bookService.bookRemove(bookId);
55+
}
56+
},
57+
};
58+
59+
export default bookMutationFields;

imports/graphql/mutations/rootMutation.js

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { GraphQLObjectType } from 'graphql';
22

3+
import annotationMutationFields from './annotations';
4+
import bookMutationFields from './books';
35
import commentMutationFields from './comments';
46

57
/**
@@ -10,6 +12,8 @@ const RootMutations = new GraphQLObjectType({
1012
name: 'RootMutationType',
1113
description: 'Root mutation object type',
1214
fields: {
15+
...annotationMutationFields,
16+
...bookMutationFields,
1317
...commentMutationFields,
1418
},
1519
});

imports/graphql/types/index.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { GraphQLObjectType, GraphQLID } from 'graphql';
2+
3+
4+
5+
export const RemoveType = new GraphQLObjectType({
6+
name: 'RemoveType',
7+
fields: {
8+
_id: {
9+
type: GraphQLID,
10+
},
11+
},
12+
});

imports/graphql/types/models/book.js

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {
2+
GraphQLObjectType,
3+
GraphQLString,
4+
GraphQLInt,
5+
GraphQLBoolean,
6+
GraphQLList,
7+
GraphQLInputObjectType
8+
} from 'graphql';
9+
import GraphQLJSON from 'graphql-type-json';
10+
import GraphQLDate from 'graphql-date';
11+
12+
const BookInputType = new GraphQLInputObjectType({
13+
name: 'BookInput',
14+
description: 'A book input type',
15+
fields: {
16+
title: {
17+
type: GraphQLString
18+
},
19+
slug: {
20+
type: GraphQLString
21+
},
22+
author: {
23+
type: GraphQLString
24+
},
25+
chapters: {
26+
type: new GraphQLList(GraphQLJSON)
27+
},
28+
coverImage: {
29+
type: GraphQLString
30+
},
31+
year: {
32+
type: GraphQLInt
33+
},
34+
publisher: {
35+
type: GraphQLString
36+
},
37+
citation: {
38+
type: GraphQLString
39+
},
40+
tenantId: {
41+
type: GraphQLString
42+
}
43+
}
44+
});
45+
46+
const BookType = new GraphQLObjectType({
47+
name: 'BookType',
48+
description: 'A single book',
49+
fields: {
50+
_id: {
51+
type: GraphQLString
52+
},
53+
title: {
54+
type: GraphQLString
55+
},
56+
slug: {
57+
type: GraphQLString
58+
},
59+
author: {
60+
type: GraphQLString
61+
},
62+
chapters: {
63+
type: new GraphQLList(GraphQLJSON)
64+
},
65+
coverImage: {
66+
type: GraphQLString
67+
},
68+
year: {
69+
type: GraphQLInt
70+
},
71+
publisher: {
72+
type: GraphQLString
73+
},
74+
citation: {
75+
type: GraphQLString
76+
},
77+
tenantId: {
78+
type: GraphQLString
79+
}
80+
}
81+
});
82+
83+
export { BookInputType, BookType }

0 commit comments

Comments
 (0)