Skip to content

Commit

Permalink
Merge f579770 into 42b1955
Browse files Browse the repository at this point in the history
  • Loading branch information
MrOrz committed Jun 16, 2020
2 parents 42b1955 + f579770 commit d92093c
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ LIFF_DEV_PORT=8080
JWT_SECRET=secret

# MONGODB
MONGODB_URI=mongodb://root:root-test-password@localhost:27017
MONGODB_URI=mongodb://root:root-test-password@localhost:27017/cofacts?authSource=admin

# Uncomment this to bypass in-client check in LIFF, enable debugging on extenral browsers
# DEBUG_LIFF=1
3 changes: 0 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,5 @@ script:
- npm run i18n:validate
- npm run test -- --coverage

env:
- MONGODB_URI=mongodb://root:root-test-password@localhost:27017

after_success:
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
103 changes: 103 additions & 0 deletions src/database/models/__tests__/userArticleLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ const FIXED_DATE = 612921600000;
describe('userArticleLink', () => {
beforeAll(async () => {
MockDate.set(FIXED_DATE);
});

beforeEach(async () => {
if (await UserArticleLink.collectionExists()) {
await (await UserArticleLink.client).drop();
}
Expand Down Expand Up @@ -119,4 +121,105 @@ describe('userArticleLink', () => {
delete updatedData._id;
expect(updatedData).toMatchSnapshot();
});

it('[model] finds by specified params', async () => {
const fixtures = [
{
userId: 'u2',
articleId: 'a1',
createdAt: new Date('2020-01-01T18:10:18.314Z'),
},
{
userId: 'u1',
articleId: 'a2',
createdAt: new Date('2020-01-01T19:10:18.314Z'),
},
{
userId: 'u1',
articleId: 'a3',
createdAt: new Date('2020-01-01T21:10:18.314Z'),
},
{
userId: 'u1',
articleId: 'a4',
createdAt: new Date('2020-01-01T20:10:18.314Z'),
},
{
userId: 'u1',
articleId: 'a5',
createdAt: new Date('2020-01-01T22:10:18.314Z'),
},
{
userId: 'u2',
articleId: 'a2',
createdAt: new Date('2020-01-01T23:10:18.314Z'),
},
];

for (const fixture of fixtures) {
await UserArticleLink.create(fixture);
}

// No skip, no limit
const allResult = await UserArticleLink.find({ userId: 'u1' });
expect(
allResult.map(
({
_id, // eslint-disable-line no-unused-vars
...fields
}) => fields
)
).toMatchInlineSnapshot(`
Array [
Object {
"articleId": "a5",
"createdAt": 2020-01-01T22:10:18.314Z,
"userId": "u1",
},
Object {
"articleId": "a3",
"createdAt": 2020-01-01T21:10:18.314Z,
"userId": "u1",
},
Object {
"articleId": "a4",
"createdAt": 2020-01-01T20:10:18.314Z,
"userId": "u1",
},
Object {
"articleId": "a2",
"createdAt": 2020-01-01T19:10:18.314Z,
"userId": "u1",
},
]
`);

// Skip & limit works
const result = await UserArticleLink.find({
userId: 'u1',
skip: 1,
limit: 2,
});
return expect(
result.map(
({
_id, // eslint-disable-line no-unused-vars
...fields
}) => fields
)
).toMatchInlineSnapshot(`
Array [
Object {
"articleId": "a3",
"createdAt": 2020-01-01T21:10:18.314Z,
"userId": "u1",
},
Object {
"articleId": "a4",
"createdAt": 2020-01-01T20:10:18.314Z,
"userId": "u1",
},
]
`);
});
});
6 changes: 6 additions & 0 deletions src/database/models/userArticleLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ class UserArticleLink extends Base {
);
}

static async find({ userId, skip = 0, limit = 20 }) {
return await (await this.client)
.find({ userId }, { limit, skip, sort: { createdAt: -1 } })
.toArray();
}

/**
* An atomic and upsert enabled operation.
* @typedef Timestamps
Expand Down
9 changes: 4 additions & 5 deletions src/database/mongoClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import { MongoClient } from 'mongodb';

const MONGODB_URI = process.env.MONGODB_URI;

// Database Name
const dbName = 'cofacts';

// Somewhere outside the class:
//
async function instantiateClientAndConnect() {
const client = new CofactsMongoClient(MONGODB_URI);
const client = new CofactsMongoClient(MONGODB_URI, {
useUnifiedTopology: true,
});
await client.mongoClient.connect();
return client;
}
Expand All @@ -34,7 +33,7 @@ export default class CofactsMongoClient {
}

get db() {
return this.mongoClient.db(dbName);
return this.mongoClient.db();
}

/**
Expand Down
82 changes: 82 additions & 0 deletions src/graphql/__tests__/userArticleLinks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import UserArticleLink from 'src/database/models/userArticleLink';
import { gql } from '../testUtils';

jest.mock('src/database/models/userArticleLink');

it('context rejects anonymous users', async () => {
const result = await gql`
{
userArticleLinks {
articleId
}
}
`();
expect(result).toMatchInlineSnapshot(`
Object {
"data": Object {
"userArticleLinks": null,
},
"errors": Array [
[GraphQLError: Invalid authentication header],
],
}
`);
});

it('invokes ArticleReplyLinks.find() properly', async () => {
UserArticleLink.find = jest.fn();

// No params
await gql`
{
userArticleLinks {
articleId
}
}
`(
{},
{
userId: 'U12345678',
}
);

expect(UserArticleLink.find.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
Object {
"limit": undefined,
"skip": undefined,
"userId": "U12345678",
},
],
]
`);

UserArticleLink.find.mockClear();

// Has limit and skip
await gql`
{
userArticleLinks(limit: 100, skip: 20) {
articleId
}
}
`(
{},
{
userId: 'U12345678',
}
);

expect(UserArticleLink.find.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
Object {
"limit": 100,
"skip": 20,
"userId": "U12345678",
},
],
]
`);
});
13 changes: 9 additions & 4 deletions src/graphql/directives/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ import { AuthenticationError } from 'apollo-server-koa';
import { SchemaDirectiveVisitor } from 'graphql-tools';

/**
* When field with @auth is accessed, make sure the user is logged in (i.e. `userContext` in context).
* When field with @auth is accessed, make sure the user is logged in.
* Optional arg "check" specifies fields to check in GraphQL context.
* If "check" is not given, ['userId', 'userContext'] is checked by default.
*/
class AuthDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const { resolve } = field;
const { check = ['userId', 'userContext'] } = this.args;

field.resolve = (...args) => {
const [, , context] = args;

if (!context.userId || !context.userContext) {
throw new AuthenticationError('Invalid authentication header');
}
check.forEach(checkedField => {
if (!context[checkedField]) {
throw new AuthenticationError('Invalid authentication header');
}
});

return resolve(...args);
};
Expand Down
6 changes: 6 additions & 0 deletions src/graphql/resolvers/Query.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import UserArticleLink from 'src/database/models/userArticleLink';

export default {
insights() {
// Resolvers in next level
Expand All @@ -7,4 +9,8 @@ export default {
context(root, args, context) {
return context.userContext;
},

userArticleLinks(root, { skip, limit }, { userId }) {
return UserArticleLink.find({ userId, skip, limit });
},
};
17 changes: 14 additions & 3 deletions src/graphql/typeDefs.graphql
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
directive @auth on FIELD_DEFINITION
directive @auth(check: [String]) on FIELD_DEFINITION

type Query {
insights: MessagingAPIInsight

# Current user's chatbot context
context: UserContext @auth

# TODO: Implement after database is established
readArticleIds(skip: Int, limit: Int): [String]
# Returns articleReplyLinks of the current user
userArticleLinks(skip: Int, limit: Int): [UserArticleLink!] @auth(check: ["userId"])
}

type Mutation {
Expand Down Expand Up @@ -160,3 +160,14 @@ enum FeedbackVote {
UPVOTE
DOWNVOTE
}

# Date time object <> ISO timestamp
scalar Date

type UserArticleLink {
articleId: String!
createdAt: Date!
lastViewedAt: Date
lastRepliedAt: Date
lastPositiveFeedbackRepliedAt: Date
}

0 comments on commit d92093c

Please sign in to comment.