├── .dockerignore
├── src
├── rock
│ ├── models
│ │ ├── binary-files
│ │ │ ├── queries.js
│ │ │ ├── mutations.js
│ │ │ ├── schema.js
│ │ │ ├── index.js
│ │ │ ├── resolver.js
│ │ │ ├── tables.js
│ │ │ └── model.js
│ │ ├── campuses
│ │ │ ├── queries.js
│ │ │ ├── index.js
│ │ │ ├── schema.js
│ │ │ ├── model.js
│ │ │ ├── tables.js
│ │ │ └── resolver.js
│ │ ├── likes
│ │ │ ├── mutations.js
│ │ │ ├── queries.js
│ │ │ ├── schema.js
│ │ │ ├── index.js
│ │ │ ├── resolver.js
│ │ │ ├── __tests__
│ │ │ │ └── resolver.spec.js
│ │ │ └── model.js
│ │ ├── feeds
│ │ │ ├── index.js
│ │ │ ├── __tests__
│ │ │ │ ├── queries.js
│ │ │ │ └── __snapshots__
│ │ │ │ │ ├── queries.js.snap
│ │ │ │ │ └── resolver.spec.js.snap
│ │ │ ├── queries.js
│ │ │ └── resolver.js
│ │ ├── finances
│ │ │ ├── __tests__
│ │ │ │ ├── queries.js
│ │ │ │ └── __snapshots__
│ │ │ │ │ └── queries.js.snap
│ │ │ ├── util
│ │ │ │ ├── __tests__
│ │ │ │ │ ├── language.js
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ ├── submit-transaction.js.snap
│ │ │ │ │ │ └── language.js.snap
│ │ │ │ │ ├── statement.js
│ │ │ │ │ └── formatTransaction.js
│ │ │ │ ├── logError.js
│ │ │ │ ├── language.js
│ │ │ │ └── nmi.js
│ │ │ ├── index.js
│ │ │ ├── model.js
│ │ │ ├── queries.js
│ │ │ ├── models
│ │ │ │ ├── FinancialAccount.js
│ │ │ │ ├── FinancialBatch.js
│ │ │ │ ├── __tests__
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── Transaction.js.snap
│ │ │ │ │ └── FinancialBatch.js
│ │ │ │ └── ScheduledTransaction.js
│ │ │ ├── mutations.js
│ │ │ └── schema.js
│ │ ├── groups
│ │ │ ├── __tests__
│ │ │ │ ├── mutations.js
│ │ │ │ └── __snapshots__
│ │ │ │ │ └── mutations.js.snap
│ │ │ ├── mutations.js
│ │ │ ├── index.js
│ │ │ ├── queries.js
│ │ │ └── schema.js
│ │ ├── people
│ │ │ ├── __tests__
│ │ │ │ ├── mutations.js
│ │ │ │ └── __snapshots__
│ │ │ │ │ └── mutations.js.snap
│ │ │ ├── queries.js
│ │ │ ├── mutations.js
│ │ │ ├── index.js
│ │ │ ├── schema.js
│ │ │ └── tables.js
│ │ ├── system
│ │ │ ├── queries.js
│ │ │ ├── __tests__
│ │ │ │ ├── __snapshots__
│ │ │ │ │ └── resolver.spec.js.snap
│ │ │ │ ├── fieldTypeResolvers.js
│ │ │ │ └── resolver.spec.js
│ │ │ ├── index.js
│ │ │ ├── schema.js
│ │ │ ├── resolver.js
│ │ │ └── fieldTypeResolvers.js
│ │ └── index.js
│ └── index.js
├── util
│ ├── scalars
│ │ └── Date
│ │ │ ├── schema.js
│ │ │ ├── index.js
│ │ │ └── resolver.js
│ ├── index.js
│ ├── node
│ │ ├── index.js
│ │ ├── schema.js
│ │ ├── resolver.js
│ │ ├── __test__
│ │ │ ├── queries.integration.js
│ │ │ ├── resolver.spec.js
│ │ │ └── model.spec.js
│ │ └── model.js
│ ├── cache
│ │ ├── index.js
│ │ ├── defaults.js
│ │ ├── __test__
│ │ │ ├── mutations.integration.js
│ │ │ ├── index.spec.js
│ │ │ └── memory-cache.spec.js
│ │ └── memory-cache.js
│ ├── model.js
│ ├── __tests__
│ │ └── heighliner.spec.js
│ └── heighliner.js
├── apollos
│ ├── models
│ │ └── users
│ │ │ ├── queries.js
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ ├── makeNewGuid.js
│ │ │ ├── mutations.js
│ │ │ ├── schema.js
│ │ │ ├── __tests__
│ │ │ ├── queries.integration.js
│ │ │ └── model.spec.js
│ │ │ ├── api.js
│ │ │ └── resolver.js
│ ├── README.md
│ ├── index.js
│ ├── __tests__
│ │ └── mongo.spec.js
│ └── mongo.js
├── expression-engine
│ ├── models
│ │ ├── ee
│ │ │ ├── index.js
│ │ │ ├── snippets.js
│ │ │ ├── sites.js
│ │ │ ├── playa.js
│ │ │ ├── tags.js
│ │ │ ├── matrix.js
│ │ │ ├── model.js
│ │ │ └── __tests__
│ │ │ │ └── model.spec.js
│ │ ├── navigation
│ │ │ ├── queries.js
│ │ │ ├── schema.js
│ │ │ ├── index.js
│ │ │ ├── resolver.js
│ │ │ ├── tables.js
│ │ │ └── model.js
│ │ ├── files
│ │ │ ├── index.js
│ │ │ ├── resolver.js
│ │ │ ├── schema.js
│ │ │ └── tables.js
│ │ ├── content
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ ├── __tests__
│ │ │ │ ├── __snapshots__
│ │ │ │ │ └── model.js.snap
│ │ │ │ └── images.spec.js
│ │ │ ├── queries.js
│ │ │ ├── images.js
│ │ │ ├── schema.js
│ │ │ └── tables.js
│ │ └── index.js
│ ├── README.md
│ ├── index.js
│ └── mysql.js
├── esv
│ ├── models
│ │ └── scripture
│ │ │ ├── queries.js
│ │ │ ├── schema.js
│ │ │ ├── __tests__
│ │ │ ├── model.js
│ │ │ └── resolver.js
│ │ │ ├── resolver.js
│ │ │ ├── index.js
│ │ │ └── model.js
│ ├── __tests__
│ │ ├── __snapshots__
│ │ │ └── fetch.js.snap
│ │ └── fetch.js
│ ├── index.js
│ └── fetch.js
├── __mocks__
│ ├── redis.js
│ └── bull.js
├── google-site-search
│ ├── models
│ │ ├── geolocate
│ │ │ ├── queries.js
│ │ │ ├── index.js
│ │ │ ├── model.js
│ │ │ ├── schema.js
│ │ │ └── resolver.js
│ │ └── search
│ │ │ ├── queries.js
│ │ │ ├── index.js
│ │ │ ├── model.js
│ │ │ ├── schema.js
│ │ │ ├── __test__
│ │ │ └── queries.integration.js
│ │ │ └── resolver.js
│ ├── README.md
│ ├── index.js
│ ├── fetch.js
│ └── __test__
│ │ └── fetch.spec.js
├── constants.js
├── routes
│ ├── graphiql.js
│ ├── engine.js
│ ├── errors.js
│ ├── util.js
│ ├── cache.js
│ └── datadog.js
├── server.js
└── __tests__
│ └── server.integration.js
├── .travis
├── QA.md
├── heighliner.enc
├── run_for_coverage
├── ssh-config
└── run_on_pull_requests
├── .gitignore
├── .babelrc
├── scripts
├── preprocessor.js
├── graphql-transformer.js
└── commit.js
├── .eslintrc
├── docker-compose.yml
├── .env.example
├── .vscode
├── launch.json
└── settings.json
├── Dockerfile
├── webpack.config.js
├── .travis.yml
├── README.md
└── package.json
/.dockerignore:
--------------------------------------------------------------------------------
1 | data
2 | scripts
3 | tmp
4 |
5 |
--------------------------------------------------------------------------------
/src/rock/models/binary-files/queries.js:
--------------------------------------------------------------------------------
1 | export default [];
2 |
--------------------------------------------------------------------------------
/src/util/scalars/Date/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | scalar Date
4 | `
5 | ];
6 |
--------------------------------------------------------------------------------
/src/apollos/models/users/queries.js:
--------------------------------------------------------------------------------
1 | export default ["currentUser: User", "topics: [String]"];
2 |
--------------------------------------------------------------------------------
/src/expression-engine/models/ee/index.js:
--------------------------------------------------------------------------------
1 | import { EE } from "./model";
2 |
3 | export { EE };
4 |
--------------------------------------------------------------------------------
/.travis/QA.md:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # QA Checklist
4 |
5 | - [ ] do something
6 | - [ ] do something else
7 |
--------------------------------------------------------------------------------
/.travis/heighliner.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewSpring/Heighliner/HEAD/.travis/heighliner.enc
--------------------------------------------------------------------------------
/src/esv/models/scripture/queries.js:
--------------------------------------------------------------------------------
1 | export default ["scripture(query: String!): ESVScripture"];
2 |
--------------------------------------------------------------------------------
/src/rock/models/binary-files/mutations.js:
--------------------------------------------------------------------------------
1 | export default ["attachPhotoIdToUser(id: ID!): Boolean"];
2 |
--------------------------------------------------------------------------------
/src/rock/models/campuses/queries.js:
--------------------------------------------------------------------------------
1 | export default ["campuses(id: Int, name: String): [Campus]"];
2 |
--------------------------------------------------------------------------------
/src/expression-engine/models/navigation/queries.js:
--------------------------------------------------------------------------------
1 | export default ["navigation(nav: String!): [Navigation]"];
2 |
--------------------------------------------------------------------------------
/src/rock/models/likes/mutations.js:
--------------------------------------------------------------------------------
1 | export default ["toggleLike(nodeId: String!): LikesMutationResponse"];
2 |
--------------------------------------------------------------------------------
/src/__mocks__/redis.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line
2 | import redis from "redis-mock";
3 | export default redis;
4 |
--------------------------------------------------------------------------------
/src/__mocks__/bull.js:
--------------------------------------------------------------------------------
1 | export default jest.fn(() => ({
2 | process: jest.fn(() => {}),
3 | add: jest.fn(() => {})
4 | }));
5 |
--------------------------------------------------------------------------------
/src/esv/models/scripture/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | type ESVScripture {
4 | html: String
5 | }
6 | `
7 | ];
8 |
--------------------------------------------------------------------------------
/src/google-site-search/models/geolocate/queries.js:
--------------------------------------------------------------------------------
1 | export default ["geolocate(origin: String, destinations: String): GGeoLocate"];
2 |
--------------------------------------------------------------------------------
/src/util/scalars/Date/index.js:
--------------------------------------------------------------------------------
1 | export { default as resolver } from "./resolver";
2 | export { default as schema } from "./schema";
3 |
--------------------------------------------------------------------------------
/src/util/scalars/Date/resolver.js:
--------------------------------------------------------------------------------
1 | import GraphQLDate from "graphql-date";
2 |
3 | export default {
4 | Date: GraphQLDate
5 | };
6 |
--------------------------------------------------------------------------------
/src/rock/models/binary-files/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | type BinaryFile implements Node {
4 | id: ID!
5 | }
6 | `
7 | ];
8 |
--------------------------------------------------------------------------------
/.travis/run_for_coverage:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | yarn coverage
5 | coveralls < ./coverage/lcov.info || true # ignore coveralls error
6 |
7 |
--------------------------------------------------------------------------------
/src/google-site-search/models/search/queries.js:
--------------------------------------------------------------------------------
1 | export default [
2 | "search(query: String!, first: Int = 10, after: Int = 1, site: String): SSSearch"
3 | ];
4 |
--------------------------------------------------------------------------------
/src/rock/models/feeds/index.js:
--------------------------------------------------------------------------------
1 | import resolvers from "./resolver";
2 | import queries from "./queries";
3 |
4 | export default {
5 | resolvers,
6 | queries
7 | };
8 |
--------------------------------------------------------------------------------
/src/rock/models/finances/__tests__/queries.js:
--------------------------------------------------------------------------------
1 | import queries from "../queries";
2 |
3 | it("has all needed queries", () => {
4 | expect(queries).toMatchSnapshot();
5 | });
6 |
--------------------------------------------------------------------------------
/src/rock/models/likes/queries.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `recentlyLiked(
3 | limit: Int = 20,
4 | skip: Int = 0,
5 | cache: Boolean = true
6 | ): [Node]`
7 | ];
8 |
--------------------------------------------------------------------------------
/src/util/index.js:
--------------------------------------------------------------------------------
1 | import { parseGlobalId, createGlobalId } from "./node";
2 | import { Heighliner } from "./model";
3 |
4 | export { createGlobalId, parseGlobalId, Heighliner };
5 |
--------------------------------------------------------------------------------
/src/rock/models/finances/util/__tests__/language.js:
--------------------------------------------------------------------------------
1 | import codes from "../language";
2 |
3 | it("should match expected codes", () => {
4 | expect(codes).toMatchSnapshot();
5 | });
6 |
--------------------------------------------------------------------------------
/src/rock/models/groups/__tests__/mutations.js:
--------------------------------------------------------------------------------
1 | import mutations from "../mutations";
2 |
3 | it("should contain mutations", () => {
4 | expect(mutations).toMatchSnapshot();
5 | });
6 |
--------------------------------------------------------------------------------
/src/rock/models/people/__tests__/mutations.js:
--------------------------------------------------------------------------------
1 | import mutations from "../mutations";
2 |
3 | it("should have all needed mutations", () => {
4 | expect(mutations).toMatchSnapshot();
5 | });
6 |
--------------------------------------------------------------------------------
/src/rock/models/system/queries.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `definedValues(
3 | id: Int!,
4 | limit: Int = 20,
5 | skip: Int = 0,
6 | all: Boolean = false,
7 | ): [DefinedValue]`
8 | ];
9 |
--------------------------------------------------------------------------------
/src/rock/models/groups/mutations.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `requestGroupInfo(
3 | groupId: ID!,
4 | message: String!,
5 | communicationPreference: String!,
6 | ): GroupsMutationResponse`
7 | ];
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | data/*
2 | /.remote
3 | .env
4 | .env.prod
5 | .env.dev
6 | npm-debug.log
7 | coverage
8 | tmp
9 | .tmp
10 | typings
11 | lib
12 | dist
13 | .nyc_output
14 | node_modules
15 | coverage.lcov
16 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0"],
3 | "plugins": ["transform-runtime", "transform-react-jsx"],
4 | "env": {
5 | "development": {
6 | "sourceMaps": "inline"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.travis/ssh-config:
--------------------------------------------------------------------------------
1 | Host github.com
2 | User git
3 | IdentityFile ~/.ssh/id_rsa
4 | StrictHostKeyChecking no
5 | PasswordAuthentication no
6 | CheckHostIP no
7 | BatchMode yes
8 |
--------------------------------------------------------------------------------
/src/rock/models/feeds/__tests__/queries.js:
--------------------------------------------------------------------------------
1 | import Queries from "../queries";
2 |
3 | describe("Feeds Queries", () => {
4 | it("has all needed queries", () => {
5 | expect(Queries).toMatchSnapshot();
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/src/rock/models/feeds/queries.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `userFeed(
3 | filters: [String]!
4 | limit: Int = 20,
5 | skip: Int = 0,
6 | status: String = "open",
7 | cache: Boolean = true
8 | ): [Node]`
9 | ];
10 |
--------------------------------------------------------------------------------
/src/rock/models/likes/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | type LikesMutationResponse implements MutationResponse {
4 | error: String
5 | success: Boolean!
6 | code: Int
7 | like: Node
8 | }
9 | `
10 | ];
11 |
--------------------------------------------------------------------------------
/src/util/node/index.js:
--------------------------------------------------------------------------------
1 | import schema from "./schema";
2 | import resolver from "./resolver";
3 | import model, { createGlobalId, parseGlobalId } from "./model";
4 |
5 | export { schema, resolver, model, createGlobalId, parseGlobalId };
6 |
--------------------------------------------------------------------------------
/src/util/node/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | interface Node {
4 | id: ID!
5 | }
6 |
7 | interface MutationResponse {
8 | error: String
9 | success: Boolean!
10 | code: Int
11 | }
12 | `
13 | ];
14 |
--------------------------------------------------------------------------------
/scripts/preprocessor.js:
--------------------------------------------------------------------------------
1 | // fileTransformer.js
2 | // const path = require("path");
3 | const fs = require("fs");
4 |
5 | module.exports = {
6 | process(src) {
7 | console.log(src);
8 | return src;
9 | },
10 | };
11 |
12 |
--------------------------------------------------------------------------------
/src/esv/models/scripture/__tests__/model.js:
--------------------------------------------------------------------------------
1 | // { ESV } errors out for some reason
2 | import ESV from "../model";
3 |
4 | it("should expose the get method", () => {
5 | const esv = new ESV.ESV();
6 | expect(esv.get).toBeDefined();
7 | });
8 |
--------------------------------------------------------------------------------
/src/rock/models/people/queries.js:
--------------------------------------------------------------------------------
1 | export default [
2 | "people(email: String): [Person]",
3 |
4 | "person(guid: ID): Person",
5 |
6 | "currentPerson(cache: Boolean = true): Person",
7 |
8 | "currentFamily: [GroupMember]"
9 | ];
10 |
--------------------------------------------------------------------------------
/src/esv/models/scripture/resolver.js:
--------------------------------------------------------------------------------
1 | export default {
2 | Query: {
3 | scripture(_, { query }, { models }) {
4 | return models.ESV.get(query);
5 | }
6 | },
7 |
8 | ESVScripture: {
9 | html: data => data
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/src/util/node/resolver.js:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | Query: {
4 | node: (_, { id }, { models }) => models.Node.get(id),
5 | },
6 | Node: {
7 | __resolveType: ({ __type }, _, { schema }) => schema.getType(__type),
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/src/rock/models/system/__tests__/__snapshots__/resolver.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Attribute has id 1`] = `"6e705ae05557e2a6d5121b432daf07ab"`;
4 |
5 | exports[`AttributeValue has id 1`] = `"6e705ae05557e2a6d5121b432daf07ab"`;
6 |
--------------------------------------------------------------------------------
/src/expression-engine/models/files/index.js:
--------------------------------------------------------------------------------
1 | import { gql } from "../../../util";
2 | import schema from "./schema";
3 | import resolvers from "./resolver";
4 | import models from "./model";
5 |
6 | export default {
7 | schema,
8 | resolvers,
9 | models
10 | };
11 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/prefer-default-export
2 | export const FOLLOWABLE_TOPICS = [
3 | "Articles",
4 | "Devotionals",
5 | "Events", // These are forced
6 | "Music",
7 | "News",
8 | "Series",
9 | "Sermons",
10 | "Stories",
11 | "Studies"
12 | ];
13 |
--------------------------------------------------------------------------------
/scripts/graphql-transformer.js:
--------------------------------------------------------------------------------
1 | // fileTransformer.js
2 | const path = require("path");
3 | const fs = require("fs");
4 |
5 | module.exports = {
6 | process(src) {
7 | return fs.readSync(src, "utf8");
8 | },
9 | getCacheKey(fileData, filename) {
10 | return filename;
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/src/expression-engine/models/navigation/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | type Navigation implements Node {
4 | id: ID!
5 | text: String
6 | link: String
7 | absoluteLink: String
8 | sort: Int
9 | image: String
10 | children: [Navigation]
11 | }
12 | `
13 | ];
14 |
--------------------------------------------------------------------------------
/src/apollos/models/users/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | User Type
8 | =======================
9 |
--------------------------------------------------------------------------------
/src/apollos/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Apollos Application Library
8 | =======================
9 |
--------------------------------------------------------------------------------
/src/expression-engine/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Expression Engine
8 | =======================
9 |
--------------------------------------------------------------------------------
/src/esv/models/scripture/index.js:
--------------------------------------------------------------------------------
1 | import { gql } from "../../../util";
2 | import schema from "./schema";
3 | import resolvers from "./resolver";
4 | import models from "./model";
5 | import queries from "./queries";
6 |
7 | export default {
8 | schema,
9 | resolvers,
10 | models,
11 | queries
12 | };
13 |
--------------------------------------------------------------------------------
/src/expression-engine/models/content/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Connector Type
8 | =======================
9 |
--------------------------------------------------------------------------------
/src/google-site-search/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Apollos Application Library
8 | =======================
9 |
--------------------------------------------------------------------------------
/src/google-site-search/models/search/index.js:
--------------------------------------------------------------------------------
1 | import { gql } from "../../../util";
2 | import schema from "./schema";
3 | import resolvers from "./resolver";
4 | import models from "./model";
5 | import queries from "./queries";
6 |
7 | export default {
8 | schema,
9 | resolvers,
10 | models,
11 | queries
12 | };
13 |
--------------------------------------------------------------------------------
/src/rock/models/campuses/index.js:
--------------------------------------------------------------------------------
1 | import schema from "./schema";
2 | import resolvers from "./resolver";
3 | import models from "./model";
4 | import queries from "./queries";
5 | // import mocks from "./mocks";
6 |
7 | export default {
8 | schema,
9 | resolvers,
10 | models,
11 | queries
12 | // mocks,
13 | };
14 |
--------------------------------------------------------------------------------
/src/rock/models/people/mutations.js:
--------------------------------------------------------------------------------
1 | export default [
2 | "setPhoneNumber(phoneNumber: String!): PhoneNumberMutationResponse",
3 | "saveDeviceRegistrationId(registrationId: String!, uuid: String!): DeviceRegistrationMutationResponse",
4 | "setPersonAttribute(key: String!, value: String!): AttributeValueMutationResponse"
5 | ];
6 |
--------------------------------------------------------------------------------
/src/apollos/models/users/index.js:
--------------------------------------------------------------------------------
1 | import schema from "./schema";
2 | import resolvers from "./resolver";
3 | import models from "./model";
4 | import queries from "./queries";
5 | import mutations from "./mutations";
6 |
7 | export default {
8 | schema,
9 | resolvers,
10 | models,
11 | queries,
12 | mutations
13 | };
14 |
--------------------------------------------------------------------------------
/src/expression-engine/models/navigation/index.js:
--------------------------------------------------------------------------------
1 | import { gql } from "../../../util";
2 | import schema from "./schema";
3 | import resolvers from "./resolver";
4 | import models from "./model";
5 | import queries from "./queries";
6 |
7 | export default {
8 | schema,
9 | resolvers,
10 | models,
11 | queries
12 | };
13 |
--------------------------------------------------------------------------------
/src/google-site-search/models/geolocate/index.js:
--------------------------------------------------------------------------------
1 | import { gql } from "../../../util";
2 | import schema from "./schema";
3 | import resolvers from "./resolver";
4 | import models from "./model";
5 | import queries from "./queries";
6 |
7 | export default {
8 | schema,
9 | resolvers,
10 | models,
11 | queries
12 | };
13 |
--------------------------------------------------------------------------------
/src/rock/models/groups/index.js:
--------------------------------------------------------------------------------
1 | import schema from "./schema";
2 | import resolvers from "./resolver";
3 | import models from "./model";
4 | import queries from "./queries";
5 | import mutations from "./mutations";
6 |
7 | export default {
8 | schema,
9 | resolvers,
10 | models,
11 | queries,
12 | mutations
13 | };
14 |
--------------------------------------------------------------------------------
/src/rock/models/likes/index.js:
--------------------------------------------------------------------------------
1 | import schema from "./schema";
2 | import models from "./model";
3 | import mutations from "./mutations";
4 | import resolvers from "./resolver";
5 | import queries from "./queries";
6 |
7 | export default {
8 | schema,
9 | models,
10 | mutations,
11 | resolvers,
12 | queries
13 | };
14 |
--------------------------------------------------------------------------------
/src/rock/models/binary-files/index.js:
--------------------------------------------------------------------------------
1 | import schema from "./schema";
2 | import resolvers from "./resolver";
3 | import models from "./model";
4 | import queries from "./queries";
5 | import mutations from "./mutations";
6 |
7 | export default {
8 | schema,
9 | resolvers,
10 | models,
11 | queries,
12 | mutations
13 | };
14 |
--------------------------------------------------------------------------------
/src/rock/models/groups/__tests__/__snapshots__/mutations.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should contain mutations 1`] = `
4 | Array [
5 | "requestGroupInfo(
6 | groupId: ID!,
7 | message: String!,
8 | communicationPreference: String!,
9 | ): GroupsMutationResponse",
10 | ]
11 | `;
12 |
--------------------------------------------------------------------------------
/src/rock/models/people/index.js:
--------------------------------------------------------------------------------
1 |
2 | import schema from "./schema";
3 | import resolvers from "./resolver";
4 | import models from "./model";
5 | import mutations from "./mutations";
6 | import queries from "./queries";
7 |
8 | export default {
9 | schema,
10 | resolvers,
11 | models,
12 | queries,
13 | mutations,
14 | };
15 |
--------------------------------------------------------------------------------
/src/routes/graphiql.js:
--------------------------------------------------------------------------------
1 | import { graphiqlExpress } from "apollo-server-express";
2 |
3 | export default app => {
4 | if (process.env.SENTRY_ENVIRONMENT !== "production") {
5 | app.use("/view", graphiqlExpress({ endpointURL: "/graphql" }));
6 | app.use("/graphql/view", graphiqlExpress({ endpointURL: "/graphql" }));
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/src/rock/models/finances/util/__tests__/__snapshots__/submit-transaction.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`logs an error if missing a schedule in Rock 1`] = `
4 | Array [
5 | "
6 | Scheduled Transaction is missing for person 1 with
7 | GatewayScheduleId of 19
8 | ",
9 | ]
10 | `;
11 |
--------------------------------------------------------------------------------
/src/apollos/models/users/makeNewGuid.js:
--------------------------------------------------------------------------------
1 | function s4() {
2 | return Math.floor((1 + Math.random()) * 0x10000)
3 | .toString(16)
4 | .substring(1);
5 | }
6 |
7 | function makeNewGuid() {
8 | const guid = `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`;
9 | return guid.toUpperCase();
10 | }
11 |
12 | export default makeNewGuid;
13 |
--------------------------------------------------------------------------------
/src/rock/models/system/index.js:
--------------------------------------------------------------------------------
1 | import models, { Rock } from "./model";
2 | import schema from "./schema";
3 | import resolvers from "./resolver";
4 | import queries from "./queries";
5 | // import mocks from "./mocks";
6 |
7 | export default {
8 | schema,
9 | resolvers,
10 | models,
11 | queries
12 | // mocks,
13 | };
14 |
15 | export { Rock };
16 |
--------------------------------------------------------------------------------
/src/esv/__tests__/__snapshots__/fetch.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ESV \`ESVFetchConnector\` should format the query 1`] = `"http://www.esvapi.org/v2/rest/passageQuery?key=test-key&passage=john 3:16&include-headings=false&include-passage-references=false&include-footnotes=false&include-audio-link=false&include-short-copyright=false"`;
4 |
--------------------------------------------------------------------------------
/src/expression-engine/models/content/index.js:
--------------------------------------------------------------------------------
1 | import { gql } from "../../../util";
2 | import schema from "./schema";
3 | import resolvers from "./resolver";
4 | import models from "./model";
5 | import tables from "./tables";
6 | import queries from "./queries";
7 |
8 | export default {
9 | tables,
10 | schema,
11 | resolvers,
12 | models,
13 | queries
14 | };
15 |
--------------------------------------------------------------------------------
/src/rock/models/feeds/__tests__/__snapshots__/queries.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Feeds Queries has all needed queries 1`] = `
4 | Array [
5 | "userFeed(
6 | filters: [String]!
7 | limit: Int = 20,
8 | skip: Int = 0,
9 | status: String = \\"open\\",
10 | cache: Boolean = true
11 | ): [Node]",
12 | ]
13 | `;
14 |
--------------------------------------------------------------------------------
/src/rock/models/finances/index.js:
--------------------------------------------------------------------------------
1 | import schema from "./schema";
2 | import resolvers from "./resolver";
3 | import models from "./model";
4 | import queries from "./queries";
5 | import mutations from "./mutations";
6 | // import mocks from "./mocks";
7 |
8 | export default {
9 | schema,
10 | resolvers,
11 | models,
12 | queries,
13 | mutations
14 | // mocks,
15 | };
16 |
--------------------------------------------------------------------------------
/src/rock/models/finances/model.js:
--------------------------------------------------------------------------------
1 | import Transaction from "./models/Transaction";
2 | import ScheduledTransaction from "./models/ScheduledTransaction";
3 | import SavedPayment from "./models/SavedPayment";
4 | import FinancialAccount from "./models/FinancialAccount";
5 |
6 | export default {
7 | Transaction,
8 | ScheduledTransaction,
9 | SavedPayment,
10 | FinancialAccount
11 | };
12 |
--------------------------------------------------------------------------------
/src/esv/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from "./fetch";
2 |
3 | import { createApplication } from "../util/heighliner";
4 |
5 | import Scripture from "./models/scripture";
6 |
7 | export const { schema, resolvers, models, queries, mocks } = createApplication([
8 | Scripture
9 | ]);
10 |
11 | export default {
12 | models,
13 | resolvers,
14 | mocks,
15 | schema,
16 | connect
17 | };
18 |
--------------------------------------------------------------------------------
/src/apollos/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from "./mongo";
2 | import Users from "./models/users";
3 |
4 | import { createApplication } from "../util/heighliner";
5 |
6 | export const {
7 | schema,
8 | resolvers,
9 | models,
10 | queries,
11 | mutations
12 | } = createApplication([Users]);
13 |
14 | export default {
15 | models,
16 | resolvers,
17 | schema,
18 | connect,
19 | mutations
20 | };
21 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": ["airbnb-base","prettier"],
4 | "env": {
5 | "node": true,
6 | "es6": true,
7 | "jest": true
8 | },
9 | "globals": {
10 | },
11 | "rules": {
12 | "quotes": [2, "double"],
13 | "no-underscore-dangle": 0,
14 | "class-methods-use-this": 0,
15 | "import/no-extraneous-dependencies": [1],
16 | "no-useless-escape": 0
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/routes/engine.js:
--------------------------------------------------------------------------------
1 | import { Engine } from "apollo-engine";
2 |
3 | const apolloEngine = new Engine({
4 | engineConfig: {
5 | apiKey: process.env.ENGINE_API_KEY
6 | },
7 | graphqlPort: process.env.PORT
8 | });
9 |
10 | if (process.env.NODE_ENV === "production") apolloEngine.start();
11 |
12 | export default app => {
13 | if (process.env.NODE_ENV === "production") {
14 | app.use(apolloEngine.expressMiddleware());
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/src/google-site-search/models/search/model.js:
--------------------------------------------------------------------------------
1 | import { GoogleConnector } from "../../fetch";
2 |
3 | class SSearch extends GoogleConnector {
4 | cx = process.env.SEARCH_CX;
5 | key = process.env.SEARCH_KEY;
6 | url = process.env.SEARCH_URL;
7 |
8 | query(query) {
9 | const endpoint = `${this.url}key=${this.key}&cx=${this.cx}&q=${query}`;
10 | return this.get(endpoint);
11 | }
12 | }
13 |
14 | export default {
15 | SSearch
16 | };
17 |
--------------------------------------------------------------------------------
/src/google-site-search/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from "./fetch";
2 |
3 | import { createApplication } from "../util/heighliner";
4 |
5 | import Search from "./models/search";
6 | import Geolcate from "./models/geolocate";
7 |
8 | export const { schema, resolvers, models, queries, mocks } = createApplication([
9 | Search,
10 | Geolcate
11 | ]);
12 |
13 | export default {
14 | models,
15 | resolvers,
16 | mocks,
17 | schema,
18 | connect
19 | };
20 |
--------------------------------------------------------------------------------
/src/rock/models/people/__tests__/__snapshots__/mutations.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should have all needed mutations 1`] = `
4 | Array [
5 | "setPhoneNumber(phoneNumber: String!): PhoneNumberMutationResponse",
6 | "saveDeviceRegistrationId(registrationId: String!, uuid: String!): DeviceRegistrationMutationResponse",
7 | "setPersonAttribute(key: String!, value: String!): AttributeValueMutationResponse",
8 | ]
9 | `;
10 |
--------------------------------------------------------------------------------
/src/google-site-search/models/geolocate/model.js:
--------------------------------------------------------------------------------
1 | import { GoogleConnector } from "../../fetch";
2 |
3 | class GGeolocate extends GoogleConnector {
4 | key = process.env.GOOGLE_GEO_LOCATE;
5 | url =
6 | "https://maps.googleapis.com/maps/api/distancematrix/json?units=imperial&";
7 |
8 | query(query) {
9 | const endpoint = `${this.url}${query}&key=${this.key}`;
10 | return this.get(endpoint);
11 | }
12 | }
13 |
14 | export default {
15 | GGeolocate
16 | };
17 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | # mySQL:
2 | # image: mysql:5.5
3 | # volumes:
4 | # - ./scripts/mysql/:/etc/mysql/conf.d
5 | # ports:
6 | # - "3306:3306"
7 | # environment:
8 | # MYSQL_ROOT_PASSWORD: password
9 | # MYSQL_DATABASE: ee_local
10 | # MYSQL_USER: root
11 |
12 | # mongoDB:
13 | # image: mongo:latest
14 | # ports:
15 | # - "27017:27017"
16 |
17 | redis:
18 | image: redis
19 | ports:
20 | - "6379:6379"
21 | volumes:
22 | - ./data/redis:/data
23 |
--------------------------------------------------------------------------------
/src/google-site-search/models/search/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | type SSSearchResult {
4 | id: ID
5 | title: String
6 | htmlTitle: String
7 | link: String
8 | displayLink: String
9 | description: String
10 | htmlDescription: String
11 | type: String
12 | section: String
13 | image: String
14 | }
15 |
16 | type SSSearch {
17 | total: Int
18 | next: Int
19 | previous: Int
20 | items: [SSSearchResult]
21 | }
22 | `
23 | ];
24 |
--------------------------------------------------------------------------------
/src/rock/models/system/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | type DefinedValue implements Node {
4 | id: ID!
5 | _id: Int
6 | value: String
7 | description: String
8 | }
9 | type Attribute implements Node {
10 | id: ID!
11 | key: String!
12 | description: String!
13 | order: Int
14 | values: [AttributeValue]
15 | }
16 | type AttributeValue implements Node {
17 | attribute: Attribute
18 | id: ID!
19 | value: String
20 | }
21 | `
22 | ];
23 |
--------------------------------------------------------------------------------
/src/esv/models/scripture/__tests__/resolver.js:
--------------------------------------------------------------------------------
1 | import casual from "casual";
2 | import Resolver from "../resolver";
3 |
4 | const sampleData = casual.description;
5 |
6 | it("`Query` exposes scripture function", () => {
7 | const { Query } = Resolver;
8 | expect(Query.scripture).toBeTruthy();
9 | });
10 |
11 | it("`ESVScripture` returns html from data", () => {
12 | const { ESVScripture } = Resolver;
13 | const html = ESVScripture.html(sampleData);
14 | expect(html).toEqual(sampleData);
15 | });
16 |
--------------------------------------------------------------------------------
/src/google-site-search/models/geolocate/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | type GGeoLocate {
4 | destination_addresses: [String]
5 | origin_addresses: [String]
6 | rows: [GGeoRow]
7 | status: String
8 | }
9 |
10 | type GGeoRow {
11 | elements: [GGeoElement]
12 | }
13 |
14 | type GGeoElement {
15 | distance: GGeoValue
16 | duration: GGeoValue
17 | status: String
18 | }
19 |
20 | type GGeoValue {
21 | text: String
22 | value: Int
23 | }
24 | `
25 | ];
26 |
--------------------------------------------------------------------------------
/src/expression-engine/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from "./mysql";
2 |
3 | import { createApplication } from "../util/heighliner";
4 |
5 | import Content from "./models/content";
6 | import Files from "./models/files";
7 | import Navigation from "./models/navigation";
8 |
9 | export const { models, resolvers, mocks, schema, queries } = createApplication([
10 | Content,
11 | Files,
12 | Navigation
13 | ]);
14 |
15 | export default {
16 | models,
17 | resolvers,
18 | mocks,
19 | schema,
20 | connect
21 | };
22 |
--------------------------------------------------------------------------------
/src/rock/models/groups/queries.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `groups(
3 | attributes: [String],
4 | limit: Int = 20,
5 | offset: Int = 0,
6 | query: String,
7 | clientIp: String,
8 | campuses: [String],
9 | campus: String,
10 | latitude: Float,
11 | longitude: Float,
12 | zip: String,
13 | schedules: [Int],
14 | ): GroupSearch`,
15 |
16 | // XXX clientIp and campuses are depracated
17 | //
18 | // XXX should this take a group type id?
19 | "groupAttributes: [DefinedValue]"
20 | ];
21 |
--------------------------------------------------------------------------------
/src/routes/errors.js:
--------------------------------------------------------------------------------
1 | import raven from "raven";
2 |
3 | export default app => {
4 | if (process.env.NODE_ENV === "production") {
5 | // The request handler must be the first item
6 | app.use(raven.middleware.express.requestHandler(process.env.SENTRY));
7 | }
8 | };
9 |
10 | export const errors = app => {
11 | // The error handler must be before any other error middleware
12 | if (process.env.NODE_ENV === "production") {
13 | app.use(raven.middleware.express.errorHandler(process.env.SENTRY));
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/src/esv/models/scripture/model.js:
--------------------------------------------------------------------------------
1 | import { ESVFetchConnector } from "../../fetch";
2 |
3 | import { defaultCache } from "../../../util/cache";
4 |
5 | class ESV extends ESVFetchConnector {
6 | __type = "ESV";
7 |
8 | constructor({ cache } = { cache: defaultCache }) {
9 | super();
10 | this.cache = cache;
11 | }
12 |
13 | async get(query) {
14 | return await this.cache.get(`${this.__type}:${query}`, () =>
15 | this.getFromAPI(query)
16 | );
17 | }
18 | }
19 |
20 | export default {
21 | ESV
22 | };
23 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # Databases
2 | REDIS_HOST=
3 | MONGO_URL=
4 | MYSQL_HOST=
5 | MYSQL_USER=
6 | MYSQL_PASSWORD=
7 | MYSQL_DB=
8 | MYSQL_SSL=
9 | MSSQL_HOST=
10 | MSSQL_USER=
11 | MSSQL_PASSWORD=
12 | MSSQL_DB=
13 | MSSQL_INSTANCE=
14 |
15 | # App
16 | PORT=8888
17 |
18 | # Externals
19 | ROCK_URL=
20 | ROCK_TOKEN=
21 |
22 | SEARCH_URL=
23 | SEARCH_KEY=
24 | SEARCH_CX=
25 |
26 | SENTRY=
27 | SLACK=
28 |
29 | GOOGLE_GEO_LOCATE=
30 | DATADOG_API_KEY=
31 | ENGINE_API_KEY=
32 |
33 | # DEBUG=metrics
34 | DATADOG_APP_KEY=
35 | TRACER_APP_KEY=
36 |
37 | NMI_GATEWAY=
38 | NMI_KEY=
39 |
--------------------------------------------------------------------------------
/src/util/cache/index.js:
--------------------------------------------------------------------------------
1 | import { InMemoryCache } from "./memory-cache";
2 | import { RedisCache, connect as RedisConnect } from "./redis";
3 |
4 | import { defaultCache, resolvers, mutations } from "./defaults";
5 |
6 | export async function createCache(monitor) {
7 | const datadog = monitor && monitor.datadog;
8 | const REDIS = await RedisConnect({ datadog });
9 | return REDIS ? new RedisCache() : new InMemoryCache();
10 | }
11 |
12 | export {
13 | InMemoryCache,
14 | RedisCache,
15 | RedisConnect,
16 | defaultCache,
17 | resolvers,
18 | mutations
19 | };
20 |
--------------------------------------------------------------------------------
/src/expression-engine/models/content/__tests__/__snapshots__/model.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`findByCampusName should pass correct value for channelTitles 1`] = `
4 | Object {
5 | "entry_date": Object {
6 | "$lte": literal {
7 | "val": "UNIX_TIMESTAMP(NOW())",
8 | },
9 | },
10 | "expiration_date": Object {
11 | "$or": Array [
12 | Object {
13 | "$eq": 0,
14 | },
15 | Object {
16 | "$gt": literal {
17 | "val": "UNIX_TIMESTAMP(NOW())",
18 | },
19 | },
20 | ],
21 | },
22 | }
23 | `;
24 |
--------------------------------------------------------------------------------
/src/rock/models/binary-files/resolver.js:
--------------------------------------------------------------------------------
1 | export default {
2 | Mutation: {
3 | async attachPhotoIdToUser(_, props, { models, user }) {
4 | try {
5 | // NOTE: context person could be cached
6 | // therefore we need to query again
7 | const person = await models.User.getUserProfile(user.PersonId);
8 | return models.BinaryFile.attachPhotoIdToUser({
9 | personId: person.Id,
10 | previousPhotoId: person.PhotoId,
11 | newPhotoId: props.id
12 | });
13 | } catch (err) {
14 | throw err;
15 | }
16 | }
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for node debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Launch Program",
11 | "program": "${workspaceFolder}/src/server.js",
12 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/babel-node",
13 | "env": {
14 | "PORT": "8888"
15 | }
16 | },
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/src/rock/models/campuses/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | type Location implements Node {
4 | id: ID!
5 | name: String
6 | street1: String
7 | street2: String
8 | city: String
9 | state: String
10 | country: String
11 | zip: String
12 | latitude: Float
13 | longitude: Float
14 | distance: Float
15 | }
16 |
17 | type Campus implements Node {
18 | id: ID!
19 | entityId: Int!
20 | name: String
21 | shortCode: String
22 | guid: String
23 | services: [String]
24 | url: String
25 | locationId: ID
26 | location: Location
27 | }
28 | `
29 | ];
30 |
--------------------------------------------------------------------------------
/src/expression-engine/models/files/resolver.js:
--------------------------------------------------------------------------------
1 | import { createGlobalId } from "../../../util";
2 |
3 | export default {
4 | File: {
5 | id: ({ file_id }, _, $, { parentType }) =>
6 | createGlobalId(file_id, parentType.name),
7 | file: ({ fileName }) => fileName || null,
8 | label: ({ fileLabel }) => fileLabel || null,
9 |
10 | url: ({ url }) => url,
11 |
12 | // deprecated
13 | s3: ({ s3 }) => s3,
14 | cloudfront: ({ cloudfront }) => cloudfront || null,
15 |
16 | fileName: ({ fileName }) => fileName || null,
17 | fileType: ({ fileType }) => fileType || null,
18 | fileLabel: ({ fileLabel }) => fileLabel || null
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/src/apollos/models/users/mutations.js:
--------------------------------------------------------------------------------
1 | export default [
2 | "loginUser(email: String!, password: String!): LoginMutationResponse",
3 | "registerUser(email: String!, password: String!, firstName: String!, lastName: String!): LoginMutationResponse",
4 | "logoutUser: Boolean",
5 | "changeUserPassword(oldPassword: String!, newPassword: String!): Boolean",
6 | "forgotUserPassword(email: String!, sourceURL: String): Boolean",
7 | "resetUserPassword(token: String!, newPassword: String!): LoginMutationResponse",
8 | "toggleTopic(topic: String!): Boolean",
9 | "updateProfile(input: UserProfileInput): Boolean",
10 | "updateHomeAddress(input: HomeAddressInput): Boolean"
11 | ];
12 |
--------------------------------------------------------------------------------
/src/util/cache/defaults.js:
--------------------------------------------------------------------------------
1 | import { createGlobalId } from "../node/model";
2 |
3 | export const defaultCache = {
4 | get: (id, lookup) => Promise.resolve().then(lookup),
5 | set: id => Promise.resolve().then(() => true),
6 | del() {},
7 | encode: (obj, prefix) => `${prefix}${JSON.stringify(obj)}`
8 | };
9 |
10 | export const resolvers = {
11 | Mutation: {
12 | cache(_, { id, type }, { cache, models }) {
13 | if (type && id) id = createGlobalId(id, type);
14 | return Promise.resolve()
15 | .then(() => cache.del(id))
16 | .then(() => models.Node.get(id));
17 | }
18 | }
19 | };
20 |
21 | export const mutations = ["cache(id: ID!, type: String): Node"];
22 |
--------------------------------------------------------------------------------
/.travis/run_on_pull_requests:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | openssl aes-256-cbc -K $encrypted_de5ad5555d6e_key -iv $encrypted_de5ad5555d6e_iv -in .travis/heighliner.enc -out ~/.ssh/id_rsa -d
5 | chmod 600 ~/.ssh/id_rsa
6 | eval `ssh-agent -s`
7 | ssh-add ~/.ssh/id_rsa
8 |
9 | BUILD_TAG="GH$TRAVIS_PULL_REQUEST-B$TRAVIS_BUILD_NUMBER"
10 | REPO=`git config remote.origin.url`
11 | SSH_REPO=${REPO/https:\/\/github.com\//git@github.com:}
12 |
13 | git config user.name "Travis CI"
14 | git config user.email "$COMMIT_AUTHOR_EMAIL"
15 |
16 | # create a new new tag (GH###-B#)
17 | git tag $BUILD_TAG
18 |
19 | # push tag to github
20 | git push -q $SSH_REPO refs/tags/$BUILD_TAG
21 |
22 | echo "Pushed Tag: $BUILD_TAG to Github!"
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/expression-engine/models/files/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | # interface File {
4 | # id: ID!
5 | # file: String!
6 | # type: String
7 | # label: String
8 |
9 | # s3: String
10 | # cloudfront: String
11 |
12 | # # deprecated
13 | # fileName: String!
14 | # fileType: String
15 | # fileLabel: String
16 | # }
17 |
18 | type File implements Node {
19 | id: ID!
20 | file: String!
21 | label: String
22 | size: String
23 |
24 | url: String
25 |
26 | # deprecated
27 | s3: String
28 | cloudfront: String
29 |
30 | duration: String
31 | title: String
32 |
33 | # deprecated
34 | fileName: String!
35 | fileType: String
36 | fileLabel: String
37 | }
38 | `
39 | ];
40 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "editor.tabSize": 2,
4 | "editor.rulers": [100],
5 | "files.trimTrailingWhitespace": true,
6 | "files.insertFinalNewline": true,
7 | "editor.wrappingColumn": 100,
8 | "files.exclude": {
9 | // "**/.git": true,
10 | // "**/.DS_Store": true,
11 | // "node_modules": true,
12 | // "lib": true,
13 | // "Dockerfile": true,
14 | // "data": true,
15 | // "coverage": true,
16 | // ".babelrc": true,
17 | // ".gitignore": true,
18 | // ".eslintrc": true,
19 | // ".env*": true,
20 | // "**/*.yml": true,
21 | // "scripts": true,
22 | // "npm-debug.log": true,
23 | // "webpack.config.js": true,
24 | // "yarn.lock": true
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/expression-engine/models/ee/snippets.js:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-shadowed-variable */
2 |
3 | import { INTEGER, STRING } from "sequelize";
4 |
5 | import { MySQLConnector } from "../../mysql";
6 |
7 | const snippetsSchema = {
8 | snippet_id: { type: INTEGER, primaryKey: true },
9 | site_id: { type: STRING },
10 | snippet_name: { type: STRING },
11 | snippet_contents: { type: STRING }
12 | };
13 |
14 | let Snippets;
15 | export { Snippets, snippetsSchema };
16 |
17 | export function connect() {
18 | Snippets = new MySQLConnector("exp_snippets", snippetsSchema);
19 |
20 | return {
21 | Snippets
22 | };
23 | }
24 |
25 | // export function bind({
26 | // ChannelData,
27 | // Sites,
28 | // }): void {
29 |
30 | // };
31 |
32 | export default {
33 | connect
34 | // bind,
35 | };
36 |
--------------------------------------------------------------------------------
/src/rock/models/finances/queries.js:
--------------------------------------------------------------------------------
1 | export default [
2 | "savedPayments(limit: Int = 20, skip: Int = 0, cache: Boolean = true): [SavedPayment]",
3 | "savedPayment(id: ID!): SavedPayment",
4 |
5 | `transactions(
6 | limit: Int = 20,
7 | skip: Int = 0,
8 | cache: Boolean = true,
9 | start: String,
10 | end: String,
11 | people: [Int] = [],
12 | ): [Transaction]`,
13 |
14 | `scheduledTransactions(
15 | limit: Int = 20, skip: Int = 0, cache: Boolean = true, isActive: Boolean = true
16 | ): [ScheduledTransaction]`,
17 |
18 | `accounts(
19 | allFunds: Boolean = false, name: String, isActive: Boolean = true, isPublic: Boolean = true, isTaxDeductable: Boolean = true
20 | ): [FinancialAccount]`,
21 |
22 | "accountFromCashTag(cashTag: String!): FinancialAccount"
23 | ];
24 |
--------------------------------------------------------------------------------
/src/routes/util.js:
--------------------------------------------------------------------------------
1 | import bodyParser from "body-parser";
2 | import cors from "cors";
3 |
4 | export default app => {
5 | const sites = /^http(s?):\/\/.*.?(newspring|newspringfuse|newspringnetwork|apollos.netlify|newspring.github).(com|cc|io|dev)\/?$/;
6 | const local = /^http(s?):\/\/localhost:\d*$/;
7 |
8 | const corsOptions = {
9 | origin: (origin, callback) => {
10 | const originIsWhitelisted = sites.test(origin) || local.test(origin);
11 | callback(null, originIsWhitelisted);
12 | },
13 | credentials: true
14 | };
15 |
16 | app.use(cors(corsOptions));
17 |
18 | app.use(
19 | bodyParser.urlencoded({
20 | extended: true
21 | })
22 | );
23 |
24 | app.use(bodyParser.json());
25 |
26 | app.get("/util/ping", (req, res) => {
27 | res.status(200).end();
28 | });
29 | };
30 |
--------------------------------------------------------------------------------
/src/expression-engine/models/navigation/resolver.js:
--------------------------------------------------------------------------------
1 | import { createGlobalId } from "../../../util";
2 |
3 | export default {
4 | Query: {
5 | navigation: (_, { nav }, { models }) => models.Navigation.find({ nav })
6 | },
7 |
8 | Navigation: {
9 | id: ({ id }, _, $, { parentType }) => createGlobalId(id, parentType.name),
10 | text: ({ text }) => text,
11 | link: ({ link }) => link,
12 | absoluteLink: ({ link, url }) => `${url}${link.substring(1, link.length)}`,
13 | sort: ({ sort }) => sort,
14 | image: ({ image }) => image,
15 | children: ({ children, id }, _, { models }) => {
16 | // tslint:disable-line
17 | if (children) return children;
18 |
19 | // XXX hookup up find by parent method
20 | return null;
21 | // return models.Navigation.findByParent(id);
22 | }
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/src/rock/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from "./mssql";
2 |
3 | import { createApplication } from "../util/heighliner";
4 |
5 | import People from "./models/people";
6 | import Finances from "./models/finances";
7 | import Campuses from "./models/campuses";
8 | import System from "./models/system";
9 | import Groups from "./models/groups";
10 | import BinaryFiles from "./models/binary-files";
11 | import Feeds from "./models/feeds";
12 | import Likes from "./models/likes";
13 |
14 | export const {
15 | mutations,
16 | queries,
17 | models,
18 | resolvers,
19 | mocks,
20 | schema
21 | } = createApplication([
22 | People,
23 | Finances,
24 | Campuses,
25 | System,
26 | Groups,
27 | BinaryFiles,
28 | Feeds,
29 | Likes
30 | ]);
31 |
32 | export default {
33 | models,
34 | mutations,
35 | resolvers,
36 | mocks,
37 | schema,
38 | connect
39 | };
40 |
--------------------------------------------------------------------------------
/src/rock/models/finances/__tests__/__snapshots__/queries.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`has all needed queries 1`] = `
4 | Array [
5 | "savedPayments(limit: Int = 20, skip: Int = 0, cache: Boolean = true): [SavedPayment]",
6 | "savedPayment(id: ID!): SavedPayment",
7 | "transactions(
8 | limit: Int = 20,
9 | skip: Int = 0,
10 | cache: Boolean = true,
11 | start: String,
12 | end: String,
13 | people: [Int] = [],
14 | ): [Transaction]",
15 | "scheduledTransactions(
16 | limit: Int = 20, skip: Int = 0, cache: Boolean = true, isActive: Boolean = true
17 | ): [ScheduledTransaction]",
18 | "accounts(
19 | allFunds: Boolean = false, name: String, isActive: Boolean = true, isPublic: Boolean = true, isTaxDeductable: Boolean = true
20 | ): [FinancialAccount]",
21 | "accountFromCashTag(cashTag: String!): FinancialAccount",
22 | ]
23 | `;
24 |
--------------------------------------------------------------------------------
/src/expression-engine/models/index.js:
--------------------------------------------------------------------------------
1 | import { merge } from "lodash";
2 |
3 | import channels from "./../models/content/tables";
4 | import assets from "./../models/files/tables";
5 | import matrix from "./ee/matrix";
6 | import playa from "./ee/playa";
7 | import tags from "./ee/tags";
8 | import sites from "./ee/sites";
9 | import snippets from "./ee/snippets";
10 | import navee from "./../models/navigation/tables";
11 |
12 | const tables = {
13 | assets,
14 | channels,
15 | matrix,
16 | playa,
17 | sites,
18 | tags,
19 | navee,
20 | snippets
21 | };
22 |
23 | export function createTables() {
24 | let createdTables = {};
25 |
26 | for (const table in tables) {
27 | createdTables = merge(createdTables, tables[table].connect());
28 | }
29 |
30 | for (const table in tables) {
31 | if (tables[table].bind) tables[table].bind(createdTables);
32 | }
33 |
34 | return createdTables;
35 | }
36 |
--------------------------------------------------------------------------------
/src/rock/models/likes/resolver.js:
--------------------------------------------------------------------------------
1 | const MutationReponseResolver = {
2 | error: ({ error }) => error,
3 | success: ({ success, error }) => success || !error,
4 | code: ({ code }) => code
5 | };
6 |
7 | export default {
8 | Query: {
9 | recentlyLiked(_, { limit, skip, cache }, { models, user }) {
10 | const userId = user ? user._id : null;
11 | return models.Like.getRecentlyLiked(
12 | { limit, skip, cache },
13 | userId,
14 | models.Node
15 | );
16 | }
17 | },
18 | Mutation: {
19 | toggleLike(_, { nodeId }, { models, person }) {
20 | if (!person) throw new Error("User is not logged in!");
21 | if (!nodeId) throw new Error("EntryId is missing!");
22 | return models.Like.toggleLike(nodeId, person.PrimaryAliasId, models.Node);
23 | }
24 | },
25 | LikesMutationResponse: {
26 | ...MutationReponseResolver,
27 | like: ({ like }) => like
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/src/google-site-search/models/geolocate/resolver.js:
--------------------------------------------------------------------------------
1 | export default {
2 | Query: {
3 | geolocate(_, { origin, destinations }, { models }) {
4 | const query = `origins=${encodeURI(origin)}&destinations=${encodeURI(
5 | destinations
6 | )}`;
7 | return models.GGeolocate.query(query);
8 | }
9 | },
10 |
11 | GGeoLocate: {
12 | destination_addresses: ({ destination_addresses }) => destination_addresses,
13 | origin_addresses: ({ origin_addresses }) => origin_addresses,
14 | rows: ({ rows }) => rows,
15 | status: ({ status }) => status
16 | },
17 |
18 | GGeoRow: {
19 | elements: ({ elements }) => elements
20 | },
21 |
22 | GGeoElement: {
23 | distance: ({ distance }) => distance,
24 | duration: ({ duration }) => duration,
25 | status: ({ status }) => status
26 | },
27 |
28 | GGeoValue: {
29 | text: ({ text }) => text,
30 | value: ({ value }) => value
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/src/google-site-search/fetch.js:
--------------------------------------------------------------------------------
1 | import fetch from "isomorphic-fetch";
2 | // import DataLoader from "dataloader";
3 |
4 | export const connect = address =>
5 | new Promise(cb => {
6 | cb(true);
7 | });
8 |
9 | export class GoogleConnector {
10 | count = 0;
11 |
12 | get(endpoint) {
13 | const label = `GoogleConnector${this.getCount()}`;
14 | const headers = {
15 | "user-agent": "Heighliner",
16 | "Content-Type": "application/json"
17 | };
18 |
19 | const options = { method: "GET", headers };
20 | console.time(label); // tslint:disable-line
21 | // XXX we can't cache google site search legally
22 | return fetch(endpoint, options)
23 | .then(x => (x.json ? x.json() : x.text()))
24 | .then(x => {
25 | console.timeEnd(label);
26 | return x;
27 | }); // tslint:disable-line
28 | }
29 |
30 | getCount() {
31 | this.count++;
32 | return this.count;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 |
3 | import util from "./routes/util";
4 | import engine from "./routes/engine";
5 | import graphiql from "./routes/graphiql";
6 | import sentry, { errors } from "./routes/errors";
7 | import setupDatadog from "./routes/datadog";
8 | import createApp from "./routes/graphql";
9 | import cacheUtils from "./routes/cache";
10 |
11 | const app = express();
12 |
13 | engine(app);
14 | util(app);
15 | graphiql(app);
16 | sentry(app);
17 | const datadog = setupDatadog(app);
18 | cacheUtils(app, { datadog });
19 | createApp(app, { datadog });
20 | errors(app);
21 |
22 | // Listen for incoming HTTP requests
23 | const listener = app.listen(process.env.PORT || 80, () => {
24 | let host = listener.address().address;
25 | if (host === "::") host = "localhost";
26 | const port = listener.address().port;
27 | // eslint-disable-next-line
28 | console.log("Listening at http://%s%s", host, port === 80 ? "" : `:${port}`);
29 | });
30 |
--------------------------------------------------------------------------------
/src/rock/models/system/__tests__/fieldTypeResolvers.js:
--------------------------------------------------------------------------------
1 | import Resolver from "../fieldTypeResolvers";
2 | import Moment from "moment";
3 |
4 | jest.mock("moment", () => () => ({
5 | toString: () => "harambe"
6 | }));
7 |
8 | describe("Date", () => {
9 | const date = Resolver["Rock.Field.Types.DateFieldType"];
10 |
11 | it("uses default value if no value passed", () => {
12 | expect(date(null, "2016-05-28")).toEqual("2016-05-28");
13 | });
14 |
15 | // uses moment mock
16 | it("uses value if present", () => {
17 | expect(date("2016-05-28", "2099-01-01")).toEqual("harambe");
18 | });
19 | });
20 |
21 | describe("SelectSingle", () => {
22 | const singleSelect = Resolver["Rock.Field.Types.SelectSingleFieldType"];
23 |
24 | it("uses default if no value", () => {
25 | expect(singleSelect(null, "hai")).toEqual("hai");
26 | });
27 |
28 | it("uses value if present", () => {
29 | expect(singleSelect("baramhe", "hai")).toEqual("baramhe");
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/src/expression-engine/models/ee/sites.js:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-shadowed-variable */
2 |
3 | import { INTEGER, STRING } from "sequelize";
4 | import { unserialize } from "php-unserialize";
5 |
6 | import { MySQLConnector, Tables } from "../../mysql";
7 |
8 | const siteSchema = {
9 | site_id: { type: INTEGER, primaryKey: true },
10 | site_label: { type: STRING },
11 | site_name: { type: STRING },
12 | site_pages: { type: STRING }
13 | };
14 |
15 | let Sites;
16 | export { Sites, siteSchema };
17 |
18 | export function connect() {
19 | Sites = new MySQLConnector("exp_sites", siteSchema);
20 |
21 | // helper to parse through the sites pages module
22 | Sites.parsePage = function(page) {
23 | return unserialize(new Buffer(page, "base64").toString());
24 | };
25 |
26 | return {
27 | Sites
28 | };
29 | }
30 |
31 | // export function bind({
32 | // ChannelData,
33 | // Sites,
34 | // }) {
35 |
36 | // };
37 |
38 | export default {
39 | connect
40 | // bind,
41 | };
42 |
--------------------------------------------------------------------------------
/src/google-site-search/__test__/fetch.spec.js:
--------------------------------------------------------------------------------
1 | // import casual from "casual";
2 |
3 | import { GoogleConnector } from "../fetch";
4 |
5 | // test("`connect` should fail without any env vars", async (t) => {
6 | // const SS = await connect(casual.url);
7 | // t.falsy(SS);
8 | // });
9 |
10 | // test("`connect` should fail without all env vars", async (t) => {
11 | // process.env.SEARCH_URL = casual.url;
12 | // process.env.SEARCH_KEY = casual.word;
13 |
14 | // const SS = await connect(casual.url);
15 | // t.falsy(SS);
16 | // });
17 |
18 | // test("`connect` should return true if proper env vars", async (t) => {
19 | // process.env.SEARCH_URL = casual.url;
20 | // process.env.SEARCH_KEY = casual.word;
21 | // process.env.SEARCH_CX = casual.word;
22 |
23 | // const SS = await connect(casual.url);
24 | // t.truthy(SS);
25 | // });
26 |
27 | it("should expose get function", async () => {
28 | const testFetcher = new GoogleConnector();
29 | expect(testFetcher.get).toBeTruthy();
30 | });
31 |
--------------------------------------------------------------------------------
/src/rock/models/index.js:
--------------------------------------------------------------------------------
1 | import { merge } from "lodash";
2 |
3 | import people from "./people/tables";
4 | import finances from "./finances/tables";
5 | import system from "./system/tables";
6 | import campuses from "./campuses/tables";
7 | import groups from "./groups/tables";
8 | import binaryFiles from "./binary-files/tables";
9 |
10 | const tables = {
11 | people,
12 | finances,
13 | system,
14 | campuses,
15 | groups,
16 | binaryFiles
17 | };
18 |
19 | export function createTables() {
20 | let createdTables = {};
21 |
22 | for (const table in tables) {
23 | try {
24 | createdTables = merge(createdTables, tables[table].connect());
25 | } catch (e) {
26 | console.error(e);
27 | }
28 | }
29 |
30 | for (const table in tables) {
31 | try {
32 | if (tables[table].bind) tables[table].bind(createdTables);
33 | } catch (e) {
34 | console.error(`in binding ${table}`);
35 | console.error(e);
36 | }
37 | }
38 |
39 | return createdTables;
40 | }
41 |
--------------------------------------------------------------------------------
/src/expression-engine/models/content/queries.js:
--------------------------------------------------------------------------------
1 | // XXX implement pagination instead of skip
2 | // use `after` for ^^
3 | export default [
4 | `content(
5 | channel: String!,
6 | collection: ID,
7 | limit: Int = 20,
8 | skip: Int = 0,
9 | status: String = "open",
10 | cache: Boolean = true
11 | ): [Content]`,
12 |
13 | `feed(
14 | excludeChannels: [String],
15 | limit: Int = 20,
16 | skip: Int = 0,
17 | status: String = "open",
18 | cache: Boolean = true
19 | ): [Content]`,
20 |
21 | // XXX deprecated tagName
22 | `taggedContent(
23 | includeChannels: [String],
24 | excludedIds: [String],
25 | tagName: String,
26 | tags: [String],
27 | limit: Int = 20,
28 | skip: Int = 0,
29 | status: String = "open",
30 | cache: Boolean = true
31 | ): [Content]`,
32 |
33 | "lowReorderSets(setName: String!): [Content]",
34 |
35 | "live: LiveFeed",
36 |
37 | `contentWithUrlTitle(
38 | channel: String!,
39 | urlTitle: String!,
40 | ): String`
41 | ];
42 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # FROM mhart/alpine-node:base
2 | # FROM mhart/alpine-node:base-0.10
3 | FROM mhart/alpine-node:8
4 |
5 | WORKDIR /src
6 | ADD . .
7 |
8 | # If you have native dependencies, you'll need extra tools
9 | # Install required APKs needed for building, install node modules, fix phantom, then cleanup.
10 | RUN apk add --update git python build-base curl bash && \
11 | echo "Fixing PhantomJS" && \
12 | curl -Ls "https://github.com/dustinblackman/phantomized/releases/download/2.1.1/dockerized-phantomjs.tar.gz" | tar xz -C / && \
13 | echo "Installing node modules" && \
14 | sed -i '/postinstall/d' package.json && \
15 | npm install --production && \
16 | apk del git python build-base curl && \
17 | rm -Rf /etc/ssl/certs/* && \
18 | apk add ca-certificates && \
19 | rm -rf /usr/share/man /tmp/* /var/tmp/* /var/cache/apk/* /root/.npm /root/.node-gyp
20 |
21 | # If you had native dependencies you can now remove build tools
22 | # RUN apk del make gcc g++ python && \
23 | # rm -rf /tmp/* /var/cache/apk/* /root/.npm /root/.node-gyp
24 |
25 | EXPOSE 80
26 | CMD ["node", "./lib/server.js"]
27 |
--------------------------------------------------------------------------------
/src/expression-engine/models/ee/playa.js:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-shadowed-variable */
2 |
3 | import { INTEGER, STRING } from "sequelize";
4 |
5 | import { MySQLConnector, Tables } from "../../mysql";
6 |
7 | const playaSchema = {
8 | rel_id: { type: INTEGER, primaryKey: true },
9 | parent_entry_id: { type: INTEGER },
10 | parent_field_id: { type: INTEGER },
11 | parent_col_id: { type: INTEGER },
12 | parent_row_id: { type: INTEGER },
13 | parent_var_id: { type: INTEGER },
14 | parent_element_id: { type: STRING },
15 | child_entry_id: { type: INTEGER },
16 | rel_order: { type: INTEGER }
17 | };
18 | let Playa;
19 | export { Playa, playaSchema };
20 |
21 | export function connect() {
22 | Playa = new MySQLConnector("exp_playa_relationships", playaSchema);
23 |
24 | return {
25 | Playa
26 | };
27 | }
28 |
29 | export function bind({ ChannelData, Playa }) {
30 | // get access to matrix from channel data
31 | ChannelData.model.hasMany(Playa.model, { foreignKey: "entry_id" });
32 | Playa.model.belongsTo(ChannelData.model, { foreignKey: "entry_id" });
33 | }
34 |
35 | export default {
36 | connect,
37 | bind
38 | };
39 |
--------------------------------------------------------------------------------
/src/util/node/__test__/queries.integration.js:
--------------------------------------------------------------------------------
1 | // import express from "express";
2 | // import { apolloExpress } from "apollo-server";
3 | // import { tester } from "graphql-tester";
4 | // import { create } from "graphql-tester/lib/main/servers/express";
5 | // import bodyParser from "body-parser";
6 | // import { createApp } from "../../../schema";
7 |
8 | // let Heighliner;
9 | // beforeEach(async () => {
10 | // const app = express();
11 | // const { graphql } = await createApp();
12 |
13 | // app.use(bodyParser.json());
14 |
15 | // app.use("/graphql", apolloExpress(graphql));
16 |
17 | // Heighliner = tester({
18 | // server: create(app),
19 | // url: "/graphql",
20 | // contentType: "application/json",
21 | // });
22 | // });
23 |
24 | // XXX figure out how to mock node resolver
25 | xit("Valid queries should return success", async () => {
26 | const response = await Heighliner(
27 | JSON.stringify({
28 | query: `{
29 | node(id: "VXNlcjptdGhlMmhUQWhQU0dESEpuZQ=="){
30 | id
31 | }
32 | }`
33 | })
34 | );
35 |
36 | // t.true(response.success);
37 | expect(response.status).toEqual(200);
38 | });
39 |
--------------------------------------------------------------------------------
/src/util/cache/__test__/mutations.integration.js:
--------------------------------------------------------------------------------
1 | // import express from "express";
2 | // import { apolloExpress } from "apollo-server";
3 | // import { tester } from "graphql-tester";
4 | // import { create } from "graphql-tester/lib/main/servers/express";
5 | // import bodyParser from "body-parser";
6 | // import { createApp } from "../../../schema";
7 |
8 | // let Heighliner;
9 | // beforeEach(async () => {
10 | // const app = express();
11 | // const { graphql } = await createApp();
12 |
13 | // app.use(bodyParser.json());
14 |
15 | // app.use("/graphql", apolloExpress(graphql));
16 |
17 | // Heighliner = tester({
18 | // server: create(app),
19 | // url: "/graphql",
20 | // contentType: "application/json",
21 | // });
22 | // });
23 |
24 | xit("Valid queries should return success", async () => {
25 | const response = await Heighliner(
26 | JSON.stringify({
27 | query: `
28 | mutation ClearCache {
29 | cache(id:"VXNlcjpyWE5iRXlIWmhycENUdHpOZw=="){
30 | id
31 | }
32 | }
33 | `
34 | })
35 | );
36 |
37 | expect(response.success).toBeTruthy();
38 | expect(response.status).toEqual(200);
39 | });
40 |
--------------------------------------------------------------------------------
/src/util/model.js:
--------------------------------------------------------------------------------
1 | import { flatten, isNil } from "lodash";
2 | import { defaultCache } from "./cache";
3 | import { createGlobalId } from "./node/model";
4 |
5 | export class Heighliner {
6 | constructor({ cache } = { cache: defaultCache }) {
7 | this.cache = cache;
8 | }
9 |
10 | async getFromId(id, globalId) {
11 | return Promise.reject(new Error("Not implemented on this model"));
12 | }
13 |
14 | async clearCacheFromRequest({ body }) {
15 | return Promise.reject(new Error(`Caching not implement on ${body.type}`));
16 | }
17 |
18 | async getFromIds(data = []) {
19 | if (!data || !data.length) return Promise.resolve([]);
20 | return Promise.all(
21 | data.map(x =>
22 | this.getFromId(x[this.id], createGlobalId(x[this.id], this.__type))
23 | )
24 | )
25 | .then(x => flatten(x))
26 | .then(x =>
27 | x
28 | .filter(y => !isNil(y))
29 | .map(z => {
30 | const item = z;
31 | item.__type = this.__type;
32 | return item;
33 | })
34 | );
35 | }
36 |
37 | debug(data) {
38 | console.log("DEBUG:", data); // tslint:disable-line
39 | return data;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/expression-engine/models/content/images.js:
--------------------------------------------------------------------------------
1 | import { assign, filter, includes } from "lodash";
2 |
3 | const allResizings = ["xlarge", "large", "medium", "small", "xsmall"];
4 | const sourceBucket = "ns.images";
5 | const resizeBucket = "resizings";
6 |
7 | const generateFilename = (filename, size) => {
8 | if (!filename) return null;
9 |
10 | const parts = filename.split(".");
11 | parts.splice(parts.length - 1, 0, size);
12 | let result = parts.join(".");
13 | result = result.replace(sourceBucket, resizeBucket);
14 |
15 | return result;
16 | };
17 |
18 | const addResizings = (images, options = { sizes: null, ratios: [] }) => {
19 | const result = [];
20 | const resizings = options.sizes || allResizings;
21 |
22 | if (options.ratios && options.ratios.length > 0) {
23 | images = filter(images, image => includes(options.ratios, image.fileLabel));
24 | }
25 |
26 | images.map(image => {
27 | resizings.map(resize => {
28 | const resizedImage = assign({}, image);
29 | resizedImage.url = generateFilename(resizedImage.url, resize);
30 | resizedImage.size = resize;
31 | result.push(resizedImage);
32 | });
33 | });
34 |
35 | return result;
36 | };
37 |
38 | export { addResizings };
39 |
--------------------------------------------------------------------------------
/src/rock/models/finances/util/logError.js:
--------------------------------------------------------------------------------
1 | import Raven from "raven";
2 |
3 | let sentry;
4 |
5 | if (process.env.SENTRY) {
6 | sentry = new Raven.Client(process.env.SENTRY);
7 | }
8 |
9 | const report = ({ data, attemptsMade = 1 }, error) => {
10 | if (!sentry) {
11 | if (!process.env.CI) console.error("ERROR", error);
12 | return;
13 | }
14 |
15 | // don't log to sentry or slack on every attempt
16 | // only log of the first event if possible
17 | if (!attemptsMade || attemptsMade !== 1) return;
18 |
19 | if (data && data.Person) {
20 | sentry.setUserContext({ id: data.Person.Id });
21 | if (data.Person.Email) sentry.setUserContext({ email: data.Person.Email });
22 | }
23 |
24 | sentry.captureException(error, { extra: { data, attemptsMade } });
25 |
26 | // only log to slack the first time an error has happened
27 | if (process.env.SLACK) {
28 | const message = {
29 | username: "Heighliner",
30 | icon_emoji: ":feelsbulbman:",
31 | text: `ATTENTION: ${error.message}`,
32 | channel: "systems"
33 | };
34 |
35 | fetch(process.env.SLACK, {
36 | method: "POST",
37 | body: JSON.stringify(message)
38 | });
39 | }
40 | };
41 |
42 | export default report;
43 |
--------------------------------------------------------------------------------
/src/routes/cache.js:
--------------------------------------------------------------------------------
1 | import { createModels } from "./graphql";
2 | import { createCache } from "../util/cache";
3 |
4 | export default (app, monitor) => {
5 | app.post("/util/cache/flush", async (req, res) => {
6 | const cache = await createCache(monitor);
7 | cache.clearAll();
8 | res.end();
9 | });
10 |
11 | app.post("/graphql/cache", async (req, res) => {
12 | const cache = await createCache(monitor);
13 | const models = createModels({ cache });
14 |
15 | const { type, id } = req.body;
16 | if (!type || !id) {
17 | res.status(500).send({ error: "Missing `id` or `type` for request" });
18 | return;
19 | }
20 | let clearingCache;
21 | for (const model in models) {
22 | const Model = models[model];
23 | if (!Model.cacheTypes) continue;
24 | if (Model.cacheTypes.indexOf(type) === -1) continue;
25 | clearingCache = true;
26 | // XXX should we hold off the res until this responds?
27 | Model.clearCacheFromRequest(req);
28 | }
29 | if (!clearingCache) {
30 | res.status(404).send({ error: `No model found for ${type}` });
31 | return;
32 | }
33 |
34 | res.status(200).send({ message: `Cache cleared for ${type} ${id}` });
35 | });
36 | };
37 |
--------------------------------------------------------------------------------
/src/esv/__tests__/fetch.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | import casual from "casual";
4 |
5 | import { ESVFetchConnector, connect } from "../fetch";
6 |
7 | describe("ESV", () => {
8 | it("`connect` should fail without any env vars", async () => {
9 | delete process.env.ESV_KEY;
10 |
11 | const ESV = await connect();
12 | expect(ESV).toBeFalsy();
13 | });
14 |
15 | it("`connect` should be fine with esv key", async () => {
16 | process.env.ESV_KEY = casual.word;
17 |
18 | const ESV = await connect();
19 | expect(ESV).toBeTruthy();
20 | });
21 |
22 | it("`ESVFetchConnector` should export getFromAPI function", () => {
23 | const testFetcher = new ESVFetchConnector();
24 | expect(testFetcher.getFromAPI).toBeTruthy();
25 | });
26 |
27 | it("`ESVFetchConnector` should format the query", () => {
28 | process.env.ESV_KEY = "test-key";
29 |
30 | const testFetcher = new ESVFetchConnector();
31 | expect(testFetcher.getRequest("john 3:16")).toMatchSnapshot();
32 | });
33 |
34 | it("`ESVFetchConnector` should keep track of connectors", () => {
35 | const testFetcher = new ESVFetchConnector();
36 | expect(testFetcher.getCount()).toEqual(1);
37 | expect(testFetcher.getCount()).toEqual(2);
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/src/expression-engine/models/ee/tags.js:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-shadowed-variable */
2 |
3 | import { INTEGER, STRING } from "sequelize";
4 |
5 | import { MySQLConnector } from "../../mysql";
6 |
7 | const tagSchema = {
8 | tag_id: { type: INTEGER, primaryKey: true },
9 | tag_name: { type: STRING },
10 | entry_date: { type: INTEGER },
11 | clicks: { type: INTEGER }
12 | };
13 |
14 | const tagEntriesSchema = {
15 | tag_id: { type: INTEGER, primaryKey: true },
16 | entry_id: { type: INTEGER }
17 | };
18 |
19 | let Tags;
20 | let TagEntries;
21 | export { Tags, tagSchema, TagEntries, tagEntriesSchema };
22 |
23 | export function connect() {
24 | Tags = new MySQLConnector("exp_tag_tags", tagSchema);
25 | TagEntries = new MySQLConnector("exp_tag_entries", tagEntriesSchema);
26 |
27 | return {
28 | Tags,
29 | TagEntries
30 | };
31 | }
32 |
33 | export function bind({ ChannelData, Tags, TagEntries }) {
34 | ChannelData.model.hasMany(TagEntries.model, { foreignKey: "entry_id" });
35 | TagEntries.model.belongsTo(ChannelData.model, { foreignKey: "entry_id" });
36 |
37 | Tags.model.hasMany(TagEntries.model, { foreignKey: "tag_id" });
38 | TagEntries.model.belongsTo(Tags.model, { foreignKey: "tag_id" });
39 | }
40 |
41 | export default {
42 | connect,
43 | bind
44 | };
45 |
--------------------------------------------------------------------------------
/src/util/node/__test__/resolver.spec.js:
--------------------------------------------------------------------------------
1 | import casual from "casual";
2 | import Resolver from "../resolver";
3 |
4 | const sampleData = {
5 | _id: casual.word,
6 | __type: "Test"
7 | };
8 |
9 | it("Node should only have a __resolveType on the resolver", () => {
10 | const { Node } = Resolver;
11 |
12 | expect(Node.__resolveType).toBeTruthy();
13 | expect(Object.keys(Node).length).toEqual(1);
14 | expect(Object.keys(Node)[0]).toEqual("__resolveType");
15 | });
16 |
17 | it("Node should return the type from the data passed to it", () => {
18 | const { Node } = Resolver;
19 |
20 | const schema = {
21 | getType(type) {
22 | expect(type).toEqual(sampleData.__type);
23 | return type;
24 | }
25 | };
26 |
27 | const __type = Node.__resolveType(sampleData, null, { schema });
28 | expect(__type).toEqual(sampleData.__type);
29 | });
30 |
31 | it("Query node should return the data via the `Node` class", () => {
32 | const { Query } = Resolver;
33 |
34 | const fakeId = casual.word;
35 | const models = {
36 | Node: {
37 | get(id) {
38 | expect(id).toEqual(fakeId);
39 | return sampleData;
40 | }
41 | }
42 | };
43 |
44 | const data = Query.node(null, { id: fakeId }, { models });
45 | expect(data).toEqual(sampleData);
46 | });
47 |
--------------------------------------------------------------------------------
/src/rock/models/system/resolver.js:
--------------------------------------------------------------------------------
1 | import { createGlobalId } from "../../../util";
2 |
3 | export default {
4 | Query: {
5 | definedValues: (_, { limit, id, skip, all }, { models }) => {
6 | const query = { offset: skip };
7 | if (!all) query.limit = limit;
8 |
9 | return models.Rock.getDefinedValuesByTypeId(id, query);
10 | }
11 | },
12 |
13 | DefinedValue: {
14 | id: ({ Id }, _, $, { parentType }) => createGlobalId(Id, parentType.name),
15 | _id: ({ Id }) => Id,
16 | value: ({ Value }) => Value,
17 | description: ({ Description }) => Description
18 | },
19 |
20 | Attribute: {
21 | id: ({ Id }, _, $, { parentType }) => createGlobalId(Id, parentType.name),
22 | key: ({ Key }) => Key,
23 | description: ({ Description }) => Description,
24 | order: ({ Order }) => Order,
25 | values: ({ Id, EntityId }, _, { models, ...rest }) =>
26 | models.Rock.getAttributeValuesFromAttributeId(
27 | Id,
28 | { models, ...rest },
29 | EntityId
30 | )
31 | },
32 |
33 | AttributeValue: {
34 | attribute: ({ AttributeId }, _, { models }) =>
35 | models.Rock.getAttributeFromId(AttributeId),
36 | id: ({ Id }, _, $, { parentType }) => createGlobalId(Id, parentType.name),
37 | value: ({ Value }) => Value
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/src/rock/models/groups/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | type GroupMember implements Node {
4 | id: ID!
5 | role: String
6 | person: Person
7 | status: Int
8 | }
9 |
10 | type GroupLocation implements Node {
11 | id: ID!
12 | location: Location
13 | }
14 |
15 | # XXX abstract
16 | type GroupSchedule implements Node {
17 | id: ID!
18 | name: String
19 | description: String
20 | start: String
21 | end: String
22 | day: String
23 | time: String
24 | iCal: String
25 | }
26 |
27 | type Group implements Node {
28 | active: Boolean
29 | ageRange: [Int]
30 | campus: Campus
31 | demographic: String
32 | description: String
33 | distance: Float
34 | entityId: Int
35 | id: ID!
36 | guid: String
37 | kidFriendly: Boolean
38 | name: String
39 | photo: String
40 | tags: [DefinedValue]
41 | type: String
42 | groupType: Int
43 | schedule: GroupSchedule
44 | members: [GroupMember]
45 | locations: [GroupLocation]
46 | isLiked: Boolean
47 | }
48 |
49 | type GroupSearch {
50 | count: Int
51 | results: [Group]
52 | }
53 |
54 | type GroupsMutationResponse implements MutationResponse {
55 | error: String
56 | success: Boolean!
57 | code: Int
58 | }
59 |
60 | `
61 | ];
62 |
--------------------------------------------------------------------------------
/src/apollos/models/users/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | type Hashes {
4 | when: String!
5 | hashedToken: ID!
6 | }
7 |
8 | type UserTokens {
9 | tokens: [Hashes]
10 | }
11 |
12 | type UserRock {
13 | id: Int
14 | alias: Int
15 | }
16 |
17 | type UserService {
18 | rock: UserRock
19 | resume: UserTokens
20 | }
21 |
22 | type UserEmail {
23 | address: String!
24 | verified: Boolean
25 | }
26 |
27 | type User implements Node {
28 | id: ID!
29 | # We should investigate how best to represent dates
30 | createdAt: String!
31 | services: UserService @deprecated(reason: "This is a private server-only field")
32 | emails: [UserEmail] @deprecated(reason: "Use email instead")
33 | email: String
34 | followedTopics: [String]
35 | }
36 |
37 | type LoginMutationResponse {
38 | id: ID!
39 | token: String
40 | }
41 |
42 | input UserProfileInput {
43 | NickName: String
44 | FirstName: String
45 | LastName: String
46 | Email: String
47 | BirthMonth: String
48 | BirthDay: String
49 | BirthYear: String
50 | Campus: ID
51 | }
52 |
53 | input HomeAddressInput {
54 | Street1: String
55 | Street2: String
56 | City: String
57 | State: String
58 | PostalCode: String
59 | }
60 | `
61 | ];
62 |
--------------------------------------------------------------------------------
/src/__tests__/server.integration.js:
--------------------------------------------------------------------------------
1 | // import express from "express";
2 | // import { apolloExpress } from "apollo-server";
3 | // import { tester } from "graphql-tester";
4 | // import { create } from "graphql-tester/lib/main/servers/express";
5 | // import bodyParser from "body-parser";
6 | // import { createApp } from "../schema";
7 |
8 | // let Heighliner;
9 | // beforeEach(async () => {
10 | // const app = express();
11 | // const { graphql } = await createApp();
12 |
13 | // app.use(bodyParser.json());
14 |
15 | // app.use("/graphql", apolloExpress(graphql));
16 |
17 | // Heighliner = tester({
18 | // server: create(app),
19 | // url: "/graphql",
20 | // contentType: "application/json",
21 | // });
22 | // });
23 |
24 | xit("Valid queries should return success", () =>
25 | Heighliner(JSON.stringify({ query: "{ currentUser { id } }" })).then(
26 | response => {
27 | expect(response.success).toBeTruthy();
28 | expect(response.status).toEqual(200);
29 | expect(response.data).toBeTruthy();
30 | }
31 | ));
32 |
33 | xit("Invalid queries should fail", () =>
34 | Heighliner(JSON.stringify({ query: "{ foobar { id } }" })).then(response => {
35 | expect(response.success).toBeFalsy;
36 | expect(response.status).toEqual(400);
37 | expect(response.errors).toBeTruthy();
38 | }));
39 |
--------------------------------------------------------------------------------
/src/esv/fetch.js:
--------------------------------------------------------------------------------
1 | import fetch from "isomorphic-fetch";
2 |
3 | export function connect() {
4 | return Promise.resolve(!!process.env.ESV_KEY);
5 | }
6 |
7 | export class ESVFetchConnector {
8 | baseUrl = "http://www.esvapi.org/v2/rest/passageQuery";
9 | key = process.env.ESV_KEY;
10 | count = 0;
11 |
12 | getFromAPI(query) {
13 | const label = `ESVFetchConnector${this.getCount()}`;
14 |
15 | const request = this.getRequest(query);
16 |
17 | const headers = {
18 | "user-agent": "Heighliner",
19 | "Content-Type": "application/text"
20 | };
21 |
22 | const options = { method: "GET", headers };
23 |
24 | console.time(label);
25 |
26 | return fetch(request, options)
27 | .then(x => x.text())
28 | .then(x => {
29 | console.timeEnd(label);
30 | return x;
31 | });
32 | }
33 |
34 | getRequest(query) {
35 | let request = `${this.baseUrl}?key=${this.key}`;
36 | request += `&passage=${query}`;
37 | request += "&include-headings=false";
38 | request += "&include-passage-references=false";
39 | request += "&include-footnotes=false";
40 | request += "&include-audio-link=false";
41 | request += "&include-short-copyright=false";
42 | return request;
43 | }
44 |
45 | getCount() {
46 | this.count++;
47 | return this.count;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const nodeExternals = require("webpack-node-externals");
2 | const DotenvPlugin = require("webpack-dotenv-plugin");
3 | const NpmInstallPlugin = require("npm-install-webpack-plugin");
4 | const { getIfUtils, removeEmpty } = require("webpack-config-utils");
5 | const webpack = require("webpack");
6 | const path = require("path");
7 |
8 | const { ifProduction, ifNotProduction } = getIfUtils(process.env.NODE_ENV);
9 |
10 | module.exports = {
11 | entry: "./src/server.js",
12 | target: "node",
13 | output: {
14 | path: path.resolve(__dirname, "./lib"),
15 | filename: "server.js",
16 | },
17 | recordsPath: path.resolve(__dirname, "lib/_records"),
18 | plugins: removeEmpty([
19 | ifProduction(new webpack.optimize.DedupePlugin()),
20 | ifProduction(new webpack.optimize.UglifyJsPlugin({
21 | compress: {
22 | screw_ie8: true,
23 | warnings: false,
24 | },
25 | })),
26 | ifNotProduction(new NpmInstallPlugin()),
27 | ifNotProduction(new webpack.HotModuleReplacementPlugin()),
28 | new webpack.NoEmitOnErrorsPlugin(),
29 | ifNotProduction(ifNotProduction() && new DotenvPlugin({ sample: "./.env.example" })),
30 | ]),
31 | module: {
32 | loaders: [
33 | {
34 | test: /\.js$/,
35 | exclude: /(node_modules|bower_components)/,
36 | loader: "babel-loader",
37 | },
38 | ],
39 | },
40 | externals: [nodeExternals()],
41 | };
42 |
--------------------------------------------------------------------------------
/src/util/node/model.js:
--------------------------------------------------------------------------------
1 |
2 | import Crypto from "crypto";
3 | const secret = process.env.SECRET || "LZEVhlgzFZKClu1r";
4 |
5 | export default class Node {
6 |
7 | constructor(context) {
8 | this.models = context.models;
9 | }
10 |
11 | // XXX what do we want to do about errors here?
12 | async get(encodedId) {
13 | const { __type, id } = parseGlobalId(encodedId);
14 |
15 | if (!this.models || !this.models[__type] || !this.models[__type].getFromId) {
16 | return Promise.reject(`No model found using ${__type}`);
17 | }
18 |
19 | try {
20 | const data = await (this.models[__type].getFromId(id, encodedId));
21 | if(!data) return null;
22 | data.__type = __type;
23 | return data;
24 | } catch (e) {
25 | return Promise.reject(e.message);
26 | }
27 | }
28 |
29 | }
30 |
31 | export function createGlobalId(id, type) {
32 | const cipher = Crypto.createCipher("aes192", secret);
33 |
34 | let encrypted = cipher.update(`${type}:${id}`, "utf8", "hex");
35 | encrypted += cipher.final("hex");
36 |
37 | return encodeURI(encrypted);
38 | }
39 |
40 | export function parseGlobalId(encodedId) {
41 | const decipher = Crypto.createDecipher("aes192", secret);
42 |
43 | let decrypted = decipher.update(decodeURI(encodedId), "hex", "utf8");
44 | decrypted += decipher.final("utf8");
45 |
46 | const [__type, id] = decrypted.toString().split(":");
47 | return { __type, id };
48 | }
49 |
--------------------------------------------------------------------------------
/src/util/__tests__/heighliner.spec.js:
--------------------------------------------------------------------------------
1 | import {
2 | createQueries,
3 | createMutations,
4 | // loadApplications, // XXX test this
5 | // createSchema, // XXX test this
6 | } from "../heighliner";
7 |
8 | xit("`createQueries` should return an array with `type Query`", () => {
9 | const queries = createQueries([]);
10 | expect(/type Query/.test(queries.join(" "))).toBeTruthy();
11 | });
12 |
13 | xit("`createQueries` should include the node interface", () => {
14 | const queries = createQueries([]);
15 | expect(/node\(id: ID!\): Node/.test(queries.join(" "))).toBeTruthy();
16 | });
17 |
18 | xit("`createQueries` should allow passing in new queries", () => {
19 | const queries = createQueries(["foo: Node"]);
20 | expect(/foo: Node/.test(queries.join(" "))).toBeTruthy();
21 | });
22 |
23 | xit("`createMutations` should return an array with `type Mutation`", () => {
24 | const mutations = createMutations([]);
25 | expect(/type Mutation/.test(mutations.join(" "))).toBeTruthy();
26 | });
27 |
28 | xit("`createMutations` should include the cache interface", () => {
29 | const mutations = createMutations([]);
30 | expect(/cache\(id: ID!, type: String\): Node/.test(mutations.join(" "))).toBeTruthy();
31 | });
32 |
33 | xit("`createMutations` should allow passing in new mutations", () => {
34 | const mutations = createMutations(["foo(id: String): Node"]);
35 | expect(/foo\(id: String\): Node/.test(mutations.join(" "))).toBeTruthy();
36 | });
37 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required # force to run in container
2 | language: node_js
3 | node_js:
4 | - '6'
5 | cache:
6 | yarn: true
7 | directories:
8 | - $HOME/.npm
9 | - $HOME/.yarn-cache
10 | - node_modules
11 | before_install:
12 | - echo 'America/New_York' | sudo tee /etc/timezone
13 | - sudo dpkg-reconfigure --frontend noninteractive tzdata
14 | - chmod +x $TRAVIS_BUILD_DIR/.travis/run_on_pull_requests
15 | - chmod +x $TRAVIS_BUILD_DIR/.travis/run_for_coverage
16 | - npm install -g coveralls
17 | script:
18 | - npm i
19 | # - yarn danger
20 | - yarn test
21 | after_success:
22 | - 'if [ "$TRAVIS_EVENT_TYPE" == "cron" ]; then bash ./.travis/run_for_coverage; fi'
23 | - 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then bash ./.travis/run_on_pull_requests; fi'
24 | notifications:
25 | slack:
26 | secure: enlvPiXHu5nK5VQORUioMTj5eCHBUpriMDevnWSYPYQ9nGJgaPROOEeki9I4LkF+Y6BDI+8ijWwwg8f3bdZKclSzbf0ZOw8bE8THc9sJfBPmbYX86TvvnuLBXB/OHY0lP58lLd+MZI7BycNlg8SKaUICrYApbOEq8PlVaokCnTTDgn2Y4kwpWQBkMKBckIxP6HxI9sREzoG7DFPcD/wCoMS0M8h7bw/cfUukQEczwsOS0AcmEcLRUHQW+5n7qCRtVAO6MQ5WHgE29q37nk2iXf36c6sBJWePvB9Kp/0r7gRHGqC8Kr352LF99O3Lu55ZcjoTiqngQVDPkcghkMnKSIIh5P5KSkk55n15qFBqc3L/KCMnyJApcNyMaGiRmW/PMQ9YT9lg0nmUdG+WTvXrsTv4hIqJ4w5UqXuJS6XQEKcykIr2ozqZEaYngSIUrmHGUiFQSFPpZ5wky3lY0XeEgq0fE+pm0epevJ4xwm3YAPllBbCr+QOzHg4lD0g4kw3ufQLOIEIZRefs0LSFO6O7MsKEC1qGc0Do7EpTX6D7WueP9sdidmNjyyt/NGE8rAK8I+At+O/HyKTfrq6buYuYVW2fn3fTRxuHXOkM7aehoQQrDAxN/I7/CCh+rwdYqq/q3Iji979iqXC9goASANYYg/PIHdVwiN0/OXXvP+h+aR0=
27 |
--------------------------------------------------------------------------------
/src/rock/models/feeds/__tests__/__snapshots__/resolver.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Feed Query should return a list of user's likes with correct filter 1`] = `
4 | Array [
5 | Object {
6 | "__type": "Content",
7 | "channelName": "articles",
8 | "content": Object {
9 | "images": Array [
10 | Object {
11 | "label": "2:1",
12 | "url": "//drhztd8q3iayu.cloudfront.net/newspring/editorial/articles/newspring.blog.hero.monasterypews.large.jpg",
13 | },
14 | ],
15 | },
16 | "id": "16c44ac3fe07af726455feac35ab2be9",
17 | "title": "One place where everyone is welcome",
18 | },
19 | ]
20 | `;
21 |
22 | exports[`Feed Query should return array of combined Transaction and SavedPayment data 1`] = `
23 | Array [
24 | Object {
25 | "__type": "Transaction",
26 | "date": "today",
27 | "details": "Details Object",
28 | "entityId": 3,
29 | "id": 2,
30 | "payment": "Payment Object",
31 | "person": "12345-ABCDE",
32 | "schedule": "Schedule Object",
33 | "status": "sample status",
34 | "summary": "sample summary",
35 | },
36 | Object {
37 | "Name": "my saved payment",
38 | "__type": "SavedPayment",
39 | "code": "1234567812345678",
40 | "date": "today",
41 | "entityId": 5,
42 | "expirationMonth": "123456",
43 | "expirationYear": "12345678",
44 | "guid": "1234567890",
45 | "id": 4,
46 | "payment": "Payment Object",
47 | },
48 | ]
49 | `;
50 |
--------------------------------------------------------------------------------
/src/rock/models/people/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | type PhoneNumber implements Node {
4 | id: ID!
5 | countryCode: String
6 | description: String
7 | canText: Boolean!
8 | rawNumber: String!
9 | number: String!
10 | person: Person
11 | }
12 |
13 | type Person implements Node {
14 | id: ID!
15 | entityId: Int!
16 | guid: String!
17 | firstName: String!
18 | lastName: String!
19 | nickName: String
20 | phoneNumbers: [PhoneNumber]
21 | photo: String
22 | age: String
23 | birthDate: String
24 | birthDay: Int
25 | birthMonth: Int
26 | birthYear: Int
27 | email: String
28 | impersonationParameter(expireDateTime: String, pageId: Int, usageLimit: Int): String
29 | campus(cache: Boolean = true): Campus
30 | home(cache: Boolean = true): Location
31 | roles(cache: Boolean = true): [Group]
32 | attributes(key: String): [Attribute]
33 | groups(cache: Boolean = true, groupTypeIds: [Int] = []): [Group]
34 | followedTopics: [String]
35 | }
36 |
37 | type PhoneNumberMutationResponse implements MutationResponse {
38 | error: String
39 | success: Boolean!
40 | code: Int
41 | }
42 |
43 | type DeviceRegistrationMutationResponse implements MutationResponse {
44 | error: String
45 | success: Boolean!
46 | code: Int
47 | }
48 |
49 | type AttributeValueMutationResponse implements MutationResponse {
50 | error: String
51 | success: Boolean!
52 | code: Int
53 | }
54 | `
55 | ];
56 |
--------------------------------------------------------------------------------
/src/google-site-search/models/search/__test__/queries.integration.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { apolloExpress } from "apollo-server-express";
3 | import { tester } from "graphql-tester";
4 | import { create } from "graphql-tester/lib/main/servers/express";
5 | import bodyParser from "body-parser";
6 | import { createApp } from "../../../../schema";
7 |
8 | let Heighliner;
9 | beforeEach(async () => {
10 | const app = express();
11 | const { graphql } = await createApp();
12 |
13 | app.use(bodyParser.json());
14 |
15 | app.use("/graphql", apolloExpress(graphql));
16 |
17 | Heighliner = tester({
18 | server: create(app),
19 | url: "/graphql",
20 | contentType: "application/json"
21 | });
22 | });
23 |
24 | xit("Valid queries should return success", async () => {
25 | const response = await Heighliner(
26 | JSON.stringify({
27 | query: `
28 | query GetSearch {
29 | search(query: "hey", first: 1, after: 0, site: "example.com") {
30 | total
31 | next
32 | previous
33 | items {
34 | id
35 | title
36 | htmlTitle
37 | link
38 | displayLink
39 | description
40 | htmlDescription
41 | type
42 | section
43 | image
44 | }
45 | }
46 | }
47 | `
48 | })
49 | );
50 |
51 | expect(response.success).toBeTruthy();
52 | expect(response.status).toEqual(200);
53 | expect(response.data).toBeTruthy();
54 | });
55 |
--------------------------------------------------------------------------------
/src/rock/models/finances/util/language.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 100: "Transaction approved",
3 | 200: "We're sorry, your transaction was declined by our payment processer",
4 | 201: "It looks like this may be a fraudlent charge. Please contact your paymenr provider",
5 | 202: "We're sorry, your account has insufficient funds",
6 | 203: "We're sorry, this transaction is over your account limit",
7 | 204: "We're sorry, this transaction was not allowed",
8 | 220: "It looks like your payment information is incorrect",
9 | 221: "We couldn't find your dard issuer",
10 | 222: "Your card number is invalid",
11 | 223: "Your card has expired",
12 | 224: "Your expiration date is invalid",
13 | 225: "Your security code is invalid",
14 | // eslint-disable-next-line max-len
15 | 240: "Please call your account provider for more information as to why this transaction could not be processed",
16 | 250: "Please call our finance team",
17 | 251: "This card has been reported as lost",
18 | 252: "This card has been reported as stolen",
19 | 253: "This card is not valid",
20 | 260: "result-text",
21 | 400: "There was an error with our payment processor, you were not charged, please try again",
22 | 410: "We had an issue processing transactions, please try again",
23 | 411: "Looks like we are having connection issues with our processor, please try again later",
24 | // eslint-disable-next-line max-len
25 | 430: "This looks like a duplicate transaction, for your safety, we have declined the transaction. Please try again in five minutes"
26 | };
27 |
--------------------------------------------------------------------------------
/src/apollos/models/users/__tests__/queries.integration.js:
--------------------------------------------------------------------------------
1 | // import express from "express";
2 | // import { apolloExpress } from "apollo-server";
3 | // import { tester } from "graphql-tester";
4 | // import { create } from "graphql-tester/lib/main/servers/express";
5 | // import bodyParser from "body-parser";
6 | // import { createApp } from "../../../../schema";
7 |
8 | // let Heighliner;
9 | // beforeEach(async () => {
10 | // const app = express();
11 | // const { graphql } = await createApp();
12 |
13 | // app.use(bodyParser.json());
14 |
15 | // app.use("/graphql", apolloExpress(graphql));
16 |
17 | // Heighliner = tester({
18 | // server: create(app),
19 | // url: "/graphql",
20 | // contentType: "application/json",
21 | // });
22 | // });
23 |
24 | xit("Valid queries should return success", async () => {
25 | const response = await Heighliner(
26 | JSON.stringify({
27 | query: `
28 | query CurrentUser {
29 | currentUser {
30 | id
31 | createdAt
32 | emails {
33 | address
34 | }
35 | services {
36 | rock {
37 | id
38 | alias
39 | }
40 | resume {
41 | tokens {
42 | when
43 | hashedToken
44 | }
45 | }
46 | }
47 | }
48 | }
49 | `
50 | })
51 | );
52 |
53 | expect(response.success).toBeTruthy();
54 | expect(response.status).toEqual(200);
55 | expect(response.data).toBeTruthy();
56 | });
57 |
--------------------------------------------------------------------------------
/src/rock/models/binary-files/tables.js:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-shadowed-variable */
2 |
3 | import { INTEGER, STRING, BOOLEAN } from "sequelize";
4 |
5 | import { MSSQLConnector } from "../../mssql";
6 |
7 | const binaryFileSchema = {
8 | Id: { type: INTEGER, primaryKey: true },
9 | BinaryFileTypeId: { type: INTEGER },
10 | Description: { type: STRING },
11 | FileName: { type: STRING },
12 | IsSystem: { type: BOOLEAN },
13 | IsTemporary: { type: BOOLEAN },
14 | MimeType: { type: STRING },
15 | Path: { type: STRING },
16 | StorageEntitySettings: { type: String },
17 | StorageEntityTypeId: { type: INTEGER }
18 | };
19 |
20 | const binaryFileTypeSchema = {
21 | Id: { type: INTEGER, primaryKey: true },
22 | AllowCaching: { type: BOOLEAN },
23 | Description: { type: STRING },
24 | IsSystem: { type: BOOLEAN },
25 | Name: { type: STRING },
26 | StorageEntityTypeId: { type: INTEGER }
27 | };
28 |
29 | let BinaryFile;
30 | let BinaryFileType;
31 | export { BinaryFile, binaryFileSchema, BinaryFileType, binaryFileTypeSchema };
32 |
33 | export function connect() {
34 | BinaryFile = new MSSQLConnector("BinaryFile", binaryFileSchema);
35 | BinaryFileType = new MSSQLConnector("BinaryFileType", binaryFileTypeSchema);
36 |
37 | return {
38 | BinaryFile,
39 | BinaryFileType
40 | };
41 | }
42 |
43 | export function bind({ BinaryFile }) {
44 | BinaryFile.model.belongsTo(BinaryFileType.model, {
45 | foreignKey: "BinaryFileTypeId",
46 | targetKey: "Id"
47 | });
48 | }
49 |
50 | export default {
51 | connect,
52 | bind
53 | };
54 |
--------------------------------------------------------------------------------
/src/util/cache/memory-cache.js:
--------------------------------------------------------------------------------
1 | import Crypto from "crypto";
2 |
3 | export class InMemoryCache {
4 | constructor(cache = {}, secret = "InMemoryCache") {
5 | // XXX this is really only used for testing purposes
6 | this.cache = cache;
7 | this.secret = secret;
8 | }
9 |
10 | get(id, lookup, { ttl, cache } = { ttl: 86400, cache: true }) {
11 | let fromCache = false;
12 | return new Promise(done => {
13 | const data = this.cache[id];
14 | if ((!data || !cache) && lookup) return lookup().then(done);
15 |
16 | fromCache = true;
17 | return done(data);
18 | }).then(data => {
19 | if (data && !fromCache) {
20 | // async the save
21 | process.nextTick(() => {
22 | this.set(id, data, ttl);
23 | });
24 | }
25 |
26 | return data;
27 | });
28 | }
29 |
30 | set(id, data, ttl = 86400) {
31 | return new Promise(done => {
32 | // XXX this should technically never fail
33 | try {
34 | // save to cache
35 | this.cache[id] = data;
36 |
37 | // clear cache
38 | setTimeout(() => {
39 | delete this.cache[id];
40 | }, ttl * 60);
41 |
42 | return done(true);
43 | } catch (e) {
44 | return done(false);
45 | }
46 | });
47 | }
48 |
49 | del(id) {
50 | delete this.cache[id];
51 | }
52 |
53 | encode(obj, prefix = "") {
54 | const cipher = Crypto.createHmac("sha256", this.secret);
55 | const str = `${prefix}${JSON.stringify(obj)}`;
56 | return cipher.update(str, "utf-8").digest("hex");
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/rock/models/campuses/model.js:
--------------------------------------------------------------------------------
1 | import { merge } from "lodash";
2 | import { defaultCache } from "../../../util/cache";
3 | import { createGlobalId } from "../../../util";
4 |
5 | import {
6 | Campus as CampusTable,
7 | Location as LocationTable // XXX move to its own model
8 | } from "./tables";
9 |
10 | import { Rock } from "../system";
11 |
12 | export class Campus extends Rock {
13 | __type = "Campus";
14 |
15 | constructor({ cache } = { cache: defaultCache }) {
16 | super();
17 | this.cache = cache;
18 | }
19 |
20 | async getFromId(id, globalId) {
21 | globalId = globalId || createGlobalId(`${id}`, this.__type);
22 | return this.cache.get(globalId, () =>
23 | CampusTable.findOne({ where: { Id: id } })
24 | );
25 | }
26 |
27 | async findByLocationId(id, globalId) {
28 | globalId = globalId || createGlobalId(`${id}`, "Location");
29 | return this.cache.get(globalId, () =>
30 | LocationTable.findOne({ where: { Id: id } })
31 | );
32 | }
33 |
34 | // async findByPersonId(id) {
35 | // return
36 | // }
37 |
38 | async find(query) {
39 | query = merge({ IsActive: true }, query);
40 | for (const key in query) {
41 | if (!query[key]) delete query[key];
42 | }
43 | return this.cache
44 | .get(this.cache.encode(query), () =>
45 | CampusTable.find({
46 | where: query,
47 | attributes: ["Id"]
48 | })
49 | )
50 | .then(this.getFromIds.bind(this))
51 | .then(x => x.filter(y => y.Name !== "Central"));
52 | }
53 | }
54 |
55 | export default {
56 | Campus
57 | };
58 |
--------------------------------------------------------------------------------
/src/rock/models/system/__tests__/resolver.spec.js:
--------------------------------------------------------------------------------
1 | import Resolver from "../resolver";
2 |
3 | describe("Attribute", () => {
4 | const { Attribute } = Resolver;
5 |
6 | it("has id", () => {
7 | expect(
8 | Attribute.id({ Id: 4 }, null, null, { parentType: { name: "boi" } })
9 | ).toMatchSnapshot();
10 | });
11 | it("has key", () => {
12 | expect(Attribute.key({ Key: 2 })).toEqual(2);
13 | });
14 | it("has description", () => {
15 | expect(Attribute.description({ Description: "boi" })).toEqual("boi");
16 | });
17 | it("has order", () => {
18 | expect(Attribute.order({ Order: 2 })).toEqual(2);
19 | });
20 | it("has values", () => {
21 | const models = { Rock: { getAttributeValuesFromAttributeId: jest.fn() } };
22 | Attribute.values({ Id: 2, EntityId: 43 }, null, { models });
23 | expect(models.Rock.getAttributeValuesFromAttributeId).toBeCalledWith(
24 | 2,
25 | { models },
26 | 43
27 | );
28 | });
29 | });
30 |
31 | describe("AttributeValue", () => {
32 | const { AttributeValue } = Resolver;
33 |
34 | it("has attribute", () => {
35 | const models = { Rock: { getAttributeFromId: jest.fn() } };
36 | AttributeValue.attribute({ AttributeId: 5 }, null, { models });
37 | expect(models.Rock.getAttributeFromId).toBeCalledWith(5);
38 | });
39 | it("has id", () => {
40 | expect(
41 | AttributeValue.id({ Id: 4 }, null, null, { parentType: { name: "boi" } })
42 | ).toMatchSnapshot();
43 | });
44 | it("has value", () => {
45 | expect(AttributeValue.value({ Value: "yo" })).toEqual("yo");
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/src/rock/models/campuses/tables.js:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-shadowed-variable */
2 |
3 | import { INTEGER, STRING, BOOLEAN, GEOGRAPHY } from "sequelize";
4 |
5 | import { MSSQLConnector } from "../../mssql";
6 |
7 | const campusSchema = {
8 | Id: { type: INTEGER, primaryKey: true },
9 | Name: { type: STRING },
10 | Guid: { type: STRING },
11 | ShortCode: { type: STRING },
12 | Url: { type: STRING },
13 | LocationID: { type: INTEGER },
14 | PhoneNumber: { type: STRING },
15 | Description: { type: STRING },
16 | ServiceTimes: { type: STRING },
17 | IsActive: { type: BOOLEAN }
18 | };
19 |
20 | const locationSchema = {
21 | Id: { type: INTEGER, primaryKey: true },
22 | Name: { type: STRING },
23 | IsActive: { type: BOOLEAN },
24 | LocationTypeValueId: { type: INTEGER },
25 | GeoPoint: { type: GEOGRAPHY },
26 | GeoFence: { type: GEOGRAPHY },
27 | Street1: { type: STRING },
28 | Street2: { type: STRING },
29 | City: { type: STRING },
30 | State: { type: STRING },
31 | Country: { type: STRING },
32 | PostalCode: { type: STRING }
33 | };
34 |
35 | let Campus;
36 | let Location;
37 | export { Campus, campusSchema, Location, locationSchema };
38 |
39 | export function connect() {
40 | Campus = new MSSQLConnector("Campus", campusSchema);
41 | Location = new MSSQLConnector("Location", locationSchema);
42 |
43 | return {
44 | Campus,
45 | Location
46 | };
47 | }
48 |
49 | export function bind({ Campus, Location }) {
50 | Campus.model.belongsTo(Location.model, {
51 | foreignKey: "LocationId",
52 | targetKey: "Id"
53 | });
54 | }
55 |
56 | export default {
57 | connect,
58 | bind
59 | };
60 |
--------------------------------------------------------------------------------
/src/rock/models/finances/util/__tests__/__snapshots__/language.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`should match expected codes 1`] = `
4 | Object {
5 | "100": "Transaction approved",
6 | "200": "We're sorry, your transaction was declined by our payment processer",
7 | "201": "It looks like this may be a fraudlent charge. Please contact your paymenr provider",
8 | "202": "We're sorry, your account has insufficient funds",
9 | "203": "We're sorry, this transaction is over your account limit",
10 | "204": "We're sorry, this transaction was not allowed",
11 | "220": "It looks like your payment information is incorrect",
12 | "221": "We couldn't find your dard issuer",
13 | "222": "Your card number is invalid",
14 | "223": "Your card has expired",
15 | "224": "Your expiration date is invalid",
16 | "225": "Your security code is invalid",
17 | "240": "Please call your account provider for more information as to why this transaction could not be processed",
18 | "250": "Please call our finance team",
19 | "251": "This card has been reported as lost",
20 | "252": "This card has been reported as stolen",
21 | "253": "This card is not valid",
22 | "260": "result-text",
23 | "400": "There was an error with our payment processor, you were not charged, please try again",
24 | "410": "We had an issue processing transactions, please try again",
25 | "411": "Looks like we are having connection issues with our processor, please try again later",
26 | "430": "This looks like a duplicate transaction, for your safety, we have declined the transaction. Please try again in five minutes",
27 | }
28 | `;
29 |
--------------------------------------------------------------------------------
/src/rock/models/finances/models/FinancialAccount.js:
--------------------------------------------------------------------------------
1 | import { merge, isUndefined } from "lodash";
2 | import { createGlobalId } from "../../../../util";
3 |
4 | import { FinancialAccount as FinancialAccountTable } from "../tables";
5 |
6 | import { Rock } from "../../system";
7 |
8 | export default class FinancialAccount extends Rock {
9 | __type = "FinancialAccount";
10 |
11 | async getFromId(id, globalId) {
12 | globalId = globalId || createGlobalId(id, this.__type);
13 | return this.cache.get(globalId, () =>
14 | FinancialAccountTable.findOne({ where: { Id: id } }).then(x => {
15 | // if this is a children fund, lets get the parent
16 | if (!x.ParentAccountId) return x;
17 |
18 | return FinancialAccountTable.findOne({
19 | where: { Id: x.ParentAccountId }
20 | });
21 | })
22 | );
23 | }
24 |
25 | async find(where, { all }) {
26 | for (const key in where) {
27 | if (isUndefined(where[key])) delete where[key];
28 | }
29 | // defaults
30 | where = merge(
31 | {
32 | ParentAccountId: null,
33 | PublicDescription: {
34 | $and: {
35 | $ne: "",
36 | $not: null
37 | }
38 | },
39 | IsTaxDeductible: true
40 | },
41 | where
42 | );
43 |
44 | if (all) {
45 | where = { ParentAccountId: null, IsTaxDeductible: true };
46 | }
47 |
48 | return await this.cache.get(this.cache.encode(where), () =>
49 | FinancialAccountTable.find({
50 | where,
51 | attributes: ["Id"],
52 | order: ["Order"]
53 | }).then(this.getFromIds.bind(this))
54 | );
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/routes/datadog.js:
--------------------------------------------------------------------------------
1 | import Metrics from "datadog-metrics";
2 |
3 | export default app => {
4 | let dogstatsd;
5 | if (process.env.DATADOG_API_KEY && process.env.NODE_ENV === "production") {
6 | dogstatsd = new Metrics.BufferedMetricsLogger({
7 | apiKey: process.env.DATADOG_API_KEY,
8 | appKey: process.env.DATADOG_APP_KEY,
9 | prefix: `heighliner.${process.env.SENTRY_ENVIRONMENT}.`,
10 | flushIntervalSeconds: 15
11 | });
12 |
13 | setInterval(() => {
14 | const memUsage = process.memoryUsage();
15 | dogstatsd.gauge("memory.rss", memUsage.rss);
16 | dogstatsd.gauge("memory.heapTotal", memUsage.heapTotal);
17 | dogstatsd.gauge("memory.heapUsed", memUsage.heapUsed);
18 | }, 5000);
19 | }
20 |
21 | // datadog
22 | if (dogstatsd) {
23 | app.use((req, res, next) => {
24 | if (!req._startTime) req._startTime = new Date();
25 | const end = res.end;
26 | res.end = (chunk, encoding) => {
27 | res.end = end;
28 | res.end(chunk, encoding);
29 | const baseUrl = req.baseUrl;
30 | const statTags = [`route:${baseUrl}${req.path}`];
31 |
32 | statTags.push(`method:${req.method.toLowerCase()}`);
33 | statTags.push(`protocol:${req.protocol}`);
34 | statTags.push(`path:${baseUrl}${req.path}`);
35 | statTags.push(`response_code:${res.statusCode}`);
36 |
37 | dogstatsd.increment(`response_code.${res.statusCode}`, 1, statTags);
38 | dogstatsd.increment("response_code.all", 1, statTags);
39 |
40 | const now = new Date() - req._startTime;
41 | dogstatsd.histogram("response_time", now, statTags);
42 | };
43 |
44 | next();
45 | });
46 | }
47 | return dogstatsd;
48 | };
49 |
--------------------------------------------------------------------------------
/src/rock/models/finances/mutations.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `syncTransactions(
3 | condition: String,
4 | transaction_type: String,
5 | action_type: String,
6 | transaction_id: String,
7 | order_id: String,
8 | last_name: String,
9 | email: String,
10 | start_date: String,
11 | end_date: String,
12 | personId: Int,
13 | gateway: String = "${process.env.NMI_GATEWAY}",
14 | ): [Transaction]`,
15 |
16 | `createOrder(
17 | data: String!
18 | id: ID
19 | instant: Boolean = false,
20 | gateway: String = "${process.env.NMI_GATEWAY}",
21 | url: String
22 | ): OrderMutationResponse`,
23 |
24 | `completeOrder(
25 | token: ID!
26 | scheduleId: ID
27 | platform: String
28 | accountName: String
29 | gateway: String = "${process.env.NMI_GATEWAY}",
30 | ): CompleteOrderMutationResponse`,
31 |
32 | `validate(
33 | token: ID!
34 | gateway: String = "${process.env.NMI_GATEWAY}",
35 | ): ValidateMutationResponse`,
36 |
37 | `cancelSavedPayment(
38 | id: ID
39 | entityId: Int
40 | gateway: String = "${process.env.NMI_GATEWAY}",
41 | ): SavePaymentMutationResponse`,
42 |
43 | `savePayment(
44 | token: ID!
45 | accountName: String
46 | gateway: String = "${process.env.NMI_GATEWAY}",
47 | ): SavePaymentMutationResponse`,
48 |
49 | `updateSavedPayment(
50 | entityId: Int
51 | name: String!
52 | ): SavePaymentMutationResponse`,
53 |
54 | `cancelSchedule(
55 | id: ID
56 | entityId: Int
57 | gateway: String = "${process.env.NMI_GATEWAY}",
58 | ): ScheduledTransactionMutationResponse`,
59 |
60 | `transactionStatement(
61 | limit: Int
62 | skip: Int
63 | people: [Int]
64 | start: String
65 | end: String
66 | ): StatementMutationResponse`
67 | ];
68 |
--------------------------------------------------------------------------------
/src/expression-engine/models/navigation/tables.js:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-shadowed-variable */
2 |
3 | import { INTEGER, STRING } from "sequelize";
4 |
5 | import { MySQLConnector } from "../../mysql";
6 |
7 | const naveeSchema = {
8 | navee_id: { type: INTEGER, primaryKey: true },
9 | navigation_id: { type: INTEGER },
10 | site_id: { type: INTEGER },
11 | entry_id: { type: INTEGER },
12 | channel_id: { type: INTEGER },
13 | parent: { type: INTEGER },
14 | text: { type: STRING },
15 | link: { type: STRING },
16 | sort: { type: INTEGER },
17 | custom: { type: STRING },
18 | type: { type: STRING }
19 | };
20 |
21 | const naveeNavSchema = {
22 | navigation_id: { type: INTEGER, primaryKey: true },
23 | site_id: { type: INTEGER },
24 | nav_title: { type: INTEGER },
25 | nav_name: { type: INTEGER }
26 | };
27 |
28 | let Navee;
29 | let NaveeNav;
30 | export { Navee, naveeSchema, NaveeNav, naveeNavSchema };
31 |
32 | export function connect() {
33 | Navee = new MySQLConnector("exp_navee", naveeSchema);
34 | NaveeNav = new MySQLConnector("exp_navee_navs", naveeNavSchema);
35 |
36 | return {
37 | Navee,
38 | NaveeNav
39 | };
40 | }
41 |
42 | export function bind({ Sites, Navee, NaveeNav }) {
43 | NaveeNav.model.hasOne(Navee.model, { foreignKey: "navigation_id" });
44 | Navee.model.belongsTo(NaveeNav.model, { foreignKey: "navigation_id" });
45 |
46 | NaveeNav.model.belongsTo(Sites.model, {
47 | foreignKey: "site_id",
48 | targetKey: "site_id"
49 | });
50 | Sites.model.hasOne(NaveeNav.model, { foreignKey: "site_id" });
51 |
52 | Navee.model.belongsTo(Sites.model, {
53 | foreignKey: "site_id",
54 | targetKey: "site_id"
55 | });
56 | Sites.model.hasOne(Navee.model, { foreignKey: "site_id" });
57 | }
58 |
59 | export default {
60 | connect,
61 | bind
62 | };
63 |
--------------------------------------------------------------------------------
/src/expression-engine/models/ee/matrix.js:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-shadowed-variable */
2 |
3 | import { INTEGER } from "sequelize";
4 |
5 | import { MySQLConnector, Tables } from "../../mysql";
6 |
7 | const matrixSchema = {
8 | row_id: { type: INTEGER, primaryKey: true },
9 | site_id: { type: INTEGER },
10 | entry_id: { type: INTEGER },
11 | field_id: { type: INTEGER }
12 | };
13 |
14 | const matrixColSchema = {
15 | col_id: { type: INTEGER, primaryKey: true },
16 | site_id: { type: INTEGER },
17 | field_id: { type: INTEGER }
18 | };
19 |
20 | let Matrix;
21 | let MatrixCol;
22 | export { Matrix, matrixSchema, MatrixCol, matrixColSchema };
23 |
24 | export function connect() {
25 | Matrix = new MySQLConnector("exp_matrix_data", matrixSchema);
26 | MatrixCol = new MySQLConnector("exp_matrix_cols", matrixColSchema);
27 |
28 | return {
29 | Matrix,
30 | MatrixCol
31 | };
32 | }
33 |
34 | export function bind({ ChannelData, Matrix, MatrixCol, AssetsSelections }) {
35 | // Matrix.model.belongsTo(ChannelData.model, { foreignKey: "entry_id" });
36 | // Matrix.model.belongsTo(AssetsSelections.model, { foreignKey: "row_id" });
37 |
38 | // MatrixCol.model.belongsTo(AssetsSelections.model, { foreignKey: "col_id" });
39 |
40 | // // get access to matrix from channel data
41 | ChannelData.model.hasMany(Matrix.model, { foreignKey: "entry_id" });
42 | Matrix.model.belongsTo(ChannelData.model, { foreignKey: "entry_id" });
43 |
44 | // make it possible to get files out of matrix
45 | Matrix.model.hasMany(AssetsSelections.model, { foreignKey: "row_id" });
46 | MatrixCol.model.hasOne(AssetsSelections.model, { foreignKey: "col_id" });
47 |
48 | AssetsSelections.model.belongsTo(Matrix.model, { foreignKey: "row_id" });
49 | AssetsSelections.model.belongsTo(MatrixCol.model, { foreignKey: "col_id" });
50 | }
51 |
52 | export default {
53 | connect,
54 | bind
55 | };
56 |
--------------------------------------------------------------------------------
/src/expression-engine/models/ee/model.js:
--------------------------------------------------------------------------------
1 | import { Heighliner } from "../../../util";
2 |
3 | export class EE extends Heighliner {
4 | __type = "RockSystem";
5 | id = "entry_id";
6 |
7 | getDate(day, month, year) {
8 | if (!day || !month || !year)
9 | throw new Error("Missing information from `getDate`");
10 | return `${new Date(Number(year), Number(month) - 1, Number(day))}`;
11 | }
12 |
13 | getDateFromUnix(timestamp) {
14 | return timestamp ? `${new Date(timestamp * 1000)}` : null;
15 | }
16 |
17 | contentImages(markup) {
18 | if (!markup) return [];
19 |
20 | const images = markup.match(/src=".*\.(jpg|jpeg|png)"/gim);
21 | if (!images) return [];
22 |
23 | return images
24 | .filter(x => x.slice(5, -1) !== "")
25 | .map(image => ({
26 | fileLabel: "inline",
27 | s3: image.slice(5, -1),
28 | url: image.slice(5, -1)
29 | }));
30 | }
31 |
32 | splitByNewLines(tags) {
33 | if (!tags) return [];
34 |
35 | return tags.replace("\\n", ",").split("\n");
36 | }
37 |
38 | getSeries(series) {
39 | if (!series) return false;
40 |
41 | // format: [dddd] [some-thing] Series Title
42 | // match[1]: series id
43 | // match[2]: series slug
44 | // match[3]: series name
45 | const seriesRegex = /\[(\d*)\] \[(.*)\] (.*)/g;
46 | return seriesRegex.exec(series);
47 | }
48 |
49 | cleanMarkup(markup) {
50 | if (!markup) return false;
51 |
52 | const parsed = markup.match(/src="{assets_\d*.*}"/gim);
53 | if (!parsed) return markup;
54 |
55 | // remove {assets_IDSTRING:} and make protocal relative
56 | markup = markup.replace(/{assets_\d*.*?}/gim, link => {
57 | link = link.trim().substring(0, link.length - 1);
58 | link = link.replace(/{assets_\d*:/gim, "");
59 | return link;
60 | });
61 |
62 | // make all links protocal relative
63 | return markup.replace(/https*:\/\//g, "//");
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Heighliner
2 | ==========
3 |
4 | [](https://newspring.cc)
6 | [](https://coveralls.io/github/NewSpring/Heighliner?branch=master) [](https://travis-ci.org/NewSpring/Heighliner) [](http://commitizen.github.io/cz-cli/)
7 |
8 |
9 | An Apollo GraphQL server for NewSpring Church
10 |
11 | ### Setup
12 |
13 | Install Docker
14 |
15 | ```
16 | brew cask install docker
17 | ```
18 |
19 | Start Docker app.
20 |
21 | 
22 |
23 | Navgate to the Heighliner directory and create container.
24 |
25 | ```
26 | docker-compose up
27 | ```
28 |
29 | Set up yarn
30 |
31 | ```
32 | yarn
33 | yarn build
34 | ```
35 |
36 | ### Running the project
37 |
38 | ```
39 | yarn start
40 | ```
41 |
--------------------------------------------------------------------------------
/src/rock/models/finances/models/FinancialBatch.js:
--------------------------------------------------------------------------------
1 | import uuid from "node-uuid";
2 | import moment from "moment";
3 |
4 | import { createGlobalId } from "../../../../util";
5 |
6 | import { FinancialBatch as FinancialBatchTable } from "../tables";
7 |
8 | import { Rock } from "../../system";
9 |
10 | export default class FinancialBatch extends Rock {
11 | __type = "FinancialBatch";
12 |
13 | async getFromId(id, globalId) {
14 | globalId = globalId || createGlobalId(id, this.__type);
15 | return this.cache.get(globalId, () =>
16 | FinancialBatchTable.findOne({ where: { Id: id } })
17 | );
18 | }
19 |
20 | async findOrCreate({
21 | prefix = "Online Giving",
22 | suffix = "",
23 | currencyType,
24 | date
25 | }) {
26 | let paymentType = "";
27 | if (currencyType) paymentType = currencyType;
28 |
29 | const batchName = `${prefix} ${paymentType} ${suffix}`.trim();
30 |
31 | const batch = await FinancialBatchTable.find({
32 | where: {
33 | Status: 1,
34 | BatchStartDateTime: { $lte: date },
35 | BatchEndDateTime: { $gt: date },
36 | Name: batchName
37 | }
38 | });
39 |
40 | if (batch.length) return batch[0];
41 |
42 | // 4pm => 12:00 am => 11:59 pm day before
43 | const BatchStartDateTime = moment(date)
44 | .startOf("day")
45 | // .subtract(1, "minute")
46 | .toISOString();
47 |
48 | // 4pm => 12:00 next day => 11:59 pm
49 | const BatchEndDateTime = moment(date)
50 | .endOf("day") // .subtract(1, "minute")
51 | .toISOString();
52 |
53 | const newBatch = {
54 | Guid: uuid.v4(),
55 | Name: batchName,
56 | Status: 1,
57 | ControlAmount: 0,
58 | // XXX this is actually stored on the payment gateway if we want to make
59 | // it a dynamic value
60 | BatchStartDateTime,
61 | BatchEndDateTime
62 | };
63 |
64 | const batchId = await FinancialBatchTable.post(newBatch);
65 | return FinancialBatchTable.findOne({ where: { Id: batchId } });
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/rock/models/likes/__tests__/resolver.spec.js:
--------------------------------------------------------------------------------
1 | import Resolver from "../resolver";
2 |
3 | const mockUser = { PrimaryAliasId: "12345" };
4 | const mockModels = {
5 | Like: {
6 | toggleLike: jest.fn(),
7 | getRecentlyLiked: jest.fn()
8 | },
9 | Node: {}
10 | };
11 |
12 | describe("Likes Mutation", () => {
13 | afterEach(() => {
14 | mockModels.Like.toggleLike.mockReset();
15 | });
16 |
17 | it("should return empty array with improper input", () => {
18 | const toggleLike = Resolver.Mutation.toggleLike;
19 |
20 | expect(() => {
21 | toggleLike(null, { nodeId: "1234" }, {});
22 | }).toThrow();
23 | });
24 |
25 | it("should call toggleLike with the correct args", () => {
26 | const toggleLike = Resolver.Mutation.toggleLike;
27 |
28 | const res = toggleLike(
29 | null,
30 | { nodeId: "1234" },
31 | { models: mockModels, person: mockUser }
32 | );
33 | expect(mockModels.Like.toggleLike).toHaveBeenCalledWith(
34 | "1234",
35 | "12345",
36 | {}
37 | );
38 | });
39 | });
40 |
41 | describe("getRecentLikes", () => {
42 | afterEach(() => {
43 | mockModels.Like.getRecentlyLiked.mockReset();
44 | });
45 |
46 | it("should pass falsy for user, cache, limit, skip when not defined", () => {
47 | const recentlyLiked = Resolver.Query.recentlyLiked;
48 | recentlyLiked(null, {}, { models: mockModels, user: null });
49 | expect(mockModels.Like.getRecentlyLiked).toBeCalledWith(
50 | { cache: undefined, limit: undefined, skip: undefined },
51 | null,
52 | {}
53 | );
54 | });
55 |
56 | it("should call getRecentlyLiked with proper args", () => {
57 | const recentlyLiked = Resolver.Query.recentlyLiked;
58 | recentlyLiked(
59 | null,
60 | { limit: 0, skip: 1, cache: false },
61 | { models: mockModels, user: { _id: "harambe" } }
62 | );
63 | expect(mockModels.Like.getRecentlyLiked).toBeCalledWith(
64 | { cache: false, limit: 0, skip: 1 },
65 | "harambe",
66 | {}
67 | );
68 | });
69 | });
70 |
--------------------------------------------------------------------------------
/src/rock/models/system/fieldTypeResolvers.js:
--------------------------------------------------------------------------------
1 | // import { pick } from "lodash";
2 | import moment from "moment";
3 |
4 | function castToBoolean(val) {
5 | if (val.toLowerCase() === "true") return true;
6 | return false;
7 | }
8 |
9 | // XXX there are currently 97 of class types we need to model
10 | export default {
11 | "Rock.Field.Types.TextFieldType": function(value, defaultValue) {
12 | if (!value && defaultValue) return defaultValue;
13 | return value;
14 | },
15 | "Rock.Field.Types.DateFieldType": function(value, defaultValue) {
16 | if (!value && defaultValue) return defaultValue;
17 | return moment(value).toString();
18 | },
19 | "Rock.Field.Types.SelectSingleFieldType": function(value, defaultValue) {
20 | if (!value && defaultValue) return defaultValue;
21 | return value;
22 | },
23 | "Rock.Field.Types.BooleanFieldType": function(value, defaultValue) {
24 | if (!value && defaultValue) return castToBoolean(defaultValue);
25 | return castToBoolean(value);
26 | },
27 | "Rock.Field.Types.ImageFieldType": function(value, defaultValue) {
28 | if (
29 | !this ||
30 | !this.models ||
31 | !this.models.BinaryFile ||
32 | (!value && !defaultValue)
33 | )
34 | return Promise.resolve({});
35 |
36 | if (!value && defaultValue)
37 | return this.models.BinaryFile.getFromGuid(defaultValue);
38 | return this.models.BinaryFile.getFromGuid(value);
39 | },
40 | "Rock.Field.Types.DecimalRangeFieldType": function(value, defaultValue) {
41 | if (!value && !defaultValue) return [];
42 | if (!value && defaultValue) value = defaultValue;
43 |
44 | const range = value.split(",");
45 | return range.map(x => Number(x));
46 | },
47 | "Rock.Field.Types.DefinedValueFieldType": function(value, defaultValue) {
48 | if (!this || !this.models || !this.models.Rock || (!value && !defaultValue))
49 | return Promise.resolve({});
50 |
51 | if (!value && defaultValue)
52 | return this.models.Rock.getDefinedValueByGuid(defaultValue);
53 | return this.models.Rock.getDefinedValueByGuid(value);
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/src/apollos/models/users/api.js:
--------------------------------------------------------------------------------
1 | import fetch from "isomorphic-fetch";
2 |
3 | const baseURL = `${process.env.ROCK_URL}api`;
4 | const CONFIG = {
5 | headers: {
6 | "Authorization-Token": process.env.ROCK_TOKEN,
7 | "Content-Type": "application/json"
8 | }
9 | };
10 |
11 | async function statusResponseResolver(r = {}) {
12 | if (r.status === 204) return true;
13 | if (r.status >= 200 && r.status < 300) {
14 | try {
15 | // return await to catch before returning
16 | return await r.json();
17 | } catch (err) {
18 | // Response is not a JSON object
19 | return r;
20 | }
21 | }
22 | throw new Error(r.statusText);
23 | }
24 |
25 | export function get(url, config = {}) {
26 | return fetch(`${baseURL}${url}`, {
27 | method: "GET",
28 | ...CONFIG,
29 | ...config
30 | }).then(statusResponseResolver);
31 | }
32 |
33 | export function del(url, config = {}) {
34 | return fetch(`${baseURL}${url}`, {
35 | method: "DELETE",
36 | ...CONFIG,
37 | ...config
38 | }).then(statusResponseResolver);
39 | }
40 |
41 | export function head(url, config = {}) {
42 | return fetch(`${baseURL}${url}`, {
43 | method: "HEAD",
44 | ...CONFIG,
45 | ...config
46 | }).then(statusResponseResolver);
47 | }
48 |
49 | export function options(url, config = {}) {
50 | return fetch(`${baseURL}${url}`, {
51 | method: "OPTIONS",
52 | ...CONFIG,
53 | ...config
54 | }).then(statusResponseResolver);
55 | }
56 |
57 | export function post(url, data = {}, config = {}) {
58 | return fetch(`${baseURL}${url}`, {
59 | method: "POST",
60 | ...CONFIG,
61 | body: JSON.stringify(data),
62 | ...config
63 | }).then(statusResponseResolver);
64 | }
65 |
66 | export function put(url, data = {}, config = {}) {
67 | return fetch(`${baseURL}${url}`, {
68 | method: "PUT",
69 | ...CONFIG,
70 | body: JSON.stringify(data),
71 | ...config
72 | }).then(statusResponseResolver);
73 | }
74 |
75 | export function patch(url, data = {}, config = {}) {
76 | return fetch(`${baseURL}${url}`, {
77 | method: "PATCH",
78 | ...CONFIG,
79 | body: JSON.stringify(data),
80 | ...config
81 | }).then(statusResponseResolver);
82 | }
83 |
--------------------------------------------------------------------------------
/src/google-site-search/models/search/resolver.js:
--------------------------------------------------------------------------------
1 | function getTag(tagName, { pagemap }) {
2 | if (!pagemap || !pagemap.metatags) return null;
3 | return pagemap.metatags[0][tagName];
4 | }
5 |
6 | export default {
7 | Query: {
8 | search(_, { query, first, after, site }, { models }) {
9 | if (first > 10) first = 10;
10 | // adjust after to work with start
11 | after += 1;
12 |
13 | const fields =
14 | "fields=queries(nextPage/startIndex,previousPage/startIndex),searchInformation/totalResults,items(cacheId,title,htmlTitle,link,displayLink,snippet,htmlSnippet,pagemap(cse_image/src,metatags/og:url,metatags/article:section))"; // tslint:disable-line
15 |
16 | query += `&num=${first}&start=${after}&${fields}`;
17 |
18 | if (site) {
19 | query += `&=${site}`;
20 | }
21 |
22 | return models.SSearch.query(query).then(x => {
23 | let next, previous;
24 | if (x.queries) {
25 | next = x.queries.nextPage ? x.queries.nextPage[0].startIndex : 0;
26 | previous = x.queries.previousPage
27 | ? x.queries.previousPage[0].startIndex
28 | : 0;
29 | } else {
30 | next = 0;
31 | previous = 0;
32 | }
33 |
34 | return {
35 | total: Number(x.searchInformation.totalResults),
36 | next: Number(next),
37 | previous: Number(previous),
38 | items: x.items ? x.items : []
39 | };
40 | });
41 | }
42 | },
43 |
44 | SSSearchResult: {
45 | id: ({ cacheId }) => cacheId,
46 | title: ({ title }) => title.split("|")[0].trim(),
47 | htmlTitle: ({ htmlTitle }) => htmlTitle.split("|")[0].trim(),
48 | link: ({ link }) => link,
49 | displayLink: ({ displayLink }) => displayLink,
50 | description: ({ snippet }) => snippet,
51 | htmlDescription: ({ htmlSnippet }) => htmlSnippet,
52 | type: data => getTag("og:type", data),
53 | section: data => getTag("article:section", data),
54 | image: ({ pagemap }) => pagemap && pagemap.cse_image[0].src
55 | },
56 |
57 | SSSearch: {
58 | total: ({ total }) => total,
59 | next: ({ next }) => next,
60 | previous: ({ previous }) => previous,
61 | items: ({ items }) => items
62 | }
63 | };
64 |
--------------------------------------------------------------------------------
/src/apollos/__tests__/mongo.spec.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | import { MongoConnector, connect } from "../mongo";
4 |
5 | describe("connect", () => {
6 | it("'connect' should allow for a connection status to be returned", async () => {
7 | const originalConnect = mongoose.connect;
8 | mongoose.connect = jest.fn((url, opts, cb) => cb(new Error()));
9 | const status = await connect();
10 | expect(status).toBeDefined();
11 | expect(status).not.toBeNull();
12 | mongoose.connect = originalConnect;
13 | });
14 | });
15 |
16 | describe("MongoConnector", () => {
17 | let testModel;
18 | let originalConnect;
19 | beforeEach(async () => {
20 | if (testModel) return;
21 | originalConnect = mongoose.connect;
22 | mongoose.connect = jest.fn((url, opts, cb) => {
23 | cb(null);
24 | return {};
25 | });
26 | await connect();
27 | const schema = { _id: String };
28 | testModel = new MongoConnector("test", schema);
29 | });
30 |
31 | afterEach(() => {
32 | mongoose.connect = originalConnect;
33 | });
34 |
35 | it(" should expose the db connection", () => {
36 | expect(testModel.db).toBeTruthy();
37 | });
38 |
39 | it("should create a pluralized collection based off the model name", () => {
40 | expect(testModel.model.collection.collectionName).toEqual("tests");
41 | });
42 |
43 | it("should create a model based on the name passed", () => {
44 | expect(testModel.model.modelName).toEqual("test");
45 | });
46 |
47 | it("should use the schema passed as the second argument", () => {
48 | const { schema } = testModel.model;
49 |
50 | // expect(schema instanceof Schema).toBeTruthy();
51 | expect(schema.paths._id).toBeTruthy();
52 | expect(schema.paths._id.instance).toEqual("String");
53 | });
54 |
55 | it("findOne should be a sugared passthrough to the models findOne", async () => {
56 | const oldFindOne = testModel.model.findOne;
57 |
58 | testModel.model.findOne = function mockedFindOne(...args) {
59 | expect(args[0]).toEqual("test");
60 | return new Promise(r => r([1]));
61 | };
62 |
63 | const tests = await testModel.findOne("test");
64 | expect(tests[0]).toEqual(1);
65 |
66 | testModel.model.findOne = oldFindOne;
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/src/expression-engine/models/content/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | type LiveFeed {
4 | live: Boolean!
5 | fuse: Boolean! @deprecated(reason: "This no longer determines the live status")
6 | embedCode: String
7 | embedUrl: String
8 | videoUrl: String
9 | }
10 |
11 | # should this be a global type that implements Node?
12 | # XXX abstract from content if ^^
13 | type ContentColor {
14 | id: ID
15 | value: String!
16 | description: String
17 | }
18 |
19 | type ContentScripture {
20 | book: String
21 | passage: String
22 | }
23 |
24 | type ContentData {
25 | body: String
26 | description: String
27 | ooyalaId: String @deprecated(reason: "Use video instead")
28 | video: ContentVideo
29 | wistiaId: String
30 | speaker: String
31 | isLight: Boolean
32 | hashtag: String
33 |
34 | tags: [String] # XXX create global tag type
35 | colors: [ContentColor]
36 | images(sizes: [String], ratios: [String]): [File]
37 |
38 | # deprecated (use audio field)
39 | tracks: [File]
40 | audio: [File]
41 | scripture: [ContentScripture]
42 | isLiked: Boolean
43 | }
44 |
45 | type ContentVideo {
46 | id: String
47 | embedUrl: String
48 | videoUrl: String
49 | }
50 |
51 | type ContentMeta {
52 | site: ID
53 | channel: ID
54 | series: ID
55 | urlTitle: String
56 | summary: String
57 |
58 | date: String
59 | entryDate: String
60 | startDate: String
61 | endDate: String
62 |
63 | # XXX should this be named better?
64 | actualDate: String
65 |
66 | # deprecated
67 | siteId: ID
68 | channelId: ID
69 | }
70 |
71 | type Content implements Node {
72 | id: ID!
73 | title: String!
74 | status: String!
75 | channel: ID!
76 | channelName: String
77 | campus: Campus
78 | meta: ContentMeta
79 | content: ContentData
80 | authors: [String]
81 | parent: Content # XXX determine if this can be multiple
82 | children(channels: [String], showFutureEntries: Boolean = false): [Content]
83 | related(
84 | includeChannels: [String],
85 | limit: Int = 20,
86 | skip: Int = 0,
87 | cache: Boolean = true
88 | ): [Content]
89 |
90 | # deprecated (moved to other types)
91 | tracks: [File]
92 | seriesId: ID
93 | }
94 | `
95 | ];
96 |
--------------------------------------------------------------------------------
/src/rock/models/binary-files/model.js:
--------------------------------------------------------------------------------
1 | // import { merge } from "lodash";
2 | import isEmpty from "lodash/isEmpty";
3 | import { defaultCache } from "../../../util/cache";
4 | import { createGlobalId } from "../../../util";
5 |
6 | import {
7 | BinaryFile as BinaryFileTable
8 | // Location as LocationTable, // XXX move to its own model
9 | } from "./tables";
10 |
11 | import { Rock } from "../system/model";
12 | import * as api from "../../../apollos/models/users/api";
13 |
14 | export class BinaryFile extends Rock {
15 | __type = "BinaryFile";
16 |
17 | constructor({ cache } = { cache: defaultCache }) {
18 | super({ cache });
19 | this.cache = cache;
20 | }
21 |
22 | processFile(file) {
23 | // is relative path to Rock
24 | if (file.Path && file.Path[0] === "~") {
25 | file.Path = file.Path.substr(2);
26 | file.Path = this.baseUrl + file.Path;
27 | }
28 |
29 | // remove query string variables
30 | if (file.Path && file.Path.indexOf("?") > -1) {
31 | file.Path = file.Path.substr(0, file.Path.indexOf("?"));
32 | }
33 |
34 | return file;
35 | }
36 |
37 | async getFromId(id, globalId) {
38 | globalId = globalId || createGlobalId(`${id}`, this.__type);
39 | return this.cache.get(globalId, () =>
40 | BinaryFileTable.findOne({ where: { Id: id } }).then(this.processFile)
41 | );
42 | }
43 |
44 | async getFromGuid(Guid) {
45 | return this.cache.get(`${Guid}:BinaryFileGuid`, () =>
46 | BinaryFileTable.findOne({
47 | where: { Guid }
48 | }).then(this.processFile)
49 | );
50 | }
51 |
52 | // async getFromPerson
53 | async find(query) {
54 | return this.cache.get(this.cache.encode(query), () =>
55 | BinaryFileTable.find({
56 | where: query,
57 | attributes: ["Id"]
58 | }).then(this.getFromIds.bind(this))
59 | );
60 | }
61 |
62 | async attachPhotoIdToUser({ personId, previousPhotoId, newPhotoId } = {}) {
63 | try {
64 | await api.patch(`/People/${personId}`, {
65 | PhotoId: newPhotoId
66 | });
67 | if (!isEmpty(previousPhotoId)) {
68 | try {
69 | await api.delete(`/BinaryFiles/${previousPhotoId}`);
70 | } catch (e) {} // eslint-disable-line
71 | }
72 | } catch (err) {
73 | throw err;
74 | }
75 | }
76 | }
77 |
78 | export default {
79 | BinaryFile
80 | };
81 |
--------------------------------------------------------------------------------
/src/apollos/models/users/__tests__/model.spec.js:
--------------------------------------------------------------------------------
1 | import crypto from "crypto";
2 | import { User } from "../model";
3 |
4 | it("should expose the model", () => {
5 | const users = new User();
6 | expect(users.model).toBeTruthy();
7 | });
8 |
9 | it("`getByHashedToken` should allow searching for a raw token", async () => {
10 | const token = "testToken";
11 | const users = new User();
12 | users.model.findOne = function mockedFindOne(mongoQuery) {
13 | const matches = mongoQuery.$or;
14 |
15 | for (const match of matches) {
16 | const tok = match["services.resume.loginTokens.hashedToken"];
17 | if (tok !== token) continue;
18 |
19 | expect(tok).toEqual(token);
20 | return Promise.resolve({});
21 | }
22 |
23 | // we should never get here
24 | throw new Error();
25 | };
26 |
27 | return await users.getByHashedToken(token);
28 | });
29 |
30 | it("`getByHashedToken` should allow searching for an encrypted token", async () => {
31 | const token = "testToken";
32 | const users = new User();
33 | const encyptedToken = crypto
34 | .createHash("sha256")
35 | .update(token)
36 | .digest("base64");
37 |
38 | users.model.findOne = function mockedFindOne(mongoQuery) {
39 | const matches = mongoQuery.$or;
40 | for (const match of matches) {
41 | const tok = match["services.resume.loginTokens.hashedToken"];
42 | if (tok !== encyptedToken) continue;
43 |
44 | expect(tok).toEqual(encyptedToken);
45 | return Promise.resolve({});
46 | }
47 |
48 | // we should never get here
49 | throw new Error();
50 | };
51 |
52 | return await users.getByHashedToken(token);
53 | });
54 |
55 | xit("`getFromId` should allow searching by an id", async () => {
56 | const id = "id";
57 | const users = new User();
58 | users.model.findOne = function mockedFindOne({ _id }) {
59 | expect(_id).toEqual(id);
60 | return Promise.resolve({});
61 | };
62 |
63 | return await users.getFromId(id, null);
64 | });
65 |
66 | it("`getFromId` should try and read the data from the cache using the globalId", async () => {
67 | const id = "id";
68 | const globalId = "foo";
69 |
70 | const cache = {
71 | get(global) {
72 | expect(globalId).toEqual(global);
73 | return Promise.resolve();
74 | }
75 | };
76 |
77 | const tempUsers = new User({ cache });
78 |
79 | return await tempUsers.getFromId(id, globalId);
80 | });
81 |
--------------------------------------------------------------------------------
/src/rock/models/finances/util/__tests__/statement.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import renderer from "react-test-renderer";
3 | import { formatMoney, Statement, generatePDF } from "../statement";
4 | import ReactDOMServer from "react-dom/server";
5 |
6 | describe("formatMoney", () => {
7 | it("should render 2 decimal places", () => {
8 | expect(formatMoney(1)).toEqual("$1.00");
9 | });
10 |
11 | it("should format with commas", () => {
12 | expect(formatMoney(1234)).toEqual("$1,234.00");
13 | expect(formatMoney(1234566)).toEqual("$1,234,566.00");
14 | });
15 |
16 | it("should allow non-whole numbers", () => {
17 | expect(formatMoney(1234.6)).toEqual("$1,234.60");
18 | expect(formatMoney(0.65)).toEqual("$0.65");
19 | });
20 | });
21 |
22 | describe("Statement", () => {
23 | const person = { FirstName: "", LastName: "", nickName: "" };
24 | const home = {
25 | Street1: "3400 Vine St",
26 | Street2: "",
27 | City: "Cincinnati",
28 | State: "OH",
29 | PostalCode: "45220"
30 | };
31 | const transactions = [
32 | { Name: "Admission", Date: "2016-01-01", Amount: 15 },
33 | { Name: "Admission", Date: "2016-02-01", Amount: 15 }
34 | ];
35 | const total = 30;
36 |
37 | it("should render with data", () => {
38 | const component = renderer.create(
39 |
45 | );
46 | expect(component).toMatchSnapshot();
47 | });
48 | });
49 |
50 | describe("generatePDF", () => {
51 | xit("should call pdf.create with markup and settings", async () => {
52 | jest.mock("react-dom/server");
53 | ReactDOMServer.renderToStaticMarkup = jest.fn(c => c);
54 | const results = await generatePDF("Hello
");
55 |
56 | // XXX can't properly snapshot the results, because of a creationDate
57 | // being injected by pdf.create (which we don't have time to properly mock)
58 | // can use Buffer(results, "base64").toString() to print
59 | expect(results).toBeDefined();
60 | });
61 |
62 | xit("should fail with no component to render", async () => {
63 | jest.mock("react-dom/server");
64 | ReactDOMServer.renderToStaticMarkup = jest.fn(c => c);
65 | return generatePDF().then(
66 | res => {
67 | throw new Error("generatePDF didn't fail");
68 | },
69 | res => {
70 | expect(res).toBeDefined();
71 | }
72 | );
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/src/util/node/__test__/model.spec.js:
--------------------------------------------------------------------------------
1 | import casual from "casual";
2 | import Node, { createGlobalId, parseGlobalId } from "../model";
3 |
4 | it("`createGlobalId` should take two arguments and return a string", () => {
5 | const id = casual.word;
6 | const type = casual.word;
7 |
8 | expect(typeof createGlobalId(id, type)).toEqual("string");
9 | });
10 |
11 | it("`createGlobalId` should be decodeable by `parseGlobalId`", () => {
12 | const id = casual.word;
13 | const __type = casual.word;
14 | const globalId = createGlobalId(id, __type);
15 |
16 | expect(parseGlobalId(globalId)).toEqual({ __type, id });
17 | });
18 |
19 | it("`parseGlobalId` should take a global id and return the type and id", () => {
20 | const id = casual.word;
21 | const __type = casual.word;
22 | const globalId = createGlobalId(id, __type);
23 |
24 | expect(parseGlobalId(globalId)).toEqual({ __type, id });
25 | });
26 |
27 | it("Node class should parse an encoded id to get the type to resolve", async () => {
28 | const id = casual.word;
29 | const __type = "Test";
30 | const globalId = createGlobalId(id, __type);
31 |
32 | const context = {
33 | models: {
34 | Test: {
35 | getFromId(_id) {
36 | expect(_id).toEqual(id);
37 | return {};
38 | }
39 | }
40 | }
41 | };
42 |
43 | const node = new Node(context);
44 | node.get(globalId);
45 | });
46 |
47 | it("Node class should return data from the models `getFromId` method", async () => {
48 | const id = casual.word;
49 | const __type = "Test";
50 | const globalId = createGlobalId(id, __type);
51 | const data = { test: casual.word };
52 |
53 | const context = {
54 | models: {
55 | Test: {
56 | getFromId(_id) {
57 | return Promise.resolve(data);
58 | }
59 | }
60 | }
61 | };
62 |
63 | const node = new Node(context);
64 | const result = await node.get(globalId);
65 |
66 | expect(result.test).toEqual(data.test);
67 | });
68 |
69 | it("Node class should attach the __type to the resulting data", async () => {
70 | const id = casual.word;
71 | const __type = "Test";
72 | const globalId = createGlobalId(id, __type);
73 | const data = { test: casual.word };
74 |
75 | const context = {
76 | models: {
77 | Test: {
78 | getFromId(_id) {
79 | return Promise.resolve(data);
80 | }
81 | }
82 | }
83 | };
84 |
85 | const node = new Node(context);
86 | const result = await node.get(globalId);
87 |
88 | expect(result.__type).toEqual(__type);
89 | });
90 |
--------------------------------------------------------------------------------
/src/rock/models/finances/util/nmi.js:
--------------------------------------------------------------------------------
1 | import { Builder, parseString } from "xml2js";
2 | import fetch from "isomorphic-fetch";
3 | import { timeout, TimeoutError } from "promise-timeout";
4 |
5 | import ErrorCodes from "./language";
6 |
7 | export default (payload, gateway) => {
8 | if (!gateway) throw new Error("must be called with NMI Gateway object");
9 |
10 | const builder = new Builder();
11 | const xml = builder.buildObject(payload);
12 |
13 | return timeout(
14 | fetch(gateway.APIUrl, {
15 | method: "POST",
16 | body: `${xml}`,
17 | headers: { "Content-Type": "text/xml" }
18 | })
19 | .then(response => response.text())
20 | .then(result => {
21 | let data = result;
22 | // clean all tags to make sure they are parseable
23 | const matches = data.match(/<([^>]+)>/gim);
24 | for (const match of matches) {
25 | if (match.indexOf(",") > -1) {
26 | const matchRegex = new RegExp(match, "gmi");
27 | data = data.replace(matchRegex, match.replace(/,/gim, ""));
28 | }
29 | }
30 |
31 | return data;
32 | })
33 | .then(
34 | x =>
35 | new Promise((s, f) => {
36 | parseString(
37 | x,
38 | { trim: true, explicitArray: false, mergeAttrs: true },
39 | (err, result) => {
40 | if (err) f(err);
41 | if (!err) s(result);
42 | }
43 | );
44 | })
45 | )
46 | .then(({ response }) => {
47 | const data = response;
48 |
49 | if (data["result-code"] === "100") return data;
50 | let number = Number(data["result-code"]);
51 |
52 | let err;
53 |
54 | // special mapping to ensure duplicates
55 | if (
56 | data["result-text"] &&
57 | data["result-text"].indexOf("Duplicate") > -1
58 | ) {
59 | number = 430;
60 | }
61 |
62 | if (ErrorCodes[number] && ErrorCodes[number] !== "result-text") {
63 | err = ErrorCodes[number];
64 | } else {
65 | err = data["result-text"];
66 | }
67 | const error = new Error(err);
68 | error.code = number;
69 | throw error;
70 | }),
71 | 60000
72 | ).catch(err => {
73 | if (err instanceof TimeoutError) {
74 | throw new Error(`
75 | The request to our payment process took longer than expected.
76 | For your safety we have cancelled this action.
77 | You were not charged and should be able to try again!
78 | `);
79 | }
80 |
81 | throw err;
82 | });
83 | };
84 |
--------------------------------------------------------------------------------
/src/expression-engine/models/ee/__tests__/model.spec.js:
--------------------------------------------------------------------------------
1 | import { EE } from "../model";
2 |
3 | it("`cleanMarkup` should exist", () => {
4 | const ee = new EE();
5 | expect(ee.cleanMarkup).toBeTruthy();
6 | });
7 |
8 | it("`cleanMarkup` should be a function", () => {
9 | const ee = new EE();
10 | expect(typeof ee.cleanMarkup).toBe("function");
11 | });
12 |
13 | it("`cleanMarkup` should return false if no markup", () => {
14 | const ee = new EE();
15 | const result = ee.cleanMarkup(null);
16 | expect(result).toBe(false);
17 | });
18 |
19 | it("`cleanMarkup` should return input if nothing to parse", () => {
20 | const ee = new EE();
21 | const result = ee.cleanMarkup("test");
22 | expect(result).toBe("test");
23 | });
24 |
25 | it("`cleanMarkup` should remove simple asset tag", () => {
26 | const ee = new EE();
27 | const testImage = "//test.com/test.jpg";
28 | const testAsset = `{assets_40016:${testImage}}`;
29 | const testMarkup = `
`;
30 | const result = ee.cleanMarkup(testMarkup);
31 | expect(result).toBe(`
`);
32 | });
33 |
34 | it("`cleanMarkup` should remove https", () => {
35 | const ee = new EE();
36 | const testImage = "//test.com/test.jpg";
37 | const testAsset = `{assets_40016:https:${testImage}}`;
38 | const testMarkup = `
`;
39 | const result = ee.cleanMarkup(testMarkup);
40 | expect(result).toBe(`
`);
41 | });
42 |
43 | it("`cleanMarkup` should remove multiple asset tags", () => {
44 | const ee = new EE();
45 | const testImage1 = "//test.com/test.jpg";
46 | const testImage2 = "//test.com/test2.jpg";
47 | const testAsset1 = `{assets_40016:https:${testImage1}}`;
48 | const testAsset2 = `{assets_40017:https:${testImage2}}`;
49 | const testMarkup = `
50 |
51 |
52 |
53 |
54 | `;
55 | const result = ee.cleanMarkup(testMarkup);
56 | expect(result).toBe(`
57 |
58 |
59 |
60 |
61 | `);
62 | });
63 |
64 | it("`cleanMarkup` should remove multiple asset tags on the same line", () => {
65 | const ee = new EE();
66 | const testImage1 = "//test.com/test.jpg";
67 | const testImage2 = "//test.com/test2.jpg";
68 | const testAsset1 = `{assets_40016:https:${testImage1}}`;
69 | const testAsset2 = `{assets_40017:https:${testImage2}}`;
70 | const testMarkup = `
71 | 

72 | `;
73 | const result = ee.cleanMarkup(testMarkup);
74 | expect(result).toBe(`
75 | 

76 | `);
77 | });
78 |
--------------------------------------------------------------------------------
/src/expression-engine/models/files/tables.js:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-shadowed-variable */
2 |
3 | import { INTEGER, STRING } from "sequelize";
4 |
5 | import { MySQLConnector } from "../../mysql";
6 |
7 | const assetsFilesSchema = {
8 | file_id: { type: INTEGER, primaryKey: true },
9 | folder_id: { type: INTEGER },
10 | source_type: { type: STRING },
11 | source_id: { type: INTEGER },
12 | file_name: { type: STRING }
13 | };
14 |
15 | const assetsSelectionSchema = {
16 | file_id: { type: INTEGER },
17 | entry_id: { type: INTEGER },
18 | field_id: { type: INTEGER },
19 | col_id: { type: INTEGER },
20 | row_id: { type: INTEGER },
21 | var_id: { type: INTEGER }
22 | };
23 |
24 | const assetsFoldersSchema = {
25 | folder_id: { type: INTEGER, primaryKey: true },
26 | source_id: { type: INTEGER },
27 | parent_id: { type: INTEGER },
28 | full_path: { type: STRING }
29 | };
30 |
31 | const assetsSourcesSchema = {
32 | source_id: { type: INTEGER, primaryKey: true },
33 | settings: { type: STRING }
34 | };
35 |
36 | let Assets;
37 | let AssetsSelections;
38 | let AssetsFolders;
39 | let AssetsSources;
40 | export {
41 | Assets,
42 | assetsFilesSchema,
43 | AssetsSelections,
44 | assetsSelectionSchema,
45 | AssetsFolders,
46 | assetsFoldersSchema,
47 | AssetsSources,
48 | assetsSourcesSchema
49 | };
50 |
51 | export function connect() {
52 | Assets = new MySQLConnector("exp_assets_files", assetsFilesSchema);
53 | AssetsSelections = new MySQLConnector(
54 | "exp_assets_selections",
55 | assetsSelectionSchema
56 | );
57 | AssetsFolders = new MySQLConnector("exp_assets_folders", assetsFoldersSchema);
58 | AssetsSources = new MySQLConnector("exp_assets_sources", assetsSourcesSchema);
59 |
60 | // no primary key
61 | AssetsSelections.model.removeAttribute("id");
62 |
63 | return {
64 | Assets,
65 | AssetsSelections,
66 | AssetsFolders,
67 | AssetsSources
68 | };
69 | }
70 |
71 | export function bind({
72 | ChannelData,
73 | Assets,
74 | AssetsSelections,
75 | AssetsFolders,
76 | AssetsSources
77 | }) {
78 | Assets.model.hasMany(AssetsSelections.model, { foreignKey: "file_id" });
79 | AssetsSelections.model.belongsTo(Assets.model, { foreignKey: "file_id" });
80 |
81 | Assets.model.belongsTo(AssetsSources.model, { foreignKey: "source_id" });
82 | AssetsSources.model.hasOne(Assets.model, { foreignKey: "source_id" });
83 |
84 | Assets.model.belongsTo(AssetsFolders.model, { foreignKey: "folder_id" });
85 | AssetsFolders.model.hasOne(Assets.model, { foreignKey: "folder_id" });
86 |
87 | // get access to assets from channel data
88 | ChannelData.model.hasOne(AssetsSelections.model, { foreignKey: "entry_id" });
89 | }
90 |
91 | export default {
92 | connect,
93 | bind
94 | };
95 |
--------------------------------------------------------------------------------
/src/rock/models/feeds/resolver.js:
--------------------------------------------------------------------------------
1 | import { flatten } from "lodash";
2 |
3 | export default {
4 | Query: {
5 | userFeed: async (
6 | _,
7 | { filters, limit, skip, status, cache },
8 | { models, person, user }
9 | ) => {
10 | if (!filters) return null;
11 |
12 | const filterQueries = [];
13 |
14 | // Home feed query
15 | if (filters.includes("CONTENT")) {
16 | const topics = await models.User.getUserFollowingTopics(
17 | person && person.PrimaryAliasId
18 | );
19 |
20 | const channels = topics
21 | .map(x => x.toLowerCase())
22 | .map(x => {
23 | if (x === "series") return ["series_newspring"];
24 | if (x === "music") return ["newspring_albums"];
25 | if (x === "devotionals") return ["study_entries", "devotionals"];
26 | if (x === "events") return ["newspring_now"];
27 | return [x];
28 | })
29 | .map(flatten);
30 |
31 | const showsNews = flatten(channels).includes("news");
32 |
33 | // get user's campus to filter news by
34 | const userCampus =
35 | person && showsNews
36 | ? await models.Person.getCampusFromId(person.Id, { cache })
37 | : null;
38 |
39 | const content = await models.Content.findByCampusName(
40 | {
41 | channel_name: { $or: channels },
42 | offset: skip,
43 | limit,
44 | status
45 | },
46 | userCampus ? userCampus.Name : null,
47 | true
48 | );
49 |
50 | filterQueries.push(content);
51 | }
52 |
53 | if (filters.includes("GIVING_DASHBOARD") && person) {
54 | filterQueries.push(
55 | models.Transaction.findByPersonAlias(
56 | person.aliases,
57 | { limit: 3, offset: 0 },
58 | { cache: null }
59 | )
60 | );
61 |
62 | filterQueries.push(
63 | models.SavedPayment.findExpiringByPersonAlias(
64 | person.aliases,
65 | { limit: 3, offset: 0 },
66 | { cache: null }
67 | )
68 | );
69 | }
70 |
71 | if (filters.includes("LIKES") && user) {
72 | const likedContent = await models.Like.getLikedContent(
73 | person.PrimaryAliasId,
74 | models.Node
75 | );
76 | const reversed = Array.isArray(likedContent)
77 | ? likedContent.reverse()
78 | : likedContent;
79 | filterQueries.push(reversed);
80 | }
81 |
82 | if (!filterQueries.length) return null;
83 |
84 | return Promise.all(filterQueries)
85 | .then(flatten)
86 | .then(x => x.filter(y => Boolean(y)));
87 | }
88 | }
89 | };
90 |
--------------------------------------------------------------------------------
/src/rock/models/finances/util/__tests__/formatTransaction.js:
--------------------------------------------------------------------------------
1 | import format from "../formatTransaction";
2 |
3 | import {
4 | singleTransaction,
5 | singleACHTransaction,
6 | scheduleTransaction,
7 | multiFundScheduleTransaction,
8 | multipleTransactions
9 | } from "../__mocks__/sample-response";
10 |
11 | jest.mock("moment", () => date => ({
12 | toISOString: () => `Mocked ISODate: ${date}`,
13 | subtract: (number, size) => `Mocked subtract ${number}, ${size}`
14 | }));
15 |
16 | jest.mock("node-uuid", () => ({
17 | v4: jest.fn(() => "guid")
18 | }));
19 |
20 | it("handles standard response data", () => {
21 | expect(
22 | format(
23 | {
24 | response: singleTransaction
25 | },
26 | { Id: 3 }
27 | )
28 | ).toMatchSnapshot();
29 | });
30 |
31 | it("handles multipleTransactions", () => {
32 | expect(
33 | format(
34 | {
35 | response: multipleTransactions
36 | },
37 | { Id: 3 }
38 | )
39 | ).toMatchSnapshot();
40 | });
41 |
42 | it("handles an authenticated response", () => {
43 | expect(
44 | format(
45 | {
46 | response: singleTransaction,
47 | person: { Id: 1, PrimaryAliasId: 2 }
48 | },
49 | { Id: 3 }
50 | )
51 | ).toMatchSnapshot();
52 | });
53 |
54 | it("handles adding a saved account", () => {
55 | expect(
56 | format(
57 | {
58 | response: singleTransaction,
59 | person: { Id: 1, PrimaryAliasId: 2 },
60 | accountName: "My Credit Card"
61 | },
62 | { Id: 3 }
63 | )
64 | ).toMatchSnapshot();
65 | });
66 |
67 | it("maps the source type value", () => {
68 | expect(
69 | format(
70 | {
71 | response: singleTransaction,
72 | origin: "http://example.com/give"
73 | },
74 | { Id: 3 }
75 | )
76 | ).toMatchSnapshot();
77 | });
78 |
79 | it("sets the schedule Id to be recovered", () => {
80 | expect(
81 | format(
82 | {
83 | response: singleTransaction,
84 | scheduleId: 10
85 | },
86 | { Id: 3 }
87 | )
88 | ).toMatchSnapshot();
89 | });
90 |
91 | it("handles an ACH transaction", () => {
92 | expect(
93 | format(
94 | {
95 | response: singleACHTransaction
96 | },
97 | { Id: 3 }
98 | )
99 | ).toMatchSnapshot();
100 | });
101 |
102 | it("handles a schedule", () => {
103 | expect(
104 | format(
105 | {
106 | response: scheduleTransaction,
107 | scheduleId: 10
108 | },
109 | { Id: 3 }
110 | )
111 | ).toMatchSnapshot();
112 | });
113 |
114 | it("handles a schedule with multipleTransactions", () => {
115 | expect(
116 | format(
117 | {
118 | response: multiFundScheduleTransaction
119 | },
120 | { Id: 3 }
121 | )
122 | ).toMatchSnapshot();
123 | });
124 |
--------------------------------------------------------------------------------
/src/rock/models/campuses/resolver.js:
--------------------------------------------------------------------------------
1 | import { geography } from "mssql-geoparser";
2 | import { createGlobalId } from "../../../util";
3 |
4 | // const delay = (x, num = 5000) => new Promise((r, rej) => {
5 | // return setTimeout(() => r(x), num);
6 | // });
7 |
8 | export default {
9 | Query: {
10 | campuses: (_, { name, id }, { models }) =>
11 | models.Campus.find({ Id: id, Name: name })
12 | },
13 |
14 | Campus: {
15 | id: ({ Id }, _, $, { parentType }) => createGlobalId(Id, parentType.name),
16 | entityId: ({ Id }) => Id,
17 | name: ({ Name }) => Name,
18 | shortCode: ({ ShortCode }) => ShortCode,
19 | guid: ({ Guid }) => Guid,
20 | url: ({ Url }) => Url,
21 | services: ({ ServiceTimes }) => {
22 | if (!ServiceTimes) return [];
23 |
24 | const days = {};
25 |
26 | ServiceTimes.split("|")
27 | .filter(x => !!x)
28 | .forEach(x => {
29 | let [day, time] = x.split("^");
30 | day = day.trim();
31 | time = time.trim();
32 | if (!days[day]) days[day] = [];
33 |
34 | if (days[day].indexOf(time) === -1) days[day].push(time);
35 | });
36 |
37 | return Object.keys(days).map(x => {
38 | let str = `${x} at `;
39 | if (days[x].length === 1) {
40 | str += `& ${days[x]}`;
41 | return str;
42 | }
43 |
44 | str += `${[...days[x]].slice(0, days[x].length - 1).join(", ")} `;
45 | str += `& ${[...days[x]].pop()}`;
46 |
47 | return str;
48 | });
49 | },
50 | locationId: ({ LocationId }) => LocationId,
51 | location: ({ LocationId }, _, { models }) => {
52 | if (!LocationId) return null;
53 |
54 | return models.Campus.findByLocationId(LocationId);
55 | }
56 | },
57 |
58 | Location: {
59 | id: ({ Id }, _, $, { parentType }) => createGlobalId(Id, parentType.name),
60 | name: ({ Name }) => Name,
61 | street1: ({ Street1 }) => Street1,
62 | street2: ({ Street2 }) => Street2,
63 | city: ({ City }) => City,
64 | state: ({ State }) => State,
65 | country: ({ Country }) => Country,
66 | zip: ({ PostalCode }) => PostalCode,
67 | latitude: ({ GeoPoint, latitude }) => {
68 | if (latitude) return latitude;
69 | if (!GeoPoint) return null;
70 | try {
71 | const { points } = geography(GeoPoint);
72 | return points[0].x;
73 | } catch (e) {
74 | return null;
75 | }
76 | },
77 | longitude: ({ GeoPoint, longitude }) => {
78 | if (longitude) return longitude;
79 | if (!GeoPoint) return null;
80 | try {
81 | const { points } = geography(GeoPoint);
82 | return points[0].y;
83 | } catch (e) {
84 | return null;
85 | }
86 | },
87 | distance: ({ Id, Distance }) => {
88 | // tslint:disable-line
89 | if (Distance) return Distance;
90 |
91 | return null;
92 | // XXX get distance from the person
93 | // this is typically used from a geo based lookup
94 | }
95 | }
96 | };
97 |
--------------------------------------------------------------------------------
/src/util/cache/__test__/index.spec.js:
--------------------------------------------------------------------------------
1 | import casual from "casual";
2 | import { defaultCache, resolvers } from "../defaults";
3 | import { InMemoryCache } from "../memory-cache";
4 | import { parseGlobalId } from "../../node/model";
5 |
6 | it("the cache mutation should delete the id from the cache", () => {
7 | const id = casual.word,
8 | data = { test: casual.word },
9 | cacheData = { [id]: data },
10 | cache = new InMemoryCache(cacheData);
11 |
12 | const { Mutation } = resolvers;
13 |
14 | function get(_id) {
15 | return Promise.resolve({ [_id]: { test: casual.word } });
16 | }
17 | const context = { cache, models: { Node: { get } } };
18 |
19 | return Mutation.cache(null, { id, type: null }, context).then(() => {
20 | expect(cacheData[id]).toBeFalsy;
21 | });
22 | });
23 |
24 | it("the cache mutation should refetch and save the data in the cache", () => {
25 | const id = casual.word,
26 | data = { test: casual.word },
27 | data2 = { test: casual.word },
28 | cacheData = { [id]: data },
29 | cache = new InMemoryCache(cacheData);
30 |
31 | const { Mutation } = resolvers;
32 |
33 | function get(_id) {
34 | return cache.get(_id, () => Promise.resolve(data2));
35 | }
36 | const context = { cache, models: { Node: { get } } };
37 |
38 | return Mutation.cache(null, { id, type: null }, context).then(result => {
39 | expect(result).toEqual(data2);
40 |
41 | return new Promise((c, r) => {
42 | // cache resetting is an async action
43 | process.nextTick(() => {
44 | expect(result).toEqual(data2);
45 | c();
46 | });
47 | });
48 | });
49 | });
50 |
51 | it("the cache mutation should allow using a native id and type together", () => {
52 | const id = casual.word,
53 | type = casual.word,
54 | data = { test: casual.word },
55 | data2 = { test: casual.word },
56 | cacheData = { [id]: data },
57 | cache = new InMemoryCache(cacheData);
58 |
59 | const { Mutation } = resolvers;
60 |
61 | function get(_id) {
62 | const parsed = parseGlobalId(_id);
63 | expect(id).toEqual(parsed.id);
64 | expect(type).toEqual(parsed.__type);
65 | return cache.get(_id, () => Promise.resolve(data2));
66 | }
67 | const context = { cache, models: { Node: { get } } };
68 |
69 | return Mutation.cache(null, { id, type }, context).then(result => {
70 | expect(result).toEqual(data2);
71 |
72 | return new Promise((c, r) => {
73 | // cache resetting is an async action
74 | process.nextTick(() => {
75 | expect(result).toEqual(data2);
76 | c();
77 | });
78 | });
79 | });
80 | });
81 |
82 | // XXX how to test this
83 | it("defaultCache:get should simply run the lookup method", () =>
84 | defaultCache.get(null, () => Promise.resolve()));
85 |
86 | it("defaultCache:set should return true and do nothing", () =>
87 | defaultCache.set("test", {}).then(success => {
88 | if (!success) throw new Error();
89 | expect(true).toBe(true);
90 | }));
91 |
92 | it("defaultCache:del exist as a function but do nothing", () => {
93 | expect(() => defaultCache.del("string")).not.toThrow();
94 | });
95 |
--------------------------------------------------------------------------------
/src/rock/models/finances/models/__tests__/__snapshots__/Transaction.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`createOrder adds a job to the queue if an schedule with saved payment 1`] = `undefined`;
4 |
5 | exports[`createOrder adds a persons PrimaryAliasId to an order 1`] = `
6 | Object {
7 | "code": 100,
8 | "success": true,
9 | "transactionId": 1,
10 | "url": Object {
11 | "sale": Object {
12 | "add-customer": "",
13 | "amount": 1,
14 | "api-key": "3",
15 | "customer-id": 10,
16 | "cvv-reject": "P|N|S|U",
17 | "ip-address": "1",
18 | "order-description": "Online contribution from Apollos",
19 | "redirect-url": "https://my.newspring.cc/give/now",
20 | },
21 | },
22 | }
23 | `;
24 |
25 | exports[`createOrder attempts to look up a saved account via the id 1`] = `
26 | Object {
27 | "code": 100,
28 | "success": true,
29 | "transactionId": 1,
30 | "url": Object {
31 | "sale": Object {
32 | "amount": 1,
33 | "api-key": "3",
34 | "customer-vault-id": 100,
35 | "ip-address": "1",
36 | "order-description": "Online contribution from Apollos",
37 | "redirect-url": "https://my.newspring.cc/give/now",
38 | },
39 | },
40 | }
41 | `;
42 |
43 | exports[`createOrder correctly formats a subscription object 1`] = `
44 | Object {
45 | "code": 100,
46 | "success": true,
47 | "transactionId": 1,
48 | "url": Object {
49 | "add-subscription": Object {
50 | "amount": 1,
51 | "api-key": "3",
52 | "order-description": "Online contribution from Apollos",
53 | "redirect-url": "https://my.newspring.cc/give/now",
54 | "start-date": "01012020",
55 | },
56 | },
57 | }
58 | `;
59 |
60 | exports[`createOrder correctly formats the data object 1`] = `
61 | Object {
62 | "code": 100,
63 | "success": true,
64 | "transactionId": 1,
65 | "url": Object {
66 | "sale": Object {
67 | "add-customer": "",
68 | "amount": 1,
69 | "api-key": "3",
70 | "cvv-reject": "P|N|S|U",
71 | "ip-address": "1",
72 | "order-description": "Online contribution from Apollos",
73 | "redirect-url": "https://my.newspring.cc/give/now",
74 | "test": true,
75 | },
76 | },
77 | }
78 | `;
79 |
80 | exports[`createOrder handles a validation attempt 1`] = `
81 | Object {
82 | "code": 100,
83 | "success": true,
84 | "transactionId": 1,
85 | "url": Object {
86 | "validate": Object {
87 | "amount": 0,
88 | "api-key": "3",
89 | "customer-id": 10,
90 | "cvv-reject": "P|N|S|U",
91 | "ip-address": "1",
92 | "order-description": "Online contribution from Apollos",
93 | "redirect-url": "https://my.newspring.cc/give/now",
94 | },
95 | },
96 | }
97 | `;
98 |
99 | exports[`createOrder handles an add customer attempt 1`] = `
100 | Object {
101 | "code": 100,
102 | "success": true,
103 | "transactionId": 1,
104 | "url": Object {
105 | "add-customer": Object {
106 | "api-key": "3",
107 | "redirect-url": "https://my.newspring.cc/give/now",
108 | },
109 | },
110 | }
111 | `;
112 |
--------------------------------------------------------------------------------
/src/util/heighliner.js:
--------------------------------------------------------------------------------
1 | import { merge } from "lodash";
2 |
3 | import {
4 | schema as nodeSchema,
5 | resolver as nodeResolver,
6 | mocks as nodeMocks
7 | } from "./node";
8 |
9 | import {
10 | resolvers as cacheResolver,
11 | mutations as cacheMutation
12 | } from "./cache/defaults";
13 |
14 | import { schema as dateSchema, resolver as dateResolver } from "./scalars/Date";
15 |
16 | export function getIp(request) {
17 | return (
18 | request.headers["x-forwarded-for"] ||
19 | request.connection.remoteAddress ||
20 | request.socket.remoteAddress ||
21 | request.connection.socket.remoteAddress
22 | );
23 | }
24 |
25 | export function createQueries(queries) {
26 | return [
27 | `
28 | type Query {
29 | ${queries.join("\n")}
30 | node(id: ID!): Node
31 | }
32 | `
33 | ];
34 | }
35 |
36 | export function createMutations(mutations = []) {
37 | return [
38 | `
39 | type Mutation {
40 | ${mutations.join("\n")}
41 | ${cacheMutation.join("\n")}
42 | }
43 | `
44 | ];
45 | }
46 |
47 | export function createApplication(models) {
48 | const joined = {
49 | schema: [],
50 | models: {},
51 | resolvers: {},
52 | queries: [],
53 | mutations: []
54 | };
55 |
56 | for (const model of models) {
57 | if (model.schema) joined.schema = [...joined.schema, ...model.schema];
58 | if (model.models) joined.models = merge(joined.models, model.models);
59 | if (model.resolvers)
60 | joined.resolvers = merge(joined.resolvers, model.resolvers);
61 | if (model.queries) joined.queries = [...joined.queries, ...model.queries];
62 | if (model.mutations)
63 | joined.mutations = [...joined.mutations, ...model.mutations];
64 | }
65 |
66 | return joined;
67 | }
68 |
69 | export function loadApplications(applications) {
70 | const joined = {
71 | schema: [...nodeSchema, ...dateSchema],
72 | models: {},
73 | resolvers: merge({}, nodeResolver, cacheResolver, dateResolver),
74 | mocks: merge({}, nodeMocks)
75 | };
76 |
77 | Object.keys(applications).forEach(name => {
78 | const app = applications[name];
79 | joined.schema = [...joined.schema, ...app.schema];
80 | joined.models = merge(joined.models, app.models);
81 | joined.resolvers = merge(joined.resolvers, app.resolvers);
82 | });
83 |
84 | // dynmically create the root query mock
85 | const queries = merge({}, joined.mocks.Query);
86 | joined.mocks.Query = () => queries;
87 |
88 | // XXX dynamically create the root mutation mock
89 | const mutations = merge({}, joined.mocks.Mutation);
90 | joined.mocks.Mutation = () => mutations;
91 |
92 | return joined;
93 | }
94 |
95 | export function createSchema({ queries, schema, mutations }) {
96 | // build base level schema
97 | const root = [
98 | `
99 | schema {
100 | query: Query
101 | mutation: Mutation
102 | }
103 | `
104 | ];
105 |
106 | const query = createQueries(queries);
107 | const mutation = createMutations(mutations);
108 |
109 | // generate the final schema
110 | return [...root, ...query, ...mutation, ...schema];
111 | }
112 |
--------------------------------------------------------------------------------
/src/rock/models/finances/schema.js:
--------------------------------------------------------------------------------
1 | export default [
2 | `
3 | type TransactionDetail implements Node {
4 | id: ID!
5 | amount: Float!
6 | account: FinancialAccount
7 | }
8 |
9 | type ScheduledTransaction implements Node {
10 | id: ID!
11 | entityId: Int!
12 | reminderDate: String
13 | start: String
14 | next: String
15 | code: String
16 | gateway: Int # we use this on the client I think?
17 | end: String
18 | numberOfPayments: Int
19 | date: String
20 | details: [TransactionDetail]
21 | transactions: [Transaction]
22 | schedule: DefinedValue
23 | payment: PaymentDetail
24 | isActive: Boolean
25 | }
26 |
27 | type Transaction implements Node {
28 | id: ID!
29 | entityId: Int!
30 | summary: String
31 | status: String
32 | statusMessage: String
33 | date: String
34 | details: [TransactionDetail]
35 | payment: PaymentDetail
36 | person: Person
37 | schedule: ScheduledTransaction
38 | }
39 |
40 | type OrderMutationResponse implements MutationResponse {
41 | error: String
42 | success: Boolean!
43 | code: Int
44 | url: String
45 | transactionId: ID
46 | }
47 |
48 | type CompleteOrderMutationResponse implements MutationResponse {
49 | error: String
50 | success: Boolean!
51 | code: Int
52 | transaction: Transaction
53 | schedule: ScheduledTransaction
54 | person: Person
55 | savedPayment: SavedPayment
56 | }
57 |
58 | type ValidateMutationResponse implements MutationResponse {
59 | error: String
60 | success: Boolean!
61 | code: Int
62 | }
63 |
64 | type FinancialAccount implements Node {
65 | id: ID!
66 | entityId: Int!
67 | transactions(
68 | limit: Int = 20,
69 | skip: Int = 0,
70 | cache: Boolean = true,
71 | start: String,
72 | end: String,
73 | people: [Int],
74 | ): [Transaction]
75 | total(
76 | start: String,
77 | end: String,
78 | people: [Int],
79 | ): Int
80 | name: String
81 | order: Int
82 | description: String
83 | summary: String
84 | image: String
85 | end: String
86 | start: String
87 | images: [File]
88 | }
89 |
90 | type PaymentDetail implements Node {
91 | id: ID!
92 | accountNumber: String
93 | paymentType: String!
94 | }
95 |
96 | type SavedPayment implements Node {
97 | id: ID!
98 | entityId: Int!
99 | name: String
100 | guid: String
101 | code: String!
102 | date: String
103 | payment: PaymentDetail
104 | expirationMonth: String
105 | expirationYear: String
106 | }
107 |
108 | type SavePaymentMutationResponse implements MutationResponse {
109 | error: String
110 | success: Boolean!
111 | code: Int
112 | savedPayment: SavedPayment
113 | }
114 |
115 | type ScheduledTransactionMutationResponse implements MutationResponse {
116 | error: String
117 | success: Boolean!
118 | code: Int
119 | schedule: ScheduledTransaction
120 | }
121 |
122 | type StatementMutationResponse implements MutationResponse {
123 | error: String
124 | success: Boolean!
125 | code: Int
126 | file: String
127 | }
128 | `
129 | ];
130 |
--------------------------------------------------------------------------------
/src/util/cache/__test__/memory-cache.spec.js:
--------------------------------------------------------------------------------
1 | import casual from "casual";
2 | import { InMemoryCache } from "../memory-cache";
3 |
4 | it("`InMemoryCache` should have a way to get items from the cache", () => {
5 | const id = casual.word;
6 | const data = { test: casual.word };
7 |
8 | const cacheData = { [id]: data };
9 | const cache = new InMemoryCache(cacheData);
10 |
11 | return cache
12 | .get(id, () => Promise.resolve())
13 | .then(result => {
14 | expect(result).toEqual(data);
15 | });
16 | });
17 |
18 | it("`InMemoryCache` should use a lookup method if no cache entry exists", () => {
19 | const id = casual.word;
20 | const data = { test: casual.word };
21 |
22 | const cacheData = {};
23 | const cache = new InMemoryCache(cacheData);
24 |
25 | const spyLookup = () => Promise.resolve(data);
26 | return cache.get(id, spyLookup).then(result => {
27 | expect(result).toEqual(data);
28 | });
29 | });
30 |
31 | it("`InMemoryCache` should have a way to set items in the cache with a ttl", () => {
32 | const id = casual.word;
33 | const data = { test: casual.word };
34 |
35 | const cacheData = {};
36 | const cache = new InMemoryCache(cacheData);
37 |
38 | const spyLookup = () => Promise.resolve(data);
39 | return cache
40 | .get(id, spyLookup, { ttl: 0.1 })
41 | .then(result => {
42 | expect(result).toEqual(data);
43 | })
44 | .then(
45 | () =>
46 | new Promise((c, r) => {
47 | setTimeout(() => {
48 | expect(cacheData[id]).toBeFalsy();
49 | c();
50 | }, 0.1 * 60 + 25);
51 | })
52 | );
53 | });
54 |
55 | it("should have a way to set items in the cache", () => {
56 | const id = casual.word;
57 | const data = { test: casual.word };
58 |
59 | const cacheData = {};
60 | const cache = new InMemoryCache(cacheData);
61 |
62 | return cache.set(id, data).then(() => {
63 | expect(cacheData[id]).toEqual(data);
64 | });
65 | });
66 |
67 | it("should eventually return true if successfully set", () => {
68 | const id = casual.word;
69 | const data = { test: casual.word };
70 |
71 | const cacheData = {};
72 | const cache = new InMemoryCache(cacheData);
73 |
74 | return cache.set(id, data).then(success => {
75 | expect(cacheData[id]).toEqual(data);
76 | expect(success).toBeTruthy();
77 | });
78 | });
79 |
80 | it("should have a way to set items in the cache with a ttl", () => {
81 | const id = casual.word;
82 | const data = { test: casual.word };
83 |
84 | const cacheData = {};
85 | const cache = new InMemoryCache(cacheData);
86 |
87 | return cache
88 | .set(id, data, 0.1)
89 | .then(result => {
90 | expect(cacheData[id]).toEqual(data);
91 | })
92 | .then(
93 | () =>
94 | new Promise((c, r) => {
95 | setTimeout(() => {
96 | expect(cacheData[id]).toBeFalsy();
97 | c();
98 | }, 0.1 * 60 + 25);
99 | })
100 | );
101 | });
102 |
103 | it("`InMemoryCache` should allow removing existing cache entries", () => {
104 | const id = casual.word;
105 | const data = { test: casual.word };
106 |
107 | const cacheData = { [id]: data };
108 | const cache = new InMemoryCache(cacheData);
109 |
110 | cache.del(id);
111 | expect(cacheData[id]).toBeFalsy();
112 | });
113 |
--------------------------------------------------------------------------------
/src/rock/models/people/tables.js:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-shadowed-variable */
2 |
3 | import { INTEGER, STRING, BOOLEAN } from "sequelize";
4 |
5 | import { MSSQLConnector } from "../../mssql";
6 |
7 | const personSchema = {
8 | Id: { type: INTEGER, primaryKey: true },
9 | BirthDate: { type: INTEGER },
10 | BirthDay: { type: INTEGER },
11 | BirthMonth: { type: INTEGER },
12 | BirthYear: { type: INTEGER },
13 | ConnectionStatusValueId: { type: INTEGER },
14 | Email: { type: STRING },
15 | EmailPreference: { type: STRING },
16 | FirstName: { type: STRING },
17 | Gender: { type: INTEGER },
18 | GivingGroupId: { type: INTEGER },
19 | GivingId: { type: INTEGER },
20 | Guid: { type: STRING },
21 | IsDeceased: { type: BOOLEAN },
22 | LastName: { type: STRING },
23 | MaritalStatusValueId: { type: INTEGER },
24 | MiddleName: { type: STRING },
25 | NickName: { type: STRING },
26 | PhotoId: { type: INTEGER }
27 | };
28 |
29 | const aliasSchema = {
30 | Id: { type: INTEGER, primaryKey: true },
31 | PersonId: { type: INTEGER }
32 | };
33 |
34 | // XXX move to its own model if/when needed
35 | const phoneNumberSchema = {
36 | Id: { type: INTEGER, primaryKey: true },
37 | CountryCode: { type: STRING },
38 | Description: { type: STRING },
39 | Extension: { type: STRING },
40 | IsMessagingEnabled: { type: STRING },
41 | IsSystem: { type: BOOLEAN },
42 | IsUnlisted: { type: BOOLEAN },
43 | Number: { type: STRING },
44 | NumberFormatted: { type: STRING },
45 | PersonId: { type: INTEGER }
46 | };
47 |
48 | const personalDeviceSchema = {
49 | Id: { type: INTEGER, primaryKey: true },
50 | PersonAliasId: { type: INTEGER },
51 | DeviceRegistrationId: { type: STRING },
52 | PersonalDeviceTypeValueId: { type: INTEGER },
53 | NotificationsEnabled: { type: BOOLEAN }
54 | };
55 |
56 | let Person;
57 | let PersonAlias;
58 | let PhoneNumber;
59 | let PersonalDevice;
60 | export {
61 | Person,
62 | personSchema,
63 | PersonAlias,
64 | aliasSchema,
65 | PhoneNumber,
66 | phoneNumberSchema,
67 | PersonalDevice
68 | };
69 |
70 | export function connect() {
71 | Person = new MSSQLConnector("Person", personSchema, {}, "People");
72 | PersonAlias = new MSSQLConnector("PersonAlias", aliasSchema);
73 | PhoneNumber = new MSSQLConnector("PhoneNumber", phoneNumberSchema);
74 | PersonalDevice = new MSSQLConnector("PersonalDevice", personalDeviceSchema);
75 |
76 | return {
77 | Person,
78 | PersonAlias,
79 | PhoneNumber,
80 | PersonalDevice
81 | };
82 | }
83 |
84 | export function bind({
85 | Person,
86 | PersonAlias,
87 | PhoneNumber,
88 | PersonalDevice,
89 | Group
90 | }) {
91 | PersonAlias.model.belongsTo(Person.model, {
92 | foreignKey: "PersonId",
93 | targetKey: "Id"
94 | });
95 | Person.model.hasOne(PersonAlias.model, { foreignKey: "PersonId" });
96 |
97 | PhoneNumber.model.belongsTo(Person.model, {
98 | foreignKey: "PersonId",
99 | targetKey: "Id"
100 | });
101 | PersonalDevice.model.belongsTo(PersonAlias.model, {
102 | foreignKey: "PersonAliasId",
103 | targetKey: "Id"
104 | });
105 |
106 | Person.model.belongsToMany(Group.model, {
107 | as: "Groups",
108 | through: "GroupMember",
109 | foreignKey: "PersonId",
110 | otherKey: "GroupId"
111 | });
112 | }
113 |
114 | export default {
115 | connect,
116 | bind
117 | };
118 |
--------------------------------------------------------------------------------
/src/expression-engine/models/content/__tests__/images.spec.js:
--------------------------------------------------------------------------------
1 | import { addResizings } from "../images";
2 |
3 | const createSampleImage = () => ({
4 | url: "url.jpg",
5 | fileLabel: null
6 | });
7 |
8 | it("`addResizings` should return 5 images when handed 1", () => {
9 | const images = [createSampleImage()];
10 |
11 | const result = addResizings(images);
12 | expect(result.length).toEqual(5);
13 | });
14 |
15 | it("`addResizings` should return 15 images when handed 3", () => {
16 | const images = [
17 | createSampleImage(),
18 | createSampleImage(),
19 | createSampleImage()
20 | ];
21 |
22 | const result = addResizings(images);
23 | expect(result.length).toEqual(15);
24 | });
25 |
26 | it("`addResizings` should generate an xlarge image", () => {
27 | const images = [createSampleImage()];
28 |
29 | const results = addResizings(images);
30 | const { url, size } = results[0];
31 | expect(url.indexOf("xlarge") > -1).toBeTruthy();
32 | expect(size).toEqual("xlarge");
33 | });
34 |
35 | it("`addResizings` should generate a large image", () => {
36 | const images = [createSampleImage()];
37 |
38 | const results = addResizings(images);
39 | const { url, size } = results[1];
40 | expect(url.indexOf("large") > -1).toBeTruthy();
41 | expect(size).toEqual("large");
42 | });
43 |
44 | it("`addResizings` should generate a medium image", () => {
45 | const images = [createSampleImage()];
46 |
47 | const results = addResizings(images);
48 | const { url, size } = results[2];
49 | expect(url.indexOf("medium") > -1).toBeTruthy();
50 | expect(size).toEqual("medium");
51 | });
52 |
53 | it("`addResizings` should generate a small image", () => {
54 | const images = [createSampleImage()];
55 |
56 | const results = addResizings(images);
57 | const { url, size } = results[3];
58 | expect(url.indexOf("small") > -1).toBeTruthy();
59 | expect(size).toEqual("small");
60 | });
61 |
62 | it("`addResizings` should generate an xsmall image", () => {
63 | const images = [createSampleImage()];
64 |
65 | const results = addResizings(images);
66 | const { url, size } = results[4];
67 | expect(url.indexOf("xsmall") > -1).toBeTruthy();
68 | expect(size).toEqual("xsmall");
69 | });
70 |
71 | it("`addResizings` should return 1 image if 1 image and 1 size", () => {
72 | const images = [createSampleImage()];
73 | const options = {
74 | sizes: ["medium"],
75 | ratios: []
76 | };
77 |
78 | const results = addResizings(images, options);
79 | expect(results.length).toEqual(1);
80 | expect(results[0].url.indexOf("medium") > -1).toBeTruthy();
81 | expect(results[0].size).toEqual("medium");
82 | });
83 |
84 | it("`addResizings` should return 4 images if 2 images and 2 sizes", () => {
85 | const images = [createSampleImage(), createSampleImage()];
86 | const options = {
87 | sizes: ["medium", "large"],
88 | ratios: []
89 | };
90 |
91 | const results = addResizings(images, options);
92 | expect(results.length).toEqual(4);
93 | });
94 |
95 | it("`addResizings` should return only the ratio specified", () => {
96 | const images = [createSampleImage(), createSampleImage()];
97 | const options = {
98 | sizes: null,
99 | ratios: ["2:1"]
100 | };
101 | images[0].fileLabel = "2:1";
102 | images[1].fileLabel = "1:2";
103 |
104 | const results = addResizings(images, options);
105 | expect(results.length).toEqual(5);
106 | results.map(image => {
107 | expect(image.fileLabel).toEqual("2:1");
108 | });
109 | });
110 |
--------------------------------------------------------------------------------
/src/rock/models/finances/models/ScheduledTransaction.js:
--------------------------------------------------------------------------------
1 | import { createGlobalId } from "../../../../util";
2 | import nmi from "../util/nmi";
3 |
4 | import {
5 | Transaction as TransactionTable,
6 | ScheduledTransaction as ScheduledTransactionTable,
7 | ScheduledTransactionDetail
8 | } from "../tables";
9 |
10 | import { Rock } from "../../system";
11 |
12 | export default class ScheduledTransaction extends Rock {
13 | __type = "ScheduledTransaction";
14 |
15 | async getFromId(id, globalId) {
16 | globalId = globalId || createGlobalId(`${id}`, this.__type);
17 | return this.cache.get(globalId, () =>
18 | ScheduledTransactionTable.findOne({ where: { Id: id } })
19 | );
20 | }
21 |
22 | async getTransactionsById(id) {
23 | if (!id) return Promise.resolve(null);
24 | const globalId = createGlobalId(
25 | `${id}`,
26 | "ScheduledTransactionTransactions"
27 | );
28 | return this.cache.get(globalId, () =>
29 | TransactionTable.find({
30 | where: { ScheduledTransactionId: id },
31 | order: [["TransactionDateTime", "DESC"]]
32 | })
33 | );
34 | }
35 |
36 | async getDetailsByScheduleId(id) {
37 | if (!id) return Promise.resolve(null);
38 | const globalId = createGlobalId(`${id}`, "ScheduledTransactionDetails");
39 | // XXX why isn't this caching?
40 | return this.cache.get(globalId, () =>
41 | ScheduledTransactionDetail.find({
42 | where: { ScheduledTransactionId: id }
43 | })
44 | );
45 | }
46 |
47 | async findByPersonAlias(aliases, { limit, offset, isActive }, { cache }) {
48 | const query = { aliases, limit, offset, isActive };
49 | return await this.cache
50 | .get(
51 | this.cache.encode(query),
52 | () =>
53 | ScheduledTransactionTable.find({
54 | where: {
55 | AuthorizedPersonAliasId: { $in: aliases },
56 | IsActive: isActive
57 | },
58 | order: [["CreatedDateTime", "DESC"]],
59 | attributes: ["Id"],
60 | limit,
61 | offset
62 | }),
63 | { cache }
64 | )
65 | .then(this.getFromIds.bind(this));
66 | }
67 |
68 | async cancelNMISchedule(id, gatewayDetails) {
69 | const existing = await this.getFromId(id);
70 | if (!existing) return Promise.resolve({ error: "Schedule not found" });
71 |
72 | const payload = {
73 | "delete-subscription": {
74 | "api-key": gatewayDetails.SecurityKey,
75 | "subscription-id": existing.GatewayScheduleId
76 | }
77 | };
78 |
79 | return nmi(payload, gatewayDetails)
80 | .catch(error => {
81 | // If this schedule isn't in NMI, go ahead and clean up Rock
82 | if (
83 | !/Transaction not found/.test(error.message) &&
84 | !/No recurring subscriptions found/.test(error.message)
85 | )
86 | throw error;
87 | })
88 | .then(() => {
89 | if (existing.GatewayScheduleId) {
90 | return ScheduledTransactionTable.patch(existing.Id, {
91 | IsActive: false
92 | });
93 | }
94 |
95 | return ScheduledTransactionTable.delete(existing.Id);
96 | })
97 | .then(() => {
98 | const nodeId = createGlobalId(`${existing.Id}`, this.__type);
99 | this.cache.del(nodeId);
100 | return { scheduleId: existing.Id };
101 | })
102 | .catch(error => ({ code: error.code, error: error.message }));
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/expression-engine/models/navigation/model.js:
--------------------------------------------------------------------------------
1 | import { orderBy } from "lodash";
2 | import { defaultCache } from "../../../util/cache";
3 |
4 | // import {
5 | // ChannelTitles,
6 | // channelTitleSchema,
7 | // } from "../content/tables";
8 |
9 | import { Navee, NaveeNav } from "../navigation/tables";
10 |
11 | import { Sites } from "../ee/sites";
12 |
13 | import { EE } from "../ee";
14 |
15 | export class Navigation extends EE {
16 | constructor({ cache } = { cache: defaultCache }) {
17 | super({ cache });
18 | this.cache = cache;
19 | }
20 |
21 | // XXX add caching
22 | // XXX support getting children from the node interface
23 | async getFromId(id) {
24 | return this.cache
25 | .get(id, () =>
26 | Navee.findOne({
27 | where: { navee_id: id },
28 | include: [{ model: Sites.model }]
29 | })
30 | )
31 | .then(x => {
32 | x.site_pages = Sites.parsePage(x.exp_site.site_pages)[x.site_id];
33 | return x;
34 | })
35 | .then(x => {
36 | if (x.type === "pages" && x.entry_id) {
37 | x.link = x.site_pages.uris[x.entry_id];
38 | }
39 |
40 | return {
41 | link: x.link,
42 | id: x.navee_id,
43 | text: x.text,
44 | url: x.site_pages.url,
45 | parent: x.parent,
46 | sort: x.sort,
47 | image: x.custom
48 | };
49 | });
50 | }
51 |
52 | async find({ nav }) {
53 | const navigation = {};
54 | const orphans = [];
55 | return await NaveeNav.find({
56 | where: { nav_title: nav },
57 | include: [{ model: Navee.model }, { model: Sites.model }]
58 | })
59 | .then(data =>
60 | data.map(x => {
61 | x.exp_navee.site_pages = Sites.parsePage(x.exp_site.site_pages)[
62 | x.site_id
63 | ];
64 | return x.exp_navee;
65 | })
66 | )
67 | .then(data =>
68 | data.map(x => {
69 | if (x.type === "pages" && x.entry_id) {
70 | x.link = x.site_pages.uris[x.entry_id];
71 | }
72 |
73 | return {
74 | link: x.link,
75 | id: x.navee_id,
76 | text: x.text,
77 | url: x.site_pages.url,
78 | parent: x.parent,
79 | sort: x.sort,
80 | image: x.custom
81 | };
82 | })
83 | )
84 | .then(data => {
85 | // get all parents
86 | data
87 | .filter(x => x.parent === 0)
88 | .forEach(x => {
89 | navigation[x.id] = x;
90 | });
91 | // get all children
92 | data
93 | .filter(x => x.parent !== 0)
94 | .forEach(x => {
95 | if (navigation[x.parent]) {
96 | navigation[x.parent].children ||
97 | (navigation[x.parent].children = []); // tslint:disable-line
98 | navigation[x.parent].children.push(x);
99 | return;
100 | }
101 | // XXX make this recursive
102 | orphans.push(x);
103 | });
104 | return [navigation];
105 | })
106 | .then(() => {
107 | const results = [];
108 | for (const parent in navigation) {
109 | const item = navigation[parent];
110 | item.children = orderBy(item.children, "sort");
111 | results.push(item);
112 | }
113 | return orderBy(results, "sort");
114 | });
115 | }
116 | }
117 |
118 | export default {
119 | Navigation
120 | };
121 |
--------------------------------------------------------------------------------
/src/apollos/models/users/resolver.js:
--------------------------------------------------------------------------------
1 | import get from "lodash/get";
2 | import pick from "lodash/pick";
3 | import { FOLLOWABLE_TOPICS } from "../../../constants";
4 |
5 | export default {
6 | Query: {
7 | currentUser(_, args, { user, person }) {
8 | if (!user || !person) return null;
9 | return { user, person };
10 | },
11 | topics() {
12 | return FOLLOWABLE_TOPICS;
13 | }
14 | },
15 |
16 | UserTokens: {
17 | tokens: ({ loginTokens }) => loginTokens
18 | },
19 |
20 | UserRock: {
21 | id: ({ PersonId }) => PersonId,
22 | alias: ({ PrimaryAliasId }) => PrimaryAliasId
23 | },
24 |
25 | UserService: {
26 | rock: ({ rock }) => rock,
27 | resume: ({ resume }) => resume
28 | },
29 |
30 | User: {
31 | id: ({ user, person } = {}) => {
32 | const mongoId = get(user, "_id"); // Deprecated
33 | const rockId = get(person, "PrimaryAliasId");
34 | return rockId || mongoId;
35 | },
36 | createdAt: ({ user } = {}) => {
37 | const {
38 | createdAt, // Deprecated Mongo User
39 | CreatedDateTime // Rock User
40 | } = user;
41 | return CreatedDateTime || createdAt;
42 | },
43 | services: (props = {}) => get(props, "user.services"), // Deprecated
44 | emails: (props = {}) => get(props, "user.emails"), // Deprecated
45 | email: ({ user, person }) => {
46 | const email = get(user, "emails.0.address");
47 | if (email) return email; // Deprecated Mongo User
48 |
49 | // Rock Profile
50 | return person.Email;
51 | },
52 | followedTopics({ person }, $, { models }) {
53 | return models.User.getUserFollowingTopics(person.PrimaryAliasId);
54 | }
55 | },
56 |
57 | Mutation: {
58 | loginUser(_, props, { models }) {
59 | return models.User.loginUser(props);
60 | },
61 | registerUser(_, props, { models }) {
62 | return models.User.registerUser(props);
63 | },
64 | logoutUser(_, props, { models, authToken, user }) {
65 | return models.User.logoutUser({
66 | token: authToken,
67 | loginId: user && user.Id
68 | });
69 | },
70 | forgotUserPassword(_, props, { models }) {
71 | const { email, sourceURL } = props;
72 | return models.User.forgotPassword(email, sourceURL);
73 | },
74 | resetUserPassword(_, props, { models }) {
75 | const { token, newPassword } = props;
76 | return models.User.resetPassword(token, newPassword);
77 | },
78 | changeUserPassword(_, props, { models, user }) {
79 | const { oldPassword, newPassword } = props;
80 | return models.User.changePassword(user, oldPassword, newPassword);
81 | },
82 | toggleTopic(_, props, { models, person }) {
83 | const { topic } = props;
84 | return models.User.toggleTopic({
85 | topic,
86 | userId: person.PrimaryAliasId
87 | });
88 | },
89 | updateProfile(_, { input } = {}, { models, person }) {
90 | return models.User.updateProfile(
91 | person.Id,
92 | pick(input, [
93 | "NickName",
94 | "FirstName",
95 | "LastName",
96 | "Email",
97 | "BirthMonth",
98 | "BirthDay",
99 | "BirthYear",
100 | "Campus"
101 | ])
102 | );
103 | },
104 | updateHomeAddress(_, { input } = {}, { models, person }) {
105 | return models.User.updateHomeAddress(
106 | person.Id,
107 | pick(input, ["Street1", "Street2", "City", "State", "PostalCode"])
108 | );
109 | }
110 | }
111 | };
112 |
--------------------------------------------------------------------------------
/src/apollos/mongo.js:
--------------------------------------------------------------------------------
1 | import mongoose, { Schema } from "mongoose";
2 | // import DataLoader from "dataloader";
3 | mongoose.Promise = global.Promise;
4 |
5 | let db = mongoose.connect(
6 | process.env.MONGO_URL,
7 | {
8 | server: { reconnectTries: Number.MAX_VALUE }
9 | }
10 | );
11 | let dd;
12 |
13 | // NOTE: Wherever this is called it's too late in the game for
14 | // use to take advantage of indexing
15 | export function connect(monitor) {
16 | if (db) return Promise.resolve(true);
17 | dd = monitor && monitor.datadog;
18 | return new Promise(cb => {
19 | db = mongoose.connect(
20 | process.env.MONGO_URL,
21 | {
22 | server: { reconnectTries: Number.MAX_VALUE }
23 | },
24 | err => {
25 | if (err) {
26 | db = false;
27 | cb(false);
28 | return;
29 | }
30 |
31 | cb(true);
32 | }
33 | );
34 | });
35 | }
36 |
37 | mongoose.connection.on(
38 | "error",
39 | console.error.bind(console, "MONGO connection error:")
40 | );
41 |
42 | export class MongoConnector {
43 | constructor(collection, schema, indexes = []) {
44 | this.db = db;
45 | this.schema = new Schema(schema);
46 | indexes.forEach(({ keys, options } = {}) =>
47 | this.schema.index(keys, options)
48 | );
49 | this.model = mongoose.model(collection, this.schema);
50 | this.count = 0;
51 |
52 | // XXX integrate data loader
53 | }
54 |
55 | findOne(...args) {
56 | return this.time(this.model.findOne.apply(this.model, args));
57 | }
58 |
59 | time(promise) {
60 | const prefix = "MongoConnector";
61 | const count = this.getCount();
62 | const start = new Date();
63 | const label = `${prefix}-${count}`;
64 | if (dd) dd.increment(`${prefix}.transaction.count`);
65 | console.time(label);
66 | return promise
67 | .then(x => {
68 | const end = new Date();
69 | if (dd) dd.histogram(`${prefix}.transaction.time`, end - start, [""]);
70 | console.timeEnd(label);
71 | return x;
72 | })
73 | .catch(x => {
74 | const end = new Date();
75 | if (dd) dd.histogram(`${prefix}.transaction.time`, end - start, [""]);
76 | if (dd) dd.increment(`${prefix}.transaction.error`);
77 | console.timeEnd(label);
78 | return x;
79 | });
80 | }
81 |
82 | find(...args) {
83 | const label = `MongoConnector${this.getCount()}`;
84 | console.time(label);
85 | return this.model.find.apply(this.model, args).then(x => {
86 | console.timeEnd(label);
87 | return x;
88 | });
89 | }
90 |
91 | remove(...args) {
92 | const label = `MongoConnector${this.getCount()}`;
93 | console.time(label);
94 | return this.model.remove.apply(this.model, args).then(x => {
95 | console.timeEnd(label);
96 | return x;
97 | });
98 | }
99 |
100 | create(...args) {
101 | const label = `MongoConnector${this.getCount()}`;
102 | console.time(label);
103 | return this.model.create.apply(this.model, args).then(x => {
104 | console.timeEnd(label);
105 | return x;
106 | });
107 | }
108 |
109 | distinct(field, query) {
110 | const label = `MongoConnector${this.getCount()}`;
111 | console.time(label);
112 | return this.model.distinct(field, query).then(x => {
113 | console.timeEnd(label);
114 | return x;
115 | });
116 | }
117 |
118 | aggregate(...args) {
119 | return this.model.aggregate(...args);
120 | }
121 |
122 | getCount() {
123 | this.count++;
124 | return this.count;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/expression-engine/models/content/tables.js:
--------------------------------------------------------------------------------
1 | import { INTEGER, STRING, CHAR } from "sequelize";
2 |
3 | import { MySQLConnector } from "../../mysql";
4 |
5 | const channelSchema = {
6 | channel_id: { type: INTEGER, primaryKey: true },
7 | channel_name: { type: STRING },
8 | field_group: { type: INTEGER }
9 | };
10 |
11 | const channelFieldSchema = {
12 | field_id: { type: INTEGER, primaryKey: true },
13 | group_id: { type: INTEGER },
14 | field_name: { type: STRING },
15 | field_label: { type: STRING }
16 | };
17 |
18 | const channelTitleSchema = {
19 | entry_id: { type: INTEGER, primaryKey: true },
20 | title: { type: STRING },
21 | status: { type: STRING },
22 | channel_id: { type: INTEGER },
23 | url_title: { type: STRING },
24 | year: { type: CHAR },
25 | day: { type: CHAR },
26 | month: { type: CHAR },
27 | entry_date: { type: INTEGER },
28 | expiration_date: { type: INTEGER }
29 | };
30 |
31 | const channelDataSchema = {
32 | entry_id: { type: INTEGER, primaryKey: true },
33 | channel_id: { type: INTEGER },
34 | site_id: { type: INTEGER }
35 | };
36 |
37 | const lowReorderSetSchema = {
38 | set_id: { type: INTEGER, primaryKey: true },
39 | set_name: { type: STRING }
40 | };
41 |
42 | const lowReorderOrderSchema = {
43 | set_id: { type: INTEGER, primaryKey: true },
44 | sort_order: { type: STRING }
45 | };
46 |
47 | let Channels;
48 | let ChannelFields;
49 | let ChannelTitles;
50 | let ChannelData;
51 | let LowReorder;
52 | let LowReorderOrder;
53 | export {
54 | Channels,
55 | channelSchema,
56 | ChannelFields,
57 | channelFieldSchema,
58 | ChannelTitles,
59 | channelTitleSchema,
60 | ChannelData,
61 | channelDataSchema,
62 | LowReorder,
63 | lowReorderSetSchema,
64 | LowReorderOrder,
65 | lowReorderOrderSchema
66 | };
67 |
68 | export function connect() {
69 | Channels = new MySQLConnector("exp_channels", channelSchema);
70 | ChannelFields = new MySQLConnector("exp_channel_fields", channelFieldSchema);
71 | ChannelTitles = new MySQLConnector("exp_channel_titles", channelTitleSchema);
72 | ChannelData = new MySQLConnector("exp_channel_data", channelDataSchema);
73 | LowReorder = new MySQLConnector("exp_low_reorder_sets", lowReorderSetSchema);
74 | LowReorderOrder = new MySQLConnector(
75 | "exp_low_reorder_orders",
76 | lowReorderOrderSchema
77 | );
78 |
79 | return {
80 | Channels,
81 | ChannelFields,
82 | ChannelTitles,
83 | ChannelData,
84 | LowReorder,
85 | LowReorderOrder
86 | };
87 | }
88 |
89 | export function bind({
90 | Channels,
91 | ChannelTitles,
92 | ChannelData,
93 | ChannelFields,
94 | LowReorder,
95 | LowReorderOrder
96 | }) {
97 | Channels.model.hasMany(ChannelTitles.model, { foreignKey: "channel_id" });
98 | Channels.model.hasMany(ChannelData.model, { foreignKey: "channel_id" });
99 |
100 | Channels.model.belongsTo(ChannelFields.model, {
101 | foreignKey: "field_group",
102 | targetKey: "group_id"
103 | });
104 | ChannelFields.model.hasOne(Channels.model, { foreignKey: "field_group" });
105 |
106 | ChannelTitles.model.belongsTo(Channels.model, { foreignKey: "channel_id" });
107 | ChannelTitles.model.hasMany(ChannelData.model, { foreignKey: "entry_id" });
108 |
109 | ChannelData.model.belongsTo(Channels.model, { foreignKey: "channel_id" });
110 | ChannelData.model.belongsTo(ChannelTitles.model, { foreignKey: "entry_id" });
111 |
112 | LowReorderOrder.model.belongsTo(LowReorder.model, {
113 | foreignKey: "set_id",
114 | targetKey: "set_id"
115 | });
116 | }
117 |
118 | export default {
119 | connect,
120 | bind
121 | };
122 |
--------------------------------------------------------------------------------
/scripts/commit.js:
--------------------------------------------------------------------------------
1 | // a mix between https://github.com/commitizen/cz-conventional-changelog and
2 | // https://github.com/commitizen/cz-jira-smart-commit
3 |
4 | "format cjs";
5 |
6 | var wrap = require('word-wrap');
7 |
8 | // This can be any kind of SystemJS compatible module.
9 | // We use Commonjs here, but ES6 or AMD would do just
10 | // fine.
11 | module.exports = {
12 |
13 | // When a user runs `git cz`, prompter will
14 | // be executed. We pass you cz, which currently
15 | // is just an instance of inquirer.js. Using
16 | // this you can ask questions and get answers.
17 | //
18 | // The commit callback should be executed when
19 | // you're ready to send back a commit template
20 | // to git.
21 | //
22 | // By default, we'll de-indent your commit
23 | // template and will keep empty lines.
24 | prompter: function(cz, commit) {
25 | console.log('\nAll lines will be wrapped after 100 characters.\n');
26 |
27 | // Let's ask some questions of the user
28 | // so that we can populate our commit
29 | // template.
30 | //
31 | // See inquirer.js docs for specifics.
32 | // You can also opt to use another input
33 | // collection library if you prefer.
34 | cz.prompt([
35 | {
36 | type: 'list',
37 | name: 'type',
38 | message: 'Select the type of change that you\'re committing:',
39 | choices: [
40 | {
41 | name: 'feat: A new feature',
42 | value: 'feat'
43 | }, {
44 | name: 'fix: A bug fix',
45 | value: 'fix'
46 | }, {
47 | name: 'test: Adding missing tests',
48 | value: 'test'
49 | }, {
50 | name: 'style: Changes that do not affect the meaning of the code\n (white-space, formatting, missing semi-colons, etc)',
51 | value: 'style'
52 | }, {
53 | name: 'refactor: A code change that neither fixes a bug or adds a feature',
54 | value: 'refactor'
55 | }, {
56 | name: 'docs: Documentation only changes',
57 | value: 'docs'
58 | }, {
59 | name: 'perf: A code change that improves performance',
60 | value: 'perf'
61 | }, {
62 | name: 'chore: Changes to the build process or auxiliary tools\n and libraries such as documentation generation',
63 | value: 'chore'
64 | }]
65 | }, {
66 | type: 'input',
67 | name: 'issues',
68 | message: 'Jira Issue ID(s) (required):\n',
69 | }, {
70 | type: 'input',
71 | name: 'subject',
72 | message: 'Write a short description of the change (required):\n',
73 | }, {
74 | type: 'input',
75 | name: 'body',
76 | message: 'Provide a longer description of the change [Sent to JIRA] (optional):\n'
77 | }, {
78 | type: 'confirm',
79 | name: 'ci',
80 | message: 'Run this build on CI?\n'
81 | }
82 | ]).then(function(answers) {
83 | try {
84 | var wrapOptions = {
85 | trim: true,
86 | newline: '\n',
87 | indent:'',
88 | width: 100
89 | };
90 |
91 | var issues = answers.issues.trim();
92 | var body = answers.body ? '#comment ' + answers.body.trim(): '';
93 | var ci = answers.ci ? '' : ' [ci skip]';
94 |
95 | // Hard limit this line
96 | var head = answers.type + ': ' + answers.subject + ' ' + issues + ci;
97 | if (body) head = head + ' ' + body;
98 |
99 | // Wrap these lines at 100 characters
100 | var body = wrap(body, wrapOptions);
101 |
102 | if (!body) body = '';
103 |
104 | commit(head + '\n\n' + body);
105 | } catch (e) {
106 | console.log("COMMIT ERROR: ", e);
107 | }
108 |
109 | });
110 | }
111 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "heighliner",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "jest",
8 | "coverage": "jest --coverage",
9 | "commit": "git-cz",
10 | "lint-fix": "eslint ./src --fix",
11 | "lint": "eslint ./src",
12 | "start": "NODE_ENV=development webpack --watch & ./node_modules/.bin/nodemon ./lib/server.js",
13 | "build": "NODE_ENV=development webpack",
14 | "build:production": "NODE_ENV=production webpack",
15 | "danger": "danger run --verbose"
16 | },
17 | "author": "NewSpring",
18 | "jest": {
19 | "moduleFileExtensions": [
20 | "js",
21 | "json"
22 | ],
23 | "roots": [
24 | "./src"
25 | ],
26 | "collectCoverageFrom": [
27 | "src/**/*.js"
28 | ],
29 | "testRegex": "./__tests__/.*\\.(js)$",
30 | "testEnvironment": "node",
31 | "collectCoverage": false
32 | },
33 | "license": "ISC",
34 | "dependencies": {
35 | "@google/maps": "^0.5.5",
36 | "apollo-engine": "^0.5.6",
37 | "apollo-server-express": "^1.1.2",
38 | "bcrypt": "^1.0.3",
39 | "body-parser": "^1.14.2",
40 | "bull": "^2.0.0",
41 | "connect-datadog": "0.0.6",
42 | "cors": "^2.7.1",
43 | "credit-card-type": "^5.0.1",
44 | "datadog-metrics": "^0.3.0",
45 | "dataloader": "^1.1.0",
46 | "express": "^4.13.3",
47 | "geo-from-ip": "^1.0.6",
48 | "google-geocoding": "^0.1.7",
49 | "graphql": "^0.11.3",
50 | "graphql-date": "^1.0.3",
51 | "graphql-tools": "^2.3.0",
52 | "html-pdf": "^2.1.0",
53 | "ical": "^0.5.0",
54 | "isomorphic-fetch": "^2.2.1",
55 | "liquid-node": "^3.0.0",
56 | "lodash": "^4.15.0",
57 | "meteor-random": "^0.0.3",
58 | "moment": "^2.13.0",
59 | "mongoose": "^4.5.8",
60 | "morgan": "^1.7.0",
61 | "mssql-geoparser": "0.0.1",
62 | "mysql": "^2.10.2",
63 | "node-mssql": "0.0.1",
64 | "node-uuid": "^1.4.7",
65 | "optics-agent": "^1.1.6",
66 | "phantomjs-prebuilt": "^2.1.14",
67 | "php-unserialize": "0.0.1",
68 | "promise-timeout": "^1.0.0",
69 | "ramda": "^0.23.0",
70 | "raven": "^0.12.1",
71 | "react": "^15.4.1",
72 | "react-dom": "^15.4.1",
73 | "redis": "^2.4.2",
74 | "redis-commands": "github:newspring/redis-commands",
75 | "sequelize": "^3.24.0",
76 | "striptags": "^3.1.0",
77 | "tedious": "^1.14.0",
78 | "to-pascal-case": "^1.0.0",
79 | "to-snake-case": "^1.0.0",
80 | "truncate": "^2.0.0",
81 | "xml2js": "^0.4.17"
82 | },
83 | "devDependencies": {
84 | "babel-cli": "^6.9.0",
85 | "babel-core": "^6.21.0",
86 | "babel-eslint": "^6.1.2",
87 | "babel-loader": "^6.2.7",
88 | "babel-plugin-transform-react-jsx": "^6.8.0",
89 | "babel-plugin-transform-runtime": "^6.9.0",
90 | "babel-preset-es2015": "^6.18.0",
91 | "babel-preset-stage-0": "^6.5.0",
92 | "casual": "1.5.3",
93 | "commitizen": "^2.8.5",
94 | "coveralls": "^2.11.9",
95 | "danger": "^0.15.0",
96 | "eslint": "^3.9.1",
97 | "eslint-config-airbnb-base": "^10.0.1",
98 | "eslint-config-prettier": "^3.3.0",
99 | "eslint-plugin-babel": "^4.0.0",
100 | "eslint-plugin-import": "^2.2.0",
101 | "eslint-plugin-prettier": "^3.0.1",
102 | "ghooks": "^1.2.3",
103 | "graphql-tester": "0.0.4",
104 | "jest": "^19.0.0",
105 | "nodemon": "1.18.7",
106 | "npm-install-webpack-plugin": "^4.0.5",
107 | "prettier": "^1.15.3",
108 | "react-test-renderer": "^15.4.1",
109 | "redis-mock": "^0.16.0",
110 | "webpack": "^3.6.0",
111 | "webpack-config-utils": "^2.3.0",
112 | "webpack-dashboard": "^1.0.0-7",
113 | "webpack-dev-middleware": "^1.12.0",
114 | "webpack-dotenv-plugin": "^2.0.2",
115 | "webpack-hot-middleware": "^2.19.0",
116 | "webpack-node-externals": "^1.6.0",
117 | "word-wrap": "^1.1.0"
118 | },
119 | "config": {
120 | "commitizen": {
121 | "path": "./scripts/commit"
122 | },
123 | "ghooks": {
124 | "pre-commit": "echo npm run lint"
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/rock/models/likes/model.js:
--------------------------------------------------------------------------------
1 | import uuid from "node-uuid";
2 | import { defaultCache } from "../../../util/cache";
3 | import { MongoConnector } from "../../../apollos/mongo";
4 | import { createGlobalId } from "../../../util/node/model";
5 |
6 | const schema = {
7 | _id: String,
8 | userId: String, // AKA: PrimaryAliasId (pending migration from mongoId to rockId)
9 | entryId: String, // AKA: id returned by content
10 | type: String,
11 | createdAt: { type: Date, default: Date.now }
12 | };
13 |
14 | const Model = new MongoConnector("like", schema, [
15 | {
16 | keys: { userId: 1, entryId: 1 }
17 | },
18 | {
19 | keys: { createdAt: -1 }
20 | }
21 | ]);
22 |
23 | /*
24 | skip?: Int, // how many to trim off the front
25 | limit?: Int, // max array size at the end
26 | arr!: Array, // input array
27 | emptyRet: any // what to return if array is empty at any point (null, [], {}, etc)
28 | */
29 | export const safeTrimArray = (skip, limit, arr, emptyRet) => {
30 | if (!arr || !arr.length) return emptyRet;
31 | if (skip && skip >= arr.length) return emptyRet; // skips more than we have
32 |
33 | /*
34 | * first slice: trims the front of the array by "skip"
35 | * second: trims the back.
36 | * it checks for limit, then checks if it would be outside of array bounds
37 | * if it's outside of array bounds, just returns whole array
38 | */
39 | const trimmed = arr
40 | .slice(skip || 0)
41 | .slice(0, limit ? (limit > arr.length ? arr.length : limit) : null);
42 |
43 | if (!trimmed || !trimmed.length) return emptyRet;
44 | return trimmed;
45 | };
46 |
47 | export class Like {
48 | __type = "Like";
49 |
50 | constructor({ cache } = { cache: defaultCache }) {
51 | this.model = Model;
52 | this.cache = cache;
53 | }
54 |
55 | async getFromUserId(userId) {
56 | const guid = createGlobalId(userId);
57 | return await this.cache.get(guid, () => this.model.find({ userId }));
58 | }
59 |
60 | async getLikedContent(userId, node) {
61 | const likes = await this.getFromUserId(userId);
62 | return await likes.map(async like => await node.get(like.entryId));
63 | }
64 |
65 | async getRecentlyLiked({ limit, skip, cache }, userId, nodeModel) {
66 | const query = userId
67 | ? { userId: { $ne: userId } } // exlude user if there is one
68 | : {};
69 |
70 | query.createdAt = { $ne: null };
71 |
72 | const guid = createGlobalId(`${limit}:${skip}:${userId}`, this.__type);
73 | const entryIds = await this.cache.get(guid, async () => {
74 | const likes = await this.model.aggregate([
75 | { $match: query },
76 | { $group: { _id: "$entryId", date: { $max: "$createdAt" } } },
77 | { $sort: { date: -1 } }
78 | ]);
79 |
80 | const ids = likes.map(({ _id }) => _id);
81 | return safeTrimArray(skip, limit, ids, null);
82 | });
83 |
84 | if (!entryIds || !entryIds.length) return null;
85 |
86 | const promises = entryIds.map(x => nodeModel.get(x));
87 | return Promise.all(promises).then(likes => likes.filter(x => x));
88 | }
89 |
90 | async toggleLike(nodeId, userId, nodeModel) {
91 | const existingLike = await this.model.findOne({
92 | entryId: nodeId,
93 | userId
94 | });
95 |
96 | if (existingLike) {
97 | await this.model.remove({
98 | _id: existingLike._id
99 | });
100 | } else {
101 | await this.model.create({
102 | _id: uuid.v4(),
103 | userId,
104 | entryId: nodeId,
105 | createdAt: new Date()
106 | });
107 | }
108 |
109 | const guid = createGlobalId(userId);
110 | await this.cache.del(guid);
111 |
112 | return {
113 | like: nodeModel.get(nodeId),
114 | success: true,
115 | error: "",
116 | code: ""
117 | };
118 | }
119 |
120 | async hasUserLike({ userId, entryId, entryType } = {}) {
121 | if (!userId || !entryId || !entryType) return false;
122 | return !!(await this.model.findOne({
123 | entryId: createGlobalId(entryId, entryType), // Why are IDs encrypted?
124 | userId
125 | }));
126 | }
127 | }
128 |
129 | export default {
130 | Like
131 | };
132 |
--------------------------------------------------------------------------------
/src/expression-engine/mysql.js:
--------------------------------------------------------------------------------
1 | import Sequelize, {
2 | Options,
3 | Connection,
4 | Model,
5 | DefineOptions
6 | } from "sequelize";
7 |
8 | import { merge, isArray } from "lodash";
9 | // import DataLoader from "dataloader";
10 |
11 | import { createTables } from "./models";
12 |
13 | const noop = (...args) => {}; // tslint:disable-line
14 | const loud = console.log.bind(console, "MYSQL:"); // tslint:disable-line
15 | let db;
16 | let dd;
17 | let isReady;
18 |
19 | // MySQL connections
20 | const EESettings = {
21 | user: process.env.MYSQL_USER || "root",
22 | password: process.env.MYSQL_PASSWORD || "password",
23 | database: process.env.MYSQL_DB || "ee_local",
24 | opts: {
25 | host: process.env.MYSQL_HOST,
26 | ssl: process.env.MYSQL_SSL || false
27 | }
28 | };
29 |
30 | export function connect(monitor) {
31 | if (isReady) return Promise.resolve(true);
32 | dd = monitor && monitor.datadog;
33 | return new Promise(cb => {
34 | const opts = merge({}, EESettings.opts, {
35 | dialect: "mysql",
36 | logging: process.env.LOG === "true" ? loud : noop, // tslint:disable-line
37 | benchmark: process.env.NODE_ENV !== "production",
38 | define: {
39 | timestamps: false,
40 | freezeTableName: true
41 | }
42 | });
43 |
44 | db = new Sequelize(
45 | EESettings.database,
46 | EESettings.user,
47 | EESettings.password,
48 | opts
49 | );
50 |
51 | db.authenticate()
52 | .then(() => cb(true))
53 | .then(() => createTables())
54 | .then(() => {
55 | isReady = true;
56 | })
57 | .catch(e => {
58 | console.error(e); // tslint:disable-line
59 | db = false;
60 | cb(false);
61 | });
62 | });
63 | }
64 |
65 | export class MySQLConnector {
66 | prefix = "exp_";
67 | count = 0;
68 |
69 | constructor(tableName, schema = {}, options = {}) {
70 | this.db = db;
71 | options = merge(options, { tableName, underscored: true });
72 | this.model = db.define(tableName, schema, options);
73 |
74 | // XXX integrate data loader
75 | }
76 |
77 | find(...args) {
78 | return this.time(
79 | this.model.findAll
80 | .apply(this.model, args)
81 | .then(this.getValues)
82 | .then(data => data.map(this.mergeData))
83 | );
84 | }
85 |
86 | findOne(...args) {
87 | return this.time(
88 | this.model.findOne
89 | .apply(this.model, args)
90 | .then(x => x.dataValues)
91 | .then(this.mergeData)
92 | );
93 | }
94 |
95 | mergeData = data => {
96 | const keys = [];
97 | for (const key in data) {
98 | if (key.indexOf(this.prefix) > -1) keys.push(key);
99 | }
100 |
101 | for (const key of keys) {
102 | const table = data[key];
103 | if (!data[key]) continue;
104 |
105 | if (isArray(table)) {
106 | data[key] = this.getValues(table).map(this.mergeData);
107 | } else if (data[key] && data[key].dataValues) {
108 | data[key] = this.mergeData(data[key].dataValues);
109 | }
110 | }
111 |
112 | return data;
113 | };
114 |
115 | getValues(data) {
116 | return data.map(x => x.dataValues);
117 | }
118 |
119 | queryCount() {
120 | this.count++;
121 | return this.count;
122 | }
123 |
124 | time(promise) {
125 | const prefix = "MYSQLConnector";
126 | const count = this.queryCount();
127 | const start = new Date();
128 | const label = `${prefix}-${count}`;
129 | if (dd) dd.increment(`${prefix}.transaction.count`);
130 | console.time(label); // tslint:disable-line
131 | return promise
132 | .then(x => {
133 | const end = new Date();
134 | if (dd) dd.histogram(`${prefix}.transaction.time`, end - start, [""]);
135 | console.timeEnd(label); // tslint:disable-line
136 | return x;
137 | })
138 | .catch(x => {
139 | const end = new Date();
140 | if (dd) dd.histogram(`${prefix}.transaction.time`, end - start, [""]);
141 | if (dd) dd.increment(`${prefix}.transaction.error`);
142 | console.timeEnd(label); // tslint:disable-line
143 | return x;
144 | });
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/rock/models/finances/models/__tests__/FinancialBatch.js:
--------------------------------------------------------------------------------
1 | import moment from "moment";
2 |
3 | import FinancialBatch from "../FinancialBatch";
4 | import { FinancialBatch as FinancialBatchTable } from "../../tables";
5 |
6 | import { createGlobalId } from "../../../../../util/node";
7 |
8 | jest.mock("../../tables", () => ({
9 | FinancialBatch: {
10 | find: jest.fn(),
11 | findOne: jest.fn(),
12 | delete: jest.fn(),
13 | post: jest.fn()
14 | }
15 | }));
16 |
17 | jest.mock("../../util/nmi");
18 |
19 | jest.mock("moment");
20 |
21 | jest.mock("node-uuid", () => ({
22 | v4: jest.fn(() => "guid")
23 | }));
24 |
25 | const mockedCache = {
26 | get: jest.fn((id, lookup) => Promise.resolve().then(lookup)),
27 | set: jest.fn(() => Promise.resolve().then(() => true)),
28 | del() {},
29 | encode: jest.fn((obj, prefix) => `${prefix}${JSON.stringify(obj)}`)
30 | };
31 |
32 | it("sets the __type", () => {
33 | const Local = new FinancialBatch({ cache: mockedCache });
34 | expect(Local.__type).toBe("FinancialBatch");
35 | });
36 |
37 | describe("getFromId", () => {
38 | it("tries to load the passed id", async () => {
39 | const id = 1;
40 | const localCache = { ...mockedCache };
41 | localCache.get = jest.fn((guid, cb) => cb());
42 |
43 | const Local = new FinancialBatch({ cache: localCache });
44 | const nodeId = createGlobalId(1, Local.__type);
45 | FinancialBatchTable.findOne.mockReturnValueOnce([]);
46 | const result = await Local.getFromId(1);
47 |
48 | expect(localCache.get.mock.calls[0][0]).toEqual(nodeId);
49 | expect(FinancialBatchTable.findOne).toBeCalledWith({ where: { Id: id } });
50 | expect(result).toEqual([]);
51 | });
52 | });
53 |
54 | describe("findOrCreate", () => {
55 | it("looks up batches based on currencyType and date", async () => {
56 | const localCache = { ...mockedCache };
57 | localCache.get = jest.fn((guid, cb) => cb());
58 |
59 | const Local = new FinancialBatch({ cache: localCache });
60 | FinancialBatchTable.find.mockReturnValueOnce([{ Id: 1 }]);
61 | const result = await Local.findOrCreate({
62 | currencyType: "Visa",
63 | date: "date"
64 | });
65 |
66 | expect(FinancialBatchTable.find).toBeCalledWith({
67 | where: {
68 | Status: 1,
69 | BatchStartDateTime: { $lte: "date" },
70 | BatchEndDateTime: { $gt: "date" },
71 | Name: "Online Giving Visa"
72 | }
73 | });
74 | expect(result).toEqual({ Id: 1 });
75 | });
76 |
77 | it("looks up batches based on currencyType and date", async () => {
78 | const localCache = { ...mockedCache };
79 | localCache.get = jest.fn((guid, cb) => cb());
80 |
81 | const Local = new FinancialBatch({ cache: localCache });
82 | FinancialBatchTable.find.mockReturnValueOnce([]);
83 |
84 | const toISOString = jest.fn(() => "date");
85 | // const subtract = jest.fn(() => ({ toISOString }));
86 | const startOf = jest.fn(() => ({ toISOString }));
87 | const endOf = jest.fn(() => ({ toISOString }));
88 |
89 | moment.mockReturnValueOnce({ startOf }).mockReturnValueOnce({ endOf });
90 |
91 | FinancialBatchTable.post.mockReturnValueOnce(Promise.resolve({ Id: 1 }));
92 | FinancialBatchTable.findOne.mockReturnValueOnce({ Id: 1 });
93 | const result = await Local.findOrCreate({
94 | currencyType: "Visa",
95 | date: "date"
96 | });
97 |
98 | expect(moment).toBeCalledWith("date");
99 | expect(startOf).toBeCalledWith("day");
100 | // expect(subtract).toBeCalledWith(1, "minute");
101 | expect(toISOString).toBeCalled();
102 |
103 | expect(moment).toBeCalledWith("date");
104 | expect(endOf).toBeCalledWith("day");
105 | // expect(subtract).toBeCalledWith(1, "minute");
106 | expect(toISOString).toBeCalled();
107 |
108 | expect(FinancialBatchTable.post).toBeCalledWith({
109 | Guid: "guid",
110 | Name: "Online Giving Visa",
111 | Status: 1,
112 | ControlAmount: 0,
113 | BatchStartDateTime: "date",
114 | BatchEndDateTime: "date"
115 | });
116 | expect(FinancialBatchTable.findOne).toBeCalledWith({
117 | where: { Id: 1 }
118 | });
119 | expect(result).toEqual({ Id: 1 });
120 | });
121 | });
122 |
--------------------------------------------------------------------------------